Initial commit.

This commit is contained in:
Jonas Kvinge
2018-02-27 18:06:05 +01:00
parent 85d9664df7
commit b2b1ba7abe
1393 changed files with 177311 additions and 1 deletions

View File

@@ -0,0 +1,171 @@
/*
* SBSystemPreferences.h
*
* Generated with:
* sdef "/Applications/System Preferences.app" | sdp -fh --basename
*SBSystemPreferences -o SBSystemPreferences.h
*/
#import <AppKit/AppKit.h>
#import <ScriptingBridge/ScriptingBridge.h>
@class SBSystemPreferencesApplication, SBSystemPreferencesDocument,
SBSystemPreferencesWindow, SBSystemPreferencesPane,
SBSystemPreferencesAnchor;
enum SBSystemPreferencesSaveOptions {
SBSystemPreferencesSaveOptionsYes = 'yes ' /* Save the file. */,
SBSystemPreferencesSaveOptionsNo = 'no ' /* Do not save the file. */,
SBSystemPreferencesSaveOptionsAsk =
'ask ' /* Ask the user whether or not to save the file. */
};
typedef enum SBSystemPreferencesSaveOptions SBSystemPreferencesSaveOptions;
enum SBSystemPreferencesPrintingErrorHandling {
SBSystemPreferencesPrintingErrorHandlingStandard =
'lwst' /* Standard PostScript error handling */,
SBSystemPreferencesPrintingErrorHandlingDetailed =
'lwdt' /* print a detailed report of PostScript errors */
};
typedef enum SBSystemPreferencesPrintingErrorHandling
SBSystemPreferencesPrintingErrorHandling;
/*
* Standard Suite
*/
// The application's top-level scripting object.
@interface SBSystemPreferencesApplication : SBApplication
- (SBElementArray*)documents;
- (SBElementArray*)windows;
@property(copy, readonly) NSString* name; // The name of the application.
@property(readonly) BOOL frontmost; // Is this the active application?
@property(copy, readonly)
NSString* version; // The version number of the application.
- (id)open:(id)x; // Open a document.
- (void)print:(id)x
withProperties:(NSDictionary*)withProperties
printDialog:(BOOL)printDialog; // Print a document.
- (void)quitSaving:
(SBSystemPreferencesSaveOptions)saving; // Quit the application.
- (BOOL)exists:(id)x; // Verify that an object exists.
@end
// A document.
@interface SBSystemPreferencesDocument : SBObject
@property(copy, readonly) NSString* name; // Its name.
@property(readonly) BOOL modified; // Has it been modified since the last save?
@property(copy, readonly) NSURL* file; // Its location on disk, if it has one.
- (void)closeSaving:(SBSystemPreferencesSaveOptions)saving
savingIn:(NSURL*)savingIn; // Close a document.
- (void)saveIn:(NSURL*)in_ as:(id)as; // Save a document.
- (void)printWithProperties:(NSDictionary*)withProperties
printDialog:(BOOL)printDialog; // Print a document.
- (void) delete; // Delete an object.
- (void)duplicateTo:(SBObject*)to
withProperties:(NSDictionary*)withProperties; // Copy an object.
- (void)moveTo:(SBObject*)to; // Move an object to a new location.
@end
// A window.
@interface SBSystemPreferencesWindow : SBObject
@property(copy, readonly) NSString* name; // The title of the window.
- (NSInteger)id; // The unique identifier of the window.
@property NSInteger index; // The index of the window, ordered front to back.
@property NSRect bounds; // The bounding rectangle of the window.
@property(readonly) BOOL closeable; // Does the window have a close button?
@property(readonly)
BOOL miniaturizable; // Does the window have a minimize button?
@property BOOL miniaturized; // Is the window minimized right now?
@property(readonly) BOOL resizable; // Can the window be resized?
@property BOOL visible; // Is the window visible right now?
@property(readonly) BOOL zoomable; // Does the window have a zoom button?
@property BOOL zoomed; // Is the window zoomed right now?
@property(copy, readonly) SBSystemPreferencesDocument*
document; // The document whose contents are displayed in the window.
- (void)closeSaving:(SBSystemPreferencesSaveOptions)saving
savingIn:(NSURL*)savingIn; // Close a document.
- (void)saveIn:(NSURL*)in_ as:(id)as; // Save a document.
- (void)printWithProperties:(NSDictionary*)withProperties
printDialog:(BOOL)printDialog; // Print a document.
- (void) delete; // Delete an object.
- (void)duplicateTo:(SBObject*)to
withProperties:(NSDictionary*)withProperties; // Copy an object.
- (void)moveTo:(SBObject*)to; // Move an object to a new location.
@end
/*
* System Preferences
*/
// System Preferences top level scripting object
@interface SBSystemPreferencesApplication (SystemPreferences)
- (SBElementArray*)panes;
@property(copy)
SBSystemPreferencesPane* currentPane; // the currently selected pane
@property(copy, readonly) SBSystemPreferencesWindow*
preferencesWindow; // the main preferences window
@property BOOL showAll; // Is SystemPrefs in show all view. (Setting to false
// will do nothing)
@end
// a preference pane
@interface SBSystemPreferencesPane : SBObject
- (SBElementArray*)anchors;
- (NSString*)id; // locale independent name of the preference pane; can refer
// to a pane using the expression: pane id "<name>"
@property(copy, readonly)
NSString* localizedName; // localized name of the preference pane
@property(copy, readonly) NSString* name; // name of the preference pane as it
// appears in the title bar; can
// refer to a pane using the
// expression: pane "<name>"
- (void)closeSaving:(SBSystemPreferencesSaveOptions)saving
savingIn:(NSURL*)savingIn; // Close a document.
- (void)saveIn:(NSURL*)in_ as:(id)as; // Save a document.
- (void)printWithProperties:(NSDictionary*)withProperties
printDialog:(BOOL)printDialog; // Print a document.
- (void) delete; // Delete an object.
- (void)duplicateTo:(SBObject*)to
withProperties:(NSDictionary*)withProperties; // Copy an object.
- (void)moveTo:(SBObject*)to; // Move an object to a new location.
- (id)reveal; // Reveals an anchor within a preference pane or preference pane
// itself
@end
// an anchor within a preference pane
@interface SBSystemPreferencesAnchor : SBObject
@property(copy, readonly)
NSString* name; // name of the anchor within a preference pane
- (void)closeSaving:(SBSystemPreferencesSaveOptions)saving
savingIn:(NSURL*)savingIn; // Close a document.
- (void)saveIn:(NSURL*)in_ as:(id)as; // Save a document.
- (void)printWithProperties:(NSDictionary*)withProperties
printDialog:(BOOL)printDialog; // Print a document.
- (void) delete; // Delete an object.
- (void)duplicateTo:(SBObject*)to
withProperties:(NSDictionary*)withProperties; // Copy an object.
- (void)moveTo:(SBObject*)to; // Move an object to a new location.
- (id)reveal; // Reveals an anchor within a preference pane or preference pane
// itself
@end

91
src/core/appearance.cpp Normal file
View File

@@ -0,0 +1,91 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "appearance.h"
#include <QApplication>
#include <QSettings>
#include "settings/appearancesettingspage.h"
const char *Appearance::kUseCustomColorSet = "use-custom-set";
const char *Appearance::kForegroundColor = "foreground-color";
const char *Appearance::kBackgroundColor = "background-color";
const QPalette Appearance::kDefaultPalette = QPalette();
Appearance::Appearance(QObject *parent) : QObject(parent) {
QSettings s;
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
QPalette p = QApplication::palette();
background_color_ = s.value(kBackgroundColor, p.color(QPalette::WindowText)).value<QColor>();
foreground_color_ = s.value(kForegroundColor, p.color(QPalette::Window)).value<QColor>();
}
void Appearance::LoadUserTheme() {
QSettings s;
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
bool use_a_custom_color_set = s.value(kUseCustomColorSet).toBool();
if (!use_a_custom_color_set) return;
ChangeForegroundColor(foreground_color_);
ChangeBackgroundColor(background_color_);
}
void Appearance::ResetToSystemDefaultTheme() {
QApplication::setPalette(kDefaultPalette);
}
void Appearance::ChangeForegroundColor(const QColor &color) {
// Get the application palette
QPalette p = QApplication::palette();
// Modify the palette
p.setColor(QPalette::WindowText, color);
p.setColor(QPalette::Text, color);
// Make the modified palette the new application's palette
QApplication::setPalette(p);
foreground_color_ = color;
}
void Appearance::ChangeBackgroundColor(const QColor &color) {
// Get the application palette
QPalette p = QApplication::palette();
// Modify the palette
p.setColor(QPalette::Window, color);
p.setColor(QPalette::Base, color);
// Make the modified palette the new application's palette
QApplication::setPalette(p);
background_color_ = color;
}

51
src/core/appearance.h Normal file
View File

@@ -0,0 +1,51 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef APPEARANCE_H
#define APPEARANCE_H
#include "config.h"
#include <QColor>
#include <QPalette>
class Appearance : public QObject {
public:
explicit Appearance(QObject* parent = nullptr);
// Load the user preferred theme, which could the default system theme or a
// custom set of colors that user has chosen
void LoadUserTheme();
void ResetToSystemDefaultTheme();
void ChangeForegroundColor(const QColor& color);
void ChangeBackgroundColor(const QColor& color);
static const char* kSettingsGroup;
static const char* kUseCustomColorSet;
static const char* kForegroundColor;
static const char* kBackgroundColor;
static const QPalette kDefaultPalette;
private:
QColor foreground_color_;
QColor background_color_;
};
#endif // APPEARANCE_H

217
src/core/application.cpp Normal file
View File

@@ -0,0 +1,217 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "application.h"
#include "config.h"
#include "core/appearance.h"
#include "core/database.h"
#include "core/lazy.h"
#include "core/player.h"
#include "core/tagreaderclient.h"
#include "core/taskmanager.h"
#include "engine/enginetype.h"
#include "engine/enginedevice.h"
#include "device/devicemanager.h"
#include "collection/collectionbackend.h"
#include "collection/collection.h"
#include "playlist/playlistbackend.h"
#include "playlist/playlistmanager.h"
#include "covermanager/albumcoverloader.h"
#include "covermanager/coverproviders.h"
#include "covermanager/currentartloader.h"
#ifdef HAVE_LIBLASTFM
#include "covermanager/lastfmcoverprovider.h"
#endif // HAVE_LIBLASTFM
#include "covermanager/amazoncoverprovider.h"
#include "covermanager/discogscoverprovider.h"
#include "covermanager/musicbrainzcoverprovider.h"
bool Application::kIsPortable = false;
class ApplicationImpl {
public:
ApplicationImpl(Application *app) :
tag_reader_client_([=]() {
TagReaderClient *client = new TagReaderClient(app);
app->MoveToNewThread(client);
client->Start();
return client;
}),
database_([=]() {
Database *db = new Database(app, app);
app->MoveToNewThread(db);
DoInAMinuteOrSo(db, SLOT(DoBackup()));
return db;
}),
appearance_([=]() { return new Appearance(app); }),
task_manager_([=]() { return new TaskManager(app); }),
player_([=]() { return new Player(app, app); }),
enginedevice_([=]() { return new EngineDevice(app); }),
device_manager_([=]() { return new DeviceManager(app, app); }),
collection_([=]() { return new Collection(app, app); }),
playlist_backend_([=]() {
PlaylistBackend *backend = new PlaylistBackend(app, app);
app->MoveToThread(backend, database_->thread());
return backend;
}),
playlist_manager_([=]() { return new PlaylistManager(app); }),
cover_providers_([=]() {
CoverProviders *cover_providers = new CoverProviders(app);
// Initialize the repository of cover providers.
#ifdef HAVE_LIBLASTFM
cover_providers->AddProvider(new LastFmCoverProvider(app));
#endif
cover_providers->AddProvider(new AmazonCoverProvider(app));
cover_providers->AddProvider(new DiscogsCoverProvider(app));
cover_providers->AddProvider(new MusicbrainzCoverProvider(app));
return cover_providers;
}),
album_cover_loader_([=]() {
AlbumCoverLoader *loader = new AlbumCoverLoader(app);
app->MoveToNewThread(loader);
return loader;
}),
current_art_loader_([=]() { return new CurrentArtLoader(app, app); })
{ }
Lazy<TagReaderClient> tag_reader_client_;
Lazy<Database> database_;
Lazy<Appearance> appearance_;
Lazy<TaskManager> task_manager_;
Lazy<Player> player_;
Lazy<EngineDevice> enginedevice_;
Lazy<DeviceManager> device_manager_;
Lazy<Collection> collection_;
Lazy<PlaylistBackend> playlist_backend_;
Lazy<PlaylistManager> playlist_manager_;
Lazy<CoverProviders> cover_providers_;
Lazy<AlbumCoverLoader> album_cover_loader_;
Lazy<CurrentArtLoader> current_art_loader_;
};
Application::Application(QObject *parent)
: QObject(parent), p_(new ApplicationImpl(this)) {
enginedevice()->Init();
collection()->Init();
tag_reader_client();
}
Application::~Application() {
// It's important that the device manager is deleted before the database.
// Deleting the database deletes all objects that have been created in its
// thread, including some device collection backends.
p_->device_manager_.reset();
for (QThread *thread : threads_) {
thread->quit();
}
for (QThread *thread : threads_) {
thread->wait();
}
}
void Application::MoveToNewThread(QObject *object) {
QThread *thread = new QThread(this);
MoveToThread(object, thread);
thread->start();
threads_ << thread;
}
void Application::MoveToThread(QObject *object, QThread *thread) {
object->setParent(nullptr);
object->moveToThread(thread);
}
void Application::AddError(const QString& message) { emit ErrorAdded(message); }
QString Application::language_without_region() const {
const int underscore = language_name_.indexOf('_');
if (underscore != -1) {
return language_name_.left(underscore);
}
return language_name_;
}
void Application::ReloadSettings() { emit SettingsChanged(); }
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) {
emit SettingsDialogRequested(page);
}
AlbumCoverLoader *Application::album_cover_loader() const {
return p_->album_cover_loader_.get();
}
Appearance *Application::appearance() const { return p_->appearance_.get(); }
CoverProviders *Application::cover_providers() const {
return p_->cover_providers_.get();
}
CurrentArtLoader *Application::current_art_loader() const {
return p_->current_art_loader_.get();
}
Database *Application::database() const { return p_->database_.get(); }
DeviceManager *Application::device_manager() const {
return p_->device_manager_.get();
}
Collection *Application::collection() const { return p_->collection_.get(); }
CollectionBackend *Application::collection_backend() const {
return collection()->backend();
}
CollectionModel *Application::collection_model() const { return collection()->model(); }
Player *Application::player() const { return p_->player_.get(); }
PlaylistBackend *Application::playlist_backend() const {
return p_->playlist_backend_.get();
}
PlaylistManager *Application::playlist_manager() const {
return p_->playlist_manager_.get();
}
TagReaderClient *Application::tag_reader_client() const {
return p_->tag_reader_client_.get();
}
TaskManager *Application::task_manager() const {
return p_->task_manager_.get();
}
EngineDevice *Application::enginedevice() const {
//qLog(Debug) << __PRETTY_FUNCTION__;
return p_->enginedevice_.get();
}

103
src/core/application.h Normal file
View File

@@ -0,0 +1,103 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef APPLICATION_H_
#define APPLICATION_H_
#include "config.h"
#include <memory>
#include <QObject>
#include "settings/settingsdialog.h"
class ApplicationImpl;
class TagReaderClient;
class Database;
class Appearance;
class TaskManager;
class Player;
class DeviceManager;
class Collection;
class PlaylistBackend;
class PlaylistManager;
class AlbumCoverLoader;
class CoverProviders;
class CurrentArtLoader;
class CollectionBackend;
class CollectionModel;
class EngineDevice;
class Application : public QObject {
Q_OBJECT
public:
static bool kIsPortable;
explicit Application(QObject *parent = nullptr);
~Application();
const QString &language_name() const { return language_name_; }
// Same as language_name, but remove the region code at the end if there is one
QString language_without_region() const;
void set_language_name(const QString &name) { language_name_ = name; }
TagReaderClient *tag_reader_client() const;
Database *database() const;
Appearance *appearance() const;
TaskManager *task_manager() const;
Player *player() const;
EngineDevice *enginedevice() const;
DeviceManager *device_manager() const;
Collection *collection() const;
PlaylistBackend *playlist_backend() const;
PlaylistManager *playlist_manager() const;
CoverProviders *cover_providers() const;
AlbumCoverLoader *album_cover_loader() const;
CurrentArtLoader *current_art_loader() const;
CollectionBackend *collection_backend() const;
CollectionModel *collection_model() const;
void MoveToNewThread(QObject *object);
void MoveToThread(QObject *object, QThread *thread);
public slots:
void AddError(const QString &message);
void ReloadSettings();
void OpenSettingsDialogAtPage(SettingsDialog::Page page);
signals:
void ErrorAdded(const QString &message);
void SettingsChanged();
void SettingsDialogRequested(SettingsDialog::Page page);
private:
QString language_name_;
std::unique_ptr<ApplicationImpl> p_;
QList<QThread*> threads_;
};
#endif // APPLICATION_H_

103
src/core/cachedlist.h Normal file
View File

@@ -0,0 +1,103 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2011, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CACHEDLIST_H
#define CACHEDLIST_H
#include "config.h"
#include <QDateTime>
#include <QSettings>
template <typename T>
class CachedList {
public:
// Use a CachedList when you want to download and save a list of things from a
// remote service, updating it only periodically.
// T must be a registered metatype and must support being stored in
// QSettings. This usually means you have to implement QDataStream streaming
// operators, and use qRegisterMetaTypeStreamOperators.
typedef QList<T> ListType;
CachedList(const QString &settings_group, const QString &name, int cache_duration_secs)
: settings_group_(settings_group), name_(name), cache_duration_secs_(cache_duration_secs) {
}
void Load() {
QSettings s;
s.beginGroup(settings_group_);
last_updated_ = s.value("last_refreshed_" + name_).toDateTime();
data_.clear();
const int count = s.beginReadArray(name_ + "_data");
for (int i = 0; i < count; ++i) {
s.setArrayIndex(i);
data_ << s.value("value").value<T>();
}
s.endArray();
}
void Save() const {
QSettings s;
s.beginGroup(settings_group_);
s.setValue("last_refreshed_" + name_, last_updated_);
s.beginWriteArray(name_ + "_data", data_.size());
for (int i = 0; i < data_.size(); ++i) {
s.setArrayIndex(i);
s.setValue("value", QVariant::fromValue(data_[i]));
}
s.endArray();
}
void Update(const ListType &data) {
data_ = data;
last_updated_ = QDateTime::currentDateTime();
Save();
}
bool IsStale() const {
return last_updated_.isNull() || last_updated_.secsTo(QDateTime::currentDateTime()) > cache_duration_secs_;
}
void Sort() { qSort(data_); }
const ListType &Data() const { return data_; }
operator ListType() const { return data_; }
// Q_FOREACH support
typedef typename ListType::const_iterator const_iterator;
const_iterator begin() const { return data_.begin(); }
const_iterator end() const { return data_.end(); }
private:
const QString settings_group_;
const QString name_;
const int cache_duration_secs_;
QDateTime last_updated_;
ListType data_;
};
#endif // CACHEDLIST_H

View File

@@ -0,0 +1,388 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "commandlineoptions.h"
#include "version.h"
#include "core/logging.h"
#include <cstdlib>
#include <getopt.h>
#include <iostream>
#include <QBuffer>
#include <QCoreApplication>
#include <QFileInfo>
const char *CommandlineOptions::kHelpText =
"%1: strawberry [%2] [%3]\n"
"\n"
"%4:\n"
" -p, --play %5\n"
" -t, --play-pause %6\n"
" -u, --pause %7\n"
" -s, --stop %8\n"
" -q, --stop-after-current %9\n"
" -r, --previous %10\n"
" -f, --next %11\n"
" -v, --volume <value> %12\n"
" --volume-up %13\n"
" --volume-down %14\n"
" --volume-increase-by %15\n"
" --volume-decrease-by %16\n"
" --seek-to <seconds> %17\n"
" --seek-by <seconds> %18\n"
" --restart-or-previous %19\n"
"\n"
"%20:\n"
" -c, --create <name> %21\n"
" -a, --append %22\n"
" -l, --load %23\n"
" -k, --play-track <n> %24\n"
"\n"
"%25:\n"
" -o, --show-osd %26\n"
" -y, --toggle-pretty-osd %27\n"
" -g, --language <lang> %28\n"
" --quiet %29\n"
" --verbose %30\n"
" --log-levels <levels> %31\n"
" --version %32\n";
const char *CommandlineOptions::kVersionText = "Strawberry %1";
CommandlineOptions::CommandlineOptions(int argc, char* *argv)
: argc_(argc),
argv_(argv),
url_list_action_(UrlList_None),
player_action_(Player_None),
set_volume_(-1),
volume_modifier_(0),
seek_to_(-1),
seek_by_(0),
play_track_at_(-1),
show_osd_(false),
toggle_pretty_osd_(false),
log_levels_(logging::kDefaultLogLevels) {
#ifdef Q_OS_DARWIN
// Remove -psn_xxx option that Mac passes when opened from Finder.
RemoveArg("-psn", 1);
#endif
// Remove the -session option that KDE passes
RemoveArg("-session", 2);
}
void CommandlineOptions::RemoveArg(const QString& starts_with, int count) {
for (int i = 0; i < argc_; ++i) {
QString opt(argv_[i]);
if (opt.startsWith(starts_with)) {
for (int j = i; j < argc_ - count + 1; ++j) {
argv_[j] = argv_[j + count];
}
argc_ -= count;
break;
}
}
}
bool CommandlineOptions::Parse() {
static const struct option kOptions[] = {
{"help", no_argument, 0, 'h'},
{"play", no_argument, 0, 'p'},
{"play-pause", no_argument, 0, 't'},
{"pause", no_argument, 0, 'u'},
{"stop", no_argument, 0, 's'},
{"stop-after-current", no_argument, 0, 'q'},
{"previous", no_argument, 0, 'r'},
{"next", no_argument, 0, 'f'},
{"volume", required_argument, 0, 'v'},
{"volume-up", no_argument, 0, VolumeUp},
{"volume-down", no_argument, 0, VolumeDown},
{"volume-increase-by", required_argument, 0, VolumeIncreaseBy},
{"volume-decrease-by", required_argument, 0, VolumeDecreaseBy},
{"seek-to", required_argument, 0, SeekTo},
{"seek-by", required_argument, 0, SeekBy},
{"restart-or-previous", no_argument, 0, RestartOrPrevious},
{"create", required_argument, 0, 'c'},
{"append", no_argument, 0, 'a'},
{"load", no_argument, 0, 'l'},
{"play-track", required_argument, 0, 'k'},
{"show-osd", no_argument, 0, 'o'},
{"toggle-pretty-osd", no_argument, 0, 'y'},
{"language", required_argument, 0, 'g'},
{"quiet", no_argument, 0, Quiet},
{"verbose", no_argument, 0, Verbose},
{"log-levels", required_argument, 0, LogLevels},
{"version", no_argument, 0, Version},
{0, 0, 0, 0}};
// Parse the arguments
bool ok = false;
forever {
int c = getopt_long(argc_, argv_, "hptusqrfv:c:alk:oyg:", kOptions, nullptr);
// End of the options
if (c == -1) break;
switch (c) {
case 'h': {
QString translated_help_text =
QString(kHelpText)
.arg(tr("Usage"), tr("options"), tr("URL(s)"),
tr("Player options"),
tr("Start the playlist currently playing"),
tr("Play if stopped, pause if playing"),
tr("Pause playback"), tr("Stop playback"),
tr("Stop playback after current track"))
.arg(tr("Skip backwards in playlist"),
tr("Skip forwards in playlist"),
tr("Set the volume to <value> percent"),
tr("Increase the volume by 4%"),
tr("Decrease the volume by 4%"),
tr("Increase the volume by <value> percent"),
tr("Decrease the volume by <value> percent"))
.arg(tr("Seek the currently playing track to an absolute "
"position"),
tr("Seek the currently playing track by a relative "
"amount"),
tr("Restart the track, or play the previous track if "
"within 8 seconds of start."),
tr("Playlist options"),
tr("Create a new playlist with files"),
tr("Append files/URLs to the playlist"),
tr("Loads files/URLs, replacing current playlist"),
tr("Play the <n>th track in the playlist"))
.arg(tr("Other options"), tr("Display the on-screen-display"),
tr("Toggle visibility for the pretty on-screen-display"),
tr("Change the language"),
tr("Equivalent to --log-levels *:1"),
tr("Equivalent to --log-levels *:3"),
tr("Comma separated list of class:level, level is 0-3"))
.arg(tr("Print out version information"));
std::cout << translated_help_text.toLocal8Bit().constData();
return false;
}
case 'p':
player_action_ = Player_Play;
break;
case 't':
player_action_ = Player_PlayPause;
break;
case 'u':
player_action_ = Player_Pause;
break;
case 's':
player_action_ = Player_Stop;
break;
case 'q':
player_action_ = Player_StopAfterCurrent;
break;
case 'r':
player_action_ = Player_Previous;
break;
case 'f':
player_action_ = Player_Next;
break;
case 'c':
url_list_action_ = UrlList_CreateNew;
playlist_name_ = QString(optarg);
break;
case 'a':
url_list_action_ = UrlList_Append;
break;
case 'l':
url_list_action_ = UrlList_Load;
break;
case 'o':
show_osd_ = true;
break;
case 'y':
toggle_pretty_osd_ = true;
break;
case 'g':
language_ = QString(optarg);
break;
case VolumeUp:
volume_modifier_ = +4;
break;
case VolumeDown:
volume_modifier_ = -4;
break;
case Quiet:
log_levels_ = "1";
break;
case Verbose:
log_levels_ = "3";
break;
case LogLevels:
log_levels_ = QString(optarg);
break;
case Version: {
QString version_text = QString(kVersionText).arg(STRAWBERRY_VERSION_DISPLAY);
std::cout << version_text.toLocal8Bit().constData() << std::endl;
std::exit(0);
}
case 'v':
set_volume_ = QString(optarg).toInt(&ok);
if (!ok) set_volume_ = -1;
break;
case VolumeIncreaseBy:
volume_modifier_ = QString(optarg).toInt(&ok);
if (!ok) volume_modifier_ = 0;
break;
case VolumeDecreaseBy:
volume_modifier_ = -QString(optarg).toInt(&ok);
if (!ok) volume_modifier_ = 0;
break;
case SeekTo:
seek_to_ = QString(optarg).toInt(&ok);
if (!ok) seek_to_ = -1;
break;
case SeekBy:
seek_by_ = QString(optarg).toInt(&ok);
if (!ok) seek_by_ = 0;
break;
case RestartOrPrevious:
player_action_ = Player_RestartOrPrevious;
break;
case 'k':
play_track_at_ = QString(optarg).toInt(&ok);
if (!ok) play_track_at_ = -1;
break;
case '?':
default:
return false;
}
}
// Get any filenames or URLs following the arguments
for (int i = optind; i < argc_; ++i) {
QString value = QFile::decodeName(argv_[i]);
QFileInfo file_info(value);
if (file_info.exists())
urls_ << QUrl::fromLocalFile(file_info.canonicalFilePath());
else
urls_ << QUrl::fromUserInput(value);
}
return true;
}
bool CommandlineOptions::is_empty() const {
return player_action_ == Player_None &&
set_volume_ == -1 &&
volume_modifier_ == 0 &&
seek_to_ == -1 &&
seek_by_ == 0 &&
play_track_at_ == -1 &&
show_osd_ == false &&
toggle_pretty_osd_ == false &&
urls_.isEmpty();
}
bool CommandlineOptions::contains_play_options() const {
return player_action_ != Player_None || play_track_at_ != -1 || !urls_.isEmpty();
}
QByteArray CommandlineOptions::Serialize() const {
QBuffer buf;
buf.open(QIODevice::WriteOnly);
QDataStream s(&buf);
s << *this;
buf.close();
return buf.data().toBase64();
}
void CommandlineOptions::Load(const QByteArray &serialized) {
QByteArray copy = QByteArray::fromBase64(serialized);
QBuffer buf(&copy);
buf.open(QIODevice::ReadOnly);
QDataStream s(&buf);
s >> *this;
}
QString CommandlineOptions::tr(const char *source_text) {
return QObject::tr(source_text);
}
QDataStream& operator<<(QDataStream &s, const CommandlineOptions &a) {
s << qint32(a.player_action_)
<< qint32(a.url_list_action_)
<< a.set_volume_
<< a.volume_modifier_
<< a.seek_to_
<< a.seek_by_
<< a.play_track_at_
<< a.show_osd_
<< a.urls_
<< a.log_levels_
<< a.toggle_pretty_osd_;
return s;
}
QDataStream& operator>>(QDataStream &s, CommandlineOptions &a) {
quint32 player_action = 0;
quint32 url_list_action = 0;
s >> player_action
>> url_list_action
>> a.set_volume_
>> a.volume_modifier_
>> a.seek_to_
>> a.seek_by_
>> a.play_track_at_
>> a.show_osd_
>> a.urls_
>> a.log_levels_
>> a.toggle_pretty_osd_;
a.player_action_ = CommandlineOptions::PlayerAction(player_action);
a.url_list_action_ = CommandlineOptions::UrlListAction(url_list_action);
return s;
}

View File

@@ -0,0 +1,128 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COMMANDLINEOPTIONS_H
#define COMMANDLINEOPTIONS_H
#include "config.h"
#include <QList>
#include <QUrl>
#include <QDataStream>
class CommandlineOptions {
friend QDataStream &operator<<(QDataStream &s, const CommandlineOptions &a);
friend QDataStream &operator>>(QDataStream &s, CommandlineOptions &a);
public:
explicit CommandlineOptions(int argc = 0, char **argv = nullptr);
static const char *kHelpText;
static const char *kVersionText;
// Don't change the values or order, these get serialised and sent to
// possibly a different version of Strawberry
enum UrlListAction {
UrlList_Append = 0,
UrlList_Load = 1,
UrlList_None = 2,
UrlList_CreateNew = 3,
};
enum PlayerAction {
Player_None = 0,
Player_Play = 1,
Player_PlayPause = 2,
Player_Pause = 3,
Player_Stop = 4,
Player_Previous = 5,
Player_Next = 6,
Player_RestartOrPrevious = 7,
Player_StopAfterCurrent = 8,
};
bool Parse();
bool is_empty() const;
bool contains_play_options() const;
UrlListAction url_list_action() const { return url_list_action_; }
PlayerAction player_action() const { return player_action_; }
int set_volume() const { return set_volume_; }
int volume_modifier() const { return volume_modifier_; }
int seek_to() const { return seek_to_; }
int seek_by() const { return seek_by_; }
int play_track_at() const { return play_track_at_; }
bool show_osd() const { return show_osd_; }
bool toggle_pretty_osd() const { return toggle_pretty_osd_; }
QList<QUrl> urls() const { return urls_; }
QString language() const { return language_; }
QString log_levels() const { return log_levels_; }
QString playlist_name() const { return playlist_name_; }
QByteArray Serialize() const;
void Load(const QByteArray &serialized);
private:
// These are "invalid" characters to pass to getopt_long for options that
// shouldn't have a short (single character) option.
enum LongOptions {
VolumeUp = 256,
VolumeDown,
SeekTo,
SeekBy,
Quiet,
Verbose,
LogLevels,
Version,
VolumeIncreaseBy,
VolumeDecreaseBy,
RestartOrPrevious
};
QString tr(const char *source_text);
void RemoveArg(const QString &starts_with, int count);
private:
int argc_;
char **argv_;
UrlListAction url_list_action_;
PlayerAction player_action_;
// Don't change the type of these.
int set_volume_;
int volume_modifier_;
int seek_to_;
int seek_by_;
int play_track_at_;
bool show_osd_;
bool toggle_pretty_osd_;
QString language_;
QString log_levels_;
QString playlist_name_;
QList<QUrl> urls_;
};
QDataStream &operator<<(QDataStream &s, const CommandlineOptions &a);
QDataStream &operator>>(QDataStream &s, CommandlineOptions &a);
#endif // COMMANDLINEOPTIONS_H

684
src/core/database.cpp Normal file
View File

@@ -0,0 +1,684 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "database.h"
#include "scopedtransaction.h"
#include "utilities.h"
#include "core/application.h"
#include "core/logging.h"
#include "core/taskmanager.h"
#include <boost/scope_exit.hpp>
#include <sqlite3.h>
#include <QCoreApplication>
#include <QDir>
#include <QLibrary>
#include <QLibraryInfo>
#include <QSqlDriver>
#include <QSqlQuery>
#include <QtDebug>
#include <QThread>
#include <QUrl>
#include <QVariant>
const char *Database::kDatabaseFilename = "strawberry.db";
const int Database::kSchemaVersion = 0;
const char *Database::kMagicAllSongsTables = "%allsongstables";
int Database::sNextConnectionId = 1;
QMutex Database::sNextConnectionIdMutex;
Database::Token::Token(const QString &token, int start, int end)
: token(token), start_offset(start), end_offset(end) {}
struct sqlite3_tokenizer_module {
int iVersion;
int (*xCreate)(int argc, /* Size of argv array */
const char *const *argv, /* Tokenizer argument strings */
sqlite3_tokenizer** ppTokenizer); /* OUT: Created tokenizer */
int (*xDestroy)(sqlite3_tokenizer *pTokenizer);
int (*xOpen)(
sqlite3_tokenizer *pTokenizer, /* Tokenizer object */
const char *pInput, int nBytes, /* Input buffer */
sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */
);
int (*xClose)(sqlite3_tokenizer_cursor *pCursor);
int (*xNext)(
sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */
const char* *ppToken, int *pnBytes, /* OUT: Normalized text for token */
int *piStartOffset, /* OUT: Byte offset of token in input buffer */
int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */
int *piPosition); /* OUT: Number of tokens returned before this one */
};
struct sqlite3_tokenizer {
const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */
/* Tokenizer implementations will typically add additional fields */
};
struct sqlite3_tokenizer_cursor {
sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */
/* Tokenizer implementations will typically add additional fields */
};
sqlite3_tokenizer_module *Database::sFTSTokenizer = nullptr;
int Database::FTSCreate(int argc, const char *const *argv, sqlite3_tokenizer **tokenizer) {
*tokenizer = reinterpret_cast<sqlite3_tokenizer*>(new UnicodeTokenizer);
return SQLITE_OK;
}
int Database::FTSDestroy(sqlite3_tokenizer *tokenizer) {
UnicodeTokenizer *real_tokenizer = reinterpret_cast<UnicodeTokenizer*>(tokenizer);
delete real_tokenizer;
return SQLITE_OK;
}
int Database::FTSOpen(sqlite3_tokenizer *pTokenizer, const char *input, int bytes, sqlite3_tokenizer_cursor **cursor) {
UnicodeTokenizerCursor *new_cursor = new UnicodeTokenizerCursor;
new_cursor->pTokenizer = pTokenizer;
new_cursor->position = 0;
QString str = QString::fromUtf8(input, bytes).toLower();
QChar *data = str.data();
// Decompose and strip punctuation.
QList<Token> tokens;
QString token;
int start_offset = 0;
int offset = 0;
for (int i = 0; i < str.length(); ++i) {
QChar c = data[i];
ushort unicode = c.unicode();
if (unicode <= 0x007f) {
offset += 1;
}
else if (unicode >= 0x0080 && unicode <= 0x07ff) {
offset += 2;
}
else if (unicode >= 0x0800) {
offset += 3;
}
// Unicode astral planes unsupported in Qt?
/*else if (unicode >= 0x010000 && unicode <= 0x10ffff) {
offset += 4;
}*/
if (!data[i].isLetterOrNumber()) {
// Token finished.
if (token.length() != 0) {
tokens << Token(token, start_offset, offset - 1);
start_offset = offset;
token.clear();
}
else {
++start_offset;
}
}
else {
if (data[i].decompositionTag() != QChar::NoDecomposition) {
token.push_back(data[i].decomposition()[0]);
} else {
token.push_back(data[i]);
}
}
if (i == str.length() - 1) {
if (token.length() != 0) {
tokens << Token(token, start_offset, offset);
token.clear();
}
}
}
new_cursor->tokens = tokens;
*cursor = reinterpret_cast<sqlite3_tokenizer_cursor*>(new_cursor);
return SQLITE_OK;
}
int Database::FTSClose(sqlite3_tokenizer_cursor *cursor) {
UnicodeTokenizerCursor *real_cursor = reinterpret_cast<UnicodeTokenizerCursor*>(cursor);
delete real_cursor;
return SQLITE_OK;
}
int Database::FTSNext(sqlite3_tokenizer_cursor *cursor, const char* *token, int *bytes, int *start_offset, int *end_offset, int *position) {
UnicodeTokenizerCursor *real_cursor = reinterpret_cast<UnicodeTokenizerCursor*>(cursor);
QList<Token> tokens = real_cursor->tokens;
if (real_cursor->position >= tokens.size()) {
return SQLITE_DONE;
}
Token t = tokens[real_cursor->position];
QByteArray utf8 = t.token.toUtf8();
*token = utf8.constData();
*bytes = utf8.size();
*start_offset = t.start_offset;
*end_offset = t.end_offset;
*position = real_cursor->position++;
real_cursor->current_utf8 = utf8;
return SQLITE_OK;
}
void Database::StaticInit() {
sFTSTokenizer = new sqlite3_tokenizer_module;
sFTSTokenizer->iVersion = 0;
sFTSTokenizer->xCreate = &Database::FTSCreate;
sFTSTokenizer->xDestroy = &Database::FTSDestroy;
sFTSTokenizer->xOpen = &Database::FTSOpen;
sFTSTokenizer->xNext = &Database::FTSNext;
sFTSTokenizer->xClose = &Database::FTSClose;
return;
}
Database::Database(Application *app, QObject *parent, const QString &database_name) :
QObject(parent),
app_(app),
mutex_(QMutex::Recursive),
injected_database_name_(database_name),
query_hash_(0),
startup_schema_version_(-1) {
{
QMutexLocker l(&sNextConnectionIdMutex);
connection_id_ = sNextConnectionId++;
}
directory_ = QDir::toNativeSeparators(Utilities::GetConfigPath(Utilities::Path_Root));
QMutexLocker l(&mutex_);
Connect();
}
QSqlDatabase Database::Connect() {
QMutexLocker l(&connect_mutex_);
// Create the directory if it doesn't exist
if (!QFile::exists(directory_)) {
QDir dir;
if (!dir.mkpath(directory_)) {
}
}
const QString connection_id = QString("%1_thread_%2").arg(connection_id_).arg(reinterpret_cast<quint64>(QThread::currentThread()));
// Try to find an existing connection for this thread
QSqlDatabase db = QSqlDatabase::database(connection_id);
if (db.isOpen()) {
return db;
}
db = QSqlDatabase::addDatabase("QSQLITE", connection_id);
if (!injected_database_name_.isNull())
db.setDatabaseName(injected_database_name_);
else
db.setDatabaseName(directory_ + "/" + kDatabaseFilename);
if (!db.open()) {
app_->AddError("Database: " + db.lastError().text());
return db;
}
// Find Sqlite3 functions in the Qt plugin.
StaticInit();
{
#ifdef SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
// In case sqlite>=3.12 is compiled without -DSQLITE_ENABLE_FTS3_TOKENIZER (generally a good idea due to security reasons) the fts3 support should be enabled explicitly.
QVariant v = db.driver()->handle();
if (v.isValid() && qstrcmp(v.typeName(), "sqlite3*") == 0) {
sqlite3 *handle = *static_cast<sqlite3**>(v.data());
if (handle) sqlite3_db_config(handle, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, NULL);
}
#endif
QSqlQuery set_fts_tokenizer(db);
set_fts_tokenizer.prepare("SELECT fts3_tokenizer(:name, :pointer)");
set_fts_tokenizer.bindValue(":name", "unicode");
set_fts_tokenizer.bindValue(":pointer", QByteArray(reinterpret_cast<const char*>(&sFTSTokenizer), sizeof(&sFTSTokenizer)));
if (!set_fts_tokenizer.exec()) {
qLog(Warning) << "Couldn't register FTS3 tokenizer : " << set_fts_tokenizer.lastError();
}
// Implicit invocation of ~QSqlQuery() when leaving the scope
// to release any remaining database locks!
}
if (db.tables().count() == 0) {
// Set up initial schema
qLog(Info) << "Creating initial database schema";
UpdateDatabaseSchema(0, db);
}
// Attach external databases
for (const QString &key : attached_databases_.keys()) {
QString filename = attached_databases_[key].filename_;
if (!injected_database_name_.isNull()) filename = injected_database_name_;
// Attach the db
QSqlQuery q(db);
q.prepare("ATTACH DATABASE :filename AS :alias");
q.bindValue(":filename", filename);
q.bindValue(":alias", key);
if (!q.exec()) {
qFatal("Couldn't attach external database '%s'", key.toLatin1().constData());
}
}
if (startup_schema_version_ == -1) {
UpdateMainSchema(&db);
}
// We might have to initialise the schema in some attached databases now, if
// they were deleted and don't match up with the main schema version.
for (const QString &key : attached_databases_.keys()) {
if (attached_databases_[key].is_temporary_ && attached_databases_[key].schema_.isEmpty())
continue;
// Find out if there are any tables in this database
QSqlQuery q(db);
q.prepare(QString("SELECT ROWID FROM %1.sqlite_master WHERE type='table'").arg(key));
if (!q.exec() || !q.next()) {
q.finish();
ExecSchemaCommandsFromFile(db, attached_databases_[key].schema_, 0);
}
}
return db;
}
void Database::UpdateMainSchema(QSqlDatabase *db) {
// Get the database's schema version
int schema_version = 0;
{
QSqlQuery q("SELECT version FROM schema_version", *db);
if (q.next()) schema_version = q.value(0).toInt();
// Implicit invocation of ~QSqlQuery() when leaving the scope
// to release any remaining database locks!
}
startup_schema_version_ = schema_version;
if (schema_version > kSchemaVersion) {
qLog(Warning) << "The database schema (version" << schema_version << ") is newer than I was expecting";
return;
}
if (schema_version < kSchemaVersion) {
// Update the schema
for (int v = schema_version + 1; v <= kSchemaVersion; ++v) {
UpdateDatabaseSchema(v, *db);
}
}
}
void Database::RecreateAttachedDb(const QString &database_name) {
if (!attached_databases_.contains(database_name)) {
qLog(Warning) << "Attached database does not exist:" << database_name;
return;
}
const QString filename = attached_databases_[database_name].filename_;
QMutexLocker l(&mutex_);
{
QSqlDatabase db(Connect());
QSqlQuery q(db);
q.prepare("DETACH DATABASE :alias");
q.bindValue(":alias", database_name);
if (!q.exec()) {
qLog(Warning) << "Failed to detach database" << database_name;
return;
}
if (!QFile::remove(filename)) {
qLog(Warning) << "Failed to remove file" << filename;
}
}
// We can't just re-attach the database now because it needs to be done for
// each thread. Close all the database connections, so each thread will
// re-attach it when they next connect.
for (const QString &name : QSqlDatabase::connectionNames()) {
QSqlDatabase::removeDatabase(name);
}
}
void Database::AttachDatabase(const QString &database_name, const AttachedDatabase &database) {
attached_databases_[database_name] = database;
}
void Database::AttachDatabaseOnDbConnection(const QString &database_name, const AttachedDatabase &database, QSqlDatabase &db) {
AttachDatabase(database_name, database);
// Attach the db
QSqlQuery q(db);
q.prepare("ATTACH DATABASE :filename AS :alias");
q.bindValue(":filename", database.filename_);
q.bindValue(":alias", database_name);
if (!q.exec()) {
qFatal("Couldn't attach external database '%s'", database_name.toLatin1().constData());
}
}
void Database::DetachDatabase(const QString &database_name) {
QMutexLocker l(&mutex_);
{
QSqlDatabase db(Connect());
QSqlQuery q(db);
q.prepare("DETACH DATABASE :alias");
q.bindValue(":alias", database_name);
if (!q.exec()) {
qLog(Warning) << "Failed to detach database" << database_name;
return;
}
}
attached_databases_.remove(database_name);
}
void Database::UpdateDatabaseSchema(int version, QSqlDatabase &db) {
QString filename;
if (version == 0) filename = ":/schema/schema.sql";
else filename = QString(":/schema/schema-%1.sql").arg(version);
qLog(Debug) << "Applying database schema update" << version << "from" << filename;
ExecSchemaCommandsFromFile(db, filename, version - 1);
}
void Database::UrlEncodeFilenameColumn(const QString &table, QSqlDatabase &db) {
QSqlQuery select(db);
select.prepare(QString("SELECT ROWID, filename FROM %1").arg(table));
QSqlQuery update(db);
update.prepare(QString("UPDATE %1 SET filename=:filename WHERE ROWID=:id").arg(table));
select.exec();
if (CheckErrors(select)) return;
while (select.next()) {
const int rowid = select.value(0).toInt();
const QString filename = select.value(1).toString();
if (filename.isEmpty() || filename.contains("://")) {
continue;
}
const QUrl url = QUrl::fromLocalFile(filename);
update.bindValue(":filename", url.toEncoded());
update.bindValue(":id", rowid);
update.exec();
CheckErrors(update);
}
}
void Database::ExecSchemaCommandsFromFile(QSqlDatabase &db, const QString &filename, int schema_version, bool in_transaction) {
// Open and read the database schema
QFile schema_file(filename);
if (!schema_file.open(QIODevice::ReadOnly))
qFatal("Couldn't open schema file %s", filename.toUtf8().constData());
ExecSchemaCommands(db, QString::fromUtf8(schema_file.readAll()), schema_version, in_transaction);
}
void Database::ExecSchemaCommands(QSqlDatabase &db, const QString &schema, int schema_version, bool in_transaction) {
// Run each command
const QStringList commands(schema.split(QRegExp("; *\n\n")));
// We don't want this list to reflect possible DB schema changes
// so we initialize it before executing any statements.
// If no outer transaction is provided the song tables need to
// be queried before beginning an inner transaction! Otherwise
// DROP TABLE commands on song tables may fail due to database
// locks.
const QStringList song_tables(SongsTables(db, schema_version));
if (!in_transaction) {
ScopedTransaction inner_transaction(&db);
ExecSongTablesCommands(db, song_tables, commands);
inner_transaction.Commit();
}
else {
ExecSongTablesCommands(db, song_tables, commands);
}
}
void Database::ExecSongTablesCommands(QSqlDatabase &db, const QStringList &song_tables, const QStringList &commands) {
for (const QString &command : commands) {
// There are now lots of "songs" tables that need to have the same schema:
// songs, magnatune_songs, and device_*_songs. We allow a magic value
// in the schema files to update all songs tables at once.
if (command.contains(kMagicAllSongsTables)) {
for (const QString &table : song_tables) {
// Another horrible hack: device songs tables don't have matching _fts
// tables, so if this command tries to touch one, ignore it.
if (table.startsWith("device_") &&
command.contains(QString(kMagicAllSongsTables) + "_fts")) {
continue;
}
qLog(Info) << "Updating" << table << "for" << kMagicAllSongsTables;
QString new_command(command);
new_command.replace(kMagicAllSongsTables, table);
QSqlQuery query(db.exec(new_command));
if (CheckErrors(query))
qFatal("Unable to update music collection database");
}
} else {
QSqlQuery query(db.exec(command));
if (CheckErrors(query)) qFatal("Unable to update music collection database");
}
}
}
QStringList Database::SongsTables(QSqlDatabase &db, int schema_version) const {
QStringList ret;
// look for the tables in the main db
for (const QString &table : db.tables()) {
if (table == "songs" || table.endsWith("_songs")) ret << table;
}
// look for the tables in attached dbs
for (const QString &key : attached_databases_.keys()) {
QSqlQuery q(db);
q.prepare(QString("SELECT NAME FROM %1.sqlite_master WHERE type='table' AND name='songs' OR name LIKE '%songs'").arg(key));
if (q.exec()) {
while (q.next()) {
QString tab_name = key + "." + q.value(0).toString();
ret << tab_name;
}
}
}
ret << "playlist_items";
return ret;
}
bool Database::CheckErrors(const QSqlQuery &query) {
QSqlError last_error = query.lastError();
if (last_error.isValid()) {
qLog(Error) << "db error: " << last_error;
qLog(Error) << "faulty query: " << query.lastQuery();
qLog(Error) << "bound values: " << query.boundValues();
return true;
}
return false;
}
bool Database::IntegrityCheck(QSqlDatabase db) {
qLog(Debug) << "Starting database integrity check";
int task_id = app_->task_manager()->StartTask(tr("Integrity check"));
bool ok = false;
bool error_reported = false;
// Ask for 10 error messages at most.
QSqlQuery q(QString("PRAGMA integrity_check(10)"), db);
while (q.next()) {
QString message = q.value(0).toString();
// If no errors are found, a single row with the value "ok" is returned
if (message == "ok") {
ok = true;
break;
} else {
if (!error_reported) { app_->AddError(tr("Database corruption detected.")); }
app_->AddError("Database: " + message);
error_reported = true;
}
}
app_->task_manager()->SetTaskFinished(task_id);
return ok;
}
void Database::DoBackup() {
QSqlDatabase db(this->Connect());
// Before we overwrite anything, make sure the database is not corrupt
QMutexLocker l(&mutex_);
const bool ok = IntegrityCheck(db);
if (ok) {
BackupFile(db.databaseName());
}
}
bool Database::OpenDatabase(const QString &filename, sqlite3 **connection) const {
int ret = sqlite3_open(filename.toUtf8(), connection);
if (ret != 0) {
if (*connection) {
const char *error_message = sqlite3_errmsg(*connection);
qLog(Error) << "Failed to open database for backup:" << filename << error_message;
}
else {
qLog(Error) << "Failed to open database for backup:" << filename;
}
return false;
}
return true;
}
void Database::BackupFile(const QString &filename) {
qLog(Debug) << "Starting database backup";
QString dest_filename = QString("%1.bak").arg(filename);
const int task_id = app_->task_manager()->StartTask(tr("Backing up database"));
sqlite3 *source_connection = nullptr;
sqlite3 *dest_connection = nullptr;
BOOST_SCOPE_EXIT((source_connection)(dest_connection)(task_id)(app_)) {
// Harmless to call sqlite3_close() with a nullptr pointer.
sqlite3_close(source_connection);
sqlite3_close(dest_connection);
app_->task_manager()->SetTaskFinished(task_id);
}
BOOST_SCOPE_EXIT_END
bool success = OpenDatabase(filename, &source_connection);
if (!success) {
return;
}
success = OpenDatabase(dest_filename, &dest_connection);
if (!success) {
return;
}
sqlite3_backup *backup = sqlite3_backup_init(dest_connection, "main", source_connection, "main");
if (!backup) {
const char *error_message = sqlite3_errmsg(dest_connection);
qLog(Error) << "Failed to start database backup:" << error_message;
return;
}
int ret = SQLITE_OK;
do {
ret = sqlite3_backup_step(backup, 16);
const int page_count = sqlite3_backup_pagecount(backup);
app_->task_manager()->SetTaskProgress(
task_id, page_count - sqlite3_backup_remaining(backup), page_count);
} while (ret == SQLITE_OK);
if (ret != SQLITE_DONE) {
qLog(Error) << "Database backup failed";
}
sqlite3_backup_finish(backup);
}

175
src/core/database.h Normal file
View File

@@ -0,0 +1,175 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef DATABASE_H
#define DATABASE_H
#include "config.h"
#include <QMap>
#include <QMutex>
#include <QObject>
#include <QSqlDatabase>
#include <QSqlError>
#include <QStringList>
#include <sqlite3.h>
#include "gtest/gtest_prod.h"
extern "C" {
struct sqlite3_tokenizer;
struct sqlite3_tokenizer_cursor;
struct sqlite3_tokenizer_module;
}
class Application;
class Database : public QObject {
Q_OBJECT
public:
Database(Application *app, QObject *parent = nullptr, const QString &database_name = QString());
struct AttachedDatabase {
AttachedDatabase() {}
AttachedDatabase(const QString &filename, const QString &schema, bool is_temporary)
: filename_(filename), schema_(schema), is_temporary_(is_temporary) {}
QString filename_;
QString schema_;
bool is_temporary_;
};
static const int kSchemaVersion;
static const char *kDatabaseFilename;
static const char *kMagicAllSongsTables;
QSqlDatabase Connect();
bool CheckErrors(const QSqlQuery &query);
QMutex *Mutex() { return &mutex_; }
void RecreateAttachedDb(const QString &database_name);
void ExecSchemaCommands(QSqlDatabase &db, const QString &schema, int schema_version, bool in_transaction = false);
int startup_schema_version() const { return startup_schema_version_; }
int current_schema_version() const { return kSchemaVersion; }
void AttachDatabase(const QString &database_name, const AttachedDatabase &database);
void AttachDatabaseOnDbConnection(const QString &database_name, const AttachedDatabase &database, QSqlDatabase &db);
void DetachDatabase(const QString &database_name);
signals:
void Error(const QString &message);
public slots:
void DoBackup();
private:
void UpdateMainSchema(QSqlDatabase *db);
void ExecSchemaCommandsFromFile(QSqlDatabase &db, const QString &filename, int schema_version, bool in_transaction = false);
void ExecSongTablesCommands(QSqlDatabase &db, const QStringList &song_tables, const QStringList &commands);
void UpdateDatabaseSchema(int version, QSqlDatabase &db);
void UrlEncodeFilenameColumn(const QString &table, QSqlDatabase &db);
QStringList SongsTables(QSqlDatabase &db, int schema_version) const;
bool IntegrityCheck(QSqlDatabase db);
void BackupFile(const QString &filename);
bool OpenDatabase(const QString &filename, sqlite3 **connection) const;
Application *app_;
// Alias -> filename
QMap<QString, AttachedDatabase> attached_databases_;
QString directory_;
QMutex connect_mutex_;
QMutex mutex_;
// This ID makes the QSqlDatabase name unique to the object as well as the
// thread
int connection_id_;
static QMutex sNextConnectionIdMutex;
static int sNextConnectionId;
// Used by tests
QString injected_database_name_;
uint query_hash_;
QStringList query_cache_;
// This is the schema version of Strawberry's DB from the app's last run.
int startup_schema_version_;
FRIEND_TEST(DatabaseTest, FTSOpenParsesSimpleInput);
FRIEND_TEST(DatabaseTest, FTSOpenParsesUTF8Input);
FRIEND_TEST(DatabaseTest, FTSOpenParsesMultipleTokens);
FRIEND_TEST(DatabaseTest, FTSCursorWorks);
FRIEND_TEST(DatabaseTest, FTSOpenLeavesCyrillicQueries);
// Do static initialisation like loading sqlite functions.
static void StaticInit();
typedef int (*Sqlite3CreateFunc)(sqlite3*, const char*, int, int, void*, void (*)(sqlite3_context*, int, sqlite3_value**), void (*)(sqlite3_context*, int, sqlite3_value**), void (*)(sqlite3_context*));
static sqlite3_tokenizer_module *sFTSTokenizer;
static int FTSCreate(int argc, const char *const *argv, sqlite3_tokenizer **tokenizer);
static int FTSDestroy(sqlite3_tokenizer *tokenizer);
static int FTSOpen(sqlite3_tokenizer *tokenizer, const char *input, int bytes, sqlite3_tokenizer_cursor **cursor);
static int FTSClose(sqlite3_tokenizer_cursor *cursor);
static int FTSNext(sqlite3_tokenizer_cursor *cursor, const char **token, int *bytes, int *start_offset, int *end_offset, int *position);
struct Token {
Token(const QString &token, int start, int end);
QString token;
int start_offset;
int end_offset;
};
// Based on sqlite3_tokenizer.
struct UnicodeTokenizer {
const sqlite3_tokenizer_module *pModule;
};
struct UnicodeTokenizerCursor {
const sqlite3_tokenizer *pTokenizer;
QList<Token> tokens;
int position;
QByteArray current_utf8;
};
};
class MemoryDatabase : public Database {
public:
MemoryDatabase(Application *app, QObject *parent = nullptr)
: Database(app, parent, ":memory:") {}
~MemoryDatabase() {
// Make sure Qt doesn't reuse the same database
QSqlDatabase::removeDatabase(Connect().connectionName());
}
};
#endif // DATABASE_H

View File

@@ -0,0 +1,47 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "dbusscreensaver.h"
#include <QCoreApplication>
#include <QDBusInterface>
#include <QDBusReply>
DBusScreensaver::DBusScreensaver(const QString &service, const QString &path, const QString &interface)
: service_(service), path_(path), interface_(interface) {}
void DBusScreensaver::Inhibit() {
QDBusInterface gnome_screensaver("org.gnome.ScreenSaver", "/", "org.gnome.ScreenSaver");
QDBusReply<quint32> reply = gnome_screensaver.call("Inhibit", QCoreApplication::applicationName(), QObject::tr("Visualizations"));
if (reply.isValid()) {
cookie_ = reply.value();
}
}
void DBusScreensaver::Uninhibit() {
QDBusInterface gnome_screensaver("org.gnome.ScreenSaver", "/", "org.gnome.ScreenSaver");
gnome_screensaver.call("UnInhibit", cookie_);
}

View File

@@ -0,0 +1,45 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef DBUSSCREENSAVER_H
#define DBUSSCREENSAVER_H
#include "config.h"
#include <QString>
#include "screensaver.h"
class DBusScreensaver : public Screensaver {
public:
DBusScreensaver(const QString &service, const QString &path, const QString &interface);
void Inhibit();
void Uninhibit();
private:
QString service_;
QString path_;
QString interface_;
quint32 cookie_;
};
#endif

123
src/core/deletefiles.cpp Normal file
View File

@@ -0,0 +1,123 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "deletefiles.h"
#include <QStringList>
#include <QTimer>
#include <QThread>
#include <QUrl>
#include "musicstorage.h"
#include "taskmanager.h"
const int DeleteFiles::kBatchSize = 50;
DeleteFiles::DeleteFiles(TaskManager* task_manager, std::shared_ptr<MusicStorage> storage)
: thread_(nullptr),
task_manager_(task_manager),
storage_(storage),
started_(false),
task_id_(0),
progress_(0) {
original_thread_ = thread();
}
DeleteFiles::~DeleteFiles() {}
void DeleteFiles::Start(const SongList &songs) {
if (thread_) return;
songs_ = songs;
task_id_ = task_manager_->StartTask(tr("Deleting files"));
task_manager_->SetTaskBlocksCollectionScans(true);
thread_ = new QThread;
connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles()));
moveToThread(thread_);
thread_->start();
}
void DeleteFiles::Start(const QStringList &filenames) {
SongList songs;
for (const QString &filename : filenames) {
Song song;
song.set_url(QUrl::fromLocalFile(filename));
songs << song;
}
Start(songs);
}
void DeleteFiles::ProcessSomeFiles() {
if (!started_) {
storage_->StartDelete();
started_ = true;
}
// None left?
if (progress_ >= songs_.count()) {
task_manager_->SetTaskProgress(task_id_, progress_, songs_.count());
storage_->FinishCopy(songs_with_errors_.isEmpty());
task_manager_->SetTaskFinished(task_id_);
emit Finished(songs_with_errors_);
// Move back to the original thread so deleteLater() can get called in
// the main thread's event loop
moveToThread(original_thread_);
deleteLater();
// Stop this thread
thread_->quit();
return;
}
// We process files in batches so we can be cancelled part-way through.
const int n = qMin(songs_.count(), progress_ + kBatchSize);
for (; progress_ < n; ++progress_) {
task_manager_->SetTaskProgress(task_id_, progress_, songs_.count());
const Song &song = songs_[progress_];
MusicStorage::DeleteJob job;
job.metadata_ = song;
if (!storage_->DeleteFromStorage(job)) {
songs_with_errors_ << song;
}
}
QTimer::singleShot(0, this, SLOT(ProcessSomeFiles()));
}

70
src/core/deletefiles.h Normal file
View File

@@ -0,0 +1,70 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef DELETEFILES_H
#define DELETEFILES_H
#include "config.h"
#include <memory>
#include <QObject>
#include "song.h"
class MusicStorage;
class TaskManager;
class DeleteFiles : public QObject {
Q_OBJECT
public:
DeleteFiles(TaskManager *task_manager, std::shared_ptr<MusicStorage> storage);
~DeleteFiles();
static const int kBatchSize;
void Start(const SongList& songs);
void Start(const QStringList& filenames);
signals:
void Finished(const SongList& songs_with_errors);
private slots:
void ProcessSomeFiles();
private:
QThread *thread_;
QThread *original_thread_;
TaskManager *task_manager_;
std::shared_ptr<MusicStorage> storage_;
SongList songs_;
bool started_;
int task_id_;
int progress_;
SongList songs_with_errors_;
};
#endif // DELETEFILES_H

View File

@@ -0,0 +1,71 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "filesystemmusicstorage.h"
#include "core/logging.h"
#include "core/utilities.h"
#include <QDir>
#include <QFile>
#include <QUrl>
FilesystemMusicStorage::FilesystemMusicStorage(const QString &root)
: root_(root) {}
bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
const QFileInfo src = QFileInfo(job.source_);
const QFileInfo dest = QFileInfo(root_ + "/" + job.destination_);
// Don't do anything if the destination is the same as the source
if (src == dest) return true;
// Create directories as required
QDir dir;
if (!dir.mkpath(dest.absolutePath())) {
qLog(Warning) << "Failed to create directory" << dest.dir().absolutePath();
return false;
}
// Remove the destination file if it exists and we want to overwrite
if (job.overwrite_ && dest.exists()) QFile::remove(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 FilesystemMusicStorage::DeleteFromStorage(const DeleteJob &job) {
QString path = job.metadata_.url().toLocalFile();
QFileInfo fileInfo(path);
if (fileInfo.isDir())
return Utilities::RemoveRecursive(path);
else
return QFile::remove(path);
}

View File

@@ -0,0 +1,43 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef FILESYSTEMMUSICSTORAGE_H
#define FILESYSTEMMUSICSTORAGE_H
#include "config.h"
#include "musicstorage.h"
class FilesystemMusicStorage : public virtual MusicStorage {
public:
explicit FilesystemMusicStorage(const QString &root);
~FilesystemMusicStorage() {}
QString LocalPath() const { return root_; }
bool CopyToStorage(const CopyJob &job);
bool DeleteFromStorage(const DeleteJob &job);
private:
QString root_;
};
#endif // FILESYSTEMMUSICSTORAGE_H

View File

@@ -0,0 +1,46 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "filesystemwatcherinterface.h"
#include "qtfslistener.h"
#ifdef Q_OS_DARWIN
#include "macfslistener.h"
#endif
FileSystemWatcherInterface::FileSystemWatcherInterface(QObject *parent)
: QObject(parent) {}
FileSystemWatcherInterface *FileSystemWatcherInterface::Create(QObject *parent) {
FileSystemWatcherInterface *ret;
#ifdef Q_OS_DARWIN
ret = new MacFSListener(parent);
#else
ret = new QtFSListener(parent);
#endif
ret->Init();
return ret;
}

View File

@@ -0,0 +1,44 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef FILESYSTEMWATCHERINTERFACE_H
#define FILESYSTEMWATCHERINTERFACE_H
#include "config.h"
#include <QObject>
class FileSystemWatcherInterface : public QObject {
Q_OBJECT
public:
explicit FileSystemWatcherInterface(QObject *parent = nullptr);
virtual void Init() {}
virtual void AddPath(const QString& path) = 0;
virtual void RemovePath(const QString& path) = 0;
virtual void Clear() = 0;
static FileSystemWatcherInterface* Create(QObject *parent = nullptr);
signals:
void PathChanged(const QString &path);
};
#endif

183
src/core/flowlayout.cpp Normal file
View File

@@ -0,0 +1,183 @@
/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
** the names of its contributors may be used to endorse or promote
** products derived from this software without specific prior written
** permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QWidget>
#include "flowlayout.h"
//! [1]
FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing)
: QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing)
{
setContentsMargins(margin, margin, margin, margin);
}
FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing)
: m_hSpace(hSpacing), m_vSpace(vSpacing) {
setContentsMargins(margin, margin, margin, margin);
}
//! [1]
//! [2]
FlowLayout::~FlowLayout() {
QLayoutItem* item;
while ((item = takeAt(0))) delete item;
}
//! [2]
//! [3]
void FlowLayout::addItem(QLayoutItem* item) { itemList.append(item); }
//! [3]
//! [4]
int FlowLayout::horizontalSpacing() const {
if (m_hSpace >= 0) {
return m_hSpace;
} else {
return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
}
}
int FlowLayout::verticalSpacing() const {
if (m_vSpace >= 0) {
return m_vSpace;
} else {
return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
}
}
//! [4]
//! [5]
int FlowLayout::count() const { return itemList.size(); }
QLayoutItem* FlowLayout::itemAt(int index) const {
return itemList.value(index);
}
QLayoutItem* FlowLayout::takeAt(int index) {
if (index >= 0 && index < itemList.size())
return itemList.takeAt(index);
else
return 0;
}
//! [5]
//! [6]
Qt::Orientations FlowLayout::expandingDirections() const { return 0; }
//! [6]
//! [7]
bool FlowLayout::hasHeightForWidth() const { return true; }
int FlowLayout::heightForWidth(int width) const {
int height = doLayout(QRect(0, 0, width, 0), true);
return height;
}
//! [7]
//! [8]
void FlowLayout::setGeometry(const QRect& rect) {
QLayout::setGeometry(rect);
doLayout(rect, false);
}
QSize FlowLayout::sizeHint() const { return minimumSize(); }
QSize FlowLayout::minimumSize() const {
QSize size;
for (QLayoutItem* item : itemList)
size = size.expandedTo(item->minimumSize());
size += QSize(2 * margin(), 2 * margin());
return size;
}
//! [8]
//! [9]
int FlowLayout::doLayout(const QRect& rect, bool testOnly) const {
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
//! [9]
//! [10]
for (QLayoutItem* item : itemList) {
QWidget* wid = item->widget();
int spaceX = horizontalSpacing();
if (spaceX == -1)
spaceX = wid->style()->layoutSpacing(
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
int spaceY = verticalSpacing();
if (spaceY == -1)
spaceY = wid->style()->layoutSpacing(
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
//! [10]
//! [11]
int nextX = x + item->sizeHint().width() + spaceX;
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
x = effectiveRect.x();
y = y + lineHeight + spaceY;
nextX = x + item->sizeHint().width() + spaceX;
lineHeight = 0;
}
if (!testOnly) item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
x = nextX;
lineHeight = qMax(lineHeight, item->sizeHint().height());
}
return y + lineHeight - rect.y() + bottom;
}
//! [11]
//! [12]
int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const {
QObject* parent = this->parent();
if (!parent) {
return -1;
} else if (parent->isWidgetType()) {
QWidget *pw = static_cast<QWidget *>(parent);
return pw->style()->pixelMetric(pm, 0, pw);
} else {
return static_cast<QLayout *>(parent)->spacing();
}
}
//! [12]

79
src/core/flowlayout.h Normal file
View File

@@ -0,0 +1,79 @@
/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
** the names of its contributors may be used to endorse or promote
** products derived from this software without specific prior written
** permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef FLOWLAYOUT_H
#define FLOWLAYOUT_H
#include <QLayout>
#include <QRect>
#include <QStyle>
#include <QWidgetItem>
//! [0]
class FlowLayout : public QLayout {
public:
FlowLayout(QWidget* parent, int margin = -1, int hSpacing = -1,
int vSpacing = -1);
FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1);
~FlowLayout();
void addItem(QLayoutItem *item);
int horizontalSpacing() const;
int verticalSpacing() const;
Qt::Orientations expandingDirections() const;
bool hasHeightForWidth() const;
int heightForWidth(int) const;
int count() const;
QLayoutItem *itemAt(int index) const;
QSize minimumSize() const;
void setGeometry(const QRect &rect);
QSize sizeHint() const;
QLayoutItem *takeAt(int index);
private:
int doLayout(const QRect &rect, bool testOnly) const;
int smartSpacing(QStyle::PixelMetric pm) const;
QList<QLayoutItem *> itemList;
int m_hSpace;
int m_vSpace;
};
//! [0]
#endif

80
src/core/iconloader.cpp Normal file
View File

@@ -0,0 +1,80 @@
/*
* Strawberry Music Player
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QFile>
#include <QDir>
#include <QtDebug>
#include <QSettings>
#include "iconloader.h"
#include "core/logging.h"
#include "core/appearance.h"
QList<int> IconLoader::sizes_;
QString IconDefault(":/icons/64x64/strawberry.png");
void IconLoader::Init() {
//qLog(Debug) << __PRETTY_FUNCTION__;
sizes_.clear();
sizes_ << 22 << 32 << 48 << 64;
if (!QFile::exists(IconDefault)) {
qLog(Error) << "Default icon does not exist" << IconDefault;
}
}
QIcon IconLoader::Load(const QString &name) {
QIcon ret;
//qLog(Debug) << __PRETTY_FUNCTION__ << name;
if (name.isEmpty()) {
qLog(Warning) << "Icon name is empty!";
ret.addFile(IconDefault, QSize(64, 64));
return ret;
}
const QString path(":icons/%1x%2/%3.png");
for (int size : sizes_) {
QString filename(path.arg(size).arg(size).arg(name));
if (QFile::exists(filename)) ret.addFile(filename, QSize(size, size));
}
// Load icon from system theme only if it hasn't been found
if (ret.isNull()) {
ret = QIcon::fromTheme(name);
if (!ret.isNull()) return ret;
qLog(Warning) << "Couldn't load icon" << name;
}
if (ret.isNull()) {
ret.addFile(IconDefault, QSize(64, 64));
}
return ret;
}

38
src/core/iconloader.h Normal file
View File

@@ -0,0 +1,38 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ICONLOADER_H
#define ICONLOADER_H
#include <QIcon>
class IconLoader {
public:
static void Init();
static QIcon Load(const QString &name);
private:
IconLoader() {}
static QList<int> sizes_;
};
#endif // ICONLOADER_H

33
src/core/mac_delegate.h Normal file
View File

@@ -0,0 +1,33 @@
#import <AppKit/NSApplication.h>
#include "config.h"
#include "macglobalshortcutbackend.h"
class PlatformInterface;
@class SPMediaKeyTap;
@interface AppDelegate : NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate> {
PlatformInterface* application_handler_;
NSMenu* dock_menu_;
MacGlobalShortcutBackend* shortcut_handler_;
SPMediaKeyTap* key_tap_;
}
- (id) initWithHandler: (PlatformInterface*)handler;
// NSApplicationDelegate
- (BOOL) applicationShouldHandleReopen: (NSApplication*)app hasVisibleWindows:(BOOL)flag;
- (NSMenu*) applicationDockMenu: (NSApplication*)sender;
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification;
- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication*)sender;
// NSUserNotificationCenterDelegate
- (BOOL) userNotificationCenter: (id)center
shouldPresentNotification: (id)notification;
- (void) setDockMenu: (NSMenu*)menu;
- (MacGlobalShortcutBackend*) shortcut_handler;
- (void) setShortcutHandler: (MacGlobalShortcutBackend*)backend;
- (void) mediaKeyTap: (SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event;
@end

37
src/core/mac_startup.h Normal file
View File

@@ -0,0 +1,37 @@
#ifndef MAC_STARTUP_H
#define MAC_STARTUP_H
#include "config.h"
#include <QKeySequence>
class MacGlobalShortcutBackend;
class QObject;
class QWidget;
class PlatformInterface {
public:
// Called when the application should show itself.
virtual void Activate() = 0;
virtual bool LoadUrl(const QString& url) = 0;
virtual ~PlatformInterface() {}
};
namespace mac {
void MacMain();
void SetShortcutHandler(MacGlobalShortcutBackend* handler);
void SetApplicationHandler(PlatformInterface* handler);
void CheckForUpdates();
QString GetBundlePath();
QString GetResourcesPath();
QString GetApplicationSupportPath();
QString GetMusicDirectory();
void EnableFullScreen(const QWidget& main_window);
} // namespace mac
#endif

457
src/core/mac_startup.mm Normal file
View File

@@ -0,0 +1,457 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#import <AppKit/NSApplication.h>
#import <AppKit/NSEvent.h>
#import <AppKit/NSGraphics.h>
#import <AppKit/NSNibDeclarations.h>
#import <AppKit/NSViewController.h>
#import <Foundation/NSBundle.h>
#import <Foundation/NSError.h>
#import <Foundation/NSFileManager.h>
#import <Foundation/NSPathUtilities.h>
#import <Foundation/NSProcessInfo.h>
#import <Foundation/NSThread.h>
#import <Foundation/NSTimer.h>
#import <Foundation/NSURL.h>
#import <IOKit/hidsystem/ev_keymap.h>
#import <Kernel/AvailabilityMacros.h>
#import <QuartzCore/CALayer.h>
#import "3rdparty/SPMediaKeyTap/SPMediaKeyTap.h"
#include "config.h"
#include "globalshortcuts.h"
#include "mac_delegate.h"
#include "mac_startup.h"
#include "mac_utilities.h"
#include "macglobalshortcutbackend.h"
#include "utilities.h"
#include "core/logging.h"
#include "core/scoped_cftyperef.h"
#include "core/scoped_nsautorelease_pool.h"
#ifdef HAVE_SPARKLE
#import <Sparkle/SUUpdater.h>
#endif
#include <QApplication>
#include <QCoreApplication>
#include <QDir>
#include <QEvent>
#include <QFile>
#include <QSettings>
#include <QWidget>
#include <QtDebug>
QDebug operator<<(QDebug dbg, NSObject* object) {
QString ns_format = [[NSString stringWithFormat:@"%@", object] UTF8String];
dbg.nospace() << ns_format;
return dbg.space();
}
// Capture global media keys on Mac (Cocoa only!)
// See: http://www.rogueamoeba.com/utm/2007/09/29/apple-keyboard-media-key-event-handling/
@interface MacApplication : NSApplication {
PlatformInterface* application_handler_;
AppDelegate* delegate_;
// shortcut_handler_ only used to temporarily save it
// AppDelegate does all the heavy-shortcut-lifting
MacGlobalShortcutBackend* shortcut_handler_;
}
- (MacGlobalShortcutBackend*)shortcut_handler;
- (void)SetShortcutHandler:(MacGlobalShortcutBackend*)handler;
- (PlatformInterface*)application_handler;
- (void)SetApplicationHandler:(PlatformInterface*)handler;
@end
#ifdef HAVE_BREAKPAD
static bool BreakpadCallback(int, int, mach_port_t, void*) { return true; }
static BreakpadRef InitBreakpad() {
ScopedNSAutoreleasePool pool;
BreakpadRef breakpad = nil;
NSDictionary* plist = [[NSBundle mainBundle] infoDictionary];
if (plist) {
breakpad = BreakpadCreate(plist);
BreakpadSetFilterCallback(breakpad, &BreakpadCallback, nullptr);
}
[pool release];
return breakpad;
}
#endif // HAVE_BREAKPAD
@implementation AppDelegate
- (id)init {
if ((self = [super init])) {
application_handler_ = nil;
shortcut_handler_ = nil;
dock_menu_ = nil;
}
return self;
}
- (id)initWithHandler:(PlatformInterface*)handler {
application_handler_ = handler;
#ifdef HAVE_BREAKPAD
breakpad_ = InitBreakpad();
#endif
// Register defaults for the whitelist of apps that want to use media keys
[[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
[SPMediaKeyTap defaultMediaKeyUserBundleIdentifiers], kMediaKeyUsingBundleIdentifiersDefaultsKey,
nil]];
return self;
}
- (BOOL) applicationShouldHandleReopen: (NSApplication*)app hasVisibleWindows:(BOOL)flag {
if (application_handler_) {
application_handler_->Activate();
}
return YES;
}
- (void)setDockMenu:(NSMenu*)menu {
dock_menu_ = menu;
}
- (NSMenu*)applicationDockMenu:(NSApplication*)sender {
return dock_menu_;
}
- (void)setShortcutHandler:(MacGlobalShortcutBackend*)backend {
shortcut_handler_ = backend;
}
- (MacGlobalShortcutBackend*)shortcut_handler {
return shortcut_handler_;
}
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification {
key_tap_ = [[SPMediaKeyTap alloc] initWithDelegate:self];
if ([SPMediaKeyTap usesGlobalMediaKeyTap] &&
![[NSProcessInfo processInfo]
isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){
.majorVersion = 10,
.minorVersion = 12,
.patchVersion = 0}]) {
[key_tap_ startWatchingMediaKeys];
}
else {
qLog(Warning) << "Media key monitoring disabled";
}
}
- (BOOL)application:(NSApplication*)app openFile:(NSString*)filename {
qLog(Debug) << "Wants to open:" << [filename UTF8String];
if (application_handler_->LoadUrl(QString::fromUtf8([filename UTF8String]))) {
return YES;
}
return NO;
}
- (void)application:(NSApplication*)app openFiles:(NSArray*)filenames {
qLog(Debug) << "Wants to open:" << filenames;
[filenames enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL* stop) {
[self application:app openFile:(NSString*)object];
}];
}
- (void) mediaKeyTap: (SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event {
NSAssert([event type] == NSSystemDefined && [event subtype] == SPSystemDefinedEventMediaKeys, @"Unexpected NSEvent in mediaKeyTap:receivedMediaKeyEvent:");
int key_code = (([event data1] & 0xFFFF0000) >> 16);
int key_flags = ([event data1] & 0x0000FFFF);
BOOL key_is_released = (((key_flags & 0xFF00) >> 8)) == 0xB;
// not used. keep just in case
// int key_repeat = (key_flags & 0x1);
if (!shortcut_handler_) {
return;
}
if (key_is_released) {
shortcut_handler_->MacMediaKeyPressed(key_code);
}
}
- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication*) sender {
#ifdef HAVE_BREAKPAD
BreakpadRelease(breakpad_);
#endif
return NSTerminateNow;
}
- (BOOL) userNotificationCenter: (id)center shouldPresentNotification: (id)notification {
// Always show notifications, even if Strawberry is in the foreground.
return YES;
}
@end
@implementation MacApplication
- (id)init {
if ((self = [super init])) {
[self SetShortcutHandler:nil];
}
return self;
}
- (MacGlobalShortcutBackend*)shortcut_handler {
// should be the same as delegate_'s shortcut handler
return shortcut_handler_;
}
- (void)SetShortcutHandler:(MacGlobalShortcutBackend*)handler {
shortcut_handler_ = handler;
if (delegate_) [delegate_ setShortcutHandler:handler];
}
- (PlatformInterface*)application_handler {
return application_handler_;
}
- (void)SetApplicationHandler:(PlatformInterface*)handler {
delegate_ = [[AppDelegate alloc] initWithHandler:handler];
// App-shortcut-handler set before delegate is set.
// this makes sure the delegate's shortcut_handler is set
[delegate_ setShortcutHandler:shortcut_handler_];
[self setDelegate:delegate_];
[[NSUserNotificationCenter defaultUserNotificationCenter]
setDelegate:delegate_];
}
- (void)sendEvent:(NSEvent*)event {
// If event tap is not installed, handle events that reach the app instead
BOOL shouldHandleMediaKeyEventLocally = ![SPMediaKeyTap usesGlobalMediaKeyTap];
if(shouldHandleMediaKeyEventLocally && [event type] == NSSystemDefined && [event subtype] == SPSystemDefinedEventMediaKeys) {
[(id)[self delegate] mediaKeyTap:nil receivedMediaKeyEvent:event];
}
[super sendEvent:event];
}
@end
namespace mac {
void MacMain() {
ScopedNSAutoreleasePool pool;
// Creates and sets the magic global variable so QApplication will find it.
[MacApplication sharedApplication];
#ifdef HAVE_SPARKLE
// Creates and sets the magic global variable for Sparkle.
[[SUUpdater sharedUpdater] setDelegate:NSApp];
#endif
}
void SetShortcutHandler(MacGlobalShortcutBackend* handler) {
[NSApp SetShortcutHandler:handler];
}
void SetApplicationHandler(PlatformInterface* handler) {
[NSApp SetApplicationHandler:handler];
}
void CheckForUpdates() {
#ifdef HAVE_SPARKLE
[[SUUpdater sharedUpdater] checkForUpdates:NSApp];
#endif
}
QString GetBundlePath() {
ScopedCFTypeRef<CFURLRef> app_url(CFBundleCopyBundleURL(CFBundleGetMainBundle()));
ScopedCFTypeRef<CFStringRef> mac_path(CFURLCopyFileSystemPath(app_url.get(), kCFURLPOSIXPathStyle));
const char* path = CFStringGetCStringPtr(mac_path.get(), CFStringGetSystemEncoding());
QString bundle_path = QString::fromUtf8(path);
return bundle_path;
}
QString GetResourcesPath() {
QString bundle_path = GetBundlePath();
return bundle_path + "/Contents/Resources";
}
QString GetApplicationSupportPath() {
ScopedNSAutoreleasePool pool;
NSArray* paths = NSSearchPathForDirectoriesInDomains(
NSApplicationSupportDirectory, NSUserDomainMask, YES);
QString ret;
if ([paths count] > 0) {
NSString* user_path = [paths objectAtIndex:0];
ret = QString::fromUtf8([user_path UTF8String]);
} else {
ret = "~/Collection/Application Support";
}
return ret;
}
QString GetMusicDirectory() {
ScopedNSAutoreleasePool pool;
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSMusicDirectory,
NSUserDomainMask, YES);
QString ret;
if ([paths count] > 0) {
NSString* user_path = [paths objectAtIndex:0];
ret = QString::fromUtf8([user_path UTF8String]);
} else {
ret = "~/Music";
}
return ret;
}
static int MapFunctionKey(int keycode) {
switch (keycode) {
// Function keys
case NSInsertFunctionKey: return Qt::Key_Insert;
case NSDeleteFunctionKey: return Qt::Key_Delete;
case NSPauseFunctionKey: return Qt::Key_Pause;
case NSPrintFunctionKey: return Qt::Key_Print;
case NSSysReqFunctionKey: return Qt::Key_SysReq;
case NSHomeFunctionKey: return Qt::Key_Home;
case NSEndFunctionKey: return Qt::Key_End;
case NSLeftArrowFunctionKey: return Qt::Key_Left;
case NSUpArrowFunctionKey: return Qt::Key_Up;
case NSRightArrowFunctionKey: return Qt::Key_Right;
case NSDownArrowFunctionKey: return Qt::Key_Down;
case NSPageUpFunctionKey: return Qt::Key_PageUp;
case NSPageDownFunctionKey: return Qt::Key_PageDown;
case NSScrollLockFunctionKey: return Qt::Key_ScrollLock;
case NSF1FunctionKey: return Qt::Key_F1;
case NSF2FunctionKey: return Qt::Key_F2;
case NSF3FunctionKey: return Qt::Key_F3;
case NSF4FunctionKey: return Qt::Key_F4;
case NSF5FunctionKey: return Qt::Key_F5;
case NSF6FunctionKey: return Qt::Key_F6;
case NSF7FunctionKey: return Qt::Key_F7;
case NSF8FunctionKey: return Qt::Key_F8;
case NSF9FunctionKey: return Qt::Key_F9;
case NSF10FunctionKey: return Qt::Key_F10;
case NSF11FunctionKey: return Qt::Key_F11;
case NSF12FunctionKey: return Qt::Key_F12;
case NSF13FunctionKey: return Qt::Key_F13;
case NSF14FunctionKey: return Qt::Key_F14;
case NSF15FunctionKey: return Qt::Key_F15;
case NSF16FunctionKey: return Qt::Key_F16;
case NSF17FunctionKey: return Qt::Key_F17;
case NSF18FunctionKey: return Qt::Key_F18;
case NSF19FunctionKey: return Qt::Key_F19;
case NSF20FunctionKey: return Qt::Key_F20;
case NSF21FunctionKey: return Qt::Key_F21;
case NSF22FunctionKey: return Qt::Key_F22;
case NSF23FunctionKey: return Qt::Key_F23;
case NSF24FunctionKey: return Qt::Key_F24;
case NSF25FunctionKey: return Qt::Key_F25;
case NSF26FunctionKey: return Qt::Key_F26;
case NSF27FunctionKey: return Qt::Key_F27;
case NSF28FunctionKey: return Qt::Key_F28;
case NSF29FunctionKey: return Qt::Key_F29;
case NSF30FunctionKey: return Qt::Key_F30;
case NSF31FunctionKey: return Qt::Key_F31;
case NSF32FunctionKey: return Qt::Key_F32;
case NSF33FunctionKey: return Qt::Key_F33;
case NSF34FunctionKey: return Qt::Key_F34;
case NSF35FunctionKey: return Qt::Key_F35;
case NSMenuFunctionKey: return Qt::Key_Menu;
case NSHelpFunctionKey: return Qt::Key_Help;
}
return 0;
}
QKeySequence KeySequenceFromNSEvent(NSEvent* event) {
NSString* str = [event charactersIgnoringModifiers];
NSString* upper = [str uppercaseString];
const char* chars = [upper UTF8String];
NSUInteger modifiers = [event modifierFlags];
int key = 0;
unsigned char c = chars[0];
switch (c) {
case 0x1b: key = Qt::Key_Escape; break;
case 0x09: key = Qt::Key_Tab; break;
case 0x0d: key = Qt::Key_Return; break;
case 0x08: key = Qt::Key_Backspace; break;
case 0x03: key = Qt::Key_Enter; break;
}
if (key == 0) {
if (c >= 0x20 && c <= 0x7e) { // ASCII from space to ~
key = c;
} else {
key = MapFunctionKey([event keyCode]);
if (key == 0) {
return QKeySequence();
}
}
}
if (modifiers & NSShiftKeyMask) {
key += Qt::SHIFT;
}
if (modifiers & NSControlKeyMask) {
key += Qt::META;
}
if (modifiers & NSAlternateKeyMask) {
key += Qt::ALT;
}
if (modifiers & NSCommandKeyMask) {
key += Qt::CTRL;
}
return QKeySequence(key);
}
void DumpDictionary(CFDictionaryRef dict) {
NSDictionary* d = (NSDictionary*)dict;
NSLog(@"%@", d);
}
// NSWindowCollectionBehaviorFullScreenPrimary
static const NSUInteger kFullScreenPrimary = 1 << 7;
void EnableFullScreen(const QWidget& main_window) {
NSView* view = reinterpret_cast<NSView*>(main_window.winId());
NSWindow* window = [view window];
[window setCollectionBehavior:kFullScreenPrimary];
}
float GetDevicePixelRatio(QWidget* widget) {
NSView* view = reinterpret_cast<NSView*>(widget->winId());
return [[view window] backingScaleFactor];
}
} // namespace mac

38
src/core/mac_utilities.h Normal file
View File

@@ -0,0 +1,38 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2011, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QKeySequence>
#include <CoreFoundation/CFDictionary.h>
#ifdef __OBJC__
@class NSEvent;
#else
class NSEvent;
#endif
namespace mac {
QKeySequence KeySequenceFromNSEvent(NSEvent* event);
void DumpDictionary(CFDictionaryRef dict);
float GetDevicePixelRatio(QWidget* widget);
}

63
src/core/macfslistener.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MACFSLISTENER_H
#define MACFSLISTENER_H
#include "config.h"
#include <CoreServices/CoreServices.h>
#include <QObject>
#include <QSet>
#include <QTimer>
#include "filesystemwatcherinterface.h"
class MacFSListener : public FileSystemWatcherInterface {
Q_OBJECT
public:
explicit MacFSListener(QObject *parent = nullptr);
void Init();
void AddPath(const QString &path);
void RemovePath(const QString &path);
void Clear();
signals:
void PathChanged(const QString &path);
private slots:
void UpdateStream();
private:
void UpdateStreamAsync();
static void EventStreamCallback(ConstFSEventStreamRef stream, void *user_data, size_t num_events, void *event_paths, const FSEventStreamEventFlags event_flags[], const FSEventStreamEventId event_ids[]);
CFRunLoopRef run_loop_;
FSEventStreamRef stream_;
QSet<QString> paths_;
QTimer update_timer_;
};
#endif

112
src/core/macfslistener.mm Normal file
View File

@@ -0,0 +1,112 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "macfslistener.h"
#include "config.h"
#include <CoreFoundation/CFArray.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSString.h>
#include "core/logging.h"
#include "core/scoped_nsobject.h"
MacFSListener::MacFSListener(QObject* parent)
: FileSystemWatcherInterface(parent), run_loop_(nullptr), stream_(nullptr) {
update_timer_.setSingleShot(true);
update_timer_.setInterval(2000);
connect(&update_timer_, SIGNAL(timeout()), SLOT(UpdateStream()));
}
void MacFSListener::Init() { run_loop_ = CFRunLoopGetCurrent(); }
void MacFSListener::EventStreamCallback(
ConstFSEventStreamRef stream,
void* user_data,
size_t num_events,
void* event_paths,
const FSEventStreamEventFlags event_flags[],
const FSEventStreamEventId event_ids[]) {
MacFSListener* me = reinterpret_cast<MacFSListener*>(user_data);
char** paths = reinterpret_cast<char**>(event_paths);
for (int i = 0; i < num_events; ++i) {
QString path = QString::fromUtf8(paths[i]);
qLog(Debug) << "Something changed at:" << path;
while (path.endsWith('/')) {
path.chop(1);
}
emit me->PathChanged(path);
}
}
void MacFSListener::AddPath(const QString& path) {
Q_ASSERT(run_loop_);
paths_.insert(path);
UpdateStreamAsync();
}
void MacFSListener::RemovePath(const QString& path) {
Q_ASSERT(run_loop_);
paths_.remove(path);
UpdateStreamAsync();
}
void MacFSListener::Clear() {
paths_.clear();
UpdateStreamAsync();
}
void MacFSListener::UpdateStreamAsync() { update_timer_.start(); }
void MacFSListener::UpdateStream() {
if (stream_) {
FSEventStreamStop(stream_);
FSEventStreamInvalidate(stream_);
FSEventStreamRelease(stream_);
stream_ = nullptr;
}
if (paths_.empty()) {
return;
}
scoped_nsobject<NSMutableArray> array([[NSMutableArray alloc] init]);
for (const QString& path : paths_) {
scoped_nsobject<NSString> string(
[[NSString alloc] initWithUTF8String:path.toUtf8().constData()]);
[array addObject:string.get()];
}
FSEventStreamContext context;
memset(&context, 0, sizeof(context));
context.info = this;
CFAbsoluteTime latency = 1.0;
stream_ = FSEventStreamCreate(nullptr, &EventStreamCallback, &context, // Copied
reinterpret_cast<CFArrayRef>(array.get()),
kFSEventStreamEventIdSinceNow, latency,
kFSEventStreamCreateFlagNone);
FSEventStreamScheduleWithRunLoop(stream_, run_loop_, kCFRunLoopDefaultMode);
FSEventStreamStart(stream_);
}

View File

@@ -0,0 +1,46 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "macscreensaver.h"
#include <QtDebug>
#include "core/utilities.h"
// kIOPMAssertionTypePreventUserIdleDisplaySleep from Lion.
#define kLionDisplayAssertion CFSTR("PreventUserIdleDisplaySleep")
MacScreensaver::MacScreensaver() : assertion_id_(0) {}
void MacScreensaver::Inhibit() {
CFStringRef assertion_type = (Utilities::GetMacVersion() >= 7) ? kLionDisplayAssertion : kIOPMAssertionTypeNoDisplaySleep;
IOPMAssertionCreateWithName(
assertion_type,
kIOPMAssertionLevelOn,
CFSTR("Showing full-screen Strawberry visualisations"),
&assertion_id_);
}
void MacScreensaver::Uninhibit() {
IOPMAssertionRelease(assertion_id_);
}

41
src/core/macscreensaver.h Normal file
View File

@@ -0,0 +1,41 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MACSCREENSAVER_H
#define MACSCREENSAVER_H
#include "config.h"
#include "screensaver.h"
#include <IOKit/pwr_mgt/IOPMLib.h>
class MacScreensaver : public Screensaver {
public:
MacScreensaver();
void Inhibit();
void Uninhibit();
private:
IOPMAssertionID assertion_id_;
};
#endif

View File

@@ -0,0 +1,61 @@
/*
*Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MACSYSTEMTRAYICON_H
#define MACSYSTEMTRAYICON_H
#include "config.h"
#include <memory>
#include "systemtrayicon.h"
class MacSystemTrayIconPrivate;
class MacSystemTrayIcon : public SystemTrayIcon {
Q_OBJECT
public:
MacSystemTrayIcon(QObject *parent = nullptr);
~MacSystemTrayIcon();
void SetupMenu(QAction *previous, QAction *play, QAction *stop, QAction *stop_after, QAction *next, QAction *mute, QAction *quit);
void SetNowPlaying(const Song& song, const QString& image_path);
void ClearNowPlaying();
private:
void SetupMenuItem(QAction *action);
private slots:
void ActionChanged();
protected:
// SystemTrayIcon
void UpdateIcon();
private:
QPixmap orange_icon_;
QPixmap grey_icon_;
std::unique_ptr<MacSystemTrayIconPrivate> p_;
Q_DISABLE_COPY(MacSystemTrayIcon);
};
#endif // MACSYSTEMTRAYICON_H

View File

@@ -0,0 +1,210 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "macsystemtrayicon.h"
#include "core/mac_delegate.h"
#include "core/song.h"
#include <QAction>
#include <QApplication>
#include <QIcon>
#include <QtDebug>
#include <AppKit/NSMenu.h>
#include <AppKit/NSMenuItem.h>
@interface Target :NSObject {
QAction* action_;
}
- (id) initWithQAction: (QAction*)action;
- (void) clicked;
@end
@implementation Target // <NSMenuValidation>
- (id) init {
return [super init];
}
- (id) initWithQAction: (QAction*)action {
action_ = action;
return self;
}
- (BOOL) validateMenuItem: (NSMenuItem*)menuItem {
// This is called when the menu is shown.
return action_->isEnabled();
}
- (void) clicked {
action_->trigger();
}
@end
class MacSystemTrayIconPrivate {
public:
MacSystemTrayIconPrivate() {
dock_menu_ = [[NSMenu alloc] initWithTitle:@"DockMenu"];
QString title = QT_TR_NOOP("Now Playing");
NSString* t = [[NSString alloc] initWithUTF8String:title.toUtf8().constData()];
now_playing_ = [[NSMenuItem alloc]
initWithTitle:t
action:nullptr
keyEquivalent:@""];
now_playing_artist_ = [[NSMenuItem alloc]
initWithTitle:@"Nothing to see here"
action:nullptr
keyEquivalent:@""];
now_playing_title_ = [[NSMenuItem alloc]
initWithTitle:@"Nothing to see here"
action:nullptr
keyEquivalent:@""];
[dock_menu_ insertItem:now_playing_title_ atIndex:0];
[dock_menu_ insertItem:now_playing_artist_ atIndex:0];
[dock_menu_ insertItem:now_playing_ atIndex:0];
// Don't look now.
// This must be called after our custom NSApplicationDelegate has been set.
[(AppDelegate*)([NSApp delegate]) setDockMenu:dock_menu_];
ClearPlaying();
}
void AddMenuItem(QAction* action) {
// Strip accelarators from name.
QString text = action->text().remove("&");
NSString* title = [[NSString alloc] initWithUTF8String: text.toUtf8().constData()];
// Create an object that can receive user clicks and pass them on to the QAction.
Target* target = [[Target alloc] initWithQAction:action];
NSMenuItem* item = [[[NSMenuItem alloc]
initWithTitle:title
action:@selector(clicked)
keyEquivalent:@""] autorelease];
[item setEnabled:action->isEnabled()];
[item setTarget:target];
[dock_menu_ addItem:item];
actions_[action] = item;
}
void ActionChanged(QAction* action) {
NSMenuItem* item = actions_[action];
NSString* title = [[NSString alloc] initWithUTF8String: action->text().toUtf8().constData()];
[item setTitle:title];
}
void AddSeparator() {
NSMenuItem* separator = [NSMenuItem separatorItem];
[dock_menu_ addItem:separator];
}
void ShowPlaying(const QString& artist, const QString& title) {
ClearPlaying(); // Makes sure the order is consistent.
[now_playing_artist_ setTitle:
[[NSString alloc] initWithUTF8String: artist.toUtf8().constData()]];
[now_playing_title_ setTitle:
[[NSString alloc] initWithUTF8String: title.toUtf8().constData()]];
title.isEmpty() ? HideItem(now_playing_title_) : ShowItem(now_playing_title_);
artist.isEmpty() ? HideItem(now_playing_artist_) : ShowItem(now_playing_artist_);
artist.isEmpty() && title.isEmpty() ? HideItem(now_playing_) : ShowItem(now_playing_);
}
void ClearPlaying() {
// Hiding doesn't seem to work in the dock menu.
HideItem(now_playing_);
HideItem(now_playing_artist_);
HideItem(now_playing_title_);
}
private:
void HideItem(NSMenuItem* item) {
if ([dock_menu_ indexOfItem:item] != -1) {
[dock_menu_ removeItem:item];
}
}
void ShowItem(NSMenuItem* item, int index = 0) {
if ([dock_menu_ indexOfItem:item] == -1) {
[dock_menu_ insertItem:item atIndex:index];
}
}
QMap<QAction*, NSMenuItem*> actions_;
NSMenu* dock_menu_;
NSMenuItem* now_playing_;
NSMenuItem* now_playing_artist_;
NSMenuItem* now_playing_title_;
Q_DISABLE_COPY(MacSystemTrayIconPrivate);
};
MacSystemTrayIcon::MacSystemTrayIcon(QObject* parent)
: SystemTrayIcon(parent),
orange_icon_(QPixmap(":/icons/64x64/strawberry.png").scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)),
grey_icon_(QPixmap(":icon_large_grey.png").scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)) {
QApplication::setWindowIcon(orange_icon_);
}
MacSystemTrayIcon::~MacSystemTrayIcon() {
}
void MacSystemTrayIcon::SetupMenu(QAction* previous, QAction* play, QAction* stop, QAction* stop_after, QAction* next, QAction* mute, QAction* quit) {
p_.reset(new MacSystemTrayIconPrivate());
SetupMenuItem(previous);
SetupMenuItem(play);
SetupMenuItem(stop);
SetupMenuItem(stop_after);
SetupMenuItem(next);
p_->AddSeparator();
SetupMenuItem(mute);
p_->AddSeparator();
Q_UNUSED(quit); // Mac already has a Quit item.
}
void MacSystemTrayIcon::SetupMenuItem(QAction* action) {
p_->AddMenuItem(action);
connect(action, SIGNAL(changed()), SLOT(ActionChanged()));
}
void MacSystemTrayIcon::UpdateIcon() {
QApplication::setWindowIcon(CreateIcon(orange_icon_, grey_icon_));
}
void MacSystemTrayIcon::ActionChanged() {
QAction* action = qobject_cast<QAction*>(sender());
p_->ActionChanged(action);
}
void MacSystemTrayIcon::ClearPlaying() {
p_->ClearPlaying();
}
void MacSystemTrayIcon::SetPlaying(const Song& song, const QString& image_path) {
p_->ShowPlaying(song.artist(), song.PrettyTitle());
}

305
src/core/main.cpp Normal file
View File

@@ -0,0 +1,305 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <memory>
#include <QtGlobal>
#include <glib-object.h>
#include <glib.h>
#ifdef HAVE_GSTREAMER
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#endif
#ifdef Q_OS_UNIX
#include <unistd.h>
#endif // Q_OS_UNIX
#ifdef HAVE_DBUS
#include "core/mpris.h"
#include "core/mpris2.h"
#include <QDBusArgument>
#include <QImage>
#endif
#ifdef Q_OS_DARWIN
#include <sys/resource.h>
#include <sys/sysctl.h>
#endif
#ifdef Q_OS_WIN32
#define _WIN32_WINNT 0x0600
#include <windows.h>
#include <iostream>
#include <qtsparkle/Updater>
#endif // Q_OS_WIN32
#include <QString>
#include <QDir>
#include <QFont>
#include <QLibraryInfo>
#include <QNetworkProxyFactory>
#include <QSslSocket>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSysInfo>
#include <QTextCodec>
#include <QTranslator>
#include <QtConcurrentRun>
#include <QtDebug>
#include "core/application.h"
#include "core/mainwindow.h"
#include "core/commandlineoptions.h"
#include "core/database.h"
#include "core/logging.h"
#include "core/mac_startup.h"
#include "core/metatypes.h"
#include "core/network.h"
#include "core/networkproxyfactory.h"
#include "core/song.h"
#include "core/utilities.h"
#include "core/iconloader.h"
#include "core/systemtrayicon.h"
#include "core/scangiomodulepath.h"
#include "engine/enginebase.h"
#ifdef HAVE_GSTREAMER
#include "engine/gstengine.h"
#endif
#include "version.h"
#include "widgets/osd.h"
#if 0
#ifdef HAVE_LIBLASTFM
#include "covermanager/lastfmcoverprovider.h"
#endif
#include "covermanager/amazoncoverprovider.h"
#include "covermanager/coverproviders.h"
#include "covermanager/musicbrainzcoverprovider.h"
#include "covermanager/discogscoverprovider.h"
#endif
#include "tagreadermessages.pb.h"
#include "qtsingleapplication.h"
#include "qtsinglecoreapplication.h"
#ifdef HAVE_DBUS
QDBusArgument &operator<<(QDBusArgument &arg, const QImage &image);
const QDBusArgument &operator>>(const QDBusArgument &arg, QImage &image);
#endif
// Load sqlite plugin on windows and mac.
#include <QtPlugin>
Q_IMPORT_PLUGIN(QSQLiteDriverPlugin)
int main(int argc, char* argv[]) {
#ifdef Q_OS_DARWIN
// Do Mac specific startup to get media keys working.
// This must go before QApplication initialisation.
mac::MacMain();
if (QSysInfo::MacintoshVersion > QSysInfo::MV_10_8) {
// Work around 10.9 issue.
// https://bugreports.qt-project.org/browse/QTBUG-32789
QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande");
}
#endif
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN)
QCoreApplication::setApplicationName("Strawberry");
QCoreApplication::setOrganizationName("Strawberry");
#else
QCoreApplication::setApplicationName("strawberry");
QCoreApplication::setOrganizationName("strawberry");
#endif
QCoreApplication::setApplicationVersion(STRAWBERRY_VERSION_DISPLAY);
QCoreApplication::setOrganizationDomain("strawbs.org");
// This makes us show up nicely in gnome-volume-control
#if !GLIB_CHECK_VERSION(2, 36, 0)
g_type_init(); // Deprecated in glib 2.36.0
#endif
g_set_application_name(QCoreApplication::applicationName().toLocal8Bit());
RegisterMetaTypes();
// Initialise logging. Log levels are set after the commandline options are
// parsed below.
logging::Init();
g_log_set_default_handler(reinterpret_cast<GLogFunc>(&logging::GLog), nullptr);
CommandlineOptions options(argc, argv);
{
// Only start a core application now so we can check if there's another
// Strawberry running without needing an X server.
// This MUST be done before parsing the commandline options so QTextCodec
// gets the right system locale for filenames.
QtSingleCoreApplication a(argc, argv);
Utilities::CheckPortable();
//crash_reporting.SetApplicationPath(a.applicationFilePath());
// 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 (a.isRunning()) {
if (options.is_empty()) {
qLog(Info) << "Strawberry is already running - activating existing window";
}
if (a.sendMessage(options.Serialize(), 5000)) {
return 0;
}
// Couldn't send the message so start anyway
}
}
#ifdef Q_OS_DARWIN
// Must happen after QCoreApplication::setOrganizationName().
setenv("XDG_CONFIG_HOME", Utilities::GetConfigPath(Utilities::Path_Root).toLocal8Bit().constData(), 1);
#endif
// Output the version, so when people attach log output to bug reports they
// don't have to tell us which version they're using.
qLog(Info) << "Strawberry" << STRAWBERRY_VERSION_DISPLAY;
// Seed the random number generators.
time_t t = time(nullptr);
srand(t);
qsrand(t);
Utilities::IncreaseFDLimit();
QtSingleApplication a(argc, argv);
// A bug in Qt means the wheel_scroll_lines setting gets ignored and replaced
// with the default value of 3 in QApplicationPrivate::initialize.
{
QSettings qt_settings(QSettings::UserScope, "Trolltech");
qt_settings.beginGroup("Qt");
QApplication::setWheelScrollLines(qt_settings.value("wheelScrollLines", QApplication::wheelScrollLines()).toInt());
}
#ifdef Q_OS_DARWIN
QCoreApplication::setCollectionPaths(
QStringList() << QCoreApplication::applicationDirPath() + "/../PlugIns");
#endif
a.setQuitOnLastWindowClosed(false);
// Do this check again because another instance might have started by now
if (a.isRunning() && a.sendMessage(options.Serialize(), 5000)) {
return 0;
}
#ifndef Q_OS_DARWIN
// Gnome on Ubuntu has menu icons disabled by default. I think that's a bad
// idea, and makes some menus in Strawberry look confusing.
QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, false);
#else
QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, true);
// Fixes focus issue with NSSearchField, see QTBUG-11401
QCoreApplication::setAttribute(Qt::AA_NativeWindows, true);
#endif
// Set the permissions on the config file on Unix - it can contain passwords
// for internet services so it's important that other users can't read it.
// On Windows these are stored in the registry instead.
#ifdef Q_OS_UNIX
{
QSettings s;
// Create the file if it doesn't exist already
if (!QFile::exists(s.fileName())) {
QFile file(s.fileName());
file.open(QIODevice::WriteOnly);
}
// Set -rw-------
QFile::setPermissions(s.fileName(), QFile::ReadOwner | QFile::WriteOwner);
}
#endif
// Resources
Q_INIT_RESOURCE(data);
#ifdef Q_OS_WIN32
// Set the language for qtsparkle
qtsparkle::LoadTranslations(language);
#endif
// Icons
IconLoader::Init();
// This is a nasty hack to ensure that everything in libprotobuf is
// initialised in the main thread. It fixes issue 3265 but nobody knows why.
// Don't remove this unless you can reproduce the error that it fixes.
//ParseAProto();
//QtConcurrent::run(&ParseAProto);
Application app;
// Network proxy
QNetworkProxyFactory::setApplicationProxyFactory(NetworkProxyFactory::Instance());
#if 0
//#ifdef HAVE_LIBLASTFM
app.cover_providers()->AddProvider(new LastFmCoverProvider);
app.cover_providers()->AddProvider(new AmazonCoverProvider);
app.cover_providers()->AddProvider(new DiscogsCoverProvider);
app.cover_providers()->AddProvider(new MusicbrainzCoverProvider);
#endif
// Create the tray icon and OSD
std::unique_ptr<SystemTrayIcon> tray_icon(SystemTrayIcon::CreateSystemTrayIcon());
OSD osd(tray_icon.get(), &app);
#ifdef HAVE_DBUS
mpris::Mpris mpris(&app);
#endif
#ifdef HAVE_GSTREAMER
gst_init(nullptr, nullptr);
gst_pb_utils_init();
#endif
// Window
MainWindow w(&app, tray_icon.get(), &osd, options);
#ifdef Q_OS_DARWIN
mac::EnableFullScreen(w);
#endif // Q_OS_DARWIN
#ifdef HAVE_GIO
ScanGIOModulePath();
#endif
#ifdef HAVE_DBUS
QObject::connect(&mpris, SIGNAL(RaiseMainWindow()), &w, SLOT(Raise()));
#endif
QObject::connect(&a, SIGNAL(messageReceived(QString)), &w, SLOT(CommandlineOptionsReceived(QString)));
int ret = a.exec();
return ret;
}

2300
src/core/mainwindow.cpp Normal file

File diff suppressed because it is too large Load Diff

353
src/core/mainwindow.h Normal file
View File

@@ -0,0 +1,353 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "config.h"
#include <memory>
#include <QMainWindow>
#include <QSettings>
#include <QSystemTrayIcon>
#include "core/lazy.h"
#include "core/mac_startup.h"
#include "core/tagreaderclient.h"
#include "engine/engine_fwd.h"
#include "collection/collectionmodel.h"
#include "playlist/playlistitem.h"
#ifdef HAVE_GSTREAMER
#include "dialogs/organisedialog.h"
#endif
#include "settings/settingsdialog.h"
class StatusView;
class About;
class AlbumCoverManager;
class Appearance;
class Application;
class ArtistInfoView;
class CommandlineOptions;
class CoverProviders;
class Database;
class DeviceManager;
class DeviceView;
class DeviceViewContainer;
class EditTagDialog;
class Equalizer;
class ErrorDialog;
class FileView;
class GlobalShortcuts;
class GroupByDialog;
class Collection;
class CollectionViewContainer;
class MimeData;
class MultiLoadingIndicator;
class OSD;
class Player;
class PlaylistBackend;
class PlaylistListContainer;
class PlaylistManager;
class QueueManager;
class Song;
class SystemTrayIcon;
class TagFetcher;
class TaskManager;
class TrackSelectionDialog;
#ifdef HAVE_GSTREAMER
class TranscodeDialog;
#endif
class Windows7ThumbBar;
class Ui_MainWindow;
class QSortFilterProxyModel;
class MainWindow : public QMainWindow, public PlatformInterface {
Q_OBJECT
public:
MainWindow(Application *app, SystemTrayIcon* tray_icon, OSD* osd, const CommandlineOptions& options, QWidget* parent = nullptr);
~MainWindow();
static const char *kSettingsGroup;
static const char *kAllFilesFilterSpec;
// Don't change the values
enum StartupBehaviour {
Startup_Remember = 1,
Startup_AlwaysShow = 2,
Startup_AlwaysHide = 3,
};
// Don't change the values
enum AddBehaviour {
AddBehaviour_Append = 1,
AddBehaviour_Enqueue = 2,
AddBehaviour_Load = 3,
AddBehaviour_OpenInNew = 4
};
// Don't change the values
enum PlayBehaviour {
PlayBehaviour_Never = 1,
PlayBehaviour_IfStopped = 2,
PlayBehaviour_Always = 3,
};
// Don't change the values
enum PlaylistAddBehaviour {
PlaylistAddBehaviour_Play = 1,
PlaylistAddBehaviour_Enqueue = 2,
};
void SetHiddenInTray(bool hidden);
void CommandlineOptionsReceived(const CommandlineOptions& options);
protected:
void keyPressEvent(QKeyEvent* event);
void resizeEvent(QResizeEvent* event);
void closeEvent(QCloseEvent* event);
#ifdef Q_OS_WIN32
bool winEvent(MSG* message, long* result);
#endif
// PlatformInterface
void Activate();
bool LoadUrl(const QString& url);
signals:
// Signals that stop playing after track was toggled.
void StopAfterToggled(bool stop);
void IntroPointReached();
private slots:
void FilePathChanged(const QString& path);
void MediaStopped();
void MediaPaused();
void MediaPlaying();
void TrackSkipped(PlaylistItemPtr item);
void ForceShowOSD(const Song& song, const bool toggle);
void PlaylistRightClick(const QPoint& global_pos, const QModelIndex& index);
void PlaylistCurrentChanged(const QModelIndex& current);
void PlaylistViewSelectionModelChanged();
void PlaylistPlay();
void PlaylistStopAfter();
void PlaylistQueue();
void PlaylistSkip();
void PlaylistRemoveCurrent();
void PlaylistEditFinished(const QModelIndex& index);
void EditTracks();
void EditTagDialogAccepted();
void RenumberTracks();
void SelectionSetValue();
void EditValue();
#ifdef HAVE_GSTREAMER
void AutoCompleteTags();
void AutoCompleteTagsAccepted();
#endif
void PlaylistUndoRedoChanged(QAction* undo, QAction* redo);
#ifdef HAVE_GSTREAMER
void AddFilesToTranscoder();
#endif
#ifdef HAVE_GSTREAMER
void PlaylistCopyToCollection();
void PlaylistMoveToCollection();
void PlaylistCopyToDevice();
void PlaylistOrganiseSelected(bool copy);
#endif
//void PlaylistDelete();
void PlaylistOpenInBrowser();
void ShowInCollection();
void ChangeCollectionQueryMode(QAction* action);
void PlayIndex(const QModelIndex& index);
void PlaylistDoubleClick(const QModelIndex& index);
void StopAfterCurrent();
void SongChanged(const Song& song);
void VolumeChanged(int volume);
#ifdef HAVE_GSTREAMER
void CopyFilesToCollection(const QList<QUrl>& urls);
void MoveFilesToCollection(const QList<QUrl>& urls);
void CopyFilesToDevice(const QList<QUrl>& urls);
#endif
void EditFileTags(const QList<QUrl>& urls);
void AddToPlaylist(QMimeData* data);
void AddToPlaylist(QAction* action);
void VolumeWheelEvent(int delta);
void ToggleShowHide();
void Seeked(qlonglong microseconds);
void UpdateTrackPosition();
void UpdateTrackSliderPosition();
void TaskCountChanged(int count);
void ShowCollectionConfig();
void ReloadSettings();
void ReloadAllSettings();
void RefreshStyleSheet();
void SetHiddenInTray() { SetHiddenInTray(true); }
void AddFile();
void AddFolder();
void AddCDTracks();
void CommandlineOptionsReceived(const QString& string_options);
void CheckForUpdates();
void PlayingWidgetPositionChanged();
void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex& index);
void ShowCoverManager();
void ShowAboutDialog();
#ifdef HAVE_GSTREAMER
void ShowTranscodeDialog();
#endif
void ShowErrorDialog(const QString& message);
void ShowQueueManager();
void EnsureSettingsDialogCreated();
void EnsureEditTagDialogCreated();
SettingsDialog* CreateSettingsDialog();
EditTagDialog* CreateEditTagDialog();
void OpenSettingsDialog();
void OpenSettingsDialogAtPage(SettingsDialog::Page page);
void TabSwitched();
void SaveGeometry();
void SavePlaybackStatus();
void LoadPlaybackStatus();
void ResumePlayback();
void Raise();
void Exit();
void HandleNotificationPreview(OSD::Behaviour type, QString line1, QString line2);
void FocusCollectionTab();
void ShowConsole();
private:
void ConnectStatusView(StatusView *statusview);
void ApplyAddBehaviour(AddBehaviour b, MimeData *data) const;
void ApplyPlayBehaviour(PlayBehaviour b, MimeData *data) const;
void CheckFullRescanRevisions();
// creates the icon by painting the full one depending on the current position
QPixmap CreateOverlayedIcon(int position, int scrobble_point);
private:
Ui_MainWindow *ui_;
Windows7ThumbBar *thumbbar_;
Application *app_;
SystemTrayIcon * tray_icon_;
OSD* osd_;
Lazy<EditTagDialog> edit_tag_dialog_;
Lazy<About> about_dialog_;
GlobalShortcuts* global_shortcuts_;
CollectionViewContainer *collection_view_;
StatusView *status_view_;
FileView *file_view_;
PlaylistListContainer *playlist_list_;
DeviceViewContainer *device_view_container_;
DeviceView *device_view_;
Lazy<SettingsDialog> settings_dialog_;
Lazy<AlbumCoverManager> cover_manager_;
std::unique_ptr<Equalizer> equalizer_;
#ifdef HAVE_GSTREAMER
Lazy<TranscodeDialog> transcode_dialog_;
#endif
Lazy<ErrorDialog> error_dialog_;
#ifdef HAVE_GSTREAMER
Lazy<OrganiseDialog> organise_dialog_;
#endif
Lazy<QueueManager> queue_manager_;
#ifdef HAVE_GSTREAMER
std::unique_ptr<TagFetcher> tag_fetcher_;
#endif
std::unique_ptr<TrackSelectionDialog> track_selection_dialog_;
#ifdef HAVE_GSTREAMER
PlaylistItemList autocomplete_tag_items_;
#endif
QAction *collection_show_all_;
QAction *collection_show_duplicates_;
QAction *collection_show_untagged_;
QMenu *playlist_menu_;
QAction *playlist_play_pause_;
QAction *playlist_stop_after_;
QAction *playlist_undoredo_;
//QAction *playlist_organise_;
QAction *playlist_show_in_collection_;
#ifdef HAVE_GSTREAMER
QAction *playlist_copy_to_collection_;
QAction *playlist_move_to_collection_;
QAction *playlist_copy_to_device_;
//QAction *playlist_delete_;
#endif
QAction *playlist_open_in_browser_;
QAction *playlist_queue_;
QAction *playlist_skip_;
QAction *playlist_add_to_another_;
QList<QAction*> playlistitem_actions_;
QAction *playlistitem_actions_separator_;
QModelIndex playlist_menu_index_;
QSortFilterProxyModel *collection_sort_model_;
QTimer *track_position_timer_;
QTimer *track_slider_timer_;
QSettings settings_;
bool was_maximized_;
int saved_playback_position_;
Engine::State saved_playback_state_;
AddBehaviour doubleclick_addmode_;
PlayBehaviour doubleclick_playmode_;
PlaylistAddBehaviour doubleclick_playlist_addmode_;
PlayBehaviour menu_playmode_;
};
#endif // MAINWINDOW_H

784
src/core/mainwindow.ui Normal file
View File

@@ -0,0 +1,784 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1131</width>
<height>685</height>
</rect>
</property>
<property name="windowTitle">
<string>Strawberry Music Player</string>
</property>
<property name="windowIcon">
<iconset resource="../../data/data.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout_8">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="sidebar_layout">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="FancyTabWidget" name="tabs" native="true"/>
</item>
<item>
<widget class="PlayingWidget" name="now_playing" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="playlist_layout">
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="PlaylistContainer" name="playlist" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<addaction name="action_edit_track"/>
<addaction name="action_edit_value"/>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="player_controls_container" native="true">
<layout class="QVBoxLayout" name="player_controls_layout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="player_controls">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>1</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="back_button">
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="pause_play_button">
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="stop_button">
<property name="enabled">
<bool>false</bool>
</property>
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="popupMode">
<enum>QToolButton::MenuButtonPopup</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="forward_button">
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_buttons">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="AnalyzerContainer" name="analyzer" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>100</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>36</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line_volume">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="Amarok::VolumeSlider" name="volume">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="status_bar" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="status_bar_layout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="Line" name="status_bar_line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="status_bar_internal" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QStackedWidget" name="status_bar_stack">
<widget class="MultiLoadingIndicator" name="multi_loading_indicator"/>
<widget class="QWidget" name="playlist_summary_page">
<layout class="QVBoxLayout" name="playlist_summary_layout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="playlist_summary">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="PlaylistSequence" name="playlist_sequence" native="true"/>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="TrackSlider" name="track_slider" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>10</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1131</width>
<height>24</height>
</rect>
</property>
<widget class="QMenu" name="menu_music">
<property name="title">
<string>&amp;Music</string>
</property>
<addaction name="action_open_file"/>
<addaction name="action_open_cd"/>
<addaction name="separator"/>
<addaction name="action_previous_track"/>
<addaction name="action_play_pause"/>
<addaction name="action_stop"/>
<addaction name="action_next_track"/>
<addaction name="separator"/>
<addaction name="action_mute"/>
<addaction name="separator"/>
<addaction name="separator"/>
<addaction name="action_quit"/>
</widget>
<widget class="QMenu" name="menu_playlist">
<property name="title">
<string>&amp;Playlist</string>
</property>
<addaction name="action_add_file"/>
<addaction name="action_add_folder"/>
<addaction name="separator"/>
<addaction name="action_shuffle_mode"/>
<addaction name="action_repeat_mode"/>
<addaction name="separator"/>
<addaction name="action_new_playlist"/>
<addaction name="action_save_playlist"/>
<addaction name="action_load_playlist"/>
<addaction name="separator"/>
<addaction name="action_jump"/>
<addaction name="action_clear_playlist"/>
<addaction name="action_shuffle"/>
<addaction name="action_remove_duplicates"/>
<addaction name="action_remove_unavailable"/>
</widget>
<widget class="QMenu" name="menu_help">
<property name="title">
<string>&amp;Help</string>
</property>
<addaction name="action_about_strawberry"/>
<addaction name="action_about_qt"/>
<addaction name="separator"/>
</widget>
<widget class="QMenu" name="menu_tools">
<property name="title">
<string>&amp;Tools</string>
</property>
<addaction name="action_cover_manager"/>
<addaction name="action_queue_manager"/>
<addaction name="action_equalizer"/>
<addaction name="separator"/>
<addaction name="action_update_collection"/>
<addaction name="action_full_collection_scan"/>
<addaction name="separator"/>
<addaction name="action_settings"/>
<addaction name="action_console"/>
</widget>
<addaction name="menu_music"/>
<addaction name="menu_playlist"/>
<addaction name="menu_tools"/>
<addaction name="menu_help"/>
</widget>
<action name="action_previous_track">
<property name="text">
<string>&amp;Previous track</string>
</property>
<property name="shortcut">
<string>F5</string>
</property>
</action>
<action name="action_play_pause">
<property name="text">
<string>P&amp;lay</string>
</property>
<property name="shortcut">
<string>F6</string>
</property>
</action>
<action name="action_stop">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Stop</string>
</property>
<property name="shortcut">
<string>F7</string>
</property>
</action>
<action name="action_next_track">
<property name="text">
<string>&amp;Next track</string>
</property>
<property name="shortcut">
<string>F8</string>
</property>
</action>
<action name="action_quit">
<property name="text">
<string>&amp;Quit</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
<property name="menuRole">
<enum>QAction::QuitRole</enum>
</property>
</action>
<action name="action_stop_after_this_track">
<property name="text">
<string>Stop after this track</string>
</property>
<property name="shortcut">
<string>Ctrl+Alt+V</string>
</property>
</action>
<action name="action_clear_playlist">
<property name="text">
<string>&amp;Clear playlist</string>
</property>
<property name="toolTip">
<string>Clear playlist</string>
</property>
<property name="shortcut">
<string>Ctrl+K</string>
</property>
</action>
<action name="action_edit_track">
<property name="text">
<string>Edit track information...</string>
</property>
<property name="shortcut">
<string>Ctrl+E</string>
</property>
</action>
<action name="action_renumber_tracks">
<property name="text">
<string>Renumber tracks in this order...</string>
</property>
</action>
<action name="action_selection_set_value">
<property name="text">
<string>Set value for all selected tracks...</string>
</property>
</action>
<action name="action_edit_value">
<property name="text">
<string>Edit tag...</string>
</property>
<property name="shortcut">
<string>F2</string>
</property>
</action>
<action name="action_settings">
<property name="text">
<string>&amp;Settings...</string>
</property>
<property name="shortcut">
<string>Ctrl+P</string>
</property>
<property name="menuRole">
<enum>QAction::PreferencesRole</enum>
</property>
</action>
<action name="action_about_strawberry">
<property name="icon">
<iconset resource="../../data/data.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property>
<property name="text">
<string>&amp;About Strawberry</string>
</property>
<property name="shortcut">
<string>F1</string>
</property>
<property name="menuRole">
<enum>QAction::AboutRole</enum>
</property>
</action>
<action name="action_shuffle">
<property name="text">
<string>S&amp;huffle playlist</string>
</property>
<property name="shortcut">
<string>Ctrl+H</string>
</property>
</action>
<action name="action_add_file">
<property name="text">
<string>&amp;Add file...</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+A</string>
</property>
</action>
<action name="action_open_file">
<property name="text">
<string>&amp;Open file...</string>
</property>
</action>
<action name="action_open_cd">
<property name="text">
<string>Open &amp;audio CD...</string>
</property>
</action>
<action name="action_cover_manager">
<property name="text">
<string>&amp;Cover Manager</string>
</property>
</action>
<action name="action_console">
<property name="text">
<string>C&amp;onsole</string>
</property>
</action>
<action name="action_shuffle_mode">
<property name="text">
<string>&amp;Shuffle mode</string>
</property>
</action>
<action name="action_repeat_mode">
<property name="text">
<string>&amp;Repeat mode</string>
</property>
</action>
<action name="action_remove_from_playlist">
<property name="text">
<string>Remove from playlist</string>
</property>
</action>
<action name="action_equalizer">
<property name="text">
<string>&amp;Equalizer</string>
</property>
</action>
<action name="action_add_folder">
<property name="text">
<string>Add &amp;folder...</string>
</property>
</action>
<action name="action_jump">
<property name="text">
<string>&amp;Jump to the currently playing track</string>
</property>
<property name="shortcut">
<string>Ctrl+J</string>
</property>
</action>
<action name="action_new_playlist">
<property name="text">
<string>&amp;New playlist</string>
</property>
<property name="shortcut">
<string>Ctrl+N</string>
</property>
</action>
<action name="action_save_playlist">
<property name="text">
<string>Save &amp;playlist...</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</action>
<action name="action_load_playlist">
<property name="text">
<string>&amp;Load playlist...</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+O</string>
</property>
</action>
<action name="action_next_playlist">
<property name="text">
<string>Go to next playlist tab</string>
</property>
</action>
<action name="action_previous_playlist">
<property name="text">
<string>Go to previous playlist tab</string>
</property>
</action>
<action name="action_update_collection">
<property name="text">
<string>&amp;Update changed collection folders</string>
</property>
</action>
<action name="action_queue_manager">
<property name="text">
<string>&amp;Queue Manager</string>
</property>
</action>
<action name="action_about_qt">
<property name="text">
<string>About &amp;Qt</string>
</property>
<property name="menuRole">
<enum>QAction::AboutQtRole</enum>
</property>
</action>
<action name="action_mute">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Mute</string>
</property>
<property name="shortcut">
<string>Ctrl+M</string>
</property>
</action>
<action name="action_full_collection_scan">
<property name="text">
<string>&amp;Do a full collection rescan</string>
</property>
</action>
<action name="action_auto_complete_tags">
<property name="icon">
<iconset resource="../../data/data.qrc">
<normaloff>:/pictures/musicbrainz.png</normaloff>:/pictures/musicbrainz.png</iconset>
</property>
<property name="text">
<string>Complete tags automatically...</string>
</property>
<property name="shortcut">
<string>Ctrl+T</string>
</property>
</action>
<action name="action_toggle_scrobbling">
<property name="text">
<string>Toggle scrobbling</string>
</property>
</action>
<action name="action_remove_duplicates">
<property name="text">
<string>Remove &amp;duplicates from playlist</string>
</property>
</action>
<action name="action_remove_unavailable">
<property name="text">
<string>Remove &amp;unavailable tracks from playlist</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>Amarok::VolumeSlider</class>
<extends>QSlider</extends>
<header>widgets/sliderwidget.h</header>
</customwidget>
<customwidget>
<class>AnalyzerContainer</class>
<extends>QWidget</extends>
<header>analyzer/analyzercontainer.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>PlaylistContainer</class>
<extends>QWidget</extends>
<header>playlist/playlistcontainer.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>TrackSlider</class>
<extends>QWidget</extends>
<header>widgets/trackslider.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>PlaylistSequence</class>
<extends>QWidget</extends>
<header>playlist/playlistsequence.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>MultiLoadingIndicator</class>
<extends>QWidget</extends>
<header>widgets/multiloadingindicator.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>PlayingWidget</class>
<extends>QWidget</extends>
<header>widgets/playingwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>FancyTabWidget</class>
<extends>QWidget</extends>
<header>widgets/fancytabwidget.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../data/data.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -0,0 +1,545 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "mergedproxymodel.h"
#include "core/logging.h"
#include <limits>
#include <QStringList>
// boost::multi_index still relies on these being in the global namespace.
using std::placeholders::_1;
using std::placeholders::_2;
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
using boost::multi_index::hashed_unique;
using boost::multi_index::identity;
using boost::multi_index::indexed_by;
using boost::multi_index::member;
using boost::multi_index::multi_index_container;
using boost::multi_index::ordered_unique;
using boost::multi_index::tag;
std::size_t hash_value(const QModelIndex& index) { return qHash(index); }
namespace {
struct Mapping {
explicit Mapping(const QModelIndex& _source_index) : source_index(_source_index) {}
QModelIndex source_index;
};
struct tag_by_source {};
struct tag_by_pointer {};
} // namespace
class MergedProxyModelPrivate {
private:
typedef multi_index_container<
Mapping*,
indexed_by<
hashed_unique<tag<tag_by_source>,
member<Mapping, QModelIndex, &Mapping::source_index> >,
ordered_unique<tag<tag_by_pointer>, identity<Mapping*> > > >
MappingContainer;
public:
MappingContainer mappings_;
};
MergedProxyModel::MergedProxyModel(QObject* parent)
: QAbstractProxyModel(parent),
resetting_model_(nullptr),
p_(new MergedProxyModelPrivate) {}
MergedProxyModel::~MergedProxyModel() { DeleteAllMappings(); }
void MergedProxyModel::DeleteAllMappings() {
const auto& begin = p_->mappings_.get<tag_by_pointer>().begin();
const auto& end = p_->mappings_.get<tag_by_pointer>().end();
qDeleteAll(begin, end);
}
void MergedProxyModel::AddSubModel(const QModelIndex& source_parent, QAbstractItemModel* submodel) {
connect(submodel, SIGNAL(modelReset()), this, SLOT(SubModelReset()));
connect(submodel, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(RowsAboutToBeInserted(QModelIndex, int, int)));
connect(submodel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(RowsAboutToBeRemoved(QModelIndex, int, int)));
connect(submodel, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(RowsInserted(QModelIndex, int, int)));
connect(submodel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(RowsRemoved(QModelIndex, int, int)));
connect(submodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(DataChanged(QModelIndex, QModelIndex)));
QModelIndex proxy_parent = mapFromSource(source_parent);
const int rows = submodel->rowCount();
if (rows) beginInsertRows(proxy_parent, 0, rows - 1);
merge_points_.insert(submodel, source_parent);
if (rows) endInsertRows();
}
void MergedProxyModel::RemoveSubModel(const QModelIndex& source_parent) {
// Find the submodel that the parent corresponded to
QAbstractItemModel* submodel = merge_points_.key(source_parent);
merge_points_.remove(submodel);
// The submodel might have been deleted already so we must be careful not
// to dereference it.
// Remove all the children of the item that got deleted
QModelIndex proxy_parent = mapFromSource(source_parent);
// We can't know how many children it had, since we can't dereference it
resetting_model_ = submodel;
beginRemoveRows(proxy_parent, 0, std::numeric_limits<int>::max() - 1);
endRemoveRows();
resetting_model_ = nullptr;
// Delete all the mappings that reference the submodel
auto it = p_->mappings_.get<tag_by_pointer>().begin();
auto end = p_->mappings_.get<tag_by_pointer>().end();
while (it != end) {
if ((*it)->source_index.model() == submodel) {
delete *it;
it = p_->mappings_.get<tag_by_pointer>().erase(it);
} else {
++it;
}
}
}
void MergedProxyModel::setSourceModel(QAbstractItemModel* source_model) {
if (sourceModel()) {
disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(SourceModelReset()));
disconnect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(RowsAboutToBeInserted(QModelIndex,int,int)));
disconnect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(RowsAboutToBeRemoved(QModelIndex,int,int)));
disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(RowsInserted(QModelIndex,int,int)));
disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(RowsRemoved(QModelIndex,int,int)));
disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(DataChanged(QModelIndex, QModelIndex)));
disconnect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), this, SLOT(LayoutAboutToBeChanged()));
disconnect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(LayoutChanged()));
}
QAbstractProxyModel::setSourceModel(source_model);
connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(SourceModelReset()));
connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(RowsAboutToBeInserted(QModelIndex, int, int)));
connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(RowsAboutToBeRemoved(QModelIndex, int, int)));
connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(RowsInserted(QModelIndex,int,int)));
connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(RowsRemoved(QModelIndex,int,int)));
connect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(DataChanged(QModelIndex,QModelIndex)));
connect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), this, SLOT(LayoutAboutToBeChanged()));
connect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(LayoutChanged()));
}
void MergedProxyModel::SourceModelReset() {
// Delete all mappings
DeleteAllMappings();
// Reset the proxy
beginResetModel();
// Clear the containers
p_->mappings_.clear();
merge_points_.clear();
endResetModel();
}
void MergedProxyModel::SubModelReset() {
QAbstractItemModel* submodel = static_cast<QAbstractItemModel*>(sender());
// TODO: When we require Qt 4.6, use beginResetModel() and endResetModel()
// in CollectionModel and catch those here - that will let us do away with this
// std::numeric_limits<int>::max() hack.
// Remove all the children of the item that got deleted
QModelIndex source_parent = merge_points_.value(submodel);
QModelIndex proxy_parent = mapFromSource(source_parent);
// We can't know how many children it had, since it's already disappeared...
resetting_model_ = submodel;
beginRemoveRows(proxy_parent, 0, std::numeric_limits<int>::max() - 1);
endRemoveRows();
resetting_model_ = nullptr;
// Delete all the mappings that reference the submodel
auto it = p_->mappings_.get<tag_by_pointer>().begin();
auto end = p_->mappings_.get<tag_by_pointer>().end();
while (it != end) {
if ((*it)->source_index.model() == submodel) {
delete *it;
it = p_->mappings_.get<tag_by_pointer>().erase(it);
} else {
++it;
}
}
// "Insert" items from the newly reset submodel
int count = submodel->rowCount();
if (count) {
beginInsertRows(proxy_parent, 0, count - 1);
endInsertRows();
}
emit SubModelReset(proxy_parent, submodel);
}
QModelIndex MergedProxyModel::GetActualSourceParent(const QModelIndex& source_parent, QAbstractItemModel* model) const {
if (!source_parent.isValid() && model != sourceModel())
return merge_points_.value(model);
return source_parent;
}
void MergedProxyModel::RowsAboutToBeInserted(const QModelIndex& source_parent, int start, int end) {
beginInsertRows(mapFromSource(GetActualSourceParent(source_parent, static_cast<QAbstractItemModel*>(sender()))), start, end);
}
void MergedProxyModel::RowsInserted(const QModelIndex&, int, int) {
endInsertRows();
}
void MergedProxyModel::RowsAboutToBeRemoved(const QModelIndex& source_parent, int start, int end) {
beginRemoveRows(mapFromSource(GetActualSourceParent(source_parent, static_cast<QAbstractItemModel*>(sender()))), start, end);
}
void MergedProxyModel::RowsRemoved(const QModelIndex&, int, int) {
endRemoveRows();
}
QModelIndex MergedProxyModel::mapToSource(const QModelIndex& proxy_index) const {
if (!proxy_index.isValid()) return QModelIndex();
Mapping* mapping = static_cast<Mapping*>(proxy_index.internalPointer());
if (p_->mappings_.get<tag_by_pointer>().find(mapping) ==
p_->mappings_.get<tag_by_pointer>().end())
return QModelIndex();
if (mapping->source_index.model() == resetting_model_) return QModelIndex();
return mapping->source_index;
}
QModelIndex MergedProxyModel::mapFromSource(const QModelIndex& source_index) const {
if (!source_index.isValid()) return QModelIndex();
if (source_index.model() == resetting_model_) return QModelIndex();
// Add a mapping if we don't have one already
const auto& it = p_->mappings_.get<tag_by_source>().find(source_index);
Mapping* mapping;
if (it != p_->mappings_.get<tag_by_source>().end()) {
mapping = *it;
} else {
mapping = new Mapping(source_index);
const_cast<MergedProxyModel*>(this)->p_->mappings_.insert(mapping);
}
return createIndex(source_index.row(), source_index.column(), mapping);
}
QModelIndex MergedProxyModel::index(int row, int column, const QModelIndex &parent) const {
QModelIndex source_index;
if (!parent.isValid()) {
source_index = sourceModel()->index(row, column, QModelIndex());
}
else {
QModelIndex source_parent = mapToSource(parent);
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
if (child_model)
source_index = child_model->index(row, column, QModelIndex());
else
source_index = source_parent.model()->index(row, column, source_parent);
}
return mapFromSource(source_index);
}
QModelIndex MergedProxyModel::parent(const QModelIndex& child) const {
QModelIndex source_child = mapToSource(child);
if (source_child.model() == sourceModel())
return mapFromSource(source_child.parent());
if (!IsKnownModel(source_child.model())) return QModelIndex();
if (!source_child.parent().isValid())
return mapFromSource(merge_points_.value(GetModel(source_child)));
return mapFromSource(source_child.parent());
}
int MergedProxyModel::rowCount(const QModelIndex& parent) const {
if (!parent.isValid()) return sourceModel()->rowCount(QModelIndex());
QModelIndex source_parent = mapToSource(parent);
if (!IsKnownModel(source_parent.model())) return 0;
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
if (child_model) {
// Query the source model but disregard what it says, so it gets a chance
// to lazy load
source_parent.model()->rowCount(source_parent);
return child_model->rowCount(QModelIndex());
}
return source_parent.model()->rowCount(source_parent);
}
int MergedProxyModel::columnCount(const QModelIndex& parent) const {
if (!parent.isValid()) return sourceModel()->columnCount(QModelIndex());
QModelIndex source_parent = mapToSource(parent);
if (!IsKnownModel(source_parent.model())) return 0;
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
if (child_model) return child_model->columnCount(QModelIndex());
return source_parent.model()->columnCount(source_parent);
}
bool MergedProxyModel::hasChildren(const QModelIndex& parent) const {
if (!parent.isValid()) return sourceModel()->hasChildren(QModelIndex());
QModelIndex source_parent = mapToSource(parent);
if (!IsKnownModel(source_parent.model())) return false;
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
if (child_model) return child_model->hasChildren(QModelIndex()) || source_parent.model()->hasChildren(source_parent);
return source_parent.model()->hasChildren(source_parent);
}
QVariant MergedProxyModel::data(const QModelIndex& proxyIndex, int role) const {
QModelIndex source_index = mapToSource(proxyIndex);
if (!IsKnownModel(source_index.model())) return QVariant();
return source_index.model()->data(source_index, role);
}
QMap<int, QVariant> MergedProxyModel::itemData(const QModelIndex& proxy_index) const {
QModelIndex source_index = mapToSource(proxy_index);
if (!source_index.isValid()) return sourceModel()->itemData(QModelIndex());
return source_index.model()->itemData(source_index);
}
Qt::ItemFlags MergedProxyModel::flags(const QModelIndex& index) const {
QModelIndex source_index = mapToSource(index);
if (!source_index.isValid()) return sourceModel()->flags(QModelIndex());
return source_index.model()->flags(source_index);
}
bool MergedProxyModel::setData(const QModelIndex& index, const QVariant& value, int role) {
QModelIndex source_index = mapToSource(index);
if (!source_index.isValid())
return sourceModel()->setData(index, value, role);
return GetModel(index)->setData(index, value, role);
}
QStringList MergedProxyModel::mimeTypes() const {
QStringList ret;
ret << sourceModel()->mimeTypes();
for (const QAbstractItemModel* model : merge_points_.keys()) {
ret << model->mimeTypes();
}
return ret;
}
QMimeData* MergedProxyModel::mimeData(const QModelIndexList& indexes) const {
if (indexes.isEmpty()) return 0;
// Only ask the first index's model
const QAbstractItemModel* model = mapToSource(indexes[0]).model();
if (!model) {
return 0;
}
// Only ask about the indexes that are actually in that model
QModelIndexList indexes_in_model;
for (const QModelIndex& proxy_index : indexes) {
QModelIndex source_index = mapToSource(proxy_index);
if (source_index.model() != model) continue;
indexes_in_model << source_index;
}
return model->mimeData(indexes_in_model);
}
bool MergedProxyModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) {
if (!parent.isValid()) {
return false;
}
return sourceModel()->dropMimeData(data, action, row, column, parent);
}
QModelIndex MergedProxyModel::FindSourceParent(const QModelIndex& proxy_index) const {
if (!proxy_index.isValid()) return QModelIndex();
QModelIndex source_index = mapToSource(proxy_index);
if (source_index.model() == sourceModel()) return source_index;
return merge_points_.value(GetModel(source_index));
}
bool MergedProxyModel::canFetchMore(const QModelIndex& parent) const {
QModelIndex source_index = mapToSource(parent);
if (!source_index.isValid())
return sourceModel()->canFetchMore(QModelIndex());
return source_index.model()->canFetchMore(source_index);
}
void MergedProxyModel::fetchMore(const QModelIndex& parent) {
QModelIndex source_index = mapToSource(parent);
if (!source_index.isValid())
sourceModel()->fetchMore(QModelIndex());
else
GetModel(source_index)->fetchMore(source_index);
}
QAbstractItemModel* MergedProxyModel::GetModel(const QModelIndex& source_index) const {
// This is essentially const_cast<QAbstractItemModel*>(source_index.model()),
// but without the const_cast
const QAbstractItemModel* const_model = source_index.model();
if (const_model == sourceModel()) return sourceModel();
for (QAbstractItemModel* submodel : merge_points_.keys()) {
if (submodel == const_model) return submodel;
}
return nullptr;
}
void MergedProxyModel::DataChanged(const QModelIndex& top_left, const QModelIndex& bottom_right) {
emit dataChanged(mapFromSource(top_left), mapFromSource(bottom_right));
}
void MergedProxyModel::LayoutAboutToBeChanged() {
old_merge_points_.clear();
for (QAbstractItemModel* key : merge_points_.keys()) {
old_merge_points_[key] = merge_points_.value(key);
}
}
void MergedProxyModel::LayoutChanged() {
for (QAbstractItemModel* key : merge_points_.keys()) {
if (!old_merge_points_.contains(key)) continue;
const int old_row = old_merge_points_[key].row();
const int new_row = merge_points_[key].row();
if (old_row != new_row) {
beginResetModel();
endResetModel();
return;
}
}
}
bool MergedProxyModel::IsKnownModel(const QAbstractItemModel* model) const {
if (model == this || model == sourceModel() ||
merge_points_.contains(const_cast<QAbstractItemModel*>(model)))
return true;
return false;
}
QModelIndexList MergedProxyModel::mapFromSource(const QModelIndexList& source_indexes) const {
QModelIndexList ret;
for (const QModelIndex& index : source_indexes) {
ret << mapFromSource(index);
}
return ret;
}
QModelIndexList MergedProxyModel::mapToSource(const QModelIndexList& proxy_indexes) const {
QModelIndexList ret;
for (const QModelIndex& index : proxy_indexes) {
ret << mapToSource(index);
}
return ret;
}

110
src/core/mergedproxymodel.h Normal file
View File

@@ -0,0 +1,110 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MERGEDPROXYMODEL_H
#define MERGEDPROXYMODEL_H
#include "config.h"
#include <memory>
#include <QAbstractProxyModel>
std::size_t hash_value(const QModelIndex& index);
class MergedProxyModelPrivate;
class MergedProxyModel : public QAbstractProxyModel {
Q_OBJECT
public:
explicit MergedProxyModel(QObject* parent = nullptr);
~MergedProxyModel();
// Make another model appear as a child of the given item in the source model.
void AddSubModel(const QModelIndex& source_parent, QAbstractItemModel* submodel);
void RemoveSubModel(const QModelIndex& source_parent);
// Find the item in the source model that is the parent of the model
// containing proxy_index. If proxy_index is in the source model, then
// this just returns mapToSource(proxy_index).
QModelIndex FindSourceParent(const QModelIndex& proxy_index) const;
// QAbstractItemModel
QModelIndex index(int row, int column, const QModelIndex& parent) const;
QModelIndex parent(const QModelIndex& child) const;
int rowCount(const QModelIndex& parent) const;
int columnCount(const QModelIndex& parent) const;
QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const;
bool hasChildren(const QModelIndex& parent) const;
QMap<int, QVariant> itemData(const QModelIndex& proxyIndex) const;
Qt::ItemFlags flags(const QModelIndex& index) const;
bool setData(const QModelIndex& index, const QVariant& value, int role);
QStringList mimeTypes() const;
QMimeData* mimeData(const QModelIndexList& indexes) const;
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
bool canFetchMore(const QModelIndex& parent) const;
void fetchMore(const QModelIndex& parent);
// QAbstractProxyModel
// Note that these implementations of map{To,From}Source will not always
// give you an index in sourceModel(), you might get an index in one of the
// child models instead.
QModelIndex mapFromSource(const QModelIndex& sourceIndex) const;
QModelIndex mapToSource(const QModelIndex& proxyIndex) const;
void setSourceModel(QAbstractItemModel* sourceModel);
// Convenience functions that call map{To,From}Source multiple times.
QModelIndexList mapFromSource(const QModelIndexList& source_indexes) const;
QModelIndexList mapToSource(const QModelIndexList& proxy_indexes) const;
signals:
void SubModelReset(const QModelIndex& root, QAbstractItemModel* model);
private slots:
void SourceModelReset();
void SubModelReset();
void RowsAboutToBeInserted(const QModelIndex& source_parent, int start, int end);
void RowsInserted(const QModelIndex& source_parent, int start, int end);
void RowsAboutToBeRemoved(const QModelIndex& source_parent, int start, int end);
void RowsRemoved(const QModelIndex& source_parent, int start, int end);
void DataChanged(const QModelIndex& top_left, const QModelIndex& bottom_right);
void LayoutAboutToBeChanged();
void LayoutChanged();
private:
QModelIndex GetActualSourceParent(const QModelIndex& source_parent,
QAbstractItemModel* model) const;
QAbstractItemModel* GetModel(const QModelIndex& source_index) const;
void DeleteAllMappings();
bool IsKnownModel(const QAbstractItemModel* model) const;
QMap<QAbstractItemModel*, QPersistentModelIndex> merge_points_;
QAbstractItemModel* resetting_model_;
QMap<QAbstractItemModel*, QModelIndex> old_merge_points_;
std::unique_ptr<MergedProxyModelPrivate> p_;
};
#endif // MERGEDPROXYMODEL_H

107
src/core/metatypes.cpp Normal file
View File

@@ -0,0 +1,107 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "metatypes.h"
#include "config.h"
#include <QMetaType>
#include <QNetworkReply>
#include <QNetworkCookie>
#include "metatypes.h"
#include "engine/enginebase.h"
#include "engine/enginetype.h"
#ifdef HAVE_GSTREAMER
# include "engine/gstengine.h"
# include "engine/gstenginepipeline.h"
#endif
#include "collection/directory.h"
#include "playlist/playlist.h"
#include "equalizer/equalizer.h"
#include "covermanager/albumcoverfetcher.h"
#ifdef HAVE_DBUS
#include <QDBusMetaType>
#include "core/mpris2.h"
#include "dbus/metatypes.h"
#endif
class QNetworkReply;
#ifdef HAVE_GSTREAMER
class GstEnginePipeline;
#endif
void RegisterMetaTypes() {
//qRegisterMetaType<CollapsibleInfoPane::Data>("CollapsibleInfoPane::Data");
qRegisterMetaType<const char*>("const char*");
qRegisterMetaType<CoverSearchResult>("CoverSearchResult");
qRegisterMetaType<CoverSearchResults>("CoverSearchResults");
qRegisterMetaType<Directory>("Directory");
qRegisterMetaType<DirectoryList>("DirectoryList");
qRegisterMetaType<Engine::SimpleMetaBundle>("Engine::SimpleMetaBundle");
qRegisterMetaType<Engine::State>("Engine::State");
qRegisterMetaType<Engine::TrackChangeFlags>("Engine::TrackChangeFlags");
qRegisterMetaType<Equalizer::Params>("Equalizer::Params");
qRegisterMetaType<EngineBase::PluginDetails>("EngineBase::PluginDetails");
qRegisterMetaType<EngineBase::OutputDetails>("EngineBase::OutputDetails");
#ifdef HAVE_GSTREAMER
qRegisterMetaType<GstBuffer*>("GstBuffer*");
qRegisterMetaType<GstElement*>("GstElement*");
qRegisterMetaType<GstEnginePipeline*>("GstEnginePipeline*");
#endif
qRegisterMetaType<PlaylistItemList>("PlaylistItemList");
qRegisterMetaType<PlaylistItemPtr>("PlaylistItemPtr");
qRegisterMetaType<QList<CoverSearchResult> >("QList<CoverSearchResult>");
qRegisterMetaType<QList<int>>("QList<int>");
qRegisterMetaType<QList<PlaylistItemPtr> >("QList<PlaylistItemPtr>");
qRegisterMetaType<PlaylistSequence::RepeatMode>("PlaylistSequence::RepeatMode");
qRegisterMetaType<PlaylistSequence::ShuffleMode>("PlaylistSequence::ShuffleMode");
qRegisterMetaType<QAbstractSocket::SocketState>("QAbstractSocket::SocketState");
qRegisterMetaType<QList<QNetworkCookie> >("QList<QNetworkCookie>");
qRegisterMetaType<QList<Song> >("QList<Song>");
qRegisterMetaType<QNetworkCookie>("QNetworkCookie");
qRegisterMetaType<QNetworkReply*>("QNetworkReply*");
qRegisterMetaType<QNetworkReply**>("QNetworkReply**");
qRegisterMetaType<SongList>("SongList");
qRegisterMetaType<Song>("Song");
qRegisterMetaTypeStreamOperators<Equalizer::Params>("Equalizer::Params");
qRegisterMetaTypeStreamOperators<QMap<int, int> >("ColumnAlignmentMap");
qRegisterMetaType<SubdirectoryList>("SubdirectoryList");
qRegisterMetaType<Subdirectory>("Subdirectory");
qRegisterMetaType<QList<QUrl>>("QList<QUrl>");
qRegisterMetaType<QAbstractSocket::SocketState>();
qRegisterMetaType<Engine::EngineType>("EngineType");
#ifdef HAVE_DBUS
qDBusRegisterMetaType<QImage>();
qDBusRegisterMetaType<TrackMetadata>();
qDBusRegisterMetaType<TrackIds>();
qDBusRegisterMetaType<QList<QByteArray>>();
qDBusRegisterMetaType<MprisPlaylist>();
qDBusRegisterMetaType<MaybePlaylist>();
qDBusRegisterMetaType<MprisPlaylistList>();
qDBusRegisterMetaType<InterfacesAndProperties>();
qDBusRegisterMetaType<ManagedObjectList>();
#endif
}

6
src/core/metatypes.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef METATYPES_H
#define METATYPES_H
void RegisterMetaTypes();
#endif

76
src/core/mimedata.h Normal file
View File

@@ -0,0 +1,76 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MIMEDATA_H
#define MIMEDATA_H
#include "config.h"
#include <QMimeData>
class MimeData : public QMimeData {
Q_OBJECT
public:
MimeData(bool clear = false, bool play_now = false, bool enqueue = false, bool open_in_new_playlist = false)
: override_user_settings_(false),
clear_first_(clear),
play_now_(play_now),
enqueue_now_(enqueue),
open_in_new_playlist_(open_in_new_playlist),
name_for_new_playlist_(QString()),
from_doubleclick_(false) {}
// If this is set then MainWindow will not touch any of the other flags.
bool override_user_settings_;
// If this is set then the playlist will be cleared before these songs
// are inserted.
bool clear_first_;
// If this is set then the first item that is inserted will start playing
// immediately. Note: this is always overridden with the user's preference
// if the MimeData goes via MainWindow, unless you set override_user_settings_.
bool play_now_;
// If this is set then the items are added to the queue after being inserted.
bool enqueue_now_;
// If this is set then the items are inserted into a newly created playlist.
bool open_in_new_playlist_;
// This serves as a name for the new playlist in 'open_in_new_playlist_' mode.
QString name_for_new_playlist_;
// This can be set if this MimeData goes via MainWindow (ie. it is created
// manually in a double-click). The MainWindow will set the above flags to
// the defaults set by the user.
bool from_doubleclick_;
// Returns a pretty name for a playlist containing songs described by this MimeData
// object. By pretty name we mean the value of 'name_for_new_playlist_' or generic
// "Playlist" string if the 'name_for_new_playlist_' attribute is empty.
QString get_name_for_new_playlist() {
return name_for_new_playlist_.isEmpty() ? tr("Playlist") : name_for_new_playlist_;
}
};
#endif // MIMEDATA_H

33
src/core/mpris.cpp Normal file
View File

@@ -0,0 +1,33 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "mpris.h"
#include "mpris2.h"
namespace mpris {
Mpris::Mpris(Application* app, QObject* parent) : QObject(parent), mpris2_(new mpris::Mpris2(app, this)) {
connect(mpris2_, SIGNAL(RaiseMainWindow()), SIGNAL(RaiseMainWindow()));
}
} // namespace mpris

52
src/core/mpris.h Normal file
View File

@@ -0,0 +1,52 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MPRIS_H
#define MPRIS_H
#include "config.h"
#include <QObject>
class Application;
namespace mpris {
class Mpris1;
class Mpris2;
class Mpris : public QObject {
Q_OBJECT
public:
explicit Mpris(Application *app, QObject *parent = nullptr);
signals:
void RaiseMainWindow();
private:
Mpris1 *mpris1_;
Mpris2 *mpris2_;
};
} // namespace mpris
#endif // MPRIS_H

543
src/core/mpris2.cpp Normal file
View File

@@ -0,0 +1,543 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <algorithm>
#include <QApplication>
#include <QDBusConnection>
#include <QtConcurrentRun>
#include "mpris2.h"
#include "core/application.h"
#include "core/mainwindow.h"
#include "core/logging.h"
#include "core/mpris_common.h"
#include "core/mpris2_player.h"
#include "core/mpris2_playlists.h"
#include "core/mpris2_root.h"
#include "core/mpris2_tracklist.h"
#include "core/player.h"
#include "core/timeconstants.h"
#include "engine/enginebase.h"
#include "playlist/playlist.h"
#include "playlist/playlistmanager.h"
#include "playlist/playlistsequence.h"
#include "covermanager/currentartloader.h"
QDBusArgument &operator<<(QDBusArgument &arg, const MprisPlaylist &playlist) {
arg.beginStructure();
arg << playlist.id << playlist.name << playlist.icon;
arg.endStructure();
return arg;
}
const QDBusArgument &operator>> (const QDBusArgument &arg, MprisPlaylist &playlist) {
arg.beginStructure();
arg >> playlist.id >> playlist.name >> playlist.icon;
arg.endStructure();
return arg;
}
QDBusArgument &operator<<(QDBusArgument &arg, const MaybePlaylist &playlist) {
arg.beginStructure();
arg << playlist.valid;
arg << playlist.playlist;
arg.endStructure();
return arg;
}
const QDBusArgument &operator>> (const QDBusArgument &arg, MaybePlaylist &playlist) {
arg.beginStructure();
arg >> playlist.valid >> playlist.playlist;
arg.endStructure();
return arg;
}
namespace mpris {
const char* Mpris2::kMprisObjectPath = "/org/mpris/MediaPlayer2";
const char* Mpris2::kServiceName = "org.mpris.MediaPlayer2.strawberry";
const char* Mpris2::kFreedesktopPath = "org.freedesktop.DBus.Properties";
Mpris2::Mpris2(Application* app, QObject* parent) : QObject(parent), app_(app) {
new Mpris2Root(this);
new Mpris2TrackList(this);
new Mpris2Player(this);
new Mpris2Playlists(this);
if (!QDBusConnection::sessionBus().registerService(kServiceName)) {
qLog(Warning) << "Failed to register" << QString(kServiceName) << "on the session bus";
return;
}
QDBusConnection::sessionBus().registerObject(kMprisObjectPath, this);
connect(app_->current_art_loader(), SIGNAL(ArtLoaded(Song,QString,QImage)), SLOT(ArtLoaded(Song,QString)));
connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State)));
connect(app_->player(), SIGNAL(VolumeChanged(int)), SLOT(VolumeChanged()));
connect(app_->player(), SIGNAL(Seeked(qlonglong)), SIGNAL(Seeked(qlonglong)));
connect(app_->playlist_manager(), SIGNAL(PlaylistManagerInitialized()), SLOT(PlaylistManagerInitialized()));
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(CurrentSongChanged(Song)));
connect(app_->playlist_manager(), SIGNAL(PlaylistChanged(Playlist*)), SLOT(PlaylistChanged(Playlist*)));
connect(app_->playlist_manager(), SIGNAL(CurrentChanged(Playlist*)), SLOT(PlaylistCollectionChanged(Playlist*)));
}
// when PlaylistManager gets it ready, we connect PlaylistSequence with this
void Mpris2::PlaylistManagerInitialized() {
connect(app_->playlist_manager()->sequence(), SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)), SLOT(ShuffleModeChanged()));
connect(app_->playlist_manager()->sequence(), SIGNAL(RepeatModeChanged(PlaylistSequence::RepeatMode)), SLOT(RepeatModeChanged()));
}
void Mpris2::EngineStateChanged(Engine::State newState) {
if (newState != Engine::Playing && newState != Engine::Paused) {
last_metadata_ = QVariantMap();
EmitNotification("Metadata");
}
EmitNotification("PlaybackStatus", PlaybackStatus(newState));
if (newState == Engine::Playing) EmitNotification("CanSeek", CanSeek(newState));
}
void Mpris2::VolumeChanged() { EmitNotification("Volume"); }
void Mpris2::ShuffleModeChanged() { EmitNotification("Shuffle"); }
void Mpris2::RepeatModeChanged() {
EmitNotification("LoopStatus");
EmitNotification("CanGoNext", CanGoNext());
EmitNotification("CanGoPrevious", CanGoPrevious());
}
void Mpris2::EmitNotification(const QString &name, const QVariant &val) {
EmitNotification(name, val, "org.mpris.MediaPlayer2.Player");
}
void Mpris2::EmitNotification(const QString &name, const QVariant &val, const QString &mprisEntity) {
QDBusMessage msg = QDBusMessage::createSignal(kMprisObjectPath, kFreedesktopPath, "PropertiesChanged");
QVariantMap map;
map.insert(name, val);
QVariantList args = QVariantList() << mprisEntity << map << QStringList();
msg.setArguments(args);
QDBusConnection::sessionBus().send(msg);
}
void Mpris2::EmitNotification(const QString &name) {
QVariant value;
if (name == "PlaybackStatus") value = PlaybackStatus();
else if (name == "LoopStatus") value = LoopStatus();
else if (name == "Shuffle") value = Shuffle();
else if (name == "Metadata") value = Metadata();
else if (name == "Volume") value = Volume();
else if (name == "Position") value = Position();
else if (name == "CanGoNext") value = CanGoNext();
else if (name == "CanGoPrevious") value = CanGoPrevious();
else if (name == "CanSeek") value = CanSeek();
if (value.isValid()) EmitNotification(name, value);
}
//------------------Root Interface--------------------------//
bool Mpris2::CanQuit() const { return true; }
bool Mpris2::CanRaise() const { return true; }
bool Mpris2::HasTrackList() const { return true; }
QString Mpris2::Identity() const { return QCoreApplication::applicationName(); }
QString Mpris2::DesktopEntryAbsolutePath() const {
QStringList xdg_data_dirs = QString(getenv("XDG_DATA_DIRS")).split(":");
xdg_data_dirs.append("/usr/local/share/");
xdg_data_dirs.append("/usr/share/");
for (const QString &directory : xdg_data_dirs) {
QString path = QString("%1/applications/%2.desktop").arg(
directory, QApplication::applicationName().toLower());
if (QFile::exists(path)) return path;
}
return QString();
}
QString Mpris2::DesktopEntry() const {
return QApplication::applicationName().toLower();
}
QStringList Mpris2::SupportedUriSchemes() const {
static QStringList res = QStringList() << "file"
<< "http"
<< "cdda"
<< "smb"
<< "sftp";
return res;
}
QStringList Mpris2::SupportedMimeTypes() const {
static QStringList res = QStringList() << "application/ogg"
<< "application/x-ogg"
<< "application/x-ogm-audio"
<< "audio/aac"
<< "audio/mp4"
<< "audio/mpeg"
<< "audio/mpegurl"
<< "audio/ogg"
<< "audio/vnd.rn-realaudio"
<< "audio/vorbis"
<< "audio/x-flac"
<< "audio/x-mp3"
<< "audio/x-mpeg"
<< "audio/x-mpegurl"
<< "audio/x-ms-wma"
<< "audio/x-musepack"
<< "audio/x-oggflac"
<< "audio/x-pn-realaudio"
<< "audio/x-scpls"
<< "audio/x-speex"
<< "audio/x-vorbis"
<< "audio/x-vorbis+ogg"
<< "audio/x-wav"
<< "video/x-ms-asf"
<< "x-content/audio-player";
return res;
}
void Mpris2::Raise() { emit RaiseMainWindow(); }
void Mpris2::Quit() { qApp->quit(); }
QString Mpris2::PlaybackStatus() const {
return PlaybackStatus(app_->player()->GetState());
}
QString Mpris2::PlaybackStatus(Engine::State state) const {
switch (state) {
case Engine::Playing: return "Playing";
case Engine::Paused: return "Paused";
default: return "Stopped";
}
}
QString Mpris2::LoopStatus() const {
if (!app_->playlist_manager()->sequence()) {
return "None";
}
switch (app_->playlist_manager()->sequence()->repeat_mode()) {
case PlaylistSequence::Repeat_Album:
case PlaylistSequence::Repeat_Playlist: return "Playlist";
case PlaylistSequence::Repeat_Track: return "Track";
default: return "None";
}
}
void Mpris2::SetLoopStatus(const QString &value) {
PlaylistSequence::RepeatMode mode = PlaylistSequence::Repeat_Off;
if (value == "None") {
mode = PlaylistSequence::Repeat_Off;
} else if (value == "Track") {
mode = PlaylistSequence::Repeat_Track;
} else if (value == "Playlist") {
mode = PlaylistSequence::Repeat_Playlist;
}
app_->playlist_manager()->active()->sequence()->SetRepeatMode(mode);
}
double Mpris2::Rate() const { return 1.0; }
void Mpris2::SetRate(double rate) {
if (rate == 0) {
app_->player()->Pause();
}
}
bool Mpris2::Shuffle() const {
return app_->playlist_manager()->sequence()->shuffle_mode() != PlaylistSequence::Shuffle_Off;
}
void Mpris2::SetShuffle(bool enable) {
app_->playlist_manager()->active()->sequence()->SetShuffleMode(enable ? PlaylistSequence::Shuffle_All : PlaylistSequence::Shuffle_Off);
}
QVariantMap Mpris2::Metadata() const { return last_metadata_; }
QString Mpris2::current_track_id() const {
return QString("/org/strawberry/strawberrymusicplayer/Track/%1").arg(QString::number(app_->playlist_manager()->active()->current_row()));
}
// We send Metadata change notification as soon as the process of
// changing song starts...
void Mpris2::CurrentSongChanged(const Song &song) {
ArtLoaded(song, "");
EmitNotification("CanGoNext", CanGoNext());
EmitNotification("CanGoPrevious", CanGoPrevious());
EmitNotification("CanSeek", CanSeek());
}
// ... and we add the cover information later, when it's available.
void Mpris2::ArtLoaded(const Song &song, const QString &art_uri) {
last_metadata_ = QVariantMap();
song.ToXesam(&last_metadata_);
using mpris::AddMetadata;
AddMetadata("mpris:trackid", current_track_id(), &last_metadata_);
if (!art_uri.isEmpty()) {
AddMetadata("mpris:artUrl", art_uri, &last_metadata_);
}
AddMetadata("year", song.year(), &last_metadata_);
AddMetadata("bitrate", song.bitrate(), &last_metadata_);
EmitNotification("Metadata", last_metadata_);
}
double Mpris2::Volume() const { return app_->player()->GetVolume() / 100.0; }
void Mpris2::SetVolume(double value) { app_->player()->SetVolume(value * 100); }
qlonglong Mpris2::Position() const {
return app_->player()->engine()->position_nanosec() / kNsecPerUsec;
}
double Mpris2::MaximumRate() const { return 1.0; }
double Mpris2::MinimumRate() const { return 1.0; }
bool Mpris2::CanGoNext() const {
return app_->playlist_manager()->active() && app_->playlist_manager()->active()->next_row() != -1;
}
bool Mpris2::CanGoPrevious() const {
return app_->playlist_manager()->active() && (app_->playlist_manager()->active()->previous_row() != -1 || app_->player()->PreviousWouldRestartTrack());
}
bool Mpris2::CanPlay() const {
return app_->playlist_manager()->active() && app_->playlist_manager()->active()->rowCount() != 0 && !(app_->player()->GetState() == Engine::Playing);
}
// This one's a bit different than MPRIS 1 - we want this to be true even when
// the song is already paused or stopped.
bool Mpris2::CanPause() const {
return (app_->player()->GetCurrentItem() && app_->player()->GetState() == Engine::Playing && !(app_->player()->GetCurrentItem()->options() & PlaylistItem::PauseDisabled)) || PlaybackStatus() == "Paused" || PlaybackStatus() == "Stopped";
}
bool Mpris2::CanSeek() const { return CanSeek(app_->player()->GetState()); }
bool Mpris2::CanSeek(Engine::State state) const {
return app_->player()->GetCurrentItem() && state != Engine::Empty;
}
bool Mpris2::CanControl() const { return true; }
void Mpris2::Next() {
if (CanGoNext()) {
app_->player()->Next();
}
}
void Mpris2::Previous() {
if (CanGoPrevious()) {
app_->player()->Previous();
}
}
void Mpris2::Pause() {
if (CanPause() && app_->player()->GetState() != Engine::Paused) {
app_->player()->Pause();
}
}
void Mpris2::PlayPause() {
if (CanPause()) {
app_->player()->PlayPause();
}
}
void Mpris2::Stop() { app_->player()->Stop(); }
void Mpris2::Play() {
if (CanPlay()) {
app_->player()->Play();
}
}
void Mpris2::Seek(qlonglong offset) {
if (CanSeek()) {
app_->player()->SeekTo(app_->player()->engine()->position_nanosec() / kNsecPerSec + offset / kUsecPerSec);
}
}
void Mpris2::SetPosition(const QDBusObjectPath &trackId, qlonglong offset) {
if (CanSeek() && trackId.path() == current_track_id() && offset >= 0) {
offset *= kNsecPerUsec;
if(offset < app_->player()->GetCurrentItem()->Metadata().length_nanosec()) {
app_->player()->SeekTo(offset / kNsecPerSec);
}
}
}
void Mpris2::OpenUri(const QString &uri) {
app_->playlist_manager()->active()->InsertUrls(QList<QUrl>() << QUrl(uri), -1, true);
}
TrackIds Mpris2::Tracks() const {
// TODO
return TrackIds();
}
bool Mpris2::CanEditTracks() const { return false; }
TrackMetadata Mpris2::GetTracksMetadata(const TrackIds &tracks) const {
// TODO
return TrackMetadata();
}
void Mpris2::AddTrack(const QString &uri, const QDBusObjectPath &afterTrack, bool setAsCurrent) {
// TODO
}
void Mpris2::RemoveTrack(const QDBusObjectPath &trackId) {
// TODO
}
void Mpris2::GoTo(const QDBusObjectPath &trackId) {
// TODO
}
quint32 Mpris2::PlaylistCount() const {
return app_->playlist_manager()->GetAllPlaylists().size();
}
QStringList Mpris2::Orderings() const { return QStringList() << "User"; }
namespace {
QDBusObjectPath MakePlaylistPath(int id) {
return QDBusObjectPath(QString("/org/strawberry/strawberrymusicplayer/PlaylistId/%1").arg(id));
}
}
MaybePlaylist Mpris2::ActivePlaylist() const {
MaybePlaylist maybe_playlist;
Playlist* current_playlist = app_->playlist_manager()->current();
maybe_playlist.valid = current_playlist;
if (!current_playlist) {
return maybe_playlist;
}
maybe_playlist.playlist.id = MakePlaylistPath(current_playlist->id());
maybe_playlist.playlist.name = app_->playlist_manager()->GetPlaylistName(current_playlist->id());
return maybe_playlist;
}
void Mpris2::ActivatePlaylist(const QDBusObjectPath &playlist_id) {
QStringList split_path = playlist_id.path().split('/');
qLog(Debug) << Q_FUNC_INFO << playlist_id.path() << split_path;
if (split_path.isEmpty()) {
return;
}
bool ok = false;
int p = split_path.last().toInt(&ok);
if (!ok) {
return;
}
if (!app_->playlist_manager()->IsPlaylistOpen(p)) {
qLog(Error) << "Playlist isn't opened!";
return;
}
app_->playlist_manager()->SetActivePlaylist(p);
app_->player()->Next();
}
// TODO: Support sort orders.
MprisPlaylistList Mpris2::GetPlaylists(quint32 index, quint32 max_count, const QString &order, bool reverse_order) {
MprisPlaylistList ret;
for (Playlist* p : app_->playlist_manager()->GetAllPlaylists()) {
MprisPlaylist mpris_playlist;
mpris_playlist.id = MakePlaylistPath(p->id());
mpris_playlist.name = app_->playlist_manager()->GetPlaylistName(p->id());
ret << mpris_playlist;
}
if (reverse_order) {
std::reverse(ret.begin(), ret.end());
}
return ret.mid(index, max_count);
}
void Mpris2::PlaylistChanged(Playlist* playlist) {
MprisPlaylist mpris_playlist;
mpris_playlist.id = MakePlaylistPath(playlist->id());
mpris_playlist.name = app_->playlist_manager()->GetPlaylistName(playlist->id());
emit PlaylistChanged(mpris_playlist);
}
void Mpris2::PlaylistCollectionChanged(Playlist* playlist) {
EmitNotification("PlaylistCount", "", "org.mpris.MediaPlayer2.Playlists");
}
} // namespace mpris

234
src/core/mpris2.h Normal file
View File

@@ -0,0 +1,234 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MPRIS2_H
#define MPRIS2_H
#include "config.h"
#include "playlist/playlistitem.h"
#include <QMetaObject>
#include <QObject>
#include <QtDBus>
class Application;
class MainWindow;
class Playlist;
typedef QList<QVariantMap> TrackMetadata;
typedef QList<QDBusObjectPath> TrackIds;
Q_DECLARE_METATYPE(TrackMetadata);
struct MprisPlaylist {
QDBusObjectPath id;
QString name;
QString icon; // Uri
};
typedef QList<MprisPlaylist> MprisPlaylistList;
Q_DECLARE_METATYPE(MprisPlaylist);
Q_DECLARE_METATYPE(MprisPlaylistList);
struct MaybePlaylist {
bool valid;
MprisPlaylist playlist;
};
Q_DECLARE_METATYPE(MaybePlaylist);
QDBusArgument &operator<<(QDBusArgument &arg, const MprisPlaylist &playlist);
const QDBusArgument &operator>> (const QDBusArgument &arg, MprisPlaylist &playlist);
QDBusArgument &operator<<(QDBusArgument &arg, const MaybePlaylist &playlist);
const QDBusArgument &operator>> (const QDBusArgument &arg, MaybePlaylist &playlist);
QDBusArgument &operator<< (QDBusArgument &arg, const QImage &image);
const QDBusArgument &operator>> (const QDBusArgument &arg, QImage &image);
namespace mpris {
class Mpris2 : public QObject {
Q_OBJECT
public:
Mpris2(Application *app, QObject *parent = nullptr);
// org.mpris.MediaPlayer2 MPRIS 2.0 Root interface
Q_PROPERTY(bool CanQuit READ CanQuit)
Q_PROPERTY(bool CanRaise READ CanRaise)
Q_PROPERTY(bool HasTrackList READ HasTrackList)
Q_PROPERTY(QString Identity READ Identity)
Q_PROPERTY(QString DesktopEntry READ DesktopEntry)
Q_PROPERTY(QStringList SupportedUriSchemes READ SupportedUriSchemes)
Q_PROPERTY(QStringList SupportedMimeTypes READ SupportedMimeTypes)
// org.mpris.MediaPlayer2 MPRIS 2.2 Root interface
Q_PROPERTY(bool CanSetFullscreen READ CanSetFullscreen)
Q_PROPERTY(bool Fullscreen READ Fullscreen WRITE SetFullscreen)
// org.mpris.MediaPlayer2.Player MPRIS 2.0 Player interface
Q_PROPERTY(QString PlaybackStatus READ PlaybackStatus)
Q_PROPERTY(QString LoopStatus READ LoopStatus WRITE SetLoopStatus)
Q_PROPERTY(double Rate READ Rate WRITE SetRate)
Q_PROPERTY(bool Shuffle READ Shuffle WRITE SetShuffle)
Q_PROPERTY(QVariantMap Metadata READ Metadata)
Q_PROPERTY(double Volume READ Volume WRITE SetVolume)
Q_PROPERTY(qlonglong Position READ Position)
Q_PROPERTY(double MinimumRate READ MinimumRate)
Q_PROPERTY(double MaximumRate READ MaximumRate)
Q_PROPERTY(bool CanGoNext READ CanGoNext)
Q_PROPERTY(bool CanGoPrevious READ CanGoPrevious)
Q_PROPERTY(bool CanPlay READ CanPlay)
Q_PROPERTY(bool CanPause READ CanPause)
Q_PROPERTY(bool CanSeek READ CanSeek)
Q_PROPERTY(bool CanControl READ CanControl)
// org.mpris.MediaPlayer2.TrackList MPRIS 2.0 Player interface
Q_PROPERTY(TrackIds Tracks READ Tracks)
Q_PROPERTY(bool CanEditTracks READ CanEditTracks)
// org.mpris.MediaPlayer2.Playlists MPRIS 2.1 Playlists interface
Q_PROPERTY(quint32 PlaylistCount READ PlaylistCount)
Q_PROPERTY(QStringList Orderings READ Orderings)
Q_PROPERTY(MaybePlaylist ActivePlaylist READ ActivePlaylist)
// Root Properties
bool CanQuit() const;
bool CanRaise() const;
bool HasTrackList() const;
QString Identity() const;
QString DesktopEntry() const;
QStringList SupportedUriSchemes() const;
QStringList SupportedMimeTypes() const;
// Root Properties added in MPRIS 2.2
bool CanSetFullscreen() const { return false; }
bool Fullscreen() const { return false; }
void SetFullscreen(bool) {}
// Methods
void Raise();
void Quit();
// Player Properties
QString PlaybackStatus() const;
QString LoopStatus() const;
void SetLoopStatus(const QString &value);
double Rate() const;
void SetRate(double value);
bool Shuffle() const;
void SetShuffle(bool value);
QVariantMap Metadata() const;
double Volume() const;
void SetVolume(double value);
qlonglong Position() const;
double MaximumRate() const;
double MinimumRate() const;
bool CanGoNext() const;
bool CanGoPrevious() const;
bool CanPlay() const;
bool CanPause() const;
bool CanSeek() const;
bool CanControl() const;
// Methods
void Next();
void Previous();
void Pause();
void PlayPause();
void Stop();
void Play();
void Seek(qlonglong offset);
void SetPosition(const QDBusObjectPath &trackId, qlonglong offset);
void OpenUri(const QString &uri);
// TrackList Properties
TrackIds Tracks() const;
bool CanEditTracks() const;
// Methods
TrackMetadata GetTracksMetadata(const TrackIds &tracks) const;
void AddTrack(const QString &uri, const QDBusObjectPath &afterTrack, bool setAsCurrent);
void RemoveTrack(const QDBusObjectPath &trackId);
void GoTo(const QDBusObjectPath &trackId);
// Playlist Properties
quint32 PlaylistCount() const;
QStringList Orderings() const;
MaybePlaylist ActivePlaylist() const;
// Methods
void ActivatePlaylist(const QDBusObjectPath &playlist_id);
QList<MprisPlaylist> GetPlaylists(quint32 index, quint32 max_count, const QString &order, bool reverse_order);
signals:
// Player
void Seeked(qlonglong position);
// TrackList
void TrackListReplaced(const TrackIds &Tracks, QDBusObjectPath CurrentTrack);
void TrackAdded(const TrackMetadata &Metadata, QDBusObjectPath AfterTrack);
void TrackRemoved(const QDBusObjectPath &trackId);
void TrackMetadataChanged(const QDBusObjectPath &trackId, const TrackMetadata &metadata);
void RaiseMainWindow();
// Playlist
void PlaylistChanged(const MprisPlaylist &playlist);
private slots:
void ArtLoaded(const Song &song, const QString &art_uri);
void EngineStateChanged(Engine::State newState);
void VolumeChanged();
void PlaylistManagerInitialized();
void CurrentSongChanged(const Song &song);
void ShuffleModeChanged();
void RepeatModeChanged();
void PlaylistChanged(Playlist *playlist);
void PlaylistCollectionChanged(Playlist *playlist);
private:
void EmitNotification(const QString &name);
void EmitNotification(const QString &name, const QVariant &val);
void EmitNotification(const QString &name, const QVariant &val, const QString &mprisEntity);
QString PlaybackStatus(Engine::State state) const;
QString current_track_id() const;
bool CanSeek(Engine::State state) const;
QString DesktopEntryAbsolutePath() const;
private:
static const char *kMprisObjectPath;
static const char *kServiceName;
static const char *kFreedesktopPath;
QVariantMap last_metadata_;
Application *app_;
};
} // namespace mpris
#endif

64
src/core/mpris_common.h Normal file
View File

@@ -0,0 +1,64 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MPRIS_COMMON_H
#define MPRIS_COMMON_H
#include "config.h"
#include <QDateTime>
#include <QObject>
#include <QStringList>
#include <QVariantMap>
namespace mpris {
inline void AddMetadata(const QString &key, const QString &metadata, QVariantMap *map) {
if (!metadata.isEmpty()) (*map)[key] = metadata;
}
inline void AddMetadataAsList(const QString &key, const QString &metadata, QVariantMap *map) {
if (!metadata.isEmpty()) (*map)[key] = QStringList() << metadata;
}
inline void AddMetadata(const QString &key, int metadata, QVariantMap *map) {
if (metadata > 0) (*map)[key] = metadata;
}
inline void AddMetadata(const QString &key, qint64 metadata, QVariantMap *map) {
if (metadata > 0) (*map)[key] = metadata;
}
inline void AddMetadata(const QString &key, double metadata, QVariantMap *map) {
if (metadata != 0.0) (*map)[key] = metadata;
}
inline void AddMetadata(const QString &key, const QDateTime &metadata, QVariantMap *map) {
if (metadata.isValid()) (*map)[key] = metadata;
}
inline QString AsMPRISDateTimeType(uint time) {
return time != -1 ? QDateTime::fromTime_t(time).toString(Qt::ISODate) : "";
}
} // namespace mpris
#endif // MPRIS_COMMON_H

View File

@@ -0,0 +1,90 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "multisortfilterproxy.h"
#include "core/logging.h"
#include <QDate>
#include <QDateTime>
#include <QTime>
MultiSortFilterProxy::MultiSortFilterProxy(QObject *parent)
: QSortFilterProxyModel(parent) {}
void MultiSortFilterProxy::AddSortSpec(int role, Qt::SortOrder order) {
sorting_ << SortSpec(role, order);
}
bool MultiSortFilterProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const {
for (const SortSpec &spec : sorting_) {
const int ret = Compare(left.data(spec.first), right.data(spec.first));
if (ret < 0) {
return spec.second == Qt::AscendingOrder;
}
else if (ret > 0) {
return spec.second != Qt::AscendingOrder;
}
}
return left.row() < right.row();
}
template <typename T>
static inline int DoCompare(T left, T right) {
if (left < right) return -1;
if (left > right) return 1;
return 0;
}
int MultiSortFilterProxy::Compare(const QVariant &left, const QVariant &right) const {
// Copied from the QSortFilterProxyModel::lessThan implementation, but returns
// -1, 0 or 1 instead of true or false.
switch (left.userType()) {
case QVariant::Invalid: return (right.type() != QVariant::Invalid) ? -1 : 0;
case QVariant::Int: return DoCompare(left.toInt(), right.toInt());
case QVariant::UInt: return DoCompare(left.toUInt(), right.toUInt());
case QVariant::LongLong: return DoCompare(left.toLongLong(), right.toLongLong());
case QVariant::ULongLong: return DoCompare(left.toULongLong(), right.toULongLong());
case QMetaType::Float: return DoCompare(left.toFloat(), right.toFloat());
case QVariant::Double: return DoCompare(left.toDouble(), right.toDouble());
case QVariant::Char: return DoCompare(left.toChar(), right.toChar());
case QVariant::Date: return DoCompare(left.toDate(), right.toDate());
case QVariant::Time: return DoCompare(left.toTime(), right.toTime());
case QVariant::DateTime: return DoCompare(left.toDateTime(), right.toDateTime());
case QVariant::String:
default:
if (isSortLocaleAware())
return left.toString().localeAwareCompare(right.toString());
else
return left.toString().compare(right.toString(), sortCaseSensitivity());
}
return 0;
}

View File

@@ -0,0 +1,45 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MULTISORTFILTERPROXY_H
#define MULTISORTFILTERPROXY_H
#include "config.h"
#include <QSortFilterProxyModel>
class MultiSortFilterProxy : public QSortFilterProxyModel {
public:
MultiSortFilterProxy(QObject *parent = nullptr);
void AddSortSpec(int role, Qt::SortOrder order = Qt::AscendingOrder);
protected:
bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
private:
int Compare(const QVariant &left, const QVariant &right) const;
typedef QPair<int, Qt::SortOrder> SortSpec;
QList<SortSpec> sorting_;
};
#endif // MULTISORTFILTERPROXY_H

28
src/core/musicstorage.cpp Normal file
View File

@@ -0,0 +1,28 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "musicstorage.h"
MusicStorage::MusicStorage()
{
}

89
src/core/musicstorage.h Normal file
View File

@@ -0,0 +1,89 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MUSICSTORAGE_H
#define MUSICSTORAGE_H
#include "config.h"
#include "song.h"
#include <functional>
#include <memory>
#include <QMetaType>
class MusicStorage {
public:
MusicStorage();
virtual ~MusicStorage() {}
enum Role {
Role_Storage = Qt::UserRole + 100,
Role_StorageForceConnect,
Role_Capacity,
Role_FreeSpace,
};
// Values are saved in the database - don't change
enum TranscodeMode {
Transcode_Always = 1,
Transcode_Never = 2,
Transcode_Unsupported = 3,
};
typedef std::function<void(float progress)> ProgressFunction;
struct CopyJob {
QString source_;
QString destination_;
Song metadata_;
bool overwrite_;
bool mark_as_listened_;
bool remove_original_;
ProgressFunction progress_;
};
struct DeleteJob {
Song metadata_;
};
virtual QString LocalPath() const { return QString(); }
virtual TranscodeMode GetTranscodeMode() const { return Transcode_Never; }
virtual Song::FileType GetTranscodeFormat() const { return Song::Type_Unknown; }
virtual bool GetSupportedFiletypes(QList<Song::FileType>* ret) { return true; }
virtual bool StartCopy(QList<Song::FileType>* supported_types) { return true;}
virtual bool CopyToStorage(const CopyJob& job) = 0;
virtual void FinishCopy(bool success) {}
virtual void StartDelete() {}
virtual bool DeleteFromStorage(const DeleteJob& job) = 0;
virtual void FinishDelete(bool success) {}
virtual void Eject() {}
};
Q_DECLARE_METATYPE(MusicStorage*);
Q_DECLARE_METATYPE(std::shared_ptr<MusicStorage>);
#endif // MUSICSTORAGE_H

224
src/core/network.cpp Normal file
View File

@@ -0,0 +1,224 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "network.h"
#include <QCoreApplication>
#include <QDir>
#include <QNetworkAccessManager>
#include <QNetworkDiskCache>
#include <QNetworkReply>
#include "core/closure.h"
#include "utilities.h"
QMutex ThreadSafeNetworkDiskCache::sMutex;
QNetworkDiskCache *ThreadSafeNetworkDiskCache::sCache = nullptr;
ThreadSafeNetworkDiskCache::ThreadSafeNetworkDiskCache(QObject *parent)
: QAbstractNetworkCache(parent) {
QMutexLocker l(&sMutex);
if (!sCache) {
sCache = new QNetworkDiskCache;
sCache->setCacheDirectory(Utilities::GetConfigPath(Utilities::Path_NetworkCache));
}
}
qint64 ThreadSafeNetworkDiskCache::cacheSize() const {
QMutexLocker l(&sMutex);
return sCache->cacheSize();
}
QIODevice *ThreadSafeNetworkDiskCache::data(const QUrl &url) {
QMutexLocker l(&sMutex);
return sCache->data(url);
}
void ThreadSafeNetworkDiskCache::insert(QIODevice *device) {
QMutexLocker l(&sMutex);
sCache->insert(device);
}
QNetworkCacheMetaData ThreadSafeNetworkDiskCache::metaData(const QUrl &url) {
QMutexLocker l(&sMutex);
return sCache->metaData(url);
}
QIODevice *ThreadSafeNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData) {
QMutexLocker l(&sMutex);
return sCache->prepare(metaData);
}
bool ThreadSafeNetworkDiskCache::remove(const QUrl &url) {
QMutexLocker l(&sMutex);
return sCache->remove(url);
}
void ThreadSafeNetworkDiskCache::updateMetaData(const QNetworkCacheMetaData &metaData) {
QMutexLocker l(&sMutex);
sCache->updateMetaData(metaData);
}
void ThreadSafeNetworkDiskCache::clear() {
QMutexLocker l(&sMutex);
sCache->clear();
}
NetworkAccessManager::NetworkAccessManager(QObject *parent)
: QNetworkAccessManager(parent) {
setCache(new ThreadSafeNetworkDiskCache(this));
}
QNetworkReply *NetworkAccessManager::createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) {
QByteArray user_agent = QString("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion()).toUtf8();
if (request.hasRawHeader("User-Agent")) {
// Append the existing user-agent set by a client collection (such as
// libmygpo-qt).
user_agent += " " + request.rawHeader("User-Agent");
}
QNetworkRequest new_request(request);
new_request.setRawHeader("User-Agent", user_agent);
if (op == QNetworkAccessManager::PostOperation &&
!new_request.header(QNetworkRequest::ContentTypeHeader).isValid()) {
new_request.setHeader(QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded");
}
// Prefer the cache unless the caller has changed the setting already
if (request.attribute(QNetworkRequest::CacheLoadControlAttribute).toInt() == QNetworkRequest::PreferNetwork) {
new_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
}
return QNetworkAccessManager::createRequest(op, new_request, outgoingData);
}
NetworkTimeouts::NetworkTimeouts(int timeout_msec, QObject *parent)
: QObject(parent), timeout_msec_(timeout_msec) {}
void NetworkTimeouts::AddReply(QNetworkReply *reply) {
if (timers_.contains(reply)) return;
connect(reply, SIGNAL(destroyed()), SLOT(ReplyFinished()));
connect(reply, SIGNAL(finished()), SLOT(ReplyFinished()));
timers_[reply] = startTimer(timeout_msec_);
}
void NetworkTimeouts::AddReply(RedirectFollower *reply) {
if (redirect_timers_.contains(reply)) {
return;
}
NewClosure(reply, SIGNAL(destroyed()), this, SLOT(RedirectFinished(RedirectFollower*)), reply);
NewClosure(reply, SIGNAL(finished()), this, SLOT(RedirectFinished(RedirectFollower*)), reply);
redirect_timers_[reply] = startTimer(timeout_msec_);
}
void NetworkTimeouts::ReplyFinished() {
QNetworkReply *reply = reinterpret_cast<QNetworkReply*>(sender());
if (timers_.contains(reply)) {
killTimer(timers_.take(reply));
}
}
void NetworkTimeouts::RedirectFinished(RedirectFollower *reply) {
if (redirect_timers_.contains(reply)) {
killTimer(redirect_timers_.take(reply));
}
}
void NetworkTimeouts::timerEvent(QTimerEvent *e) {
QNetworkReply *reply = timers_.key(e->timerId());
if (reply) {
reply->abort();
}
RedirectFollower *redirect = redirect_timers_.key(e->timerId());
if (redirect) {
redirect->abort();
}
}
RedirectFollower::RedirectFollower(QNetworkReply *first_reply, int max_redirects) : QObject(nullptr), current_reply_(first_reply), redirects_remaining_(max_redirects) {
ConnectReply(first_reply);
}
void RedirectFollower::ConnectReply(QNetworkReply *reply) {
connect(reply, SIGNAL(readyRead()), SLOT(ReadyRead()));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), SIGNAL(error(QNetworkReply::NetworkError)));
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), SIGNAL(downloadProgress(qint64,qint64)));
connect(reply, SIGNAL(uploadProgress(qint64,qint64)), SIGNAL(uploadProgress(qint64,qint64)));
connect(reply, SIGNAL(finished()), SLOT(ReplyFinished()));
}
void RedirectFollower::ReadyRead() {
// Don't re-emit this signal for redirect replies.
if (current_reply_->attribute(QNetworkRequest::RedirectionTargetAttribute).isValid()) {
return;
}
emit readyRead();
}
void RedirectFollower::ReplyFinished() {
current_reply_->deleteLater();
if (current_reply_->attribute(QNetworkRequest::RedirectionTargetAttribute).isValid()) {
if (redirects_remaining_-- == 0) {
emit finished();
return;
}
const QUrl next_url = current_reply_->url().resolved(current_reply_->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl());
QNetworkRequest req(current_reply_->request());
req.setUrl(next_url);
current_reply_ = current_reply_->manager()->get(req);
ConnectReply(current_reply_);
return;
}
emit finished();
}

128
src/core/network.h Normal file
View File

@@ -0,0 +1,128 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NETWORK_H
#define NETWORK_H
#include "config.h"
#include <QAbstractNetworkCache>
#include <QMutex>
#include <QNetworkAccessManager>
#include <QNetworkReply>
class QNetworkDiskCache;
class ThreadSafeNetworkDiskCache : public QAbstractNetworkCache {
public:
explicit ThreadSafeNetworkDiskCache(QObject *parent);
qint64 cacheSize() const;
QIODevice *data(const QUrl &url);
void insert(QIODevice *device);
QNetworkCacheMetaData metaData(const QUrl &url);
QIODevice *prepare(const QNetworkCacheMetaData &metaData);
bool remove(const QUrl &url);
void updateMetaData(const QNetworkCacheMetaData &metaData);
void clear();
private:
static QMutex sMutex;
static QNetworkDiskCache *sCache;
};
class NetworkAccessManager : public QNetworkAccessManager {
Q_OBJECT
public:
explicit NetworkAccessManager(QObject *parent = nullptr);
protected:
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData);
};
class RedirectFollower : public QObject {
Q_OBJECT
public:
explicit RedirectFollower(QNetworkReply *first_reply, int max_redirects = 5);
bool hit_redirect_limit() const { return redirects_remaining_ < 0; }
QNetworkReply *reply() const { return current_reply_; }
// These are all forwarded to the current reply.
QNetworkReply::NetworkError error() const { return current_reply_->error(); }
QString errorString() const { return current_reply_->errorString(); }
QVariant attribute(QNetworkRequest::Attribute code) const { return current_reply_->attribute(code); }
QVariant header(QNetworkRequest::KnownHeaders header) const { return current_reply_->header(header); }
qint64 bytesAvailable() const { return current_reply_->bytesAvailable(); }
QUrl url() const { return current_reply_->url(); }
QByteArray readAll() { return current_reply_->readAll(); }
void abort() { current_reply_->abort(); }
signals:
// These are all forwarded from the current reply.
void readyRead();
void error(QNetworkReply::NetworkError);
void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
// This is NOT emitted when a request that has a redirect finishes.
void finished();
private slots:
void ReadyRead();
void ReplyFinished();
private:
void ConnectReply(QNetworkReply *reply);
private:
QNetworkReply *current_reply_;
int redirects_remaining_;
};
class NetworkTimeouts : public QObject {
Q_OBJECT
public:
explicit NetworkTimeouts(int timeout_msec, QObject *parent = nullptr);
// TODO: Template this to avoid code duplication.
void AddReply(QNetworkReply *reply);
void AddReply(RedirectFollower *reply);
void SetTimeout(int msec) { timeout_msec_ = msec; }
protected:
void timerEvent(QTimerEvent *e);
private slots:
void ReplyFinished();
void RedirectFinished(RedirectFollower *redirect);
private:
int timeout_msec_;
QMap<QNetworkReply*, int> timers_;
QMap<RedirectFollower*, int> redirect_timers_;
};
#endif // NETWORK_H

View File

@@ -0,0 +1,142 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "networkproxyfactory.h"
#include <stdlib.h>
#include <QMutexLocker>
#include <QSettings>
#include <QStringList>
#include <QtDebug>
#include "core/logging.h"
NetworkProxyFactory *NetworkProxyFactory::sInstance = nullptr;
const char *NetworkProxyFactory::kSettingsGroup = "NetworkProxy";
NetworkProxyFactory::NetworkProxyFactory()
: mode_(Mode_System),
type_(QNetworkProxy::HttpProxy),
port_(8080),
use_authentication_(false) {
#ifdef Q_OS_LINUX
// Linux uses environment variables to pass proxy configuration information,
// which systemProxyForQuery doesn't support for some reason.
QStringList urls;
urls << QString::fromLocal8Bit(getenv("HTTP_PROXY"));
urls << QString::fromLocal8Bit(getenv("http_proxy"));
urls << QString::fromLocal8Bit(getenv("ALL_PROXY"));
urls << QString::fromLocal8Bit(getenv("all_proxy"));
qLog(Debug) << "Detected system proxy URLs:" << urls;
for (const QString &url_str : urls) {
if (url_str.isEmpty()) continue;
env_url_ = QUrl(url_str);
break;
}
#endif
ReloadSettings();
}
NetworkProxyFactory *NetworkProxyFactory::Instance() {
if (!sInstance) {
sInstance = new NetworkProxyFactory;
}
return sInstance;
}
void NetworkProxyFactory::ReloadSettings() {
QMutexLocker l(&mutex_);
QSettings s;
s.beginGroup(kSettingsGroup);
mode_ = Mode(s.value("mode", Mode_System).toInt());
type_ = QNetworkProxy::ProxyType(s.value("type", QNetworkProxy::HttpProxy).toInt());
hostname_ = s.value("hostname").toString();
port_ = s.value("port", 8080).toInt();
use_authentication_ = s.value("use_authentication", false).toBool();
username_ = s.value("username").toString();
password_ = s.value("password").toString();
}
QList<QNetworkProxy> NetworkProxyFactory::queryProxy(const QNetworkProxyQuery &query) {
QMutexLocker l(&mutex_);
QNetworkProxy ret;
switch (mode_) {
case Mode_System:
#ifdef Q_OS_LINUX
Q_UNUSED(query);
if (env_url_.isEmpty()) {
ret.setType(QNetworkProxy::NoProxy);
}
else {
ret.setHostName(env_url_.host());
ret.setPort(env_url_.port());
ret.setUser(env_url_.userName());
ret.setPassword(env_url_.password());
if (env_url_.scheme().startsWith("http"))
ret.setType(QNetworkProxy::HttpProxy);
else
ret.setType(QNetworkProxy::Socks5Proxy);
qLog(Debug) << "Using proxy URL:" << env_url_;
}
break;
#else
return systemProxyForQuery(query);
#endif
case Mode_Direct:
ret.setType(QNetworkProxy::NoProxy);
break;
case Mode_Manual:
ret.setType(type_);
ret.setHostName(hostname_);
ret.setPort(port_);
if (use_authentication_) {
ret.setUser(username_);
ret.setPassword(password_);
}
break;
}
return QList<QNetworkProxy>() << ret;
}

View File

@@ -0,0 +1,62 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NETWORKPROXYFACTORY_H
#define NETWORKPROXYFACTORY_H
#include "config.h"
#include <QMutex>
#include <QNetworkProxyFactory>
#include <QUrl>
class NetworkProxyFactory : public QNetworkProxyFactory {
public:
// These values are persisted
enum Mode { Mode_System = 0, Mode_Direct = 1, Mode_Manual = 2, };
static NetworkProxyFactory *Instance();
static const char *kSettingsGroup;
// These methods are thread-safe
void ReloadSettings();
QList<QNetworkProxy> queryProxy(const QNetworkProxyQuery &query);
private:
NetworkProxyFactory();
static NetworkProxyFactory *sInstance;
QMutex mutex_;
Mode mode_;
QNetworkProxy::ProxyType type_;
QString hostname_;
int port_;
bool use_authentication_;
QString username_;
QString password_;
#ifdef Q_OS_LINUX
QUrl env_url_;
#endif
};
#endif // NETWORKPROXYFACTORY_H

298
src/core/organise.cpp Normal file
View File

@@ -0,0 +1,298 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "organise.h"
#include <functional>
#include <QDir>
#include <QFileInfo>
#include <QTimer>
#include <QThread>
#include <QUrl>
#include "musicstorage.h"
#include "taskmanager.h"
#include "core/logging.h"
#include "core/tagreaderclient.h"
#include "core/utilities.h"
using std::placeholders::_1;
const int Organise::kBatchSize = 10;
const int Organise::kTranscodeProgressInterval = 500;
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)
: thread_(nullptr),
task_manager_(task_manager),
transcoder_(new Transcoder(this)),
destination_(destination),
format_(format),
copy_(copy),
overwrite_(overwrite),
mark_as_listened_(mark_as_listened),
eject_after_(eject_after),
task_count_(songs_info.count()),
transcode_suffix_(1),
tasks_complete_(0),
started_(false),
task_id_(0),
current_copy_progress_(0) {
original_thread_ = thread();
for (const NewSongInfo &song_info : songs_info) {
tasks_pending_ << Task(song_info);
}
}
void Organise::Start() {
if (thread_) return;
task_id_ = task_manager_->StartTask(tr("Organising files"));
task_manager_->SetTaskBlocksCollectionScans(true);
thread_ = new QThread;
connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles()));
connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)), SLOT(FileTranscoded(QString, QString, bool)));
moveToThread(thread_);
thread_->start();
}
void Organise::ProcessSomeFiles() {
if (!started_) {
transcode_temp_name_.open();
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();
tasks_pending_.clear();
}
started_ = true;
}
// None left?
if (tasks_pending_.isEmpty()) {
if (!tasks_transcoding_.isEmpty()) {
// Just wait - FileTranscoded will start us off again in a little while
qLog(Debug) << "Waiting for transcoding jobs";
transcode_progress_timer_.start(kTranscodeProgressInterval, this);
return;
}
UpdateProgress();
destination_->FinishCopy(files_with_errors_.isEmpty());
if (eject_after_) destination_->Eject();
task_manager_->SetTaskFinished(task_id_);
emit Finished(files_with_errors_);
// Move back to the original thread so deleteLater() can get called in
// the main thread's event loop
moveToThread(original_thread_);
deleteLater();
// Stop this thread
thread_->quit();
return;
}
// We process files in batches so we can be cancelled part-way through.
for (int i = 0; i < kBatchSize; ++i) {
SetSongProgress(0);
if (tasks_pending_.isEmpty()) break;
Task task = tasks_pending_.takeFirst();
qLog(Info) << "Processing" << task.song_info_.song_.url().toLocalFile();
// Use a Song instead of a tag reader
Song song = task.song_info_.song_;
if (!song.is_valid()) continue;
// Maybe this file is one that's been transcoded already?
if (!task.transcoded_filename_.isEmpty()) {
qLog(Debug) << "This file has already been transcoded";
// Set the new filetype on the song so the formatter gets it right
song.set_filetype(task.new_filetype_);
// Fiddle the filename extension as well to match the new type
song.set_url(QUrl::fromLocalFile(Utilities::FiddleFileExtension(song.basefilename(), task.new_extension_)));
song.set_basefilename(Utilities::FiddleFileExtension(song.basefilename(), task.new_extension_));
// Have to set this to the size of the new file or else funny stuff happens
song.set_filesize(QFileInfo(task.transcoded_filename_).size());
}
else {
// Figure out if we need to transcode it
Song::FileType dest_type = CheckTranscode(song.filetype());
if (dest_type != Song::Type_Unknown) {
// Get the preset
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.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. At that point the
// task will get re-added to the pending queue with the new filename.
transcoder_->AddJob(task.song_info_.song_.url().toLocalFile(), preset, task.transcoded_filename_);
transcoder_->Start();
continue;
}
}
MusicStorage::CopyJob job;
job.source_ = task.transcoded_filename_.isEmpty() ? task.song_info_.song_.url().toLocalFile() : task.transcoded_filename_;
job.destination_ = task.song_info_.new_filename_;
job.metadata_ = song;
job.overwrite_ = overwrite_;
job.mark_as_listened_ = mark_as_listened_;
job.remove_original_ = !copy_;
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 {
if (job.mark_as_listened_) {
emit FileCopied(job.metadata_.id());
}
}
// Clean up the temporary transcoded file
if (!task.transcoded_filename_.isEmpty())
QFile::remove(task.transcoded_filename_);
tasks_complete_++;
}
SetSongProgress(0);
QTimer::singleShot(0, this, SLOT(ProcessSomeFiles()));
}
Song::FileType Organise::CheckTranscode(Song::FileType original_type) const {
//if (original_type == Song::Type_Stream) return Song::Type_Unknown;
const MusicStorage::TranscodeMode mode = destination_->GetTranscodeMode();
const Song::FileType format = destination_->GetTranscodeFormat();
switch (mode) {
case MusicStorage::Transcode_Never:
return Song::Type_Unknown;
case MusicStorage::Transcode_Always:
if (original_type == format) return Song::Type_Unknown;
return format;
case MusicStorage::Transcode_Unsupported:
if (supported_filetypes_.isEmpty() || supported_filetypes_.contains(original_type)) return Song::Type_Unknown;
if (format != Song::Type_Unknown) return format;
// The user hasn't visited the device properties page yet to set a
// preferred format for the device, so we have to pick the best
// available one.
return Transcoder::PickBestFormat(supported_filetypes_);
}
return Song::Type_Unknown;
}
void Organise::SetSongProgress(float progress, bool transcoded) {
const int max = transcoded ? 50 : 100;
current_copy_progress_ = (transcoded ? 50 : 0) + qBound(0, static_cast<int>(progress * max), max - 1);
UpdateProgress();
}
void Organise::UpdateProgress() {
const int total = task_count_ * 100;
// Update transcoding progress
QMap<QString, float> transcode_progress = transcoder_->GetProgress();
for (const QString &filename : transcode_progress.keys()) {
if (!tasks_transcoding_.contains(filename)) continue;
tasks_transcoding_[filename].transcode_progress_ = transcode_progress[filename];
}
// Count the progress of all tasks that are in the queue. Files that need
// transcoding total 50 for the transcode and 50 for the copy, files that
// only need to be copied total 100.
int progress = tasks_complete_ * 100;
for (const Task &task : tasks_pending_) {
progress += qBound(0, static_cast<int>(task.transcode_progress_ * 50), 50);
}
for (const Task &task : tasks_transcoding_.values()) {
progress += qBound(0, static_cast<int>(task.transcode_progress_ * 50), 50);
}
// Add the progress of the track that's currently copying
progress += current_copy_progress_;
task_manager_->SetTaskProgress(task_id_, progress, total);
}
void Organise::FileTranscoded(const QString &input, const QString &output, bool success) {
qLog(Info) << "File finished" << input << success;
transcode_progress_timer_.stop();
Task task = tasks_transcoding_.take(input);
if (!success) {
files_with_errors_ << input;
}
else {
tasks_pending_ << task;
}
QTimer::singleShot(0, this, SLOT(ProcessSomeFiles()));
}
void Organise::timerEvent(QTimerEvent *e) {
QObject::timerEvent(e);
if (e->timerId() == transcode_progress_timer_.timerId()) {
UpdateProgress();
}
}

114
src/core/organise.h Normal file
View File

@@ -0,0 +1,114 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ORGANISE_H
#define ORGANISE_H
#include "config.h"
#include <memory>
#include <QBasicTimer>
#include <QObject>
#include <QTemporaryFile>
#include "organiseformat.h"
#include "transcoder/transcoder.h"
class MusicStorage;
class TaskManager;
class Organise : public QObject {
Q_OBJECT
public:
struct NewSongInfo {
NewSongInfo(const Song &song = Song(), const QString &new_filename = QString()) : song_(song), new_filename_(new_filename) {}
Song song_;
QString new_filename_;
};
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);
static const int kBatchSize;
static const int kTranscodeProgressInterval;
void Start();
signals:
void Finished(const QStringList &files_with_errors);
void FileCopied(int database_id);
protected:
void timerEvent(QTimerEvent *e);
private slots:
void ProcessSomeFiles();
void FileTranscoded(const QString &input, const QString &output, bool success);
private:
void SetSongProgress(float progress, bool transcoded = false);
void UpdateProgress();
Song::FileType CheckTranscode(Song::FileType original_type) const;
private:
struct Task {
explicit Task(const NewSongInfo &song_info = NewSongInfo()) : song_info_(song_info), transcode_progress_(0.0) {}
NewSongInfo song_info_;
float transcode_progress_;
QString transcoded_filename_;
QString new_extension_;
Song::FileType new_filetype_;
};
QThread *thread_;
QThread *original_thread_;
TaskManager *task_manager_;
Transcoder *transcoder_;
std::shared_ptr<MusicStorage> destination_;
QList<Song::FileType> supported_filetypes_;
const OrganiseFormat format_;
const bool copy_;
const bool overwrite_;
const bool mark_as_listened_;
const bool eject_after_;
int task_count_;
QBasicTimer transcode_progress_timer_;
QTemporaryFile transcode_temp_name_;
int transcode_suffix_;
QList<Task> tasks_pending_;
QMap<QString, Task> tasks_transcoding_;
int tasks_complete_;
bool started_;
int task_id_;
int current_copy_progress_;
QStringList files_with_errors_;
};
#endif // ORGANISE_H

312
src/core/organiseformat.cpp Normal file
View File

@@ -0,0 +1,312 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "organiseformat.h"
#include <QApplication>
#include <QFileInfo>
#include <QPalette>
#include <QUrl>
#include "core/arraysize.h"
#include "core/timeconstants.h"
#include "core/utilities.h"
#include "core/logging.h"
const char *OrganiseFormat::kTagPattern = "\\%([a-zA-Z]*)";
const char *OrganiseFormat::kBlockPattern = "\\{([^{}]+)\\}";
const QStringList OrganiseFormat::kKnownTags = QStringList() << "title"
<< "album"
<< "artist"
<< "artistinitial"
<< "albumartist"
<< "composer"
<< "track"
<< "disc"
<< "year"
<< "genre"
<< "comment"
<< "length"
<< "bitrate"
<< "samplerate"
<< "bitdepth"
<< "extension"
<< "performer"
<< "grouping"
<< "originalyear";
// From http://en.wikipedia.org/wiki/8.3_filename#Directory_table
const char OrganiseFormat::kInvalidFatCharacters[] = "\"*/\\:<>?|";
const int OrganiseFormat::kInvalidFatCharactersCount = arraysize(OrganiseFormat::kInvalidFatCharacters) - 1;
const char OrganiseFormat::kInvalidPrefixCharacters[] = ".";
const int OrganiseFormat::kInvalidPrefixCharactersCount = arraysize(OrganiseFormat::kInvalidPrefixCharacters) - 1;
const QRgb OrganiseFormat::SyntaxHighlighter::kValidTagColorLight = qRgb(64, 64, 255);
const QRgb OrganiseFormat::SyntaxHighlighter::kInvalidTagColorLight = qRgb(255, 64, 64);
const QRgb OrganiseFormat::SyntaxHighlighter::kBlockColorLight = qRgb(230, 230, 230);
const QRgb OrganiseFormat::SyntaxHighlighter::kValidTagColorDark = qRgb(128, 128, 255);
const QRgb OrganiseFormat::SyntaxHighlighter::kInvalidTagColorDark = qRgb(255, 128, 128);
const QRgb OrganiseFormat::SyntaxHighlighter::kBlockColorDark = qRgb(64, 64, 64);
OrganiseFormat::OrganiseFormat(const QString &format)
: format_(format),
replace_non_ascii_(false),
replace_spaces_(false),
replace_the_(false) {}
void OrganiseFormat::set_format(const QString &v) {
format_ = v;
format_.replace('\\', '/');
}
bool OrganiseFormat::IsValid() const {
int pos = 0;
QString format_copy(format_);
Validator v;
return v.validate(format_copy, pos) == QValidator::Acceptable;
}
QString OrganiseFormat::GetFilenameForSong(const Song &song) const {
QString filename = ParseBlock(format_, song);
if (QFileInfo(filename).completeBaseName().isEmpty()) {
// Avoid having empty filenames, or filenames with extension only: in this
// case, keep the original filename.
// We remove the extension from "filename" if it exists, as
// song.basefilename()
// also contains the extension.
filename =
Utilities::PathWithoutFilenameExtension(filename) + song.basefilename();
}
if (replace_spaces_) filename.replace(QRegExp("\\s"), "_");
if (replace_non_ascii_) {
QString stripped;
for (int i = 0; i < filename.length(); ++i) {
const QCharRef c = filename[i];
if (c < 128) {
stripped.append(c);
} else {
const QString decomposition = c.decomposition();
if (!decomposition.isEmpty() && decomposition[0] < 128)
stripped.append(decomposition[0]);
else
stripped.append("_");
}
}
filename = stripped;
}
// Fix any parts of the path that start with dots.
QStringList parts = filename.split("/");
for (int i = 0; i < parts.count(); ++i) {
QString *part = &parts[i];
for (int j = 0; j < kInvalidPrefixCharactersCount; ++j) {
if (part->startsWith(kInvalidPrefixCharacters[j])) {
part->replace(0, 1, '_');
break;
}
}
}
return parts.join("/");
}
QString OrganiseFormat::ParseBlock(QString block, const Song &song, bool *any_empty) const {
QRegExp tag_regexp(kTagPattern);
QRegExp block_regexp(kBlockPattern);
// Find any blocks first
int pos = 0;
while ((pos = block_regexp.indexIn(block, pos)) != -1) {
// Recursively parse the block
bool empty = false;
QString value = ParseBlock(block_regexp.cap(1), song, &empty);
if (empty) value = "";
// Replace the block's value
block.replace(pos, block_regexp.matchedLength(), value);
pos += value.length();
}
// Now look for tags
bool empty = false;
pos = 0;
while ((pos = tag_regexp.indexIn(block, pos)) != -1) {
QString value = TagValue(tag_regexp.cap(1), song);
if (value.isEmpty()) empty = true;
block.replace(pos, tag_regexp.matchedLength(), value);
pos += value.length();
}
if (any_empty) *any_empty = empty;
return block;
}
QString OrganiseFormat::TagValue(const QString &tag, const Song &song) const {
QString value;
if (tag == "title")
value = song.title();
else if (tag == "album")
value = song.album();
else if (tag == "artist")
value = song.artist();
else if (tag == "composer")
value = song.composer();
else if (tag == "performer")
value = song.performer();
else if (tag == "grouping")
value = song.grouping();
else if (tag == "genre")
value = song.genre();
else if (tag == "comment")
value = song.comment();
else if (tag == "year")
value = QString::number(song.year());
else if (tag == "originalyear")
value = QString::number(song.effective_originalyear());
else if (tag == "track")
value = QString::number(song.track());
else if (tag == "disc")
value = QString::number(song.disc());
else if (tag == "length")
value = QString::number(song.length_nanosec() / kNsecPerSec);
else if (tag == "bitrate")
value = QString::number(song.bitrate());
else if (tag == "samplerate") value = QString::number(song.samplerate());
else if (tag == "bitdepth") value = QString::number(song.bitdepth());
else if (tag == "extension")
value = QFileInfo(song.url().toLocalFile()).suffix();
else if (tag == "artistinitial") {
value = song.effective_albumartist().trimmed();
if (replace_the_ && !value.isEmpty()) value.replace(QRegExp("^the\\s+", Qt::CaseInsensitive), "");
if (!value.isEmpty()) value = value[0].toUpper();
}
else if (tag == "albumartist") {
value = song.is_compilation() ? "Various Artists" : song.effective_albumartist();
}
if (replace_the_ && (tag == "artist" || tag == "albumartist"))
value.replace(QRegExp("^the\\s+", Qt::CaseInsensitive), "");
if (value == "0" || value == "-1") value = "";
// Prepend a 0 to single-digit track numbers
if (tag == "track" && value.length() == 1) value.prepend('0');
// Replace characters that really shouldn't be in paths
for (int i = 0; i < kInvalidFatCharactersCount; ++i) {
value.replace(kInvalidFatCharacters[i], '_');
}
return value;
}
OrganiseFormat::Validator::Validator(QObject *parent) : QValidator(parent) {}
QValidator::State OrganiseFormat::Validator::validate(QString &input, int&) const {
QRegExp tag_regexp(kTagPattern);
// Make sure all the blocks match up
int block_level = 0;
for (int i = 0; i < input.length(); ++i) {
if (input[i] == '{')
block_level++;
else if (input[i] == '}')
block_level--;
if (block_level < 0 || block_level > 1) return QValidator::Invalid;
}
if (block_level != 0) return QValidator::Invalid;
// Make sure the tags are valid
int pos = 0;
while ((pos = tag_regexp.indexIn(input, pos)) != -1) {
if (!OrganiseFormat::kKnownTags.contains(tag_regexp.cap(1)))
return QValidator::Invalid;
pos += tag_regexp.matchedLength();
}
return QValidator::Acceptable;
}
OrganiseFormat::SyntaxHighlighter::SyntaxHighlighter(QObject *parent)
: QSyntaxHighlighter(parent) {}
OrganiseFormat::SyntaxHighlighter::SyntaxHighlighter(QTextEdit *parent)
: QSyntaxHighlighter(parent) {}
OrganiseFormat::SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent)
: QSyntaxHighlighter(parent) {}
void OrganiseFormat::SyntaxHighlighter::highlightBlock(const QString &text) {
const bool light = QApplication::palette().color(QPalette::Base).value() > 128;
const QRgb block_color = light ? kBlockColorLight : kBlockColorDark;
const QRgb valid_tag_color = light ? kValidTagColorLight : kValidTagColorDark;
const QRgb invalid_tag_color = light ? kInvalidTagColorLight : kInvalidTagColorDark;
QRegExp tag_regexp(kTagPattern);
QRegExp block_regexp(kBlockPattern);
QTextCharFormat block_format;
block_format.setBackground(QColor(block_color));
// Reset formatting
setFormat(0, text.length(), QTextCharFormat());
// Blocks
int pos = 0;
while ((pos = block_regexp.indexIn(text, pos)) != -1) {
setFormat(pos, block_regexp.matchedLength(), block_format);
pos += block_regexp.matchedLength();
}
// Tags
pos = 0;
while ((pos = tag_regexp.indexIn(text, pos)) != -1) {
QTextCharFormat f = format(pos);
f.setForeground(QColor(OrganiseFormat::kKnownTags.contains(tag_regexp.cap(1)) ? valid_tag_color : invalid_tag_color));
setFormat(pos, tag_regexp.matchedLength(), f);
pos += tag_regexp.matchedLength();
}
}

91
src/core/organiseformat.h Normal file
View File

@@ -0,0 +1,91 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ORGANISEFORMAT_H
#define ORGANISEFORMAT_H
#include "config.h"
#include <QSyntaxHighlighter>
#include <QValidator>
#include <QTextEdit>
#include "core/song.h"
class OrganiseFormat {
public:
explicit OrganiseFormat(const QString &format = QString());
static const char* kTagPattern;
static const char* kBlockPattern;
static const QStringList kKnownTags;
static const char kInvalidFatCharacters[];
static const int kInvalidFatCharactersCount;
static const char kInvalidPrefixCharacters[];
static const int kInvalidPrefixCharactersCount;
QString format() const { return format_; }
bool replace_non_ascii() const { return replace_non_ascii_; }
bool replace_spaces() const { return replace_spaces_; }
bool replace_the() const { return replace_the_; }
void set_format(const QString &v);
void set_replace_non_ascii(bool v) { replace_non_ascii_ = v; }
void set_replace_spaces(bool v) { replace_spaces_ = v; }
void set_replace_the(bool v) { replace_the_ = v; }
bool IsValid() const;
QString GetFilenameForSong(const Song &song) const;
class Validator : public QValidator {
public:
explicit Validator(QObject* parent = nullptr);
QValidator::State validate(QString &format, int &pos) const;
};
class SyntaxHighlighter : public QSyntaxHighlighter {
public:
static const QRgb kValidTagColorLight;
static const QRgb kInvalidTagColorLight;
static const QRgb kBlockColorLight;
static const QRgb kValidTagColorDark;
static const QRgb kInvalidTagColorDark;
static const QRgb kBlockColorDark;
explicit SyntaxHighlighter(QObject* parent = nullptr);
explicit SyntaxHighlighter(QTextEdit* parent);
explicit SyntaxHighlighter(QTextDocument* parent);
void highlightBlock(const QString &format);
};
private:
QString ParseBlock(QString block, const Song &song, bool* any_empty = nullptr) const;
QString TagValue(const QString &tag, const Song &song) const;
QString format_;
bool replace_non_ascii_;
bool replace_spaces_;
bool replace_the_;
};
#endif // ORGANISEFORMAT_H

784
src/core/player.cpp Normal file
View File

@@ -0,0 +1,784 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "player.h"
#include "config.h"
#include <memory>
#include <QSortFilterProxyModel>
#include <QtDebug>
#include <QtConcurrentRun>
#include "core/application.h"
#include "core/logging.h"
#include "core/urlhandler.h"
#include "core/timeconstants.h"
#include "engine/enginetype.h"
#include "engine/enginebase.h"
#include "engine/enginedevice.h"
#ifdef HAVE_GSTREAMER
# include "engine/gstengine.h"
#endif
#ifdef HAVE_XINE
# include "engine/xineengine.h"
#endif
#ifdef HAVE_PHONON
# include "engine/phononengine.h"
#endif
#ifdef HAVE_VLC
# include "engine/vlcengine.h"
#endif
#include "collection/collectionbackend.h"
#include "playlist/playlist.h"
#include "playlist/playlistitem.h"
#include "playlist/playlistmanager.h"
#include "analyzer/analyzercontainer.h"
#include "equalizer/equalizer.h"
#include "settings/backendsettingspage.h"
#include "settings/behavioursettingspage.h"
#include "settings/playlistsettingspage.h"
using std::shared_ptr;
Player::Player(Application *app, QObject *parent)
: PlayerInterface(parent),
app_(app),
//engine_(new GstEngine(app_->task_manager())),
//engine_(CreateEngine()),
stream_change_type_(Engine::First),
last_state_(Engine::Empty),
nb_errors_received_(0),
volume_before_mute_(50),
last_pressed_previous_(QDateTime::currentDateTime()),
menu_previousmode_(PreviousBehaviour_DontRestart),
seek_step_sec_(10)
{
//qLog(Debug) << __PRETTY_FUNCTION__;
QSettings s;
s.beginGroup(BackendSettingsPage::kSettingsGroup);
Engine::EngineType enginetype = Engine::EngineTypeFromName(s.value("engine", BackendSettingsPage::EngineText_Xine).toString().toLower());
s.endGroup();
CreateEngine(enginetype);
settings_.beginGroup("Player");
SetVolume(settings_.value("volume", 100).toInt());
#if 0
connect(engine_.get(), SIGNAL(Error(QString)), SIGNAL(Error(QString)));
connect(engine_.get(), SIGNAL(ValidSongRequested(QUrl)), SLOT(ValidSongRequested(QUrl)));
connect(engine_.get(), SIGNAL(InvalidSongRequested(QUrl)), SLOT(InvalidSongRequested(QUrl)));
#endif
}
Player::~Player() {}
EngineBase *Player::CreateEngine(Engine::EngineType enginetype) {
//qLog(Debug) << __PRETTY_FUNCTION__;
bool engine = false;
EngineBase *enginebase = nullptr;
for (int i = 1 ; !engine ; i++) {
switch(enginetype) {
case Engine::None:
#ifdef HAVE_XINE
case Engine::Xine:
engine=true;
enginetype=Engine::Xine;
enginebase = new XineEngine(app_->task_manager());
break;
#endif
#ifdef HAVE_GSTREAMER
case Engine::GStreamer:
engine=true;
enginetype=Engine::GStreamer;
enginebase = new GstEngine(app_->task_manager());
break;
#endif
#ifdef HAVE_PHONON
case Engine::Phonon:
engine=true;
enginetype=Engine::Phonon;
enginebase = new PhononEngine(app_->task_manager());
break;
#endif
#ifdef HAVE_VLC
case Engine::VLC:
engine=true;
enginetype=Engine::VLC;
enginebase = new VLCEngine(app_->task_manager());
break;
#endif
default:
if (i > 1) { qFatal("No engine available!"); return nullptr; }
QSettings s;
s.beginGroup(BackendSettingsPage::kSettingsGroup);
s.setValue("engine", "");
s.setValue("output", "");
s.setValue("device", "");
s.endGroup();
enginetype = Engine::None;
break;
}
}
QSettings s;
s.beginGroup(BackendSettingsPage::kSettingsGroup);
s.setValue("engine", Engine::EngineNameFromType(enginetype));
s.endGroup();
if (enginebase == nullptr) {
qFatal("Failed to create engine!");
return nullptr;
}
engine_.reset(enginebase);
return enginebase;
}
void Player::Init() {
//qLog(Debug) << __PRETTY_FUNCTION__;
connect(engine_.get(), SIGNAL(Error(QString)), SIGNAL(Error(QString)));
connect(engine_.get(), SIGNAL(ValidSongRequested(QUrl)), SLOT(ValidSongRequested(QUrl)));
connect(engine_.get(), SIGNAL(InvalidSongRequested(QUrl)), SLOT(InvalidSongRequested(QUrl)));
if (!engine_->Init()) qFatal("Error initialising audio engine");
connect(engine_.get(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State)));
connect(engine_.get(), SIGNAL(TrackAboutToEnd()), SLOT(TrackAboutToEnd()));
connect(engine_.get(), SIGNAL(TrackEnded()), SLOT(TrackEnded()));
connect(engine_.get(), SIGNAL(MetaData(Engine::SimpleMetaBundle)), SLOT(EngineMetadataReceived(Engine::SimpleMetaBundle)));
engine_->SetVolume(settings_.value("volume", 50).toInt());
analyzer_->SetEngine(engine_.get());
// Equalizer
qLog(Debug) << "Creating equalizer";
connect(equalizer_, SIGNAL(ParametersChanged(int,QList<int>)), app_->player()->engine(), SLOT(SetEqualizerParameters(int,QList<int>)));
connect(equalizer_, SIGNAL(EnabledChanged(bool)), app_->player()->engine(), SLOT(SetEqualizerEnabled(bool)));
connect(equalizer_, SIGNAL(StereoBalanceChanged(float)), app_->player()->engine(), SLOT(SetStereoBalance(float)));
engine_->SetEqualizerEnabled(equalizer_->is_enabled());
engine_->SetEqualizerParameters(equalizer_->preamp_value(), equalizer_->gain_values());
engine_->SetStereoBalance(equalizer_->stereo_balance());
ReloadSettings();
}
void Player::SetAnalyzer(AnalyzerContainer *analyzer) {
analyzer_ = analyzer;
}
void Player::SetEqualizer(Equalizer *equalizer) {
equalizer_ = equalizer;
}
void Player::ReloadSettings() {
//qLog(Debug) << __PRETTY_FUNCTION__;
QSettings s;
s.beginGroup(PlaylistSettingsPage::kSettingsGroup);
menu_previousmode_ = PreviousBehaviour(s.value("menu_previousmode", PreviousBehaviour_DontRestart).toInt());
s.endGroup();
s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
seek_step_sec_ = s.value("seek_step_sec", 10).toInt();
s.endGroup();
engine_->ReloadSettings();
}
void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
//qLog(Debug) << __PRETTY_FUNCTION__;
// Might've been an async load, so check we're still on the same item
shared_ptr<PlaylistItem> item = app_->playlist_manager()->active()->current_item();
if (!item) {
loading_async_ = QUrl();
return;
}
if (item->Url() != result.original_url_) return;
switch (result.type_) {
case UrlHandler::LoadResult::NoMoreTracks:
qLog(Debug) << "URL handler for" << result.original_url_ << "said no more tracks";
loading_async_ = QUrl();
NextItem(stream_change_type_);
break;
case UrlHandler::LoadResult::TrackAvailable: {
qLog(Debug) << "URL handler for" << result.original_url_ << "returned" << result.media_url_;
// If there was no length info in song's metadata, use the one provided by
// URL handler, if there is one
if (item->Metadata().length_nanosec() <= 0 && result.length_nanosec_ != -1) {
Song song = item->Metadata();
song.set_length_nanosec(result.length_nanosec_);
item->SetTemporaryMetadata(song);
app_->playlist_manager()->active()->InformOfCurrentSongChange();
}
engine_->Play(result.media_url_, stream_change_type_, item->Metadata().has_cue(), item->Metadata().beginning_nanosec(), item->Metadata().end_nanosec());
current_item_ = item;
loading_async_ = QUrl();
break;
}
case UrlHandler::LoadResult::WillLoadAsynchronously:
qLog(Debug) << "URL handler for" << result.original_url_ << "is loading asynchronously";
// We'll get called again later with either NoMoreTracks or TrackAvailable
loading_async_ = result.original_url_;
break;
}
}
void Player::Next() { NextInternal(Engine::Manual); }
void Player::NextInternal(Engine::TrackChangeFlags change) {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (HandleStopAfter()) return;
if (app_->playlist_manager()->active()->current_item()) {
const QUrl url = app_->playlist_manager()->active()->current_item()->Url();
if (url_handlers_.contains(url.scheme())) {
// The next track is already being loaded
if (url == loading_async_) return;
stream_change_type_ = change;
HandleLoadResult(url_handlers_[url.scheme()]->LoadNext(url));
return;
}
}
NextItem(change);
}
void Player::NextItem(Engine::TrackChangeFlags change) {
//qLog(Debug) << __PRETTY_FUNCTION__;
Playlist *active_playlist = app_->playlist_manager()->active();
// If we received too many errors in auto change, with repeat enabled, we stop
if (change == Engine::Auto) {
const PlaylistSequence::RepeatMode repeat_mode = active_playlist->sequence()->repeat_mode();
if (repeat_mode != PlaylistSequence::Repeat_Off) {
if ((repeat_mode == PlaylistSequence::Repeat_Track && nb_errors_received_ >= 3) || (nb_errors_received_ >= app_->playlist_manager()->active()->proxy()->rowCount())) {
// We received too many "Error" state changes: probably looping over a
// playlist which contains only unavailable elements: stop now.
nb_errors_received_ = 0;
Stop();
return;
}
}
}
// Manual track changes override "Repeat track"
const bool ignore_repeat_track = change & Engine::Manual;
int i = active_playlist->next_row(ignore_repeat_track);
if (i == -1) {
app_->playlist_manager()->active()->set_current_row(i);
emit PlaylistFinished();
Stop();
return;
}
PlayAt(i, change, false);
}
bool Player::HandleStopAfter() {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (app_->playlist_manager()->active()->stop_after_current()) {
// Find what the next track would've been, and mark that one as current
// so it plays next time the user presses Play.
const int next_row = app_->playlist_manager()->active()->next_row();
if (next_row != -1) {
app_->playlist_manager()->active()->set_current_row(next_row, true);
}
app_->playlist_manager()->active()->StopAfter(-1);
Stop(true);
return true;
}
return false;
}
void Player::TrackEnded() {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (HandleStopAfter()) return;
if (current_item_ && current_item_->IsLocalCollectionItem() && current_item_->Metadata().id() != -1) {
app_->playlist_manager()->collection_backend()->IncrementPlayCountAsync( current_item_->Metadata().id());
}
NextInternal(Engine::Auto);
}
void Player::PlayPause() {
//qLog(Debug) << __PRETTY_FUNCTION__;
switch (engine_->state()) {
case Engine::Paused:
engine_->Unpause();
break;
case Engine::Playing: {
if (current_item_->options() & PlaylistItem::PauseDisabled) {
Stop();
}
else {
engine_->Pause();
}
break;
}
case Engine::Empty:
case Engine::Error:
case Engine::Idle: {
app_->playlist_manager()->SetActivePlaylist(app_->playlist_manager()->current_id());
if (app_->playlist_manager()->active()->rowCount() == 0) break;
int i = app_->playlist_manager()->active()->current_row();
if (i == -1) i = app_->playlist_manager()->active()->last_played_row();
if (i == -1) i = 0;
PlayAt(i, Engine::First, true);
break;
}
}
}
void Player::RestartOrPrevious() {
if (engine_->position_nanosec() < 8 * kNsecPerSec) return Previous();
SeekTo(0);
}
void Player::Stop(bool stop_after) {
engine_->Stop(stop_after);
app_->playlist_manager()->active()->set_current_row(-1);
current_item_.reset();
}
void Player::StopAfterCurrent() {
app_->playlist_manager()->active()->StopAfter(app_->playlist_manager()->active()->current_row());
}
bool Player::PreviousWouldRestartTrack() const {
// Check if it has been over two seconds since previous button was pressed
return menu_previousmode_ == PreviousBehaviour_Restart && last_pressed_previous_.isValid() && last_pressed_previous_.secsTo(QDateTime::currentDateTime()) >= 2;
}
void Player::Previous() { PreviousItem(Engine::Manual); }
void Player::PreviousItem(Engine::TrackChangeFlags change) {
const bool ignore_repeat_track = change & Engine::Manual;
if (menu_previousmode_ == PreviousBehaviour_Restart) {
// Check if it has been over two seconds since previous button was pressed
QDateTime now = QDateTime::currentDateTime();
if (last_pressed_previous_.isValid() && last_pressed_previous_.secsTo(now) >= 2) {
last_pressed_previous_ = now;
PlayAt(app_->playlist_manager()->active()->current_row(), change, false);
return;
}
last_pressed_previous_ = now;
}
int i = app_->playlist_manager()->active()->previous_row(ignore_repeat_track);
app_->playlist_manager()->active()->set_current_row(i);
if (i == -1) {
Stop();
PlayAt(i, change, true);
return;
}
PlayAt(i, change, false);
}
void Player::EngineStateChanged(Engine::State state) {
if (Engine::Error == state) {
nb_errors_received_++;
}
else {
nb_errors_received_ = 0;
}
switch (state) {
case Engine::Paused:
emit Paused();
break;
case Engine::Playing:
emit Playing();
break;
case Engine::Error:
case Engine::Empty:
case Engine::Idle:
emit Stopped();
break;
}
last_state_ = state;
}
void Player::SetVolume(int value) {
int old_volume = engine_->volume();
int volume = qBound(0, value, 100);
settings_.setValue("volume", volume);
engine_->SetVolume(volume);
if (volume != old_volume) {
emit VolumeChanged(volume);
}
}
int Player::GetVolume() const { return engine_->volume(); }
void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle) {
if (change == Engine::Manual && engine_->position_nanosec() != engine_->length_nanosec()) {
emit TrackSkipped(current_item_);
const QUrl& url = current_item_->Url();
if (url_handlers_.contains(url.scheme())) {
url_handlers_[url.scheme()]->TrackSkipped();
}
}
if (current_item_ && app_->playlist_manager()->active()->has_item_at(index) && current_item_->Metadata().IsOnSameAlbum(app_->playlist_manager()->active()->item_at(index)->Metadata())) {
change |= Engine::SameAlbum;
}
if (reshuffle) app_->playlist_manager()->active()->ReshuffleIndices();
app_->playlist_manager()->active()->set_current_row(index);
if (app_->playlist_manager()->active()->current_row() == -1) {
// Maybe index didn't exist in the playlist.
return;
}
current_item_ = app_->playlist_manager()->active()->current_item();
const QUrl url = current_item_->Url();
if (url_handlers_.contains(url.scheme())) {
// It's already loading
if (url == loading_async_) return;
stream_change_type_ = change;
HandleLoadResult(url_handlers_[url.scheme()]->StartLoading(url));
}
else {
loading_async_ = QUrl();
engine_->Play(current_item_->Url(), change, current_item_->Metadata().has_cue(), current_item_->Metadata().beginning_nanosec(), current_item_->Metadata().end_nanosec());
}
}
void Player::CurrentMetadataChanged(const Song &metadata) {
// those things might have changed (especially when a previously invalid
// song was reloaded) so we push the latest version into Engine
engine_->RefreshMarkers(metadata.beginning_nanosec(), metadata.end_nanosec());
}
void Player::SeekTo(int seconds) {
const qint64 length_nanosec = engine_->length_nanosec();
// If the length is 0 then either there is no song playing, or the song isn't
// seekable.
if (length_nanosec <= 0) {
return;
}
const qint64 nanosec = qBound(0ll, qint64(seconds) * kNsecPerSec, length_nanosec);
engine_->Seek(nanosec);
emit Seeked(nanosec / 1000);
}
void Player::SeekForward() {
SeekTo(engine()->position_nanosec() / kNsecPerSec + seek_step_sec_);
//SeekTo(engine()->position_nanosec() / kNsecPerSec + 10);
}
void Player::SeekBackward() {
//SeekTo(engine()->position_nanosec() / kNsecPerSec - 10);
SeekTo(engine()->position_nanosec() / kNsecPerSec - seek_step_sec_);
}
void Player::EngineMetadataReceived(const Engine::SimpleMetaBundle &bundle) {
PlaylistItemPtr item = app_->playlist_manager()->active()->current_item();
if (!item) return;
Engine::SimpleMetaBundle bundle_copy = bundle;
// Maybe the metadata is from icycast and has "Artist - Title" shoved
// together in the title field.
const int dash_pos = bundle_copy.title.indexOf('-');
if (dash_pos != -1 && bundle_copy.artist.isEmpty()) {
// Split on " - " if it exists, otherwise split on "-".
const int space_dash_pos = bundle_copy.title.indexOf(" - ");
if (space_dash_pos != -1) {
bundle_copy.artist = bundle_copy.title.left(space_dash_pos).trimmed();
bundle_copy.title = bundle_copy.title.mid(space_dash_pos + 3).trimmed();
} else {
bundle_copy.artist = bundle_copy.title.left(dash_pos).trimmed();
bundle_copy.title = bundle_copy.title.mid(dash_pos + 1).trimmed();
}
}
Song song = item->Metadata();
song.MergeFromSimpleMetaBundle(bundle_copy);
// Ignore useless metadata
if (song.title().isEmpty() && song.artist().isEmpty()) return;
app_->playlist_manager()->active()->SetStreamMetadata(item->Url(), song);
}
PlaylistItemPtr Player::GetItemAt(int pos) const {
if (pos < 0 || pos >= app_->playlist_manager()->active()->rowCount())
return PlaylistItemPtr();
return app_->playlist_manager()->active()->item_at(pos);
}
void Player::Mute() {
const int current_volume = engine_->volume();
if (current_volume == 0) {
SetVolume(volume_before_mute_);
}
else {
volume_before_mute_ = current_volume;
SetVolume(0);
}
}
void Player::Pause() { engine_->Pause(); }
void Player::Play() {
switch (GetState()) {
case Engine::Playing:
SeekTo(0);
break;
case Engine::Paused:
engine_->Unpause();
break;
default:
PlayPause();
break;
}
}
void Player::ShowOSD() {
if (current_item_) emit ForceShowOSD(current_item_->Metadata(), false);
}
void Player::TogglePrettyOSD() {
if (current_item_) emit ForceShowOSD(current_item_->Metadata(), true);
}
void Player::TrackAboutToEnd() {
// If the current track was from a URL handler then it might have special
// behaviour to queue up a subsequent track. We don't want to preload (and
// scrobble) the next item in the playlist if it's just going to be stopped
// again immediately after.
if (app_->playlist_manager()->active()->current_item()) {
const QUrl url = app_->playlist_manager()->active()->current_item()->Url();
if (url_handlers_.contains(url.scheme())) {
url_handlers_[url.scheme()]->TrackAboutToEnd();
return;
}
}
const bool has_next_row = app_->playlist_manager()->active()->next_row() != -1;
PlaylistItemPtr next_item;
if (has_next_row) {
next_item = app_->playlist_manager()->active()->item_at(app_->playlist_manager()->active()->next_row());
}
if (engine_->is_autocrossfade_enabled()) {
// Crossfade is on, so just start playing the next track. The current one
// will fade out, and the new one will fade in
// But, if there's no next track and we don't want to fade out, then do
// nothing and just let the track finish to completion.
if (!engine_->is_fadeout_enabled() && !has_next_row) return;
// If the next track is on the same album (or same cue file), and the
// user doesn't want to crossfade between tracks on the same album, then
// don't do this automatic crossfading.
if (engine_->crossfade_same_album() || !has_next_row || !next_item || !current_item_->Metadata().IsOnSameAlbum(next_item->Metadata())) {
TrackEnded();
return;
}
}
// Crossfade is off, so start preloading the next track so we don't get a
// gap between songs.
if (!has_next_row || !next_item) return;
QUrl url = next_item->Url();
// Get the actual track URL rather than the stream URL.
if (url_handlers_.contains(url.scheme())) {
UrlHandler::LoadResult result = url_handlers_[url.scheme()]->LoadNext(url);
switch (result.type_) {
case UrlHandler::LoadResult::NoMoreTracks:
return;
case UrlHandler::LoadResult::WillLoadAsynchronously:
loading_async_ = url;
return;
case UrlHandler::LoadResult::TrackAvailable:
url = result.media_url_;
break;
}
}
engine_->StartPreloading(url, next_item->Metadata().has_cue(), next_item->Metadata().beginning_nanosec(), next_item->Metadata().end_nanosec());
}
void Player::IntroPointReached() { NextInternal(Engine::Intro); }
void Player::ValidSongRequested(const QUrl &url) {
emit SongChangeRequestProcessed(url, true);
}
void Player::InvalidSongRequested(const QUrl &url) {
// first send the notification to others...
emit SongChangeRequestProcessed(url, false);
// ... and now when our listeners have completed their processing of the
// current item we can change the current item by skipping to the next song
//NextItem(Engine::Auto);
}
void Player::RegisterUrlHandler(UrlHandler *handler) {
const QString scheme = handler->scheme();
if (url_handlers_.contains(scheme)) {
qLog(Warning) << "Tried to register a URL handler for" << scheme << "but one was already registered";
return;
}
qLog(Info) << "Registered URL handler for" << scheme;
url_handlers_.insert(scheme, handler);
connect(handler, SIGNAL(destroyed(QObject*)), SLOT(UrlHandlerDestroyed(QObject*)));
connect(handler, SIGNAL(AsyncLoadComplete(UrlHandler::LoadResult)), SLOT(HandleLoadResult(UrlHandler::LoadResult)));
}
void Player::UnregisterUrlHandler(UrlHandler *handler) {
const QString scheme = url_handlers_.key(handler);
if (scheme.isEmpty()) {
qLog(Warning) << "Tried to unregister a URL handler for" << handler->scheme() << "that wasn't registered";
return;
}
qLog(Info) << "Unregistered URL handler for" << scheme;
url_handlers_.remove(scheme);
disconnect(handler, SIGNAL(destroyed(QObject*)), this, SLOT(UrlHandlerDestroyed(QObject*)));
disconnect(handler, SIGNAL(AsyncLoadComplete(UrlHandler::LoadResult)), this, SLOT(HandleLoadResult(UrlHandler::LoadResult)));
}
const UrlHandler *Player::HandlerForUrl(const QUrl &url) const {
QMap<QString, UrlHandler*>::const_iterator it = url_handlers_.constFind(url.scheme());
if (it == url_handlers_.constEnd()) {
return nullptr;
}
return *it;
}
void Player::UrlHandlerDestroyed(QObject *object) {
UrlHandler *handler = static_cast<UrlHandler*>(object);
const QString scheme = url_handlers_.key(handler);
if (!scheme.isEmpty()) {
url_handlers_.remove(scheme);
}
}

221
src/core/player.h Normal file
View File

@@ -0,0 +1,221 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PLAYER_H
#define PLAYER_H
#include "config.h"
#include <memory>
#include <QDateTime>
#include <QObject>
#include <QSettings>
#include "config.h"
#include "core/song.h"
#include "core/urlhandler.h"
#include "covermanager/albumcoverloader.h"
#include "engine/engine_fwd.h"
#include "engine/enginetype.h"
#include "engine/enginedevice.h"
#include "playlist/playlistitem.h"
#include "analyzer/analyzercontainer.h"
#include "equalizer/equalizer.h"
class Application;
class AnalyzerContainer;
class Equalizer;
class PlayerInterface : public QObject {
Q_OBJECT
public:
explicit PlayerInterface(QObject *parent = nullptr) : QObject(parent) {}
virtual EngineBase *engine() const = 0;
virtual Engine::State GetState() const = 0;
virtual int GetVolume() const = 0;
virtual PlaylistItemPtr GetCurrentItem() const = 0;
virtual PlaylistItemPtr GetItemAt(int pos) const = 0;
virtual void RegisterUrlHandler(UrlHandler *handler) = 0;
virtual void UnregisterUrlHandler(UrlHandler *handler) = 0;
public slots:
virtual void ReloadSettings() = 0;
// Manual track change to the specified track
virtual void PlayAt(int i, Engine::TrackChangeFlags change, bool reshuffle) = 0;
// If there's currently a song playing, pause it, otherwise play the track
// that was playing last, or the first one on the playlist
virtual void PlayPause() = 0;
virtual void RestartOrPrevious() = 0;
// Skips this track. Might load more of the current radio station.
virtual void Next() = 0;
virtual void Previous() = 0;
virtual void SetVolume(int value) = 0;
virtual void VolumeUp() = 0;
virtual void VolumeDown() = 0;
virtual void SeekTo(int seconds) = 0;
// Moves the position of the currently playing song five seconds forward.
virtual void SeekForward() = 0;
// Moves the position of the currently playing song five seconds backwards.
virtual void SeekBackward() = 0;
virtual void CurrentMetadataChanged(const Song &metadata) = 0;
virtual void Mute() = 0;
virtual void Pause() = 0;
virtual void Stop(bool stop_after = false) = 0;
virtual void Play() = 0;
virtual void ShowOSD() = 0;
signals:
void Playing();
void Paused();
void Stopped();
void PlaylistFinished();
void VolumeChanged(int volume);
void Error(const QString &message);
void TrackSkipped(PlaylistItemPtr old_track);
// Emitted when there's a manual change to the current's track position.
void Seeked(qlonglong microseconds);
// Emitted when Player has processed a request to play another song. This contains
// the URL of the song and a flag saying whether it was able to play the song.
void SongChangeRequestProcessed(const QUrl &url, bool valid);
// The toggle parameter is true when user requests to toggle visibility for Pretty OSD
void ForceShowOSD(Song, bool toogle);
};
class Player : public PlayerInterface {
Q_OBJECT
public:
Player(Application *app, QObject *parent);
~Player();
static const char *kSettingsGroup;
// Don't change the values: they are saved in preferences
enum PreviousBehaviour {
PreviousBehaviour_DontRestart = 1,
PreviousBehaviour_Restart = 2
};
EngineBase *CreateEngine(Engine::EngineType enginetype);
void Init();
EngineBase *engine() const { return engine_.get(); }
Engine::State GetState() const { return last_state_; }
int GetVolume() const;
PlaylistItemPtr GetCurrentItem() const { return current_item_; }
PlaylistItemPtr GetItemAt(int pos) const;
void RegisterUrlHandler(UrlHandler *handler);
void UnregisterUrlHandler(UrlHandler *handler);
const UrlHandler *HandlerForUrl(const QUrl &url) const;
bool PreviousWouldRestartTrack() const;
void SetAnalyzer(AnalyzerContainer *analyzer);
void SetEqualizer(Equalizer *equalizer);
public slots:
void ReloadSettings();
void PlayAt(int i, Engine::TrackChangeFlags change, bool reshuffle);
void PlayPause();
void RestartOrPrevious();
void Next();
void Previous();
void SetVolume(int value);
void VolumeUp() { SetVolume(GetVolume() + 5); }
void VolumeDown() { SetVolume(GetVolume() - 5); }
void SeekTo(int seconds);
void SeekForward();
void SeekBackward();
void CurrentMetadataChanged(const Song &metadata);
void Mute();
void Pause();
void Stop(bool stop_after = false);
void StopAfterCurrent();
void IntroPointReached();
void Play();
void ShowOSD();
void TogglePrettyOSD();
private slots:
void EngineStateChanged(Engine::State);
void EngineMetadataReceived(const Engine::SimpleMetaBundle &bundle);
void TrackAboutToEnd();
void TrackEnded();
// Play the next item on the playlist - disregarding radio stations like last.fm that might have more tracks.
void NextItem(Engine::TrackChangeFlags change);
void PreviousItem(Engine::TrackChangeFlags change);
void NextInternal(Engine::TrackChangeFlags);
void ValidSongRequested(const QUrl&);
void InvalidSongRequested(const QUrl&);
void UrlHandlerDestroyed(QObject *object);
void HandleLoadResult(const UrlHandler::LoadResult &result);
private:
// Returns true if we were supposed to stop after this track.
bool HandleStopAfter();
private:
Application *app_;
QSettings settings_;
AnalyzerContainer *analyzer_;
Equalizer *equalizer_;
PlaylistItemPtr current_item_;
std::unique_ptr<EngineBase> engine_;
Engine::TrackChangeFlags stream_change_type_;
Engine::State last_state_;
int nb_errors_received_;
QMap<QString, UrlHandler*> url_handlers_;
QUrl loading_async_;
int volume_before_mute_;
QDateTime last_pressed_previous_;
PreviousBehaviour menu_previousmode_;
int seek_step_sec_;
};
#endif // PLAYER_H

31
src/core/qhash_qurl.h Normal file
View File

@@ -0,0 +1,31 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef QHASH_QURL_H
#define QHASH_QURL_H
#include <QUrl>
#if QT_VERSION < 0x040700
inline uint qHash(const QUrl& url) { return qHash(url.toEncoded()); }
#endif
#endif // QHASH_QURL_H

29
src/core/qt_blurimage.h Normal file
View File

@@ -0,0 +1,29 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2011, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef QT_BLURIMAGE_H
#define QT_BLURIMAGE_H
#include "config.h"
// Exported by QtGui
void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0);
#endif // QT_BLURIMAGE_H

43
src/core/qtfslistener.cpp Normal file
View File

@@ -0,0 +1,43 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "qtfslistener.h"
#include <QStringList>
QtFSListener::QtFSListener(QObject *parent) : FileSystemWatcherInterface(parent), watcher_(this) {
connect(&watcher_, SIGNAL(directoryChanged(const QString&)), SIGNAL(PathChanged(const QString&)));
}
void QtFSListener::AddPath(const QString &path) { watcher_.addPath(path); }
void QtFSListener::RemovePath(const QString &path) {
watcher_.removePath(path);
}
void QtFSListener::Clear() {
watcher_.removePaths(watcher_.directories());
watcher_.removePaths(watcher_.files());
}

43
src/core/qtfslistener.h Normal file
View File

@@ -0,0 +1,43 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef QTFSLISTENER_H
#define QTFSLISTENER_H
#include "config.h"
#include "filesystemwatcherinterface.h"
#include <QFileSystemWatcher>
class QtFSListener : public FileSystemWatcherInterface {
Q_OBJECT
public:
explicit QtFSListener(QObject *parent);
virtual void AddPath(const QString &path);
virtual void RemovePath(const QString &path);
virtual void Clear();
private:
QFileSystemWatcher watcher_;
};
#endif

View File

@@ -0,0 +1,263 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "iconloader.h"
#include "qtsystemtrayicon.h"
#include "core/song.h"
#include "core/logging.h"
#include <QCoreApplication>
#include <QTextDocument>
#include <QStringBuilder>
#include <QMenu>
#include <QFile>
#include <QSystemTrayIcon>
#include <QWheelEvent>
QtSystemTrayIcon::QtSystemTrayIcon(QObject *parent)
: SystemTrayIcon(parent),
tray_(new QSystemTrayIcon(this)),
menu_(new QMenu),
action_play_pause_(nullptr),
action_stop_(nullptr),
action_stop_after_this_track_(nullptr),
action_mute_(nullptr)
{
//qLog(Debug) << __PRETTY_FUNCTION__;
QIcon theme_icon = IconLoader::Load("strawberry-panel");
QIcon theme_icon_grey = IconLoader::Load("strawberry-panel-grey");
if (theme_icon.isNull() || theme_icon_grey.isNull()) {
// Load the default icon
QIcon icon(":/icons/64x64/strawberry-panel.png");
normal_icon_ = icon.pixmap(48, QIcon::Normal);
grey_icon_ = icon.pixmap(48, QIcon::Disabled);
}
else {
// Use the icons from the theme
normal_icon_ = theme_icon.pixmap(48);
grey_icon_ = theme_icon_grey.pixmap(48);
}
tray_->setIcon(normal_icon_);
tray_->installEventFilter(this);
ClearNowPlaying();
QFile pattern_file(":/misc/playing_tooltip.txt");
pattern_file.open(QIODevice::ReadOnly);
pattern_ = QString::fromLatin1(pattern_file.readAll());
connect(tray_, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(Clicked(QSystemTrayIcon::ActivationReason)));
}
QtSystemTrayIcon::~QtSystemTrayIcon() {
delete menu_;
}
bool QtSystemTrayIcon::eventFilter(QObject *object, QEvent *event) {
//qLog(Debug) << __PRETTY_FUNCTION__;
if (QObject::eventFilter(object, event)) return true;
if (object != tray_) return false;
if (event->type() == QEvent::Wheel) {
QWheelEvent *e = static_cast<QWheelEvent*>(event);
if (e->modifiers() == Qt::ShiftModifier) {
if (e->delta() > 0) {
emit SeekForward();
} else {
emit SeekBackward();
}
}
else if (e->modifiers() == Qt::ControlModifier) {
if (e->delta() < 0) {
emit NextTrack();
}
else {
emit PreviousTrack();
}
}
else {
emit ChangeVolume(e->delta());
}
return true;
}
return false;
}
void QtSystemTrayIcon::SetupMenu(QAction *previous, QAction *play, QAction *stop, QAction *stop_after, QAction *next, QAction *mute, QAction *quit) {
//qLog(Debug) << __PRETTY_FUNCTION__;
// Creating new actions and connecting them to old ones. This allows us to
// use old actions without displaying shortcuts that can not be used when
// Strawberry's window is hidden
menu_->addAction(previous->icon(), previous->text(), previous, SLOT(trigger()));
action_play_pause_ = menu_->addAction(play->icon(), play->text(), play, SLOT(trigger()));
action_stop_ = menu_->addAction(stop->icon(), stop->text(), stop, SLOT(trigger()));
action_stop_after_this_track_ = menu_->addAction(stop_after->icon(), stop_after->text(), stop_after, SLOT(trigger()));
menu_->addAction(next->icon(), next->text(), next, SLOT(trigger()));
menu_->addSeparator();
action_mute_ = menu_->addAction(mute->icon(), mute->text(), mute, SLOT(trigger()));
action_mute_->setCheckable(true);
action_mute_->setChecked(mute->isChecked());
menu_->addSeparator();
menu_->addSeparator();
menu_->addAction(quit->icon(), quit->text(), quit, SLOT(trigger()));
tray_->setContextMenu(menu_);
}
void QtSystemTrayIcon::Clicked(QSystemTrayIcon::ActivationReason reason) {
//qLog(Debug) << __PRETTY_FUNCTION__;
switch (reason) {
case QSystemTrayIcon::DoubleClick:
case QSystemTrayIcon::Trigger:
emit ShowHide();
break;
case QSystemTrayIcon::MiddleClick:
emit PlayPause();
break;
default:
break;
}
}
void QtSystemTrayIcon::ShowPopup(const QString &summary, const QString &message, int timeout) {
tray_->showMessage(summary, message, QSystemTrayIcon::NoIcon, timeout);
}
void QtSystemTrayIcon::UpdateIcon() {
tray_->setIcon(CreateIcon(normal_icon_, grey_icon_));
}
void QtSystemTrayIcon::SetPaused() {
SystemTrayIcon::SetPaused();
action_stop_->setEnabled(true);
action_stop_after_this_track_->setEnabled(true);
action_play_pause_->setIcon(IconLoader::Load("media-play"));
action_play_pause_->setText(tr("Play"));
action_play_pause_->setEnabled(true);
}
void QtSystemTrayIcon::SetPlaying(bool enable_play_pause) {
SystemTrayIcon::SetPlaying();
action_stop_->setEnabled(true);
action_stop_after_this_track_->setEnabled(true);
action_play_pause_->setIcon(IconLoader::Load("media-pause"));
action_play_pause_->setText(tr("Pause"));
action_play_pause_->setEnabled(enable_play_pause);
}
void QtSystemTrayIcon::SetStopped() {
SystemTrayIcon::SetStopped();
action_stop_->setEnabled(false);
action_stop_after_this_track_->setEnabled(false);
action_play_pause_->setIcon(IconLoader::Load("media-play"));
action_play_pause_->setText(tr("Play"));
action_play_pause_->setEnabled(true);
}
void QtSystemTrayIcon::LastFMButtonVisibilityChanged(bool value) {
}
void QtSystemTrayIcon::MuteButtonStateChanged(bool value) {
if (action_mute_) action_mute_->setChecked(value);
}
bool QtSystemTrayIcon::IsVisible() const {
return tray_->isVisible();
}
void QtSystemTrayIcon::SetVisible(bool visible) {
tray_->setVisible(visible);
}
void QtSystemTrayIcon::SetNowPlaying(const Song &song, const QString &image_path) {
#ifdef Q_WS_WIN
// Windows doesn't support HTML in tooltips, so just show something basic
tray_->setToolTip(song.PrettyTitleWithArtist());
return;
#endif
int columns = image_path == nullptr ? 1 : 2;
QString clone = pattern_;
clone.replace("%columns" , QString::number(columns));
clone.replace("%appName" , QCoreApplication::applicationName());
clone.replace("%titleKey" , tr("Title") % ":");
clone.replace("%titleValue" , song.PrettyTitle().toHtmlEscaped());
clone.replace("%artistKey" , tr("Artist") % ":");
clone.replace("%artistValue", song.artist().toHtmlEscaped());
clone.replace("%albumKey" , tr("Album") % ":");
clone.replace("%albumValue" , song.album().toHtmlEscaped());
clone.replace("%lengthKey" , tr("Length") % ":");
clone.replace("%lengthValue", song.PrettyLength().toHtmlEscaped());
if(columns == 2) {
QString final_path = image_path.startsWith("file://") ? image_path.mid(7) : image_path;
clone.replace("%image", " <td> <img src=\"" % final_path % "\" /> </td>");
}
else {
clone.replace("%image", "");
}
// TODO: we should also repaint this
tray_->setToolTip(clone);
}
void QtSystemTrayIcon::ClearNowPlaying() {
tray_->setToolTip(QCoreApplication::applicationName());
}

View File

@@ -0,0 +1,75 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef QTSYSTEMTRAYICON_H
#define QTSYSTEMTRAYICON_H
#include "config.h"
#include "systemtrayicon.h"
#include <QSystemTrayIcon>
class QtSystemTrayIcon : public SystemTrayIcon {
Q_OBJECT
public:
QtSystemTrayIcon(QObject* parent = nullptr);
~QtSystemTrayIcon();
void SetupMenu(QAction* previous, QAction *play, QAction *stop, QAction *stop_after, QAction *next, QAction *mute, QAction *quit);
bool IsVisible() const;
void SetVisible(bool visible);
void ShowPopup(const QString &summary, const QString &message, int timeout);
void SetNowPlaying(const Song& song, const QString& image_path);
void ClearNowPlaying();
protected:
// SystemTrayIcon
void UpdateIcon();
void SetPaused();
void SetPlaying(bool enable_play_pause = false);
void SetStopped();
void LastFMButtonVisibilityChanged(bool value);
void MuteButtonStateChanged(bool value);
// QObject
bool eventFilter(QObject *, QEvent *);
private slots:
void Clicked(QSystemTrayIcon::ActivationReason);
private:
QSystemTrayIcon *tray_;
QMenu *menu_;
QAction *action_play_pause_;
QAction *action_stop_;
QAction *action_stop_after_this_track_;
QAction *action_mute_;
QString pattern_;
QPixmap normal_icon_;
QPixmap grey_icon_;
};
#endif // QTSYSTEMTRAYICON_H

View File

@@ -0,0 +1,44 @@
/*
* Strawberry Music Player
* This code was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
//#ifdef HAVE_GIO
//#undef signals // Clashes with GIO, and not needed in this file
#include <gio/gio.h>
#include <QString>
#include "core/logging.h"
//namespace {
void ScanGIOModulePath() {
QString gio_module_path;
#if defined(Q_OS_WIN32)
gio_module_path = QCoreApplication::applicationDirPath() + "/gio-modules";
#endif
if (!gio_module_path.isEmpty()) {
qLog(Debug) << "Adding GIO module path:" << gio_module_path;
QByteArray bytes = gio_module_path.toLocal8Bit();
g_io_modules_scan_all_in_directory(bytes.data());
}
}
//} // namespace
//#endif // HAVE_GIO

View File

@@ -0,0 +1,21 @@
/*
* Strawberry Music Player
* This code was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
void ScanGIOModulePath();

View File

@@ -0,0 +1,65 @@
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_MAC_SCOPED_CFTYPEREF_H_
#define BASE_MAC_SCOPED_CFTYPEREF_H_
#include <CoreFoundation/CoreFoundation.h>
// ScopedCFTypeRef<> is patterned after scoped_ptr<>, but maintains ownership
// of a CoreFoundation object: any object that can be represented as a
// CFTypeRef. Style deviations here are solely for compatibility with
// scoped_ptr<>'s interface, with which everyone is already familiar.
//
// When ScopedCFTypeRef<> takes ownership of an object (in the constructor or
// in reset()), it takes over the caller's existing ownership claim. The
// caller must own the object it gives to ScopedCFTypeRef<>, and relinquishes
// an ownership claim to that object. ScopedCFTypeRef<> does not call
// CFRetain().
template <typename CFT>
class ScopedCFTypeRef {
public:
typedef CFT element_type;
explicit ScopedCFTypeRef(CFT object = nullptr) : object_(object) {}
~ScopedCFTypeRef() {
if (object_) CFRelease(object_);
}
void reset(CFT object = nullptr) {
if (object_) CFRelease(object_);
object_ = object;
}
bool operator==(CFT that) const { return object_ == that; }
bool operator!=(CFT that) const { return object_ != that; }
operator CFT() const { return object_; }
CFT get() const { return object_; }
void swap(ScopedCFTypeRef& that) {
CFT temp = that.object_;
that.object_ = object_;
object_ = temp;
}
// ScopedCFTypeRef<>::release() is like scoped_ptr<>::release. It is NOT
// a wrapper for CFRelease(). To force a ScopedCFTypeRef<> object to call
// CFRelease(), use ScopedCFTypeRef<>::reset().
CFT release() __attribute__((warn_unused_result)) {
CFT temp = object_;
object_ = nullptr;
return temp;
}
private:
CFT object_;
Q_DISABLE_COPY(ScopedCFTypeRef);
};
#endif // BASE_MAC_SCOPED_CFTYPEREF_H_

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_
#define BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_
#include <QObject>
#if defined(__OBJC__)
@class NSAutoreleasePool;
#else // __OBJC__
class NSAutoreleasePool;
#endif // __OBJC__
// ScopedNSAutoreleasePool allocates an NSAutoreleasePool when instantiated and
// sends it a -drain message when destroyed. This allows an autorelease pool to
// be maintained in ordinary C++ code without bringing in any direct Objective-C
// dependency.
class ScopedNSAutoreleasePool {
public:
ScopedNSAutoreleasePool();
~ScopedNSAutoreleasePool();
// Clear out the pool in case its position on the stack causes it to be
// alive for long periods of time (such as the entire length of the app).
// Only use then when you're certain the items currently in the pool are
// no longer needed.
void Recycle();
private:
NSAutoreleasePool* autorelease_pool_;
private:
Q_DISABLE_COPY(ScopedNSAutoreleasePool);
};
#endif // BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_

View File

@@ -0,0 +1,24 @@
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "scoped_nsautorelease_pool.h"
#import <Foundation/Foundation.h>
ScopedNSAutoreleasePool::ScopedNSAutoreleasePool()
: autorelease_pool_([[NSAutoreleasePool alloc] init]) {
Q_ASSERT(autorelease_pool_);
}
ScopedNSAutoreleasePool::~ScopedNSAutoreleasePool() {
[autorelease_pool_ drain];
}
// Cycle the internal pool, allowing everything there to get cleaned up and
// start anew.
void ScopedNSAutoreleasePool::Recycle() {
[autorelease_pool_ drain];
autorelease_pool_ = [[NSAutoreleasePool alloc] init];
Q_ASSERT(autorelease_pool_);
}

143
src/core/scoped_nsobject.h Normal file
View File

@@ -0,0 +1,143 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_MEMORY_SCOPED_NSOBJECT_H_
#define BASE_MEMORY_SCOPED_NSOBJECT_H_
#import <Foundation/Foundation.h>
// scoped_nsobject<> is patterned after scoped_ptr<>, but maintains ownership
// of an NSObject subclass object. Style deviations here are solely for
// compatibility with scoped_ptr<>'s interface, with which everyone is already
// familiar.
//
// When scoped_nsobject<> takes ownership of an object (in the constructor or
// in reset()), it takes over the caller's existing ownership claim. The
// caller must own the object it gives to scoped_nsobject<>, and relinquishes
// an ownership claim to that object. scoped_nsobject<> does not call
// -retain.
//
// scoped_nsobject<> is not to be used for NSAutoreleasePools. For
// NSAutoreleasePools use ScopedNSAutoreleasePool from
// scoped_nsautorelease_pool.h instead.
// We check for bad uses of scoped_nsobject and NSAutoreleasePool at compile
// time with a template specialization (see below).
template <typename NST>
class scoped_nsobject {
public:
explicit scoped_nsobject(NST* object = nil) : object_(object) {}
~scoped_nsobject() { [object_ release]; }
void reset(NST* object = nil) {
// We intentionally do not check that object != object_ as the caller must
// already have an ownership claim over whatever it gives to
// scoped_nsobject and ScopedCFTypeRef, whether it's in the constructor or
// in a call to reset(). In either case, it relinquishes that claim and
// the scoper assumes it.
[object_ release];
object_ = object;
}
bool operator==(NST* that) const { return object_ == that; }
bool operator!=(NST* that) const { return object_ != that; }
operator NST*() const { return object_; }
NST* get() const { return object_; }
void swap(scoped_nsobject& that) {
NST* temp = that.object_;
that.object_ = object_;
object_ = temp;
}
// scoped_nsobject<>::release() is like scoped_ptr<>::release. It is NOT
// a wrapper for [object_ release]. To force a scoped_nsobject<> object to
// call [object_ release], use scoped_nsobject<>::reset().
NST* release() __attribute__((warn_unused_result)) {
NST* temp = object_;
object_ = nil;
return temp;
}
private:
NST* object_;
Q_DISABLE_COPY(scoped_nsobject);
};
// Free functions
template <class C>
void swap(scoped_nsobject<C>& p1, scoped_nsobject<C>& p2) {
p1.swap(p2);
}
template <class C>
bool operator==(C* p1, const scoped_nsobject<C>& p2) {
return p1 == p2.get();
}
template <class C>
bool operator!=(C* p1, const scoped_nsobject<C>& p2) {
return p1 != p2.get();
}
// Specialization to make scoped_nsobject<id> work.
template <>
class scoped_nsobject<id> {
public:
explicit scoped_nsobject(id object = nil) : object_(object) {}
~scoped_nsobject() { [object_ release]; }
void reset(id object = nil) {
// We intentionally do not check that object != object_ as the caller must
// already have an ownership claim over whatever it gives to
// scoped_nsobject and ScopedCFTypeRef, whether it's in the constructor or
// in a call to reset(). In either case, it relinquishes that claim and
// the scoper assumes it.
[object_ release];
object_ = object;
}
bool operator==(id that) const { return object_ == that; }
bool operator!=(id that) const { return object_ != that; }
operator id() const { return object_; }
id get() const { return object_; }
void swap(scoped_nsobject& that) {
id temp = that.object_;
that.object_ = object_;
object_ = temp;
}
// scoped_nsobject<>::release() is like scoped_ptr<>::release. It is NOT
// a wrapper for [object_ release]. To force a scoped_nsobject<> object to
// call [object_ release], use scoped_nsobject<>::reset().
id release() __attribute__((warn_unused_result)) {
id temp = object_;
object_ = nil;
return temp;
}
private:
id object_;
Q_DISABLE_COPY(scoped_nsobject);
};
// Do not use scoped_nsobject for NSAutoreleasePools, use
// ScopedNSAutoreleasePool instead. This is a compile time check. See details
// at top of header.
template <>
class scoped_nsobject<NSAutoreleasePool> {
private:
explicit scoped_nsobject(NSAutoreleasePool* object = nil);
Q_DISABLE_COPY(scoped_nsobject);
};
#endif // BASE_MEMORY_SCOPED_NSOBJECT_H_

71
src/core/scopedgobject.h Normal file
View File

@@ -0,0 +1,71 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCOPEDGOBJECT_H
#define SCOPEDGOBJECT_H
#include "config.h"
#include <glib-object.h>
#include <QtDebug>
template <typename T>
class ScopedGObject {
public:
ScopedGObject() : object_(nullptr) {}
explicit ScopedGObject(const ScopedGObject& other) : object_(nullptr) {
reset(other.object_);
}
~ScopedGObject() { reset(); }
ScopedGObject& operator=(const ScopedGObject& other) {
reset(other.object_);
return *this;
}
void reset(T* new_object = nullptr) {
if (new_object) g_object_ref(new_object);
reset_without_add(new_object);
}
void reset_without_add(T* new_object = nullptr) {
if (object_) g_object_unref(object_);
object_ = new_object;
}
T* get() const { return object_; }
operator T*() const { return get(); }
T* operator*() const { return get(); }
operator bool() const { return get(); }
bool operator==(const ScopedGObject& other) const {
return object_ == other.object_;
}
private:
T* object_;
};
#endif // SCOPEDGOBJECT_H

View File

@@ -0,0 +1,50 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "scopedtransaction.h"
#include "core/logging.h"
#include <QSqlDatabase>
#include <QtDebug>
ScopedTransaction::ScopedTransaction(QSqlDatabase* db)
: db_(db), pending_(true) {
db->transaction();
}
ScopedTransaction::~ScopedTransaction() {
if (pending_) {
qLog(Warning) << "Rolling back transaction";
db_->rollback();
}
}
void ScopedTransaction::Commit() {
if (!pending_) {
qLog(Warning) << "Tried to commit a ScopedTransaction twice";
return;
}
db_->commit();
pending_ = false;
}

View File

@@ -0,0 +1,45 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCOPEDTRANSACTION_H
#define SCOPEDTRANSACTION_H
#include "config.h"
#include <boost/noncopyable.hpp>
class QSqlDatabase;
// Opens a transaction on a database.
// Rolls back the transaction if the object goes out of scope before Commit()
// is called.
class ScopedTransaction : boost::noncopyable {
public:
ScopedTransaction(QSqlDatabase* db);
~ScopedTransaction();
void Commit();
private:
QSqlDatabase* db_;
bool pending_;
};
#endif // SCOPEDTRANSACTION_H

62
src/core/screensaver.cpp Normal file
View File

@@ -0,0 +1,62 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QtGlobal>
#include "screensaver.h"
#ifdef HAVE_DBUS
#include "dbusscreensaver.h"
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#endif
#ifdef Q_OS_DARWIN
#include "macscreensaver.h"
#endif
#include <QtDebug>
const char *Screensaver::kGnomeService = "org.gnome.ScreenSaver";
const char *Screensaver::kGnomePath = "/";
const char *Screensaver::kGnomeInterface = "org.gnome.ScreenSaver";
const char *Screensaver::kKdeService = "org.kde.ScreenSaver";
const char *Screensaver::kKdePath = "/ScreenSaver/";
const char *Screensaver::kKdeInterface = "org.freedesktop.ScreenSaver";
Screensaver *Screensaver::screensaver_ = 0;
Screensaver *Screensaver::GetScreensaver() {
if (!screensaver_) {
#if defined(HAVE_DBUS)
if (QDBusConnection::sessionBus().interface()->isServiceRegistered(kGnomeService)) {
screensaver_ = new DBusScreensaver(kGnomeService, kGnomePath, kGnomeInterface);
}
else if (QDBusConnection::sessionBus().interface()->isServiceRegistered(kKdeService)) {
screensaver_ = new DBusScreensaver(kKdeService, kKdePath, kKdeInterface);
}
#elif defined(Q_OS_DARWIN)
screensaver_ = new MacScreensaver();
#endif
}
return screensaver_;
}

47
src/core/screensaver.h Normal file
View File

@@ -0,0 +1,47 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCREENSAVER_H
#define SCREENSAVER_H
#include "config.h"
class Screensaver {
public:
virtual ~Screensaver() {}
static const char *kGnomeService;
static const char *kGnomePath;
static const char *kGnomeInterface;
static const char *kKdeService;
static const char *kKdePath;
static const char *kKdeInterface;
virtual void Inhibit() = 0;
virtual void Uninhibit() = 0;
static Screensaver *GetScreensaver();
private:
static Screensaver *screensaver_;
};
#endif

View File

@@ -0,0 +1,56 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "settingsprovider.h"
SettingsProvider::SettingsProvider() {}
DefaultSettingsProvider::DefaultSettingsProvider() {}
void DefaultSettingsProvider::set_group(const char *group) {
while (!backend_.group().isEmpty()) backend_.endGroup();
backend_.beginGroup(group);
}
QVariant DefaultSettingsProvider::value(const QString &key, const QVariant &default_value) const {
return backend_.value(key, default_value);
}
void DefaultSettingsProvider::setValue(const QString &key, const QVariant &value) {
backend_.setValue(key, value);
}
int DefaultSettingsProvider::beginReadArray(const QString &prefix) {
return backend_.beginReadArray(prefix);
}
void DefaultSettingsProvider::beginWriteArray(const QString &prefix, int size) {
backend_.beginWriteArray(prefix, size);
}
void DefaultSettingsProvider::setArrayIndex(int i) {
backend_.setArrayIndex(i);
}
void DefaultSettingsProvider::endArray() { backend_.endArray(); }

View File

@@ -0,0 +1,62 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SETTINGSPROVIDER_H
#define SETTINGSPROVIDER_H
#include "config.h"
#include <QVariant>
#include <QSettings>
class SettingsProvider {
public:
SettingsProvider();
virtual ~SettingsProvider() {}
virtual void set_group(const char *group) = 0;
virtual QVariant value(const QString &key, const QVariant &default_value = QVariant()) const = 0;
virtual void setValue(const QString &key, const QVariant &value) = 0;
virtual int beginReadArray(const QString &prefix) = 0;
virtual void beginWriteArray(const QString &prefix, int size = -1) = 0;
virtual void setArrayIndex(int i) = 0;
virtual void endArray() = 0;
};
class DefaultSettingsProvider : public SettingsProvider {
public:
DefaultSettingsProvider();
void set_group(const char* group);
QVariant value(const QString &key, const QVariant &default_value = QVariant()) const;
void setValue(const QString &key, const QVariant &value);
int beginReadArray(const QString &prefix);
void beginWriteArray(const QString &prefix, int size = -1);
void setArrayIndex(int i);
void endArray();
private:
QSettings backend_;
};
#endif // SETTINGSPROVIDER_H

View File

@@ -0,0 +1,52 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2012, 2014, John Maguire <john.maguire@gmail.com>
* Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "signalchecker.h"
#include "core/logging.h"
gulong CheckedGConnect(gpointer source, const char *signal, GCallback callback, gpointer data, const int callback_param_count) {
guint signal_id = 0;
GQuark detail = 0;
if (!g_signal_parse_name(signal, G_OBJECT_TYPE(source), &signal_id, &detail, false)) {
qFatal("Connecting to invalid signal: %s", signal);
return 0;
}
GSignalQuery query;
g_signal_query(signal_id, &query);
// The signature for a signal callback is always:
// return_type callback(gpointer data1, params..., gpointer data2);
int signal_params = query.n_params + 2;
if (signal_params != callback_param_count) {
qFatal("Connecting callback to signal with different parameters counts");
return 0;
}
return g_signal_connect(source, signal, G_CALLBACK(callback), data);
}

38
src/core/signalchecker.h Normal file
View File

@@ -0,0 +1,38 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SIGNALCHECKER_H
#define SIGNALCHECKER_H
#include "config.h"
#include <glib-object.h>
#include <boost/function_types/function_arity.hpp>
#include <boost/typeof/typeof.hpp>
// Do not call this directly, use CHECKED_GCONNECT instead.
gulong CheckedGConnect(gpointer source, const char *signal, GCallback callback, gpointer data, const int callback_param_count);
#define FUNCTION_ARITY(callback) boost::function_types::function_arity<BOOST_TYPEOF(callback)>::value
#define CHECKED_GCONNECT(source, signal, callback, data) CheckedGConnect(source, signal, G_CALLBACK(callback), data, FUNCTION_ARITY(callback))
#endif // SIGNALCHECKER_H

161
src/core/simpletreeitem.h Normal file
View File

@@ -0,0 +1,161 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SIMPLETREEITEM_H
#define SIMPLETREEITEM_H
#include "config.h"
#include "simpletreemodel.h"
#include <QString>
#include <QList>
template <typename T>
class SimpleTreeItem {
public:
SimpleTreeItem(int _type, SimpleTreeModel<T>* _model); // For the root item
SimpleTreeItem(int _type, const QString& _key, T* _parent = nullptr);
explicit SimpleTreeItem(int _type, T* _parent = nullptr);
virtual ~SimpleTreeItem();
void InsertNotify(T* _parent);
void DeleteNotify(int child_row);
void ClearNotify();
void ChangedNotify();
void Delete(int child_row);
T* ChildByKey(const QString& key) const;
QString DisplayText() const { return display_text.isNull() ? key : display_text; }
QString SortText() const { return sort_text.isNull() ? key : sort_text; }
int type;
QString key;
QString sort_text;
QString display_text;
int row;
bool lazy_loaded;
T* parent;
QList<T*> children;
QAbstractItemModel* child_model;
SimpleTreeModel<T>* model;
};
template <typename T>
SimpleTreeItem<T>::SimpleTreeItem(int _type, SimpleTreeModel<T>* _model)
: type(_type),
row(0),
lazy_loaded(true),
parent(nullptr),
child_model(nullptr),
model(_model) {}
template <typename T>
SimpleTreeItem<T>::SimpleTreeItem(int _type, const QString& _key, T* _parent)
: type(_type),
key(_key),
lazy_loaded(false),
parent(_parent),
child_model(nullptr),
model(_parent ? _parent->model : nullptr) {
if (parent) {
row = parent->children.count();
parent->children << static_cast<T*>(this);
}
}
template <typename T>
SimpleTreeItem<T>::SimpleTreeItem(int _type, T* _parent)
: type(_type),
lazy_loaded(false),
parent(_parent),
child_model(nullptr),
model(_parent ? _parent->model : nullptr) {
if (parent) {
row = parent->children.count();
parent->children << static_cast<T*>(this);
}
}
template <typename T>
void SimpleTreeItem<T>::InsertNotify(T* _parent) {
parent = _parent;
model = parent->model;
row = parent->children.count();
model->BeginInsert(parent, row);
parent->children << static_cast<T*>(this);
model->EndInsert();
}
template <typename T>
void SimpleTreeItem<T>::DeleteNotify(int child_row) {
model->BeginDelete(static_cast<T*>(this), child_row);
delete children.takeAt(child_row);
// Adjust row numbers of those below it :(
for (int i = child_row; i < children.count(); ++i) children[i]->row--;
model->EndDelete();
}
template <typename T>
void SimpleTreeItem<T>::ClearNotify() {
if (children.count()) {
model->BeginDelete(static_cast<T*>(this), 0, children.count() - 1);
qDeleteAll(children);
children.clear();
model->EndDelete();
}
}
template <typename T>
void SimpleTreeItem<T>::ChangedNotify() {
model->EmitDataChanged(static_cast<T*>(this));
}
template <typename T>
SimpleTreeItem<T>::~SimpleTreeItem() {
qDeleteAll(children);
}
template <typename T>
void SimpleTreeItem<T>::Delete(int child_row) {
delete children.takeAt(child_row);
// Adjust row numbers of those below it :(
for (int i = child_row; i < children.count(); ++i) children[i]->row--;
}
template <typename T>
T* SimpleTreeItem<T>::ChildByKey(const QString& key) const {
for (T* child : children) {
if (child->key == key) return child;
}
return nullptr;
}
#endif // SIMPLETREEITEM_H

154
src/core/simpletreemodel.h Normal file
View File

@@ -0,0 +1,154 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SIMPLETREEMODEL_H
#define SIMPLETREEMODEL_H
#include "config.h"
#include <QAbstractItemModel>
class QModelIndex;
template <typename T>
class SimpleTreeModel : public QAbstractItemModel {
public:
explicit SimpleTreeModel(T *root = 0, QObject *parent = nullptr);
virtual ~SimpleTreeModel() {}
// QAbstractItemModel
int columnCount(const QModelIndex &parent) const;
QModelIndex index(int row, int, const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &index) const;
int rowCount(const QModelIndex &parent) const;
bool hasChildren(const QModelIndex &parent) const;
bool canFetchMore(const QModelIndex &parent) const;
void fetchMore(const QModelIndex &parent);
T *IndexToItem(const QModelIndex &index) const;
QModelIndex ItemToIndex(T *item) const;
// Called by items
void BeginInsert(T *parent, int start, int end = -1);
void EndInsert();
void BeginDelete(T *parent, int start, int end = -1);
void EndDelete();
void EmitDataChanged(T *item);
protected:
virtual void LazyPopulate(T *item) = 0;
protected:
T *root_;
};
template <typename T>
SimpleTreeModel<T>::SimpleTreeModel(T *root, QObject *parent)
: QAbstractItemModel(parent), root_(root) {}
template <typename T>
T *SimpleTreeModel<T>::IndexToItem(const QModelIndex &index) const {
if (!index.isValid()) return root_;
return reinterpret_cast<T*>(index.internalPointer());
}
template <typename T>
QModelIndex SimpleTreeModel<T>::ItemToIndex(T *item) const {
if (!item || !item->parent) return QModelIndex();
return createIndex(item->row, 0, item);
}
template <typename T>
int SimpleTreeModel<T>::columnCount(const QModelIndex&) const {
return 1;
}
template <typename T>
QModelIndex SimpleTreeModel<T>::index(int row, int, const QModelIndex &parent) const {
T *parent_item = IndexToItem(parent);
if (!parent_item || row < 0 || parent_item->children.count() <= row)
return QModelIndex();
return ItemToIndex(parent_item->children[row]);
}
template <typename T>
QModelIndex SimpleTreeModel<T>::parent(const QModelIndex &index) const {
return ItemToIndex(IndexToItem(index)->parent);
}
template <typename T>
int SimpleTreeModel<T>::rowCount(const QModelIndex &parent) const {
T *item = IndexToItem(parent);
return item->children.count();
}
template <typename T>
bool SimpleTreeModel<T>::hasChildren(const QModelIndex &parent) const {
T *item = IndexToItem(parent);
if (item->lazy_loaded)
return !item->children.isEmpty();
else
return true;
}
template <typename T>
bool SimpleTreeModel<T>::canFetchMore(const QModelIndex &parent) const {
T *item = IndexToItem(parent);
return !item->lazy_loaded;
}
template <typename T>
void SimpleTreeModel<T>::fetchMore(const QModelIndex &parent) {
T *item = IndexToItem(parent);
if (!item->lazy_loaded) {
LazyPopulate(item);
}
}
template <typename T>
void SimpleTreeModel<T>::BeginInsert(T *parent, int start, int end) {
if (end == -1) end = start;
beginInsertRows(ItemToIndex(parent), start, end);
}
template <typename T>
void SimpleTreeModel<T>::EndInsert() {
endInsertRows();
}
template <typename T>
void SimpleTreeModel<T>::BeginDelete(T *parent, int start, int end) {
if (end == -1) end = start;
beginRemoveRows(ItemToIndex(parent), start, end);
}
template <typename T>
void SimpleTreeModel<T>::EndDelete() {
endRemoveRows();
}
template <typename T>
void SimpleTreeModel<T>::EmitDataChanged(T *item) {
QModelIndex index(ItemToIndex(item));
emit dataChanged(index, index);
}
#endif // SIMPLETREEMODEL_H

1152
src/core/song.cpp Normal file

File diff suppressed because it is too large Load Diff

313
src/core/song.h Normal file
View File

@@ -0,0 +1,313 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SONG_H
#define SONG_H
#include <QFuture>
#include <QImage>
#include <QMetaType>
#include <QSharedDataPointer>
#include <QVariantMap>
#include "config.h"
#include "engine/engine_fwd.h"
namespace pb {
namespace tagreader {
class SongMetadata;
} // namespace tagreader
} // namespace pb
class QSqlQuery;
class QUrl;
#ifdef HAVE_LIBGPOD
struct _Itdb_Track;
#endif
#ifdef HAVE_LIBMTP
struct LIBMTP_track_struct;
#endif
#ifdef HAVE_LIBLASTFM
namespace lastfm {
class Track;
}
#endif
class SqlRow;
class Song {
public:
Song();
Song(const Song &other);
~Song();
static const QStringList kColumns;
static const QString kColumnSpec;
static const QString kBindSpec;
static const QString kUpdateSpec;
static const QStringList kFtsColumns;
static const QString kFtsColumnSpec;
static const QString kFtsBindSpec;
static const QString kFtsUpdateSpec;
static const QString kManuallyUnsetCover;
static const QString kEmbeddedCover;
static QString JoinSpec(const QString &table);
// Don't change these values - they're stored in the database, and defined
// in the tag reader protobuf.
// If a new lossless file is added, also add it to IsFileLossless().
enum FileType {
Type_Unknown = 0,
Type_Asf = 1,
Type_Flac = 2,
Type_Mp4 = 3,
Type_Mpc = 4,
Type_Mpeg = 5,
Type_OggFlac = 6,
Type_OggSpeex = 7,
Type_OggVorbis = 8,
Type_Aiff = 9,
Type_Wav = 10,
Type_TrueAudio = 11,
Type_Cdda = 12,
Type_OggOpus = 13,
};
static QString TextForFiletype(FileType type);
QString TextForFiletype() const { return TextForFiletype(filetype()); }
bool IsFileLossless() const;
// Sort songs alphabetically using their pretty title
static void SortSongsListAlphabetically(QList<Song> *songs);
// Constructors
void Init(const QString &title, const QString &artist, const QString &album, qint64 length_nanosec);
void Init(const QString &title, const QString &artist, const QString &album, qint64 beginning, qint64 end);
void InitFromProtobuf(const pb::tagreader::SongMetadata &pb);
void InitFromQuery(const SqlRow &query, bool reliable_metadata, int col = 0);
void InitFromFilePartial(const QString &filename); // Just store the filename: incomplete but fast
void InitArtManual(); // Check if there is already a art in the cache and
// store the filename in art_manual
#ifdef HAVE_LIBLASTFM
void InitFromLastFM(const lastfm::Track &track);
#endif
void MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle);
#ifdef HAVE_LIBGPOD
void InitFromItdb(const _Itdb_Track *track, const QString &prefix);
void ToItdb(_Itdb_Track *track) const;
#endif
#ifdef HAVE_LIBMTP
void InitFromMTP(const LIBMTP_track_struct *track, const QString &host);
void ToMTP(LIBMTP_track_struct *track) const;
#endif
// Copies important statistics from the other song to this one, overwriting
// any data that already exists. Useful when you want updated tags from disk
// but you want to keep user stats.
void MergeUserSetData(const Song &other);
static QString Decode(const QString &tag, const QTextCodec *codec = nullptr);
// Save
void BindToQuery(QSqlQuery *query) const;
void BindToFtsQuery(QSqlQuery *query) const;
#ifdef HAVE_LIBLASTFM
void ToLastFM(lastfm::Track *track, bool prefer_album_artist) const;
#endif
void ToXesam(QVariantMap *map) const;
void ToProtobuf(pb::tagreader::SongMetadata *pb) const;
// Simple accessors
bool is_valid() const;
bool is_unavailable() const;
int id() const;
const QString &title() const;
const QString &album() const;
const QString &artist() const;
const QString &albumartist() const;
int track() const;
int disc() const;
int year() const;
int originalyear() const;
const QString &genre() const;
bool is_compilation() const;
const QString &composer() const;
const QString &performer() const;
const QString &grouping() const;
const QString &comment() const;
int playcount() const;
int skipcount() const;
int lastplayed() const;
int album_id() const;
qint64 beginning_nanosec() const;
qint64 end_nanosec() const;
qint64 length_nanosec() const;
int bitrate() const;
int samplerate() const;
int bitdepth() const;
int directory_id() const;
const QUrl &url() const;
const QString &basefilename() const;
FileType filetype() const;
int filesize() const;
uint mtime() const;
uint ctime() const;
const QString &art_automatic() const;
const QString &art_manual() const;
const QString &cue_path() const;
bool has_cue() const;
const QString &effective_album() const;
int effective_originalyear() const;
const QString &effective_albumartist() const;
bool is_collection_song() const;
bool is_cdda() const;
// Playlist views are special because you don't want to fill in album artists automatically for compilations, but you do for normal albums:
const QString &playlist_albumartist() const;
// Returns true if this Song had it's cover manually unset by user.
bool has_manually_unset_cover() const;
// This method represents an explicit request to unset this song's
// cover.
void manually_unset_cover();
// Returns true if this song (it's media file) has an embedded cover.
bool has_embedded_cover() const;
// Sets a flag saying that this song (it's media file) has an embedded cover.
void set_embedded_cover();
const QImage &image() const;
// Pretty accessors
QString PrettyTitle() const;
QString PrettyTitleWithArtist() const;
QString PrettyLength() const;
QString PrettyYear() const;
QString TitleWithCompilationArtist() const;
QString SampleRateBitDepthToText() const;
// Setters
bool IsEditable() const;
void set_id(int id);
void set_album_id(int v);
void set_valid(bool v);
void set_title(const QString &v);
void set_album(const QString &v);
void set_artist(const QString &v);
void set_albumartist(const QString &v);
void set_track(int v);
void set_disc(int v);
void set_year(int v);
void set_originalyear(int v);
void set_genre(const QString &v);
void set_genre_id3(int id);
void set_compilation(bool v);
void set_composer(const QString &v);
void set_performer(const QString &v);
void set_grouping(const QString &v);
void set_comment(const QString &v);
void set_beginning_nanosec(qint64 v);
void set_end_nanosec(qint64 v);
void set_length_nanosec(qint64 v);
void set_bitrate(int v);
void set_samplerate(int v);
void set_bitdepth(int v);
void set_directory_id(int v);
void set_url(const QUrl &v);
void set_basefilename(const QString &v);
void set_filetype(FileType v);
void set_filesize(int v);
void set_mtime(int v);
void set_ctime(int v);
void set_unavailable(bool v);
void set_playcount(int v);
void set_skipcount(int v);
void set_lastplayed(int v);
void set_compilation_detected(bool v);
void set_compilation_on(bool v);
void set_compilation_off(bool v);
void set_art_automatic(const QString &v);
void set_art_manual(const QString &v);
void set_cue_path(const QString &v);
void set_image(const QImage &i);
// Comparison functions
bool IsMetadataEqual(const Song &other) const;
bool IsOnSameAlbum(const Song &other) const;
bool IsSimilar(const Song &other) const;
bool operator==(const Song &other) const;
// Two songs that are on the same album will have the same AlbumKey. It is
// more efficient to use IsOnSameAlbum, but this function can be used when
// you need to hash the key to do fast lookups.
QString AlbumKey() const;
Song &operator=(const Song &other);
private:
struct Private;
QSharedDataPointer<Private> d;
};
Q_DECLARE_METATYPE(Song);
typedef QList<Song> SongList;
Q_DECLARE_METATYPE(QList<Song>);
uint qHash(const Song &song);
// Hash function using field checked in IsSimilar function
uint HashSimilar(const Song &song);
#endif // SONG_H

623
src/core/songloader.cpp Normal file
View File

@@ -0,0 +1,623 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "songloader.h"
#include <memory>
#if defined(HAVE_GSTREAMER) && defined(HAVE_AUDIOCD)
#include <gst/audio/gstaudiocdsrc.h>
#endif
#include <QBuffer>
#include <QDirIterator>
#include <QFileInfo>
#include <QTimer>
#include <QUrl>
#include <QtDebug>
#include "config.h"
#include "core/logging.h"
#include "core/player.h"
#include "core/signalchecker.h"
#include "core/song.h"
#include "core/tagreaderclient.h"
#include "core/timeconstants.h"
#include "collection/collectionbackend.h"
#include "collection/sqlrow.h"
#include "playlistparsers/cueparser.h"
#include "playlistparsers/parserbase.h"
#include "playlistparsers/playlistparser.h"
#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER)
#include "device/cddasongloader.h"
#endif
using std::placeholders::_1;
QSet<QString> SongLoader::sRawUriSchemes;
const int SongLoader::kDefaultTimeout = 5000;
SongLoader::SongLoader(CollectionBackendInterface *collection, const Player *player, QObject *parent) :
QObject(parent),
timeout_timer_(new QTimer(this)),
playlist_parser_(new PlaylistParser(collection, this)),
cue_parser_(new CueParser(collection, this)),
timeout_(kDefaultTimeout),
state_(WaitingForType),
success_(false),
parser_(nullptr),
collection_(collection),
player_(player) {
if (sRawUriSchemes.isEmpty()) {
sRawUriSchemes << "udp"
<< "mms"
<< "mmsh"
<< "mmst"
<< "mmsu"
<< "rtsp"
<< "rtspu"
<< "rtspt"
<< "rtsph";
}
timeout_timer_->setSingleShot(true);
connect(timeout_timer_, SIGNAL(timeout()), SLOT(Timeout()));
}
SongLoader::~SongLoader() {
#ifdef HAVE_GSTREAMER
if (pipeline_) {
state_ = Finished;
gst_element_set_state(pipeline_.get(), GST_STATE_NULL);
}
#endif
}
SongLoader::Result SongLoader::Load(const QUrl &url) {
url_ = url;
if (url_.scheme() == "file") {
return LoadLocal(url_.toLocalFile());
}
if (sRawUriSchemes.contains(url_.scheme()) || player_->HandlerForUrl(url) != nullptr) {
// The URI scheme indicates that it can't possibly be a playlist, or we have
// a custom handler for the URL, so add it as a raw stream.
//AddAsRawStream();
return Success;
}
#ifdef HAVE_GSTREAMER
preload_func_ = std::bind(&SongLoader::LoadRemote, this);
#endif
return BlockingLoadRequired;
}
void SongLoader::LoadFilenamesBlocking() {
if (preload_func_) {
preload_func_();
}
}
SongLoader::Result SongLoader::LoadLocalPartial(const QString &filename) {
qLog(Debug) << "Fast Loading local file" << filename;
// First check to see if it's a directory - if so we can load all the songs
// inside right away.
if (QFileInfo(filename).isDir()) {
LoadLocalDirectory(filename);
return Success;
}
Song song;
song.InitFromFilePartial(filename);
if (song.is_valid()) songs_ << song;
return Success;
}
SongLoader::Result SongLoader::LoadAudioCD() {
#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER)
CddaSongLoader *cdda_song_loader = new CddaSongLoader;
connect(cdda_song_loader, SIGNAL(SongsDurationLoaded(SongList)), this, SLOT(AudioCDTracksLoadedSlot(SongList)));
connect(cdda_song_loader, SIGNAL(SongsMetadataLoaded(SongList)), this, SLOT(AudioCDTracksTagsLoaded(SongList)));
cdda_song_loader->LoadSongs();
return Success;
#else
return Error;
#endif
}
#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER)
void SongLoader::AudioCDTracksLoadedSlot(const SongList &songs) {
songs_ = songs;
emit AudioCDTracksLoaded();
}
void SongLoader::AudioCDTracksTagsLoaded(const SongList &songs) {
CddaSongLoader *cdda_song_loader = qobject_cast<CddaSongLoader*>(sender());
cdda_song_loader->deleteLater();
songs_ = songs;
emit LoadAudioCDFinished(true);
}
#endif
SongLoader::Result SongLoader::LoadLocal(const QString &filename) {
qLog(Debug) << "Loading local file" << filename;
// Search in the database.
QUrl url = QUrl::fromLocalFile(filename);
CollectionQuery query;
query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
query.AddWhere("filename", url.toEncoded());
if (collection_->ExecQuery(&query) && query.Next()) {
// we may have many results when the file has many sections
do {
Song song;
song.InitFromQuery(query, true);
if (song.is_valid()) {
songs_ << song;
}
} while (query.Next());
return Success;
}
// It's not in the database, load it asynchronously.
preload_func_ =
std::bind(&SongLoader::LoadLocalAsync, this, filename);
return BlockingLoadRequired;
}
void SongLoader::LoadLocalAsync(const QString &filename) {
// First check to see if it's a directory - if so we will load all the songs
// inside right away.
if (QFileInfo(filename).isDir()) {
LoadLocalDirectory(filename);
return;
}
// It's a local file, so check if it looks like a playlist.
// Read the first few bytes.
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) return;
QByteArray data(file.read(PlaylistParser::kMagicSize));
ParserBase *parser = playlist_parser_->ParserForMagic(data);
if (!parser) {
// Check the file extension as well, maybe the magic failed, or it was a
// basic M3U file which is just a plain list of filenames.
parser = playlist_parser_->ParserForExtension(QFileInfo(filename).suffix().toLower());
}
if (parser) {
qLog(Debug) << "Parsing using" << parser->name();
// It's a playlist!
LoadPlaylist(parser, filename);
return;
}
// Check if it's a cue file
QString matching_cue = filename.section('.', 0, -2) + ".cue";
if (QFile::exists(matching_cue)) {
// it's a cue - create virtual tracks
QFile cue(matching_cue);
cue.open(QIODevice::ReadOnly);
SongList song_list = cue_parser_->Load(&cue, matching_cue,
QDir(filename.section('/', 0, -2)));
for (Song song: song_list){
if (song.is_valid()) songs_ << song;
}
return;
}
// Assume it's just a normal file
Song song;
song.InitFromFilePartial(filename);
if (song.is_valid()) songs_ << song;
}
void SongLoader::LoadMetadataBlocking() {
for (int i = 0; i < songs_.size(); i++) {
EffectiveSongLoad(&songs_[i]);
}
}
void SongLoader::EffectiveSongLoad(Song *song) {
if (!song) return;
if (song->filetype() != Song::Type_Unknown) {
// Maybe we loaded the metadata already, for example from a cuesheet.
return;
}
// First, try to get the song from the collection
Song collection_song = collection_->GetSongByUrl(song->url());
if (collection_song.is_valid()) {
*song = collection_song;
} else {
// it's a normal media file
QString filename = song->url().toLocalFile();
TagReaderClient::Instance()->ReadFileBlocking(filename, song);
}
}
void SongLoader::LoadPlaylist(ParserBase *parser, const QString &filename) {
QFile file(filename);
file.open(QIODevice::ReadOnly);
songs_ = parser->Load(&file, filename, QFileInfo(filename).path());
}
static bool CompareSongs(const Song &left, const Song &right) {
// Order by artist, album, disc, track
if (left.artist() < right.artist()) return true;
if (left.artist() > right.artist()) return false;
if (left.album() < right.album()) return true;
if (left.album() > right.album()) return false;
if (left.disc() < right.disc()) return true;
if (left.disc() > right.disc()) return false;
if (left.track() < right.track()) return true;
if (left.track() > right.track()) return false;
return left.url() < right.url();
}
void SongLoader::LoadLocalDirectory(const QString &filename) {
QDirIterator it(filename, QDir::Files | QDir::NoDotAndDotDot | QDir::Readable, QDirIterator::Subdirectories);
while (it.hasNext()) {
LoadLocalPartial(it.next());
}
qStableSort(songs_.begin(), songs_.end(), CompareSongs);
// Load the first song: all songs will be loaded async, but we want the first
// one in our list to be fully loaded, so if the user has the "Start playing
// when adding to playlist" preference behaviour set, it can enjoy the first
// song being played (seek it, have moodbar, etc.)
if (!songs_.isEmpty()) EffectiveSongLoad(&(*songs_.begin()));
}
//void SongLoader::AddAsRawStream() {
// Song song;
// song.set_valid(true);
// song.set_filetype(Song::Type_Stream);
// song.set_url(url_);
// song.set_title(url_.toString());
// songs_ << song;
//}
void SongLoader::Timeout() {
state_ = Finished;
success_ = false;
StopTypefind();
}
void SongLoader::StopTypefind() {
#ifdef HAVE_GSTREAMER
// Destroy the pipeline
if (pipeline_) {
gst_element_set_state(pipeline_.get(), GST_STATE_NULL);
pipeline_.reset();
}
#endif
timeout_timer_->stop();
if (success_ && parser_) {
qLog(Debug) << "Parsing" << url_ << "with" << parser_->name();
// Parse the playlist
QBuffer buf(&buffer_);
buf.open(QIODevice::ReadOnly);
songs_ = parser_->Load(&buf);
}
else if (success_) {
//qLog(Debug) << "Loading" << url_ << "as raw stream";
// It wasn't a playlist - just put the URL in as a stream
//AddAsRawStream();
}
emit LoadRemoteFinished();
}
#ifdef HAVE_GSTREAMER
void SongLoader::LoadRemote() {
qLog(Debug) << "Loading remote file" << url_;
// It's not a local file so we have to fetch it to see what it is. We use
// gstreamer to do this since it handles funky URLs for us (http://, ssh://,
// etc) and also has typefinder plugins.
// First we wait for typefinder to tell us what it is. If it's not text/plain
// or text/uri-list assume it's a song and return success.
// Otherwise wait to get 512 bytes of data and do magic on it - if the magic
// fails then we don't know what it is so return failure.
// If the magic succeeds then we know for sure it's a playlist - so read the
// rest of the file, parse the playlist and return success.
timeout_timer_->start(timeout_);
// Create the pipeline - it gets unreffed if it goes out of scope
std::shared_ptr<GstElement> pipeline(gst_pipeline_new(nullptr), std::bind(&gst_object_unref, _1));
// Create the source element automatically based on the URL
GstElement *source = gst_element_make_from_uri(GST_URI_SRC, url_.toEncoded().constData(), nullptr, nullptr);
if (!source) {
qLog(Warning) << "Couldn't create gstreamer source element for" << url_.toString();
return;
}
// Create the other elements and link them up
GstElement *typefind = gst_element_factory_make("typefind", nullptr);
GstElement *fakesink = gst_element_factory_make("fakesink", nullptr);
gst_bin_add_many(GST_BIN(pipeline.get()), source, typefind, fakesink, nullptr);
gst_element_link_many(source, typefind, fakesink, nullptr);
// Connect callbacks
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline.get()));
CHECKED_GCONNECT(typefind, "have-type", &TypeFound, this);
gst_bus_set_sync_handler(bus, BusCallbackSync, this, NULL);
gst_bus_add_watch(bus, BusCallback, this);
// Add a probe to the sink so we can capture the data if it's a playlist
GstPad *pad = gst_element_get_static_pad(fakesink, "sink");
gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, &DataReady, this, NULL);
gst_object_unref(pad);
QEventLoop loop;
loop.connect(this, SIGNAL(LoadRemoteFinished()), SLOT(quit()));
// Start "playing"
gst_element_set_state(pipeline.get(), GST_STATE_PLAYING);
pipeline_ = pipeline;
// Wait until loading is finished
loop.exec();
}
#endif
#ifdef HAVE_GSTREAMER
void SongLoader::TypeFound(GstElement *, uint, GstCaps *caps, void *self) {
SongLoader *instance = static_cast<SongLoader*>(self);
if (instance->state_ != WaitingForType) return;
// Check the mimetype
instance->mime_type_ = gst_structure_get_name(gst_caps_get_structure(caps, 0));
qLog(Debug) << "Mime type is" << instance->mime_type_;
if (instance->mime_type_ == "text/plain" || instance->mime_type_ == "text/uri-list") {
// Yeah it might be a playlist, let's get some data and have a better look
instance->state_ = WaitingForMagic;
return;
}
// Nope, not a playlist - we're done
instance->StopTypefindAsync(true);
}
#endif
#ifdef HAVE_GSTREAMER
GstPadProbeReturn SongLoader::DataReady(GstPad*, GstPadProbeInfo *info, gpointer self) {
SongLoader *instance = reinterpret_cast<SongLoader*>(self);
if (instance->state_ == Finished)
return GST_PAD_PROBE_OK;
GstBuffer *buffer = gst_pad_probe_info_get_buffer(info);
GstMapInfo map;
gst_buffer_map(buffer, &map, GST_MAP_READ);
// Append the data to the buffer
instance->buffer_.append(reinterpret_cast<const char*>(map.data), map.size);
qLog(Debug) << "Received total" << instance->buffer_.size() << "bytes";
gst_buffer_unmap(buffer, &map);
if (instance->state_ == WaitingForMagic && (instance->buffer_.size() >= PlaylistParser::kMagicSize || !instance->IsPipelinePlaying())) {
// Got enough that we can test the magic
instance->MagicReady();
}
return GST_PAD_PROBE_OK;
}
#endif
#ifdef HAVE_GSTREAMER
gboolean SongLoader::BusCallback(GstBus *, GstMessage *msg, gpointer self) {
SongLoader *instance = reinterpret_cast<SongLoader*>(self);
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR:
instance->ErrorMessageReceived(msg);
break;
default:
break;
}
return TRUE;
}
#endif
#ifdef HAVE_GSTREAMER
GstBusSyncReply SongLoader::BusCallbackSync(GstBus *, GstMessage *msg, gpointer self) {
SongLoader *instance = reinterpret_cast<SongLoader*>(self);
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
instance->EndOfStreamReached();
break;
case GST_MESSAGE_ERROR:
instance->ErrorMessageReceived(msg);
break;
default:
break;
}
return GST_BUS_PASS;
}
#endif
#ifdef HAVE_GSTREAMER
void SongLoader::ErrorMessageReceived(GstMessage *msg) {
if (state_ == Finished) return;
GError *error;
gchar *debugs;
gst_message_parse_error(msg, &error, &debugs);
qLog(Error) << __PRETTY_FUNCTION__ << error->message;
qLog(Error) << __PRETTY_FUNCTION__ << debugs;
QString message_str = error->message;
g_error_free(error);
free(debugs);
if (state_ == WaitingForType && message_str == gst_error_get_message(GST_STREAM_ERROR, GST_STREAM_ERROR_TYPE_NOT_FOUND)) {
// Don't give up - assume it's a playlist and see if one of our parsers can
// read it.
state_ = WaitingForMagic;
return;
}
StopTypefindAsync(false);
}
#endif
#ifdef HAVE_GSTREAMER
void SongLoader::EndOfStreamReached() {
qLog(Debug) << Q_FUNC_INFO << state_;
switch (state_) {
case Finished:
break;
case WaitingForMagic:
// Do the magic on the data we have already
MagicReady();
if (state_ == Finished) break;
// It looks like a playlist, so parse it
// fallthrough
case WaitingForData:
// It's a playlist and we've got all the data - finish and parse it
StopTypefindAsync(true);
break;
case WaitingForType:
StopTypefindAsync(false);
break;
}
}
#endif
#ifdef HAVE_GSTREAMER
void SongLoader::MagicReady() {
qLog(Debug) << Q_FUNC_INFO;
parser_ = playlist_parser_->ParserForMagic(buffer_, mime_type_);
if (!parser_) {
qLog(Warning) << url_.toString() << "is text, but not a recognised playlist";
// It doesn't look like a playlist, so just finish
StopTypefindAsync(false);
return;
}
// We'll get more data and parse the whole thing in EndOfStreamReached
qLog(Debug) << "Magic says" << parser_->name();
if (parser_->name() == "ASX/INI" && url_.scheme() == "http") {
// This is actually a weird MS-WMSP stream. Changing the protocol to MMS from
// HTTP makes it playable.
parser_ = nullptr;
url_.setScheme("mms");
StopTypefindAsync(true);
}
state_ = WaitingForData;
if (!IsPipelinePlaying()) {
EndOfStreamReached();
}
}
#endif
#ifdef HAVE_GSTREAMER
bool SongLoader::IsPipelinePlaying() {
GstState state = GST_STATE_NULL;
GstState pending_state = GST_STATE_NULL;
GstStateChangeReturn ret = gst_element_get_state(pipeline_.get(), &state, &pending_state, GST_SECOND);
if (ret == GST_STATE_CHANGE_ASYNC && pending_state == GST_STATE_PLAYING) {
// We're still on the way to playing
return true;
}
return state == GST_STATE_PLAYING;
}
#endif
#ifdef HAVE_GSTREAMER
void SongLoader::StopTypefindAsync(bool success) {
state_ = Finished;
success_ = success;
metaObject()->invokeMethod(this, "StopTypefind", Qt::QueuedConnection);
}
#endif

152
src/core/songloader.h Normal file
View File

@@ -0,0 +1,152 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SONGLOADER_H
#define SONGLOADER_H
#include "config.h"
#include <functional>
#include <memory>
#ifdef HAVE_GSTREAMER
#include <gst/gst.h>
#endif
#include <QObject>
#include <QThreadPool>
#include <QUrl>
#include "song.h"
#include "core/tagreaderclient.h"
#include "musicbrainz/musicbrainzclient.h"
class CueParser;
class CollectionBackendInterface;
class ParserBase;
class Player;
class PlaylistParser;
#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER)
class CddaSongLoader;
#endif
class SongLoader : public QObject {
Q_OBJECT
public:
SongLoader(CollectionBackendInterface *collection, const Player *player, QObject *parent = nullptr);
~SongLoader();
enum Result {
Success,
Error,
BlockingLoadRequired,
};
static const int kDefaultTimeout;
const QUrl &url() const { return url_; }
const SongList &songs() const { return songs_; }
int timeout() const { return timeout_; }
void set_timeout(int msec) { timeout_ = msec; }
// If Success is returned the songs are fully loaded. If BlockingLoadRequired
// is returned LoadFilenamesBlocking() needs to be called next.
Result Load(const QUrl &url);
// Loads the files with only filenames. When finished, songs() contains a
// complete list of all Song objects, but without metadata. This method is
// blocking, do not call it from the UI thread.
void LoadFilenamesBlocking();
// Completely load songs previously loaded with LoadFilenamesBlocking(). When
// finished, the Song objects in songs() contain metadata now. This method is
// blocking, do not call it from the UI thread.
void LoadMetadataBlocking();
Result LoadAudioCD();
signals:
void AudioCDTracksLoaded();
void LoadAudioCDFinished(bool success);
void LoadRemoteFinished();
private slots:
void Timeout();
void StopTypefind();
#if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER)
void AudioCDTracksLoadedSlot(const SongList &songs);
void AudioCDTracksTagsLoaded(const SongList &songs);
#endif // HAVE_AUDIOCD
private:
enum State { WaitingForType, WaitingForMagic, WaitingForData, Finished };
Result LoadLocal(const QString &filename);
void LoadLocalAsync(const QString &filename);
void EffectiveSongLoad(Song *song);
Result LoadLocalPartial(const QString &filename);
void LoadLocalDirectory(const QString &filename);
void LoadPlaylist(ParserBase *parser, const QString &filename);
#ifdef HAVE_GSTREAMER
void LoadRemote();
// GStreamer callbacks
static void TypeFound(GstElement *typefind, uint probability, GstCaps *caps, void *self);
static GstPadProbeReturn DataReady(GstPad*, GstPadProbeInfo *buf, gpointer self);
static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage*, gpointer);
static gboolean BusCallback(GstBus*, GstMessage*, gpointer);
void StopTypefindAsync(bool success);
void ErrorMessageReceived(GstMessage *msg);
void EndOfStreamReached();
void MagicReady();
bool IsPipelinePlaying();
#endif
private:
static QSet<QString> sRawUriSchemes;
QUrl url_;
SongList songs_;
QTimer *timeout_timer_;
PlaylistParser *playlist_parser_;
CueParser *cue_parser_;
// For async loads
std::function<void()> preload_func_;
int timeout_;
State state_;
bool success_;
ParserBase *parser_;
QString mime_type_;
QByteArray buffer_;
CollectionBackendInterface *collection_;
const Player *player_;
#ifdef HAVE_GSTREAMER
std::shared_ptr<GstElement> pipeline_;
#endif
QThreadPool thread_pool_;
};
#endif // SONGLOADER_H

View File

@@ -0,0 +1,98 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QAbstractItemModel>
#include <QSet>
#include <QStandardItem>
#include "standarditemiconloader.h"
#include "covermanager/albumcoverloader.h"
StandardItemIconLoader::StandardItemIconLoader(AlbumCoverLoader *cover_loader, QObject *parent)
: QObject(parent),
cover_loader_(cover_loader),
model_(nullptr)
{
cover_options_.desired_height_ = 16;
connect(cover_loader_, SIGNAL(ImageLoaded(quint64,QImage)), SLOT(ImageLoaded(quint64,QImage)));
}
void StandardItemIconLoader::SetModel(QAbstractItemModel *model) {
if (model_) {
disconnect(model_, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(RowsAboutToBeRemoved(QModelIndex,int,int)));
}
model_ = model;
connect(model_, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), SLOT(RowsAboutToBeRemoved(QModelIndex,int,int)));
connect(model_, SIGNAL(modelAboutToBeReset()), SLOT(ModelReset()));
}
void StandardItemIconLoader::LoadIcon(const QString& art_automatic, const QString& art_manual, QStandardItem *for_item) {
const quint64 id = cover_loader_->LoadImageAsync(cover_options_, art_automatic, art_manual);
pending_covers_[id] = for_item;
}
void StandardItemIconLoader::LoadIcon(const Song& song, QStandardItem *for_item) {
const quint64 id = cover_loader_->LoadImageAsync(cover_options_, song);
pending_covers_[id] = for_item;
}
void StandardItemIconLoader::RowsAboutToBeRemoved(const QModelIndex& parent, int begin, int end) {
for (QMap<quint64, QStandardItem*>::iterator it = pending_covers_.begin() ; it != pending_covers_.end() ; ) {
const QStandardItem *item = it.value();
const QStandardItem *item_parent = item->parent();
if (item_parent && item_parent->index() == parent && item->index().row() >= begin && item->index().row() <= end) {
cover_loader_->CancelTask(it.key());
it = pending_covers_.erase(it);
}
else {
++ it;
}
}
}
void StandardItemIconLoader::ModelReset() {
cover_loader_->CancelTasks(QSet<quint64>::fromList(pending_covers_.keys()));
pending_covers_.clear();
}
void StandardItemIconLoader::ImageLoaded(quint64 id, const QImage& image) {
QStandardItem *item = pending_covers_.take(id);
if (!item) return;
if (!image.isNull()) {
item->setIcon(QIcon(QPixmap::fromImage(image)));
}
}

View File

@@ -0,0 +1,67 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef STANDARDITEMICONLOADER_H
#define STANDARDITEMICONLOADER_H
#include "config.h"
#include <QMap>
#include <QObject>
#include "covermanager/albumcoverloaderoptions.h"
class AlbumCoverLoader;
class Song;
class QAbstractItemModel;
class QModelIndex;
class QStandardItem;
// Uses an AlbumCoverLoader to asynchronously load and set an icon on a QStandardItem.
class StandardItemIconLoader : public QObject {
Q_OBJECT
public:
StandardItemIconLoader(AlbumCoverLoader *cover_loader, QObject *parent = nullptr);
AlbumCoverLoaderOptions *options() { return &cover_options_; }
void SetModel(QAbstractItemModel *model);
void LoadIcon(const QString &art_automatic, const QString &art_manual,
QStandardItem *for_item);
void LoadIcon(const Song &song, QStandardItem *for_item);
private slots:
void ImageLoaded(quint64 id, const QImage &image);
void RowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end);
void ModelReset();
private:
AlbumCoverLoader *cover_loader_;
AlbumCoverLoaderOptions cover_options_;
QAbstractItemModel *model_;
QMap<quint64, QStandardItem*> pending_covers_;
};
#endif // STANDARDITEMICONLOADER_H

View File

@@ -0,0 +1,110 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "stylesheetloader.h"
#include "core/logging.h"
#include <QFile>
#include <QEvent>
StyleSheetLoader::StyleSheetLoader(QObject *parent) : QObject(parent) {}
void StyleSheetLoader::SetStyleSheet(QWidget *widget, const QString &filename) {
filenames_[widget] = filename;
widget->installEventFilter(this);
UpdateStyleSheet(widget);
}
void StyleSheetLoader::UpdateStyleSheet(QWidget *widget) {
QString filename(filenames_[widget]);
// Load the file
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
qLog(Warning) << "error opening" << filename;
return;
}
QString contents(file.readAll());
// Replace %palette-role with actual colours
QPalette p(widget->palette());
QColor alt = p.color(QPalette::AlternateBase);
alt.setAlpha(50);
contents.replace("%palette-alternate-base", QString("rgba(%1,%2,%3,%4%)")
.arg(alt.red())
.arg(alt.green())
.arg(alt.blue())
.arg(alt.alpha()));
ReplaceColor(&contents, "Window", p, QPalette::Window);
ReplaceColor(&contents, "Background", p, QPalette::Background);
ReplaceColor(&contents, "WindowText", p, QPalette::WindowText);
ReplaceColor(&contents, "Foreground", p, QPalette::Foreground);
ReplaceColor(&contents, "Base", p, QPalette::Base);
ReplaceColor(&contents, "AlternateBase", p, QPalette::AlternateBase);
ReplaceColor(&contents, "ToolTipBase", p, QPalette::ToolTipBase);
ReplaceColor(&contents, "ToolTipText", p, QPalette::ToolTipText);
ReplaceColor(&contents, "Text", p, QPalette::Text);
ReplaceColor(&contents, "Button", p, QPalette::Button);
ReplaceColor(&contents, "ButtonText", p, QPalette::ButtonText);
ReplaceColor(&contents, "BrightText", p, QPalette::BrightText);
ReplaceColor(&contents, "Light", p, QPalette::Light);
ReplaceColor(&contents, "Midlight", p, QPalette::Midlight);
ReplaceColor(&contents, "Dark", p, QPalette::Dark);
ReplaceColor(&contents, "Mid", p, QPalette::Mid);
ReplaceColor(&contents, "Shadow", p, QPalette::Shadow);
ReplaceColor(&contents, "Highlight", p, QPalette::Highlight);
ReplaceColor(&contents, "HighlightedText", p, QPalette::HighlightedText);
ReplaceColor(&contents, "Link", p, QPalette::Link);
ReplaceColor(&contents, "LinkVisited", p, QPalette::LinkVisited);
#ifdef Q_OS_DARWIN
contents.replace("darwin", "*");
#endif
widget->setStyleSheet(contents);
}
void StyleSheetLoader::ReplaceColor(QString *css, const QString &name, const QPalette &palette, QPalette::ColorRole role) const {
css->replace("%palette-" + name + "-lighter", palette.color(role).lighter().name(), Qt::CaseInsensitive);
css->replace("%palette-" + name + "-darker", palette.color(role).darker().name(), Qt::CaseInsensitive);
css->replace("%palette-" + name, palette.color(role).name(), Qt::CaseInsensitive);
}
bool StyleSheetLoader::eventFilter(QObject *obj, QEvent *event) {
if (event->type() != QEvent::PaletteChange) return false;
QWidget *widget = qobject_cast<QWidget*>(obj);
if (!widget || !filenames_.contains(widget)) return false;
UpdateStyleSheet(widget);
return false;
}

View File

@@ -0,0 +1,53 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef STYLESHEETLOADER_H
#define STYLESHEETLOADER_H
#include "config.h"
#include <QString>
#include <QPalette>
#include <QWidget>
#include <QMap>
class StyleSheetLoader : public QObject {
public:
explicit StyleSheetLoader(QObject *parent = nullptr);
// Sets the given stylesheet on the given widget.
// If the stylesheet contains strings like %palette-[role], these get replaced
// with actual palette colours.
// The stylesheet is reloaded when the widget's palette changes.
void SetStyleSheet(QWidget *widget, const QString& filename);
protected:
bool eventFilter(QObject *obj, QEvent *event);
private:
void UpdateStyleSheet(QWidget *widget);
void ReplaceColor(QString *css, const QString& name, const QPalette& palette, QPalette::ColorRole role) const;
private:
QMap<QWidget *, QString> filenames_;
};
#endif // STYLESHEETLOADER_H

111
src/core/systemtrayicon.cpp Normal file
View File

@@ -0,0 +1,111 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <cmath>
#include <QApplication>
#include <QEvent>
#include <QWheelEvent>
#include <QPainter>
#include <QWidget>
#include <QtDebug>
#include "macsystemtrayicon.h"
#include "qtsystemtrayicon.h"
#include "systemtrayicon.h"
SystemTrayIcon::SystemTrayIcon(QObject *parent)
: QObject(parent),
percentage_(0),
playing_icon_(":/pictures/tiny-play.png"),
paused_icon_(":/pictures/tiny-pause.png")
{
}
QPixmap SystemTrayIcon::CreateIcon(const QPixmap &icon, const QPixmap &grey_icon) {
QRect rect(icon.rect());
// The angle of the line that's used to cover the icon.
// Centered on rect.topRight()
double angle = double(100 - song_progress()) / 100.0 * M_PI_2 + M_PI;
double length = sqrt(pow(rect.width(), 2.0) + pow(rect.height(), 2.0));
QPolygon mask;
mask << rect.topRight();
mask << rect.topRight() + QPoint(length * sin(angle), -length * cos(angle));
if (song_progress() > 50) mask << rect.bottomLeft();
mask << rect.topLeft();
mask << rect.topRight();
QPixmap ret(icon);
QPainter p(&ret);
// Draw the grey bit
//p.setClipRegion(mask);
//p.drawPixmap(0, 0, grey_icon);
//p.setClipping(false);
// Draw the playing or paused icon in the top-right
if (!current_state_icon().isNull()) {
int height = rect.height() / 2;
QPixmap scaled(current_state_icon().scaledToHeight(height, Qt::SmoothTransformation));
QRect state_rect(rect.width() - scaled.width(), 0, scaled.width(), scaled.height());
p.drawPixmap(state_rect, scaled);
}
p.end();
return ret;
}
void SystemTrayIcon::SetProgress(int percentage) {
percentage_ = percentage;
UpdateIcon();
}
void SystemTrayIcon::SetPaused() {
current_state_icon_ = paused_icon_;
UpdateIcon();
}
void SystemTrayIcon::SetPlaying(bool enable_play_pause) {
current_state_icon_ = playing_icon_;
UpdateIcon();
}
void SystemTrayIcon::SetStopped() {
current_state_icon_ = QPixmap();
UpdateIcon();
}
SystemTrayIcon* SystemTrayIcon::CreateSystemTrayIcon(QObject *parent) {
#ifdef Q_OS_DARWIN
return new MacSystemTrayIcon(parent);
#else
return new QtSystemTrayIcon(parent);
#endif
}

85
src/core/systemtrayicon.h Normal file
View File

@@ -0,0 +1,85 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SYSTEMTRAYICON_H
#define SYSTEMTRAYICON_H
#include "config.h"
#include <QObject>
#include <QPixmap>
class QAction;
class Song;
class SystemTrayIcon : public QObject {
Q_OBJECT
public:
SystemTrayIcon(QObject *parent = nullptr);
// Called once to create the icon's context menu
virtual void SetupMenu(QAction *previous, QAction *play, QAction *stop, QAction *stop_after, QAction *next, QAction *mute, QAction *quit) = 0;
virtual bool IsVisible() const { return true; }
virtual void SetVisible(bool visible) {}
// Called by the OSD
virtual void ShowPopup(const QString &summary, const QString &message, int timeout) {}
/**
* If this get's invoked with image_path equal to nullptr, the tooltip should
* still be shown - just without the cover art.
*/
virtual void SetNowPlaying(const Song &song, const QString &image_path) {}
virtual void ClearNowPlaying() {}
static SystemTrayIcon *CreateSystemTrayIcon(QObject *parent = nullptr);
public slots:
void SetProgress(int percentage);
virtual void SetPaused();
virtual void SetPlaying(bool enable_play_pause = false);
virtual void SetStopped();
virtual void MuteButtonStateChanged(bool value) {}
signals:
void ChangeVolume(int delta);
void SeekForward();
void SeekBackward();
void NextTrack();
void PreviousTrack();
void ShowHide();
void PlayPause();
protected:
virtual void UpdateIcon() = 0;
QPixmap CreateIcon(const QPixmap &icon, const QPixmap &grey_icon);
int song_progress() const { return percentage_; }
QPixmap current_state_icon() const { return current_state_icon_; }
private:
int percentage_;
QPixmap playing_icon_;
QPixmap paused_icon_;
QPixmap current_state_icon_;
};
#endif // SYSTEMTRAYICON_H

View File

@@ -0,0 +1,155 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "tagreaderclient.h"
#include <QCoreApplication>
#include <QFile>
#include <QProcess>
#include <QTcpServer>
#include <QThread>
#include <QUrl>
const char *TagReaderClient::kWorkerExecutableName = "strawberry-tagreader";
TagReaderClient *TagReaderClient::sInstance = nullptr;
TagReaderClient::TagReaderClient(QObject *parent) : QObject(parent), worker_pool_(new WorkerPool<HandlerType>(this)) {
sInstance = this;
worker_pool_->SetExecutableName(kWorkerExecutableName);
worker_pool_->SetWorkerCount(QThread::idealThreadCount());
connect(worker_pool_, SIGNAL(WorkerFailedToStart()), SLOT(WorkerFailedToStart()));
}
void TagReaderClient::Start() { worker_pool_->Start(); }
void TagReaderClient::WorkerFailedToStart() {
qLog(Error) << "The" << kWorkerExecutableName << "executable was not found in the current directory or on the PATH. Strawberry will not be able to read music file tags without it.";
}
TagReaderReply *TagReaderClient::ReadFile(const QString &filename) {
pb::tagreader::Message message;
pb::tagreader::ReadFileRequest *req = message.mutable_read_file_request();
req->set_filename(DataCommaSizeFromQString(filename));
return worker_pool_->SendMessageWithReply(&message);
}
TagReaderReply *TagReaderClient::SaveFile(const QString &filename, const Song &metadata) {
pb::tagreader::Message message;
pb::tagreader::SaveFileRequest *req = message.mutable_save_file_request();
req->set_filename(DataCommaSizeFromQString(filename));
metadata.ToProtobuf(req->mutable_metadata());
return worker_pool_->SendMessageWithReply(&message);
}
TagReaderReply *TagReaderClient::IsMediaFile(const QString &filename) {
pb::tagreader::Message message;
pb::tagreader::IsMediaFileRequest *req = message.mutable_is_media_file_request();
req->set_filename(DataCommaSizeFromQString(filename));
return worker_pool_->SendMessageWithReply(&message);
}
TagReaderReply *TagReaderClient::LoadEmbeddedArt(const QString &filename) {
pb::tagreader::Message message;
pb::tagreader::LoadEmbeddedArtRequest *req = message.mutable_load_embedded_art_request();
req->set_filename(DataCommaSizeFromQString(filename));
return worker_pool_->SendMessageWithReply(&message);
}
void TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) {
Q_ASSERT(QThread::currentThread() != thread());
TagReaderReply *reply = ReadFile(filename);
if (reply->WaitForFinished()) {
song->InitFromProtobuf(reply->message().read_file_response().metadata());
}
reply->deleteLater();
}
bool TagReaderClient::SaveFileBlocking(const QString &filename, const Song &metadata) {
Q_ASSERT(QThread::currentThread() != thread());
bool ret = false;
TagReaderReply *reply = SaveFile(filename, metadata);
if (reply->WaitForFinished()) {
ret = reply->message().save_file_response().success();
}
reply->deleteLater();
return ret;
}
bool TagReaderClient::IsMediaFileBlocking(const QString &filename) {
Q_ASSERT(QThread::currentThread() != thread());
bool ret = false;
TagReaderReply *reply = IsMediaFile(filename);
if (reply->WaitForFinished()) {
ret = reply->message().is_media_file_response().success();
}
reply->deleteLater();
return ret;
}
QImage TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename) {
Q_ASSERT(QThread::currentThread() != thread());
QImage ret;
TagReaderReply *reply = LoadEmbeddedArt(filename);
if (reply->WaitForFinished()) {
const std::string &data_str = reply->message().load_embedded_art_response().data();
ret.loadFromData(QByteArray(data_str.data(), data_str.size()));
}
reply->deleteLater();
return ret;
}

View File

@@ -0,0 +1,79 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2011, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERCLIENT_H
#define TAGREADERCLIENT_H
#include "config.h"
#include "song.h"
#include "tagreadermessages.pb.h"
#include "core/messagehandler.h"
#include "core/workerpool.h"
#include <QStringList>
class QLocalServer;
class QProcess;
class TagReaderClient : public QObject {
Q_OBJECT
public:
explicit TagReaderClient(QObject *parent = nullptr);
typedef AbstractMessageHandler<pb::tagreader::Message> HandlerType;
typedef HandlerType::ReplyType ReplyType;
static const char *kWorkerExecutableName;
void Start();
ReplyType *ReadFile(const QString &filename);
ReplyType *SaveFile(const QString &filename, const Song &metadata);
ReplyType *IsMediaFile(const QString &filename);
ReplyType *LoadEmbeddedArt(const QString &filename);
// Convenience functions that call the above functions and wait for a
// response. These block the calling thread with a semaphore, and must NOT
// be called from the TagReaderClient's thread.
void ReadFileBlocking(const QString &filename, Song *song);
bool SaveFileBlocking(const QString &filename, const Song &metadata);
bool IsMediaFileBlocking(const QString &filename);
QImage LoadEmbeddedArtBlocking(const QString &filename);
// TODO: Make this not a singleton
static TagReaderClient *Instance() { return sInstance; }
public slots:
private slots:
void WorkerFailedToStart();
private:
static TagReaderClient *sInstance;
WorkerPool<HandlerType> *worker_pool_;
QList<pb::tagreader::Message> message_queue_;
};
typedef TagReaderClient::ReplyType TagReaderReply;
#endif // TAGREADERCLIENT_H

138
src/core/taskmanager.cpp Normal file
View File

@@ -0,0 +1,138 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "taskmanager.h"
TaskManager::TaskManager(QObject *parent) : QObject(parent), next_task_id_(1) {}
int TaskManager::StartTask(const QString &name) {
Task t;
t.name = name;
t.progress = 0;
t.progress_max = 0;
t.blocks_collection_scans = false;
{
QMutexLocker l(&mutex_);
t.id = next_task_id_++;
tasks_[t.id] = t;
}
emit TasksChanged();
return t.id;
}
QList<TaskManager::Task> TaskManager::GetTasks() {
QList<TaskManager::Task> ret;
{
QMutexLocker l(&mutex_);
ret = tasks_.values();
}
return ret;
}
void TaskManager::SetTaskBlocksCollectionScans(int id) {
{
QMutexLocker l(&mutex_);
if (!tasks_.contains(id)) return;
Task &t = tasks_[id];
t.blocks_collection_scans = true;
}
emit TasksChanged();
emit PauseCollectionWatchers();
}
void TaskManager::SetTaskProgress(int id, int progress, int max) {
{
QMutexLocker l(&mutex_);
if (!tasks_.contains(id)) return;
Task &t = tasks_[id];
t.progress = progress;
if (max) t.progress_max = max;
}
emit TasksChanged();
}
void TaskManager::IncreaseTaskProgress(int id, int progress, int max) {
{
QMutexLocker l(&mutex_);
if (!tasks_.contains(id)) return;
Task &t = tasks_[id];
t.progress += progress;
if (max) t.progress_max = max;
}
emit TasksChanged();
}
void TaskManager::SetTaskFinished(int id) {
bool resume_collection_watchers = false;
{
QMutexLocker l(&mutex_);
if (!tasks_.contains(id)) return;
if (tasks_[id].blocks_collection_scans) {
resume_collection_watchers = true;
for (const Task &task : tasks_.values()) {
if (task.id != id && task.blocks_collection_scans) {
resume_collection_watchers = false;
break;
}
}
}
tasks_.remove(id);
}
emit TasksChanged();
if (resume_collection_watchers) emit ResumeCollectionWatchers();
}
int TaskManager::GetTaskProgress(int id) {
{
QMutexLocker l(&mutex_);
if (!tasks_.contains(id)) return 0;
return tasks_[id].progress;
}
}

82
src/core/taskmanager.h Normal file
View File

@@ -0,0 +1,82 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TASKMANAGER_H
#define TASKMANAGER_H
#include "config.h"
#include <QMap>
#include <QMutex>
#include <QObject>
class TaskManager : public QObject {
Q_OBJECT
public:
explicit TaskManager(QObject *parent = nullptr);
struct Task {
int id;
QString name;
int progress;
int progress_max;
bool blocks_collection_scans;
};
class ScopedTask {
public:
ScopedTask(const int task_id, TaskManager *task_manager) : task_id_(task_id), task_manager_(task_manager) {}
~ScopedTask() { task_manager_->SetTaskFinished(task_id_); }
private:
const int task_id_;
TaskManager *task_manager_;
Q_DISABLE_COPY(ScopedTask);
};
// Everything here is thread safe
QList<Task> GetTasks();
int StartTask(const QString &name);
void SetTaskBlocksCollectionScans(int id);
void SetTaskProgress(int id, int progress, int max = 0);
void IncreaseTaskProgress(int id, int progress, int max = 0);
void SetTaskFinished(int id);
int GetTaskProgress(int id);
signals:
void TasksChanged();
void PauseCollectionWatchers();
void ResumeCollectionWatchers();
private:
QMutex mutex_;
QMap<int, Task> tasks_;
int next_task_id_;
Q_DISABLE_COPY(TaskManager);
};
#endif // TASKMANAGER_H

25
src/core/thread.cpp Normal file
View File

@@ -0,0 +1,25 @@
/* This file is part of Clementine.
Copyright 2015, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "thread.h"
void Thread::run() {
Utilities::SetThreadIOPriority(io_priority_);
QThread::run();
}

41
src/core/thread.h Normal file
View File

@@ -0,0 +1,41 @@
/* This file is part of Clementine.
Copyright 2015, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CORE_THREAD_H_
#define CORE_THREAD_H_
#include "config.h"
#include <QThread>
#include "core/utilities.h"
// Improve QThread by adding a SetIoPriority function
class Thread : public QThread {
public:
Thread(QObject* parent = nullptr)
: QThread(parent), io_priority_(Utilities::IOPRIO_CLASS_NONE) {}
void SetIoPriority(Utilities::IoPriority priority) {
io_priority_ = priority;
}
virtual void run() override;
private:
Utilities::IoPriority io_priority_;
};
#endif // CORE_THREAD_H_

32
src/core/timeconstants.h Normal file
View File

@@ -0,0 +1,32 @@
/* This file was part of Clementine.
Copyright 2011, David Sansome <me@davidsansome.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef TIMECONSTANTS_H
#define TIMECONSTANTS_H
#include <QtGlobal>
// Use these to convert between time units
const qint64 kMsecPerSec = 1000ll;
const qint64 kUsecPerMsec = 1000ll;
const qint64 kUsecPerSec = 1000000ll;
const qint64 kNsecPerUsec = 1000ll;
const qint64 kNsecPerMsec = 1000000ll;
const qint64 kNsecPerSec = 1000000000ll;
const qint64 kSecsPerDay = 24 * 60 * 60;
#endif // TIMECONSTANTS_H

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