Merge branch 'master' into gstreamer-1.2

This commit is contained in:
David Sansome 2014-01-25 00:24:31 +11:00
commit 5ac83bd463
227 changed files with 23151 additions and 140649 deletions

3
.gitignore vendored
View File

@ -8,10 +8,10 @@
*.dll
*.exe
*.pyd
build/
3rdparty/libprojectm/config.inp
3rdparty/libprojectm/libprojectM.pc
CMakeLists.txt.user
bin/
dist/Info.plist
dist/clementine.spec
dist/maketarball.sh
@ -31,6 +31,7 @@ dist/mergetranslations/bzr/
dist/mergetranslations/svn/
dist/mergetranslations/www-bzr/
dist/mergetranslations/www-svn/
dist/windows/clementine-portable.nsi
debian/rules
debian/changelog
*-stamp

View File

@ -28,6 +28,7 @@
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <limits>
#include <iterator>

View File

@ -21,11 +21,19 @@ qt4_wrap_cpp(SQLITE-SOURCES-MOC ${SQLITE-MOC-HEADERS})
include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
add_definitions(-DQT_PLUGIN -DQT_NO_DEBUG)
add_definitions(-DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS)
add_library(sqlite STATIC sqlite3.c)
set_target_properties(sqlite PROPERTIES COMPILE_FLAGS
"-Wno-pointer-to-int-cast -Wno-int-to-pointer-cast")
find_path(SQLITE_INCLUDE_DIRS sqlite3.h)
find_library(SQLITE_LIBRARIES sqlite3)
if (SQLITE_INCLUDE_DIRS AND SQLITE_LIBRARIES)
set(SQLITE_FOUND true)
endif()
if (NOT SQLITE_FOUND)
message(SEND_ERROR "Could not find sqlite3")
endif()
include_directories(${SQLITE_INCLUDE_DIRS})
add_library(qsqlite STATIC
${SQLITE-SOURCES}
@ -33,6 +41,6 @@ add_library(qsqlite STATIC
${SQLITE-WIN32-RESOURCES}
)
target_link_libraries(qsqlite
sqlite
${SQLITE_LIBRARIES}
${QT_LIBRARIES}
)

110643
3rdparty/qsqlite/sqlite3.c vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -183,31 +183,39 @@ static const unsigned int KeyTbl[] = {
// numeric and function keypad keys
XK_KP_Space, Qt::Key_Space,
XK_KP_Tab, Qt::Key_Tab,
XK_KP_Enter, Qt::Key_Enter,
//XK_KP_F1, Qt::Key_F1,
//XK_KP_F2, Qt::Key_F2,
//XK_KP_F3, Qt::Key_F3,
//XK_KP_F4, Qt::Key_F4,
XK_KP_Home, Qt::Key_Home,
XK_KP_Left, Qt::Key_Left,
XK_KP_Up, Qt::Key_Up,
XK_KP_Right, Qt::Key_Right,
XK_KP_Down, Qt::Key_Down,
XK_KP_Prior, Qt::Key_PageUp,
XK_KP_Next, Qt::Key_PageDown,
XK_KP_End, Qt::Key_End,
XK_KP_Begin, Qt::Key_Clear,
XK_KP_Insert, Qt::Key_Insert,
XK_KP_Delete, Qt::Key_Delete,
XK_KP_Equal, Qt::Key_Equal,
XK_KP_Multiply, Qt::Key_Asterisk,
XK_KP_Add, Qt::Key_Plus,
XK_KP_Separator, Qt::Key_Comma,
XK_KP_Subtract, Qt::Key_Minus,
XK_KP_Decimal, Qt::Key_Period,
XK_KP_Divide, Qt::Key_Slash,
// special and additional keys
XK_Clear, Qt::Key_Clear,
XK_Delete, Qt::Key_Delete,
XK_space, Qt::Key_Space,
XK_exclam, Qt::Key_Exclam,
XK_quotedbl, Qt::Key_QuoteDbl,
XK_numbersign, Qt::Key_NumberSign,
XK_dollar, Qt::Key_Dollar,
XK_percent, Qt::Key_Percent,
XK_ampersand, Qt::Key_Ampersand,
XK_apostrophe, Qt::Key_Apostrophe,
XK_parenleft, Qt::Key_ParenLeft,
XK_parenright, Qt::Key_ParenRight,
XK_asterisk, Qt::Key_Asterisk,
XK_plus, Qt::Key_Plus,
XK_comma, Qt::Key_Comma,
XK_minus, Qt::Key_Minus,
XK_period, Qt::Key_Period,
XK_slash, Qt::Key_Slash,
XK_colon, Qt::Key_Colon,
XK_semicolon, Qt::Key_Semicolon,
XK_less, Qt::Key_Less,
XK_equal, Qt::Key_Equal,
XK_greater, Qt::Key_Greater,
XK_question, Qt::Key_Question,
XK_bracketleft, Qt::Key_BracketLeft,
XK_backslash, Qt::Key_Backslash,
XK_bracketright, Qt::Key_BracketRight,
XK_asciicircum, Qt::Key_AsciiCircum,
XK_underscore, Qt::Key_Underscore,
// International input method support keys

View File

@ -413,18 +413,9 @@ void FLAC::File::scan()
d->blocks.append(new UnknownMetadataBlock(blockType, d->streamInfoData));
nextBlockOffset += length + 4;
int blockCount = 0;
// Search through the remaining metadata
while(!isLastBlock) {
if (++blockCount > 1024) {
debug("FLAC::File::scan() -- FLAC stream has more than 1024 metadata "
"blocks, probably corrupt.");
setValid(false);
return;
}
header = readBlock(4);
blockType = header[0] & 0x7f;
isLastBlock = (header[0] & 0x80) != 0;

View File

@ -34,7 +34,7 @@ using namespace TagLib;
class MP4::Properties::PropertiesPrivate
{
public:
PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), channels(0), bitsPerSample(0), encrypted(false) {}
PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), channels(0), bitsPerSample(0), encrypted(false), codec(MP4::Properties::Unknown) {}
int length;
int bitrate;
@ -42,6 +42,7 @@ public:
int channels;
int bitsPerSample;
bool encrypted;
Codec codec;
};
MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style)
@ -114,6 +115,7 @@ MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style)
file->seek(atom->offset);
data = file->readBlock(atom->length);
if(data.mid(20, 4) == "mp4a") {
d->codec = AAC;
d->channels = data.toShort(40U);
d->bitsPerSample = data.toShort(42U);
d->sampleRate = data.toUInt(46U);
@ -135,10 +137,11 @@ MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style)
}
else if (data.mid(20, 4) == "alac") {
if (atom->length == 88 && data.mid(56, 4) == "alac") {
d->codec = ALAC;
d->bitsPerSample = data.at(69);
d->channels = data.at(73);
d->bitrate = data.toUInt(80U) / 1000;
d->sampleRate = data.toUInt(84U);
d->channels = data.at(73);
d->bitrate = data.toUInt(80U) / 1000;
d->sampleRate = data.toUInt(84U);
}
}
@ -189,3 +192,8 @@ MP4::Properties::isEncrypted() const
return d->encrypted;
}
MP4::Properties::Codec MP4::Properties::codec() const
{
return d->codec;
}

View File

@ -40,6 +40,12 @@ namespace TagLib {
class TAGLIB_EXPORT Properties : public AudioProperties
{
public:
enum Codec {
Unknown = 0,
AAC,
ALAC
};
Properties(File *file, Atoms *atoms, ReadStyle style = Average);
virtual ~Properties();
@ -50,6 +56,9 @@ namespace TagLib {
virtual int bitsPerSample() const;
bool isEncrypted() const;
//! Audio codec used in the MP4 file
Codec codec() const;
private:
class PropertiesPrivate;
PropertiesPrivate *d;

View File

@ -392,7 +392,7 @@ static const char *frameTranslation[][2] = {
//{ "USLT", "LYRICS" }, handled specially
};
static const TagLib::uint txxxFrameTranslationSize = 7;
static const TagLib::uint txxxFrameTranslationSize = 8;
static const char *txxxFrameTranslation[][2] = {
{ "MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" },
{ "MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" },

View File

@ -28,6 +28,7 @@
#include <tstring.h>
#include "rifffile.h"
#include <algorithm>
#include <vector>
using namespace TagLib;
@ -272,7 +273,7 @@ void RIFF::File::read()
break;
}
if(tell() + chunkSize > uint(length())) {
if(static_cast<ulonglong>(tell()) + chunkSize > static_cast<ulonglong>(length())) {
debug("RIFF::File::read() -- Chunk '" + chunkName + "' has invalid size (larger than the file size)");
setValid(false);
break;

View File

@ -185,6 +185,11 @@ bool RIFF::Info::Tag::isEmpty() const
return d->fieldListMap.isEmpty();
}
FieldListMap RIFF::Info::Tag::fieldListMap() const
{
return d->fieldListMap;
}
String RIFF::Info::Tag::fieldText(const ByteVector &id) const
{
if(d->fieldListMap.contains(id))

View File

@ -120,6 +120,18 @@ namespace TagLib {
virtual bool isEmpty() const;
/*!
* Returns a copy of the internal fields of the tag. The returned map directly
* reflects the contents of the "INFO" chunk.
*
* \note Modifying this map does not affect the tag's internal data.
* Use setFieldText() and removeField() instead.
*
* \see setFieldText()
* \see removeField()
*/
FieldListMap fieldListMap() const;
/*
* Gets the value of the field with the ID \a id.
*/

View File

@ -29,8 +29,8 @@
#include "taglib_config.h"
#define TAGLIB_MAJOR_VERSION 1
#define TAGLIB_MINOR_VERSION 8
#define TAGLIB_PATCH_VERSION 0
#define TAGLIB_MINOR_VERSION 9
#define TAGLIB_PATCH_VERSION 1
#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 1))
#define TAGLIB_IGNORE_MISSING_DESTRUCTOR _Pragma("GCC diagnostic ignored \"-Wnon-virtual-dtor\"")

View File

@ -27,6 +27,7 @@
#include <config.h>
#endif
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstring>
@ -208,6 +209,9 @@ T toNumber(const ByteVector &v, size_t offset, size_t length, bool mostSignifica
template <class T>
T toNumber(const ByteVector &v, size_t offset, bool mostSignificantByteFirst)
{
static const bool isBigEndian = (Utils::SystemByteOrder == Utils::BigEndian);
const bool swap = (mostSignificantByteFirst != isBigEndian);
if(offset + sizeof(T) > v.size())
return toNumber<T>(v, offset, v.size() - offset, mostSignificantByteFirst);
@ -215,13 +219,8 @@ T toNumber(const ByteVector &v, size_t offset, bool mostSignificantByteFirst)
T tmp;
::memcpy(&tmp, v.data() + offset, sizeof(T));
#if SYSTEM_BYTEORDER == 1
const bool swap = mostSignificantByteFirst;
#else
const bool swap = !mostSignificantByteFirst;
#endif
if(swap)
return byteSwap(tmp);
return Utils::byteSwap(tmp);
else
return tmp;
}
@ -229,17 +228,13 @@ T toNumber(const ByteVector &v, size_t offset, bool mostSignificantByteFirst)
template <class T>
ByteVector fromNumber(T value, bool mostSignificantByteFirst)
{
const size_t size = sizeof(T);
static const bool isBigEndian = (Utils::SystemByteOrder == Utils::BigEndian);
const bool swap = (mostSignificantByteFirst != isBigEndian);
#if SYSTEM_BYTEORDER == 1
const bool swap = mostSignificantByteFirst;
#else
const bool swap = !mostSignificantByteFirst;
#endif
if(swap)
value = byteSwap(value);
if(swap)
value = Utils::byteSwap(value);
return ByteVector(reinterpret_cast<const char *>(&value), size);
return ByteVector(reinterpret_cast<const char *>(&value), sizeof(T));
}
class DataPrivate : public RefCounter

View File

@ -39,7 +39,8 @@ namespace TagLib {
// A base for the generic and specialized private class types. New
// non-templatized members should be added here.
class ListPrivateBase : public RefCounter
// BIC change to RefCounter
class ListPrivateBase : public RefCounterOld
{
public:
ListPrivateBase() : autoDelete(false) {}

View File

@ -31,17 +31,18 @@ namespace TagLib {
// public members
////////////////////////////////////////////////////////////////////////////////
// BIC change to RefCounter
template <class Key, class T>
template <class KeyP, class TP>
class Map<Key, T>::MapPrivate : public RefCounter
class Map<Key, T>::MapPrivate : public RefCounterOld
{
public:
MapPrivate() : RefCounter() {}
MapPrivate() : RefCounterOld() {}
#ifdef WANT_CLASS_INSTANTIATION_OF_MAP
MapPrivate(const std::map<class KeyP, class TP>& m) : RefCounter(), map(m) {}
MapPrivate(const std::map<class KeyP, class TP>& m) : RefCounterOld(), map(m) {}
std::map<class KeyP, class TP> map;
#else
MapPrivate(const std::map<KeyP, TP>& m) : RefCounter(), map(m) {}
MapPrivate(const std::map<KeyP, TP>& m) : RefCounterOld(), map(m) {}
std::map<KeyP, TP> map;
#endif
};

View File

@ -29,6 +29,23 @@
#include "taglib_export.h"
#include "taglib.h"
#ifdef __APPLE__
# include <libkern/OSAtomic.h>
# define TAGLIB_ATOMIC_MAC
#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__)
# define NOMINMAX
# include <windows.h>
# define TAGLIB_ATOMIC_WIN
#elif defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 401) \
&& (defined(__i386__) || defined(__i486__) || defined(__i586__) || \
defined(__i686__) || defined(__x86_64) || defined(__ia64)) \
&& !defined(__INTEL_COMPILER)
# define TAGLIB_ATOMIC_GCC
#elif defined(__ia64) && defined(__INTEL_COMPILER)
# include <ia64intrin.h>
# define TAGLIB_ATOMIC_GCC
#endif
#ifndef DO_NOT_DOCUMENT // Tell Doxygen to skip this class.
/*!
* \internal
@ -38,6 +55,7 @@
*/
namespace TagLib
{
class TAGLIB_EXPORT RefCounter
{
public:
@ -52,7 +70,42 @@ namespace TagLib
class RefCounterPrivate;
RefCounterPrivate *d;
};
// BIC this old class is needed by tlist.tcc and tmap.tcc
class RefCounterOld
{
public:
RefCounterOld() : refCount(1) {}
#ifdef TAGLIB_ATOMIC_MAC
void ref() { OSAtomicIncrement32Barrier(const_cast<int32_t*>(&refCount)); }
bool deref() { return ! OSAtomicDecrement32Barrier(const_cast<int32_t*>(&refCount)); }
int32_t count() { return refCount; }
private:
volatile int32_t refCount;
#elif defined(TAGLIB_ATOMIC_WIN)
void ref() { InterlockedIncrement(&refCount); }
bool deref() { return ! InterlockedDecrement(&refCount); }
long count() { return refCount; }
private:
volatile long refCount;
#elif defined(TAGLIB_ATOMIC_GCC)
void ref() { __sync_add_and_fetch(&refCount, 1); }
bool deref() { return ! __sync_sub_and_fetch(&refCount, 1); }
int count() { return refCount; }
private:
volatile int refCount;
#else
void ref() { refCount++; }
bool deref() { return ! --refCount; }
int count() { return refCount; }
private:
uint refCount;
#endif
};
}
#endif // DO_NOT_DOCUMENT
#endif

View File

@ -268,6 +268,9 @@ String::String(const ByteVector &v, Type t)
copyFromUTF8(v.data(), v.size());
else
copyFromUTF16(v.data(), v.size(), t);
// If we hit a null in the ByteVector, shrink the string again.
d->data.resize(::wcslen(d->data.c_str()));
}
////////////////////////////////////////////////////////////////////////////////
@ -800,7 +803,7 @@ void String::copyFromUTF16(const wchar_t *s, size_t length, Type t)
if(swap) {
for(size_t i = 0; i < length; ++i)
d->data[i] = byteSwap(static_cast<ushort>(s[i]));
d->data[i] = Utils::byteSwap(static_cast<ushort>(s[i]));
}
}
@ -839,15 +842,8 @@ void String::copyFromUTF16(const char *s, size_t length, Type t)
}
}
#if SYSTEM_BYTEORDER == 1
const String::Type String::WCharByteOrder = String::UTF16LE;
#else
const String::Type String::WCharByteOrder = String::UTF16BE;
#endif
const String::Type String::WCharByteOrder
= (Utils::SystemByteOrder == Utils::BigEndian) ? String::UTF16BE : String::UTF16LE;
}

View File

@ -46,105 +46,144 @@
namespace TagLib
{
inline ushort byteSwap(ushort x)
namespace Utils
{
inline ushort byteSwap(ushort x)
{
#if defined(HAVE_GCC_BYTESWAP_16)
return __builtin_bswap16(x);
return __builtin_bswap16(x);
#elif defined(HAVE_MSC_BYTESWAP)
return _byteswap_ushort(x);
return _byteswap_ushort(x);
#elif defined(HAVE_GLIBC_BYTESWAP)
return __bswap_16(x);
return __bswap_16(x);
#elif defined(HAVE_MAC_BYTESWAP)
return OSSwapInt16(x);
return OSSwapInt16(x);
#elif defined(HAVE_OPENBSD_BYTESWAP)
return swap16(x);
return swap16(x);
#else
return ((x >> 8) & 0xff) | ((x & 0xff) << 8);
return ((x >> 8) & 0xff) | ((x & 0xff) << 8);
#endif
}
}
inline uint byteSwap(uint x)
{
inline uint byteSwap(uint x)
{
#if defined(HAVE_GCC_BYTESWAP_32)
return __builtin_bswap32(x);
return __builtin_bswap32(x);
#elif defined(HAVE_MSC_BYTESWAP)
return _byteswap_ulong(x);
return _byteswap_ulong(x);
#elif defined(HAVE_GLIBC_BYTESWAP)
return __bswap_32(x);
return __bswap_32(x);
#elif defined(HAVE_MAC_BYTESWAP)
return OSSwapInt32(x);
return OSSwapInt32(x);
#elif defined(HAVE_OPENBSD_BYTESWAP)
return swap32(x);
return swap32(x);
#else
return ((x & 0xff000000u) >> 24)
| ((x & 0x00ff0000u) >> 8)
| ((x & 0x0000ff00u) << 8)
| ((x & 0x000000ffu) << 24);
return ((x & 0xff000000u) >> 24)
| ((x & 0x00ff0000u) >> 8)
| ((x & 0x0000ff00u) << 8)
| ((x & 0x000000ffu) << 24);
#endif
}
}
inline ulonglong byteSwap(ulonglong x)
{
inline ulonglong byteSwap(ulonglong x)
{
#if defined(HAVE_GCC_BYTESWAP_64)
return __builtin_bswap64(x);
return __builtin_bswap64(x);
#elif defined(HAVE_MSC_BYTESWAP)
return _byteswap_uint64(x);
return _byteswap_uint64(x);
#elif defined(HAVE_GLIBC_BYTESWAP)
return __bswap_64(x);
return __bswap_64(x);
#elif defined(HAVE_MAC_BYTESWAP)
return OSSwapInt64(x);
return OSSwapInt64(x);
#elif defined(HAVE_OPENBSD_BYTESWAP)
return swap64(x);
return swap64(x);
#else
return ((x & 0xff00000000000000ull) >> 56)
| ((x & 0x00ff000000000000ull) >> 40)
| ((x & 0x0000ff0000000000ull) >> 24)
| ((x & 0x000000ff00000000ull) >> 8)
| ((x & 0x00000000ff000000ull) << 8)
| ((x & 0x0000000000ff0000ull) << 24)
| ((x & 0x000000000000ff00ull) << 40)
| ((x & 0x00000000000000ffull) << 56);
return ((x & 0xff00000000000000ull) >> 56)
| ((x & 0x00ff000000000000ull) >> 40)
| ((x & 0x0000ff0000000000ull) >> 24)
| ((x & 0x000000ff00000000ull) >> 8)
| ((x & 0x00000000ff000000ull) << 8)
| ((x & 0x0000000000ff0000ull) << 24)
| ((x & 0x000000000000ff00ull) << 40)
| ((x & 0x00000000000000ffull) << 56);
#endif
}
enum ByteOrder
{
LittleEndian,
BigEndian
};
#ifdef SYSTEM_BYTEORDER
# if SYSTEM_BYTEORDER == 1
const ByteOrder SystemByteOrder = LittleEndian;
# else
const ByteOrder SystemByteOrder = BigEndian;
# endif
#else
inline ByteOrder systemByteOrder()
{
union {
int i;
char c;
} u;
u.i = 1;
if(u.c == 1)
return LittleEndian;
else
return BigEndian;
}
const ByteOrder SystemByteOrder = systemByteOrder();
#endif
}
};
}
#endif

View File

@ -106,6 +106,11 @@
bit mask & shift operations.
------------------------------------------------------------------------ */
// Workaround for when MSVC doesn't have wchar_t as a built-in type.
#if defined(_MSC_VER) && !defined(_WCHAR_T_DEFINED)
# include <wchar.h>
#endif
/* Some fundamental constants */
#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD
#define UNI_MAX_BMP (UTF32)0x0000FFFF
@ -114,10 +119,10 @@
namespace Unicode {
typedef unsigned long UTF32; /* at least 32 bits */
typedef wchar_t UTF16; /* TagLib assumes that wchar_t is sufficient for UTF-16. */
typedef unsigned long UTF32; /* at least 32 bits */
typedef wchar_t UTF16; /* TagLib assumes that wchar_t is sufficient for UTF-16. */
typedef unsigned char UTF8; /* typically 8 bits */
typedef unsigned char Boolean; /* 0 or 1 */
typedef unsigned char Boolean; /* 0 or 1 */
typedef enum {
conversionOK = 0, /* conversion successful */

View File

@ -126,8 +126,9 @@ TagLib::uint WavPack::Properties::sampleFrames() const
// private members
////////////////////////////////////////////////////////////////////////////////
static const unsigned int sample_rates[] = { 6000, 8000, 9600, 11025, 12000,
16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000 };
static const unsigned int sample_rates[] = {
6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000,
32000, 44100, 48000, 64000, 88200, 96000, 192000, 0 };
#define BYTES_STORED 3
#define MONO_FLAG 4

View File

@ -75,7 +75,12 @@ namespace TagLib {
virtual int length() const;
virtual int bitrate() const;
/*!
* Returns the sample rate in Hz. 0 means unknown or custom.
*/
virtual int sampleRate() const;
virtual int channels() const;
/*!

View File

@ -96,12 +96,10 @@ find_path(SPARSEHASH_INCLUDE_DIRS google/sparsetable)
# distros. If the user seems to want Drive support (ie. they have sparsehash
# installed and haven't disabled drive), and has an old taglib, compile our
# internal one and use that instead.
option(USE_BUILTIN_TAGLIB "If the system's version of Taglib is too old for Google Drive support, compile our builtin version instead" ON)
if (USE_BUILTIN_TAGLIB AND
(NOT "${ENABLE_GOOGLE_DRIVE}" STREQUAL "OFF") AND
SPARSEHASH_INCLUDE_DIRS AND
TAGLIB_VERSION VERSION_LESS 1.8)
set(TAGLIB_VERSION 1.8)
option(USE_BUILTIN_TAGLIB "If the system's version of Taglib is too old, compile our builtin version instead" ON)
if (USE_BUILTIN_TAGLIB AND TAGLIB_VERSION VERSION_LESS 1.8)
message(STATUS "Using builtin taglib because your system's version is too old")
set(TAGLIB_VERSION 1.9.1)
set(TAGLIB_INCLUDE_DIRS "${CMAKE_BINARY_DIR}/3rdparty/taglib/headers/taglib/;${CMAKE_BINARY_DIR}/3rdparty/taglib/headers/")
set(TAGLIB_LIBRARY_DIRS "")
set(TAGLIB_LIBRARIES tag)
@ -158,6 +156,10 @@ include_directories(${Boost_INCLUDE_DIRS})
include_directories(${TAGLIB_INCLUDE_DIRS})
include_directories(${QJSON_INCLUDE_DIRS})
include_directories(${GSTREAMER_INCLUDE_DIRS})
include_directories(${GSTREAMER_APP_INCLUDE_DIRS})
include_directories(${GSTREAMER_BASE_INCLUDE_DIRS})
include_directories(${GSTREAMER_CDDA_INCLUDE_DIRS})
include_directories(${GSTREAMER_TAG_INCLUDE_DIRS})
include_directories(${GLIB_INCLUDE_DIRS})
include_directories(${GLIBCONFIG_INCLUDE_DIRS})
include_directories(${LIBXML_INCLUDE_DIRS})
@ -273,7 +275,7 @@ optional_component(VISUALISATIONS ON "Visualisations")
if(NOT HAVE_SPOTIFY_BLOB AND NOT QCA_FOUND)
message(FATAL_ERROR "Either QCA must be available or the non-GPL Spotify "
"code must be compiled in")
elif(QCA_FOUND)
elseif(QCA_FOUND)
set(HAVE_SPOTIFY_DOWNLOADER ON)
endif()
@ -304,31 +306,14 @@ if(ENABLE_VISUALISATIONS)
endif(USE_SYSTEM_PROJECTM)
endif(ENABLE_VISUALISATIONS)
# We compile our own qsqlite3 by default now, because in Qt 4.7 the symbols we
# need from it are private.
option(STATIC_SQLITE "Compile and use a static sqlite3 library" ON)
set(HAVE_STATIC_SQLITE ${STATIC_SQLITE})
if(STATIC_SQLITE)
message(STATUS "Building static qsqlite plugin")
add_subdirectory(3rdparty/qsqlite)
include_directories("3rdparty/qsqlite")
else()
if (NOT I_HATE_MY_USERS)
message(FATAL_ERROR "No, really, Clementine needs sqlite to be linked "
"statically. If it's not, Clementine won't be able to resolve the "
"functions it needs to register full text search support. Search will "
"be really slow and users will be unhappy. If you're packaging "
"Clementine for a distribution and you really hate your users and want "
"them to have a bad time please rerun cmake with -DI_HATE_MY_USERS=ON")
else()
if (NOT MY_USERS_WILL_SUFFER_BECAUSE_OF_ME)
message(FATAL_ERROR "So you really hate your users and you want to "
"create a crippled package that doesn't work properly? Please send "
"an email to the devs and help us understand why.")
endif()
endif()
endif(STATIC_SQLITE)
# Build our copy of QSqlLiteDriver.
# We do this because we can't guarantee that the driver shipped with Qt exposes the
# raw sqlite3_ functions required for FTS support. This way we know that those symbols
# exist at compile-time and that our code links to the same sqlite library as the
# Qt driver.
add_subdirectory(3rdparty/qsqlite)
include_directories("3rdparty/qsqlite")
# When/if upstream accepts our patches then these options can be used to link
# to system installed qtsingleapplication instead.

View File

@ -1,3 +1,14 @@
Version 1.2.1:
Bugfixes:
* Fix library download in the network remote.
* Fix removing songs from playlist in the network remote.
* Fix login failures with box.com.
* (Mac OS X) Add a workaround for a weird font issue on 10.9.
* (Linux) Fix a typo that would prevent the Spotify downloader button from
ever being shown.
Version 1.2:
Major features:
* Add support for indexing and playing music from Box, Dropbox, Skydrive,

26
README.md Normal file
View File

@ -0,0 +1,26 @@
Clementine
==========
Clementine is a modern music player and library organizer for Windows, Linux and Mac OS X.
- Website: http://www.clementine-player.org/
- Github: https://github.com/clementine-player/Clementine
- Buildbot: http://buildbot.clementine-player.org/grid
- Latest developer builds: http://builds.clementine-player.org/
Compiling from source
---------------------
Get the code (if you haven't already):
git clone https://github.com/clementine-player/Clementine.git && cd Clementine
Compile and install:
cd bin
cmake ..
make -j8
sudo make install
See the Wiki for more instructions and a list of dependencies:
https://github.com/clementine-player/Clementine/wiki/Compiling-from-Source

2
bin/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*

View File

@ -2,9 +2,9 @@
# Version numbers.
set(CLEMENTINE_VERSION_MAJOR 1)
set(CLEMENTINE_VERSION_MINOR 1)
set(CLEMENTINE_VERSION_MINOR 2)
set(CLEMENTINE_VERSION_PATCH 1)
#set(CLEMENTINE_VERSION_PRERELEASE rc1)
# set(CLEMENTINE_VERSION_PRERELEASE rc4)
# This should be set to OFF in a release branch
set(INCLUDE_GIT_REVISION ON)

View File

@ -17,7 +17,7 @@
</extract>
<extract>
<item begin="&lt;h3&gt;&lt;a name=&quot;{track}&quot;&gt;{track}. {Title2}&lt;/a&gt;&lt;/h3&gt;&lt;br /&gt;" end="&lt;div class=&quot;thanks&quot;&gt;"/>
</extract>
</extract>
<invalidIndicator value="The page you requested was not found on DarkLyrics.com."/>
</provider>
<provider name="directlyrics.com" title="{artist} - {title} lyrics" charset="iso-8859-1" url="http://www.directlyrics.com/{artist}-{title}-lyrics.html">
@ -43,6 +43,14 @@
</exclude>
<invalidIndicator value="Page not Found"/>
</provider>
<provider name="Encyclopaedia Metallum" title="{title Lyrics - {artist}" charset="utf-8" url="http://www.metal-archives.com/search/ajax-advanced/searching/songs/?songTitle={title}&amp;bandName={artist}&amp;ExactBandMatch=1">
<extract>
<item url="http://www.metal-archives.com/release/ajax-view-lyrics/id/{id}"/>
<item begin="id=\&quot;lyricsLink_" end="&quot;"/>
</extract>
<invalidIndicator value="&quot;iTotalRecords&quot;: 0"/>
<invalidIndicator value="lyrics not available"/>
</provider>
<provider name="letras.mus.br" title="" charset="iso-8859-1" url="http://letras.terra.com.br/winamp.php?musica={title}&amp;artista={artist}">
<urlFormat replace="_@,;&amp;\/&quot;" with="_"/>
<urlFormat replace=" " with="+"/>

View File

@ -1,5 +1,7 @@
<!doctype html>
<html>
<head>
<link href="http://clementine-player.org/favicon.ico" rel="shortcut icon">
<title>tr("Return to Clementine")</title>
<style>

5
debian/control vendored
View File

@ -33,14 +33,15 @@ Build-Depends: debhelper (>= 7),
libqca2-dev,
libchromaprint-dev | libfftw3-dev,
libfftw3-dev,
libsparsehash-dev
libsparsehash-dev,
libsqlite3-dev
Standards-Version: 3.8.1
Homepage: http://www.clementine-player.org/
Package: clementine
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends},
libqt4-sql-sqlite,
libsqlite3-0,
gstreamer0.10-plugins-base,
gstreamer0.10-plugins-good,
gstreamer0.10-alsa | gstreamer0.10-audiosink,

13
dist/CMakeLists.txt vendored
View File

@ -7,8 +7,21 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/maketarball.sh.in
${CMAKE_CURRENT_SOURCE_DIR}/maketarball.sh @ONLY)
# Create two installers, one for the normal version, one for the portable one
# Create normal installer
set(PORTABLE ";")
set(NORMAL "")
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/clementine.nsi.in
${CMAKE_CURRENT_SOURCE_DIR}/windows/clementine.nsi @ONLY)
# Create portable installer
set(PORTABLE "")
set(NORMAL ";")
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/clementine.nsi.in
${CMAKE_CURRENT_SOURCE_DIR}/windows/clementine-portable.nsi @ONLY)
# windows/windres.rc is done by src/CMakeLists.txt
if(EXISTS /etc/lsb-release)

View File

@ -15,8 +15,9 @@ BuildRequires: cmake gstreamer-devel gstreamer-plugins-base-devel
BuildRequires: libimobiledevice-devel libplist-devel usbmuxd-devel
BuildRequires: libmtp-devel protobuf-devel protobuf-compiler libcdio-devel
BuildRequires: qjson-devel qca2-devel fftw-devel sparsehash-devel
BuildRequires: sqlite-devel
Requires: libgpod protobuf-lite libcdio qjson qca-ossl
Requires: libgpod protobuf-lite libcdio qjson qca-ossl sqlite
# GStreamer codec dependencies
Requires: gstreamer-plugins-ugly

41
dist/codesign.py vendored Executable file
View File

@ -0,0 +1,41 @@
#!/usr/bin/python
# Emulates the behaviour of codesign --deep which is missing on OS X < 10.9
import os
import re
import subprocess
import sys
def SignPath(path, developer_id):
args = [
'codesign',
'--preserve-metadata=identifier,entitlements,resource-rules,requirements',
'-s', developer_id,
'-fv', path
]
subprocess.check_call(args)
def main():
if len(sys.argv) != 3:
print 'Usage: %s <developer id> <app bundle>' % sys.argv[0]
sys.exit(1)
developer_id = sys.argv[1]
app_bundle = sys.argv[2]
for root, dirs, files in os.walk(app_bundle):
for dir in dirs:
if re.search(r'\.framework$', dir):
SignPath(os.path.join(root, dir), developer_id)
for file in files:
if re.search(r'\.(dylib|so)$', file):
SignPath(os.path.join(root, file), developer_id)
elif re.match(r'(clementine-spotifyblob|clementine-tagreader|gst-plugin-scanner)', file):
SignPath(os.path.join(root, file), developer_id)
SignPath(app_bundle, developer_id)
if __name__ == '__main__':
main()

View File

@ -24,9 +24,11 @@ LABELS = {
"dmg": ["Type-Package", "OpSys-OSX", "Distro-Lion", "Arch-x86-64"],
32: ["Arch-i386"],
64: ["Arch-x86-64"],
"lucid": ["Distro-Ubuntu"],
"precise": ["Distro-Ubuntu"],
"quantal": ["Distro-Ubuntu"],
"raring": ["Distro-Ubuntu"],
"saucy": ["Distro-Ubuntu"],
"squeeze": ["Distro-Debian"],
"wheezy": ["Distro-Debian"],
}
@ -51,9 +53,11 @@ RPM_ARCH = {
}
DESCRIPTIONS = {
("deb", "lucid"): "for Ubuntu Lucid (10.04)",
("deb", "precise"): "for Ubuntu Precise (12.04)",
("deb", "quantal"): "for Ubuntu Quantal (12.10)",
("deb", "raring"): "for Ubuntu Raring (13.04)",
("deb", "raring"): "for Ubuntu Raring (13.04)",
("deb", "saucy"): "for Ubuntu Saucy (13.10)",
("deb", "squeeze"): "for Debian Squeeze",
("deb", "wheezy"): "for Debian Wheezy",
("rpm", "fc18"): "for Fedora 18",
@ -64,12 +68,16 @@ DESCRIPTIONS = {
}
RELEASES = [
("deb", "lucid", 32),
("deb", "lucid", 64),
("deb", "precise", 32),
("deb", "precise", 64),
("deb", "quantal", 32),
("deb", "quantal", 64),
("deb", "raring", 32),
("deb", "raring", 64),
("deb", "saucy", 32),
("deb", "saucy", 64),
("deb", "squeeze", 32),
("deb", "squeeze", 64),
("deb", "wheezy", 32),
@ -226,7 +234,6 @@ def main():
return 1
print
return 0
# Upload everything
for release in RELEASES:

1
dist/macdeploy.py vendored
View File

@ -108,7 +108,6 @@ QT_PLUGINS = [
'imageformats/libqjpeg.dylib',
'imageformats/libqmng.dylib',
'imageformats/libqsvg.dylib',
'imageformats/libqtiff.dylib',
]
QT_PLUGINS_SEARCH_PATH=[
'/target/plugins',

View File

@ -1,4 +1,5 @@
!define PRODUCT_NAME "Clementine"
@NORMAL@!define PRODUCT_NAME "Clementine"
@PORTABLE@!define PRODUCT_NAME "Clementine-Portable"
!define PRODUCT_PUBLISHER "Clementine"
!define PRODUCT_VERSION_MAJOR @CLEMENTINE_VERSION_MAJOR@
!define PRODUCT_VERSION_MINOR @CLEMENTINE_VERSION_MINOR@
@ -7,7 +8,8 @@
!define PRODUCT_WEB_SITE "http://www.clementine-player.org/"
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
!define PRODUCT_UNINST_ROOT_KEY "HKLM"
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES\Clementine"
@NORMAL@!define PRODUCT_INSTALL_DIR "$PROGRAMFILES\Clementine"
@PORTABLE@!define PRODUCT_INSTALL_DIR "C:\Clementine"
; Set Application Capabilities info
!define CAPABILITIES_NAME "Clementine"
@ -51,7 +53,8 @@ OutFile "${PRODUCT_NAME}Setup-@CLEMENTINE_VERSION_SPARKLE@.exe"
InstallDir "${PRODUCT_INSTALL_DIR}"
ShowInstDetails show
ShowUnInstDetails show
RequestExecutionLevel admin
@NORMAL@RequestExecutionLevel admin
@PORTABLE@RequestExecutionLevel user
Function RunClementine
ShellExecAsUser::ShellExecAsUser "" "$INSTDIR/clementine.exe" ""
@ -107,6 +110,9 @@ Section "Delete old files" oldfiles
; 1.1
Delete "$INSTDIR\libprotobuf-lite-7.dll"
; 1.2
Delete "$INSTDIR\libprotobuf-7.dll"
; mingw-w64
Delete "$INSTDIR\avcodec-52.dll"
Delete "$INSTDIR\avformat-52.dll"
@ -177,7 +183,7 @@ Section "Clementine" Clementine
File "liborc-test-0.4-0.dll"
File "libplist.dll"
File "libpng14-14.dll"
File "libprotobuf-7.dll"
File "libprotobuf-8.dll"
File "libqjson.dll"
File "libsoup-2.4-1.dll"
File "libspeex-1.dll"
@ -200,57 +206,60 @@ Section "Clementine" Clementine
File "ssleay32.dll"
File "zlib1.dll"
; Create data folder to enable portable mode
@PORTABLE@ CreateDirectory $INSTDIR\data
; Check the OS. If Vista or newer, use Default Programs
nsisos::osversion
StrCpy $R0 $0
IntCmp $R0 6 HasDefaultPrograms NoDefaultPrograms HasDefaultPrograms
@NORMAL@ nsisos::osversion
@NORMAL@ StrCpy $R0 $0
@NORMAL@ IntCmp $R0 6 HasDefaultPrograms NoDefaultPrograms HasDefaultPrograms
HasDefaultPrograms:
; Register Clementine with Default Programs
Var /GLOBAL AppIcon
Var /GLOBAL AppExe
StrCpy $AppExe "$INSTDIR\clementine.exe"
StrCpy $AppIcon "$INSTDIR\clementine.ico"
${RegisterCapabilities}
${RegisterMediaType} ".mp3" $AppExe $AppIcon "MP3 Audio File"
${RegisterMediaType} ".flac" $AppExe $AppIcon "FLAC Audio File"
${RegisterMediaType} ".ogg" $AppExe $AppIcon "OGG Audio File"
${RegisterMediaType} ".spx" $AppExe $AppIcon "OGG Speex Audio File"
${RegisterMediaType} ".m4a" $AppExe $AppIcon "MP4 Audio File"
${RegisterMediaType} ".aac" $AppExe $AppIcon "AAC Audio File"
${RegisterMediaType} ".wma" $AppExe $AppIcon "WMA Audio File"
${RegisterMediaType} ".wav" $AppExe $AppIcon "WAV Audio File"
${RegisterMediaType} ".pls" $AppExe $AppIcon "PLS Audio File"
${RegisterMediaType} ".m3u" $AppExe $AppIcon "M3U Audio File"
${RegisterMediaType} ".xspf" $AppExe $AppIcon "XSPF Audio File"
${RegisterMediaType} ".asx" $AppExe $AppIcon "Windows Media Audio/Video playlist"
${RegisterMimeType} "audio/mp3" "mp3" "{cd3afa76-b84f-48f0-9393-7edc34128127}"
${RegisterMimeType} "audio/mp4" "m4a" "{cd3afa7c-b84f-48f0-9393-7edc34128127}"
${RegisterMimeType} "audio/x-ms-wma" "wma" "{cd3afa84-b84f-48f0-9393-7edc34128127}"
${RegisterMimeType} "audio/wav" "wav" "{cd3afa7b-b84f-48f0-9393-7edc34128127}"
${RegisterMimeType} "audio/mpegurl" "m3u" "{cd3afa78-b84f-48f0-9393-7edc34128127}"
${RegisterMimeType} "application/x-wmplayer" "asx" "{cd3afa96-b84f-48f0-9393-7edc34128127}"
Goto done
NoDefaultPrograms:
${registerExtension} "$INSTDIR\clementine.exe" ".mp3" "MP3 Audio File"
${registerExtension} "$INSTDIR\clementine.exe" ".flac" "FLAC Audio File"
${registerExtension} "$INSTDIR\clementine.exe" ".ogg" "OGG Audio File"
${registerExtension} "$INSTDIR\clementine.exe" ".spx" "OGG Speex Audio File"
${registerExtension} "$INSTDIR\clementine.exe" ".m4a" "MP4 Audio File"
${registerExtension} "$INSTDIR\clementine.exe" ".aac" "AAC Audio File"
${registerExtension} "$INSTDIR\clementine.exe" ".wma" "WMA Audio File"
${registerExtension} "$INSTDIR\clementine.exe" ".wav" "WAV Audio File"
${registerExtension} "$INSTDIR\clementine.exe" ".pls" "PLS Audio File"
${registerExtension} "$INSTDIR\clementine.exe" ".m3u" "M3U Audio File"
${registerExtension} "$INSTDIR\clementine.exe" ".xspf" "XSPF Audio File"
${registerExtension} "$INSTDIR\clementine.exe" ".asx" "Windows Media Audio/Video playlist"
done:
@NORMAL@ HasDefaultPrograms:
@NORMAL@ ; Register Clementine with Default Programs
@NORMAL@ Var /GLOBAL AppIcon
@NORMAL@ Var /GLOBAL AppExe
@NORMAL@ StrCpy $AppExe "$INSTDIR\clementine.exe"
@NORMAL@ StrCpy $AppIcon "$INSTDIR\clementine.ico"
@NORMAL@
@NORMAL@ ${RegisterCapabilities}
@NORMAL@
@NORMAL@ ${RegisterMediaType} ".mp3" $AppExe $AppIcon "MP3 Audio File"
@NORMAL@ ${RegisterMediaType} ".flac" $AppExe $AppIcon "FLAC Audio File"
@NORMAL@ ${RegisterMediaType} ".ogg" $AppExe $AppIcon "OGG Audio File"
@NORMAL@ ${RegisterMediaType} ".spx" $AppExe $AppIcon "OGG Speex Audio File"
@NORMAL@ ${RegisterMediaType} ".m4a" $AppExe $AppIcon "MP4 Audio File"
@NORMAL@ ${RegisterMediaType} ".aac" $AppExe $AppIcon "AAC Audio File"
@NORMAL@ ${RegisterMediaType} ".wma" $AppExe $AppIcon "WMA Audio File"
@NORMAL@ ${RegisterMediaType} ".wav" $AppExe $AppIcon "WAV Audio File"
@NORMAL@
@NORMAL@ ${RegisterMediaType} ".pls" $AppExe $AppIcon "PLS Audio File"
@NORMAL@ ${RegisterMediaType} ".m3u" $AppExe $AppIcon "M3U Audio File"
@NORMAL@ ${RegisterMediaType} ".xspf" $AppExe $AppIcon "XSPF Audio File"
@NORMAL@ ${RegisterMediaType} ".asx" $AppExe $AppIcon "Windows Media Audio/Video playlist"
@NORMAL@
@NORMAL@ ${RegisterMimeType} "audio/mp3" "mp3" "{cd3afa76-b84f-48f0-9393-7edc34128127}"
@NORMAL@ ${RegisterMimeType} "audio/mp4" "m4a" "{cd3afa7c-b84f-48f0-9393-7edc34128127}"
@NORMAL@ ${RegisterMimeType} "audio/x-ms-wma" "wma" "{cd3afa84-b84f-48f0-9393-7edc34128127}"
@NORMAL@ ${RegisterMimeType} "audio/wav" "wav" "{cd3afa7b-b84f-48f0-9393-7edc34128127}"
@NORMAL@
@NORMAL@ ${RegisterMimeType} "audio/mpegurl" "m3u" "{cd3afa78-b84f-48f0-9393-7edc34128127}"
@NORMAL@ ${RegisterMimeType} "application/x-wmplayer" "asx" "{cd3afa96-b84f-48f0-9393-7edc34128127}"
@NORMAL@ Goto done
@NORMAL@ NoDefaultPrograms:
@NORMAL@ ${registerExtension} "$INSTDIR\clementine.exe" ".mp3" "MP3 Audio File"
@NORMAL@ ${registerExtension} "$INSTDIR\clementine.exe" ".flac" "FLAC Audio File"
@NORMAL@ ${registerExtension} "$INSTDIR\clementine.exe" ".ogg" "OGG Audio File"
@NORMAL@ ${registerExtension} "$INSTDIR\clementine.exe" ".spx" "OGG Speex Audio File"
@NORMAL@ ${registerExtension} "$INSTDIR\clementine.exe" ".m4a" "MP4 Audio File"
@NORMAL@ ${registerExtension} "$INSTDIR\clementine.exe" ".aac" "AAC Audio File"
@NORMAL@ ${registerExtension} "$INSTDIR\clementine.exe" ".wma" "WMA Audio File"
@NORMAL@ ${registerExtension} "$INSTDIR\clementine.exe" ".wav" "WAV Audio File"
@NORMAL@
@NORMAL@ ${registerExtension} "$INSTDIR\clementine.exe" ".pls" "PLS Audio File"
@NORMAL@ ${registerExtension} "$INSTDIR\clementine.exe" ".m3u" "M3U Audio File"
@NORMAL@ ${registerExtension} "$INSTDIR\clementine.exe" ".xspf" "XSPF Audio File"
@NORMAL@ ${registerExtension} "$INSTDIR\clementine.exe" ".asx" "Windows Media Audio/Video playlist"
@NORMAL@ done:
SectionEnd
Section "Qt image format plugins" imageformats
@ -885,27 +894,27 @@ Section "projectM presets" projectm-presets
File "/oname=Zylot - Wisps.milk" "../../3rdparty/libprojectm/presets/Zylot - Wisps.milk"
SectionEnd
Section "Start menu items" startmenu
; Create Start Menu folders and shortcuts.
SetShellVarContext all
CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\clementine.exe"
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
SectionEnd
@NORMAL@Section "Start menu items" startmenu
@NORMAL@ ; Create Start Menu folders and shortcuts.
@NORMAL@ SetShellVarContext all
@NORMAL@
@NORMAL@ CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
@NORMAL@ CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\clementine.exe"
@NORMAL@ CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
@NORMAL@SectionEnd
Section "Uninstaller"
; Create uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "${PRODUCT_NAME}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\Uninstall.exe"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\clementine.ico"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_DISPLAY_VERSION}"
WriteRegDWORD ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "VersionMajor" "${PRODUCT_VERSION_MAJOR}"
WriteRegDWORD ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "VersionMinor" "${PRODUCT_VERSION_MINOR}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
@NORMAL@ WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "${PRODUCT_NAME}"
@NORMAL@ WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\Uninstall.exe"
@NORMAL@ WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\clementine.ico"
@NORMAL@ WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_DISPLAY_VERSION}"
@NORMAL@ WriteRegDWORD ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "VersionMajor" "${PRODUCT_VERSION_MAJOR}"
@NORMAL@ WriteRegDWORD ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "VersionMinor" "${PRODUCT_VERSION_MINOR}"
@NORMAL@ WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
@NORMAL@ WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
SectionEnd
Section "Uninstall"
@ -1640,36 +1649,36 @@ Section "Uninstall"
; Remove the Shortcuts
SetShellVarContext all
Delete "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk"
Delete "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk"
RMDir /r "$SMPROGRAMS\${PRODUCT_NAME}"
@NORMAL@ Delete "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk"
@NORMAL@ Delete "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk"
@NORMAL@ RMDir /r "$SMPROGRAMS\${PRODUCT_NAME}"
; Remove the entry from 'installed programs list'
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
@NORMAL@ DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
; Check the OS. If Vista or newer, use Default Programs
nsisos::osversion
StrCpy $R0 $0
IntCmp $R0 6 HasDefaultPrograms NoDefaultPrograms HasDefaultPrograms
@NORMAL@ nsisos::osversion
@NORMAL@ StrCpy $R0 $0
@NORMAL@ IntCmp $R0 6 HasDefaultPrograms NoDefaultPrograms HasDefaultPrograms
HasDefaultPrograms:
@NORMAL@ HasDefaultPrograms:
; Unregister from Default Programs
${UnRegisterCapabilities}
Goto done
NoDefaultPrograms:
@NORMAL@ ${UnRegisterCapabilities}
@NORMAL@ Goto done
@NORMAL@ NoDefaultPrograms:
; Remove file associations
${unregisterExtension} ".mp3" "MP3 Audio File"
${unregisterExtension} ".flac" "FLAC Audio File"
${unregisterExtension} ".ogg" "OGG Audio File"
${unregisterExtension} ".spx" "OGG Speex Audio File"
${unregisterExtension} ".mp4" "MP4 Audio File"
${unregisterExtension} ".aac" "AAC Audio File"
${unregisterExtension} ".wma" "WMA Audio File"
${unregisterExtension} ".wav" "WAV Audio File"
${unregisterExtension} ".pls" "PLS Audio File"
${unregisterExtension} ".m3u" "M3U Audio File"
${unregisterExtension} ".xspf" "XSPF Audio File"
${unregisterExtension} ".asx" "Windows Media Audio/Video playlist"
done:
@NORMAL@ ${unregisterExtension} ".mp3" "MP3 Audio File"
@NORMAL@ ${unregisterExtension} ".flac" "FLAC Audio File"
@NORMAL@ ${unregisterExtension} ".ogg" "OGG Audio File"
@NORMAL@ ${unregisterExtension} ".spx" "OGG Speex Audio File"
@NORMAL@ ${unregisterExtension} ".mp4" "MP4 Audio File"
@NORMAL@ ${unregisterExtension} ".aac" "AAC Audio File"
@NORMAL@ ${unregisterExtension} ".wma" "WMA Audio File"
@NORMAL@ ${unregisterExtension} ".wav" "WAV Audio File"
@NORMAL@
@NORMAL@ ${unregisterExtension} ".pls" "PLS Audio File"
@NORMAL@ ${unregisterExtension} ".m3u" "M3U Audio File"
@NORMAL@ ${unregisterExtension} ".xspf" "XSPF Audio File"
@NORMAL@ ${unregisterExtension} ".asx" "Windows Media Audio/Video playlist"
@NORMAL@ done:
SectionEnd

View File

@ -33,7 +33,7 @@ ClosureBase::~ClosureBase() {
CallbackClosure::CallbackClosure(
QObject* sender,
const char* signal,
std::tr1::function<void()> callback)
boost::function<void()> callback)
: ClosureBase(new ObjectHelper(sender, signal, this)),
callback_(callback) {
}
@ -67,7 +67,7 @@ void Unpack(QList<QGenericArgument>*) {}
_detail::ClosureBase* NewClosure(
QObject* sender,
const char* signal,
std::tr1::function<void()> callback) {
boost::function<void()> callback) {
return new _detail::CallbackClosure(
sender, signal, callback);
}

View File

@ -18,7 +18,7 @@
#ifndef CLOSURE_H
#define CLOSURE_H
#include <tr1/functional>
#include <functional>
#include <QMetaMethod>
#include <QObject>
@ -158,12 +158,12 @@ class CallbackClosure : public ClosureBase {
CallbackClosure(
QObject* sender,
const char* signal,
std::tr1::function<void()> callback);
boost::function<void()> callback);
virtual void Invoke();
private:
std::tr1::function<void()> callback_;
boost::function<void()> callback_;
};
} // namespace _detail
@ -194,13 +194,13 @@ _detail::ClosureBase* NewClosure(
_detail::ClosureBase* NewClosure(
QObject* sender,
const char* signal,
std::tr1::function<void()> callback);
boost::function<void()> callback);
template <typename... Args>
_detail::ClosureBase* NewClosure(
QObject* sender,
const char* signal,
std::tr1::function<void(Args...)> callback,
boost::function<void(Args...)> callback,
const Args&... args) {
return NewClosure(sender, signal, boost::bind(callback, args...));
}

View File

@ -257,6 +257,7 @@ message ResponseSongFileChunk {
optional SongMetadata song_metadata = 6; // only sent with first chunk!
optional bytes data = 7;
optional int32 size = 8;
optional bytes file_hash = 9;
}
message ResponseLibraryChunk {
@ -264,6 +265,7 @@ message ResponseLibraryChunk {
optional int32 chunk_count = 2;
optional bytes data = 3;
optional int32 size = 4;
optional bytes file_hash = 5;
}
message ResponseSongOffer {
@ -276,7 +278,7 @@ message RequestRateSong {
// The message itself
message Message {
optional int32 version = 1 [default=11];
optional int32 version = 1 [default=14];
optional MsgType type = 2 [default=UNKNOWN]; // What data is in the message?
optional RequestConnect request_connect = 21;

View File

@ -132,7 +132,7 @@ void TagReader::ReadFile(const QString& filename,
TagLib::Tag* tag = fileref->tag();
if (tag) {
Decode(tag->title(), NULL, song->mutable_title());
Decode(tag->artist(), NULL, song->mutable_artist());
Decode(tag->artist(), NULL, song->mutable_artist()); // TPE1
Decode(tag->album(), NULL, song->mutable_album());
Decode(tag->genre(), NULL, song->mutable_genre());
song->set_year(tag->year());
@ -166,8 +166,7 @@ void TagReader::ReadFile(const QString& filename,
if (!map["TIT1"].isEmpty()) // content group
Decode(map["TIT1"].front()->toString(), NULL, song->mutable_grouping());
if (!map["TPE1"].isEmpty()) // ID3v2: lead performer/soloist
Decode(map["TPE1"].front()->toString(), NULL, song->mutable_performer());
// Skip TPE1 (which is the artist) here because we already fetched it
if (!map["TPE2"].isEmpty()) // non-standard: Apple, Microsoft
Decode(map["TPE2"].front()->toString(), NULL, song->mutable_albumartist());
@ -452,6 +451,9 @@ void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap& map,
if (!map["COVERART"].isEmpty())
song->set_art_automatic(kEmbeddedCover);
if (!map["METADATA_BLOCK_PICTURE"].isEmpty())
song->set_art_automatic(kEmbeddedCover);
if (!map["FMPS_RATING"].isEmpty() && song->rating() <= 0)
song->set_rating(TStringToQString( map["FMPS_RATING"].front() ).trimmed().toFloat());
@ -534,7 +536,7 @@ bool TagReader::SaveFile(const QString& filename,
return false;
fileref->tag()->setTitle(StdStringToTaglibString(song.title()));
fileref->tag()->setArtist(StdStringToTaglibString(song.artist()));
fileref->tag()->setArtist(StdStringToTaglibString(song.artist())); // TPE1
fileref->tag()->setAlbum(StdStringToTaglibString(song.album()));
fileref->tag()->setGenre(StdStringToTaglibString(song.genre()));
fileref->tag()->setComment(StdStringToTaglibString(song.comment()));
@ -547,7 +549,7 @@ bool TagReader::SaveFile(const QString& filename,
SetTextFrame("TBPM", song.bpm() <= 0 -1 ? QString() : QString::number(song.bpm()), tag);
SetTextFrame("TCOM", song.composer(), tag);
SetTextFrame("TIT1", song.grouping(), tag);
SetTextFrame("TPE1", song.performer(), tag);
// Skip TPE1 (which is the artist) here because we already set it
SetTextFrame("TPE2", song.albumartist(), tag);
SetTextFrame("TCMP", std::string(song.compilation() ? "1" : "0"), tag);
} else if (TagLib::FLAC::File* file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
@ -786,6 +788,25 @@ QByteArray TagReader::LoadEmbeddedArt(const QString& filename) const {
if (xiph_comment) {
TagLib::Ogg::FieldListMap map = xiph_comment->fieldListMap();
// Other than the below mentioned non-standard COVERART, METADATA_BLOCK_PICTURE
// is the proposed tag for cover pictures.
// (see http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE)
if (map.contains("METADATA_BLOCK_PICTURE")) {
TagLib::StringList pict_list = map["METADATA_BLOCK_PICTURE"];
for(std::list<TagLib::String>::iterator it = pict_list.begin(); it != pict_list.end(); ++it) {
QByteArray data(QByteArray::fromBase64(it->toCString()));
TagLib::ByteVector tdata(data.data(), data.size());
TagLib::FLAC::Picture p(tdata);
if (p.type() == TagLib::FLAC::Picture::FrontCover)
return QByteArray(p.data().data(), p.data().size());
}
// If there was no specific front cover, just take the first picture
QByteArray data(QByteArray::fromBase64(map["METADATA_BLOCK_PICTURE"].front().toCString()));
TagLib::ByteVector tdata(data.data(), data.size());
TagLib::FLAC::Picture p(tdata);
return QByteArray(p.data().data(), p.data().size());
}
// Ogg lacks a definitive standard for embedding cover art, but it seems
// b64 encoding a field called COVERART is the general convention
if (!map.contains("COVERART"))
@ -856,23 +877,25 @@ bool TagReader::ReadCloudFile(const QUrl& download_url,
stream,
true,
TagLib::AudioProperties::Accurate));
} else if (mime_type == "application/ogg" ||
mime_type == "audio/ogg") {
tag.reset(new TagLib::Ogg::Vorbis::File(
stream,
true,
TagLib::AudioProperties::Accurate));
}
}
#ifdef TAGLIB_HAS_OPUS
else if (mime_type == "application/opus" ||
mime_type == "audio/opus") {
else if ((mime_type == "application/opus" ||
mime_type == "audio/opus" ||
mime_type == "application/ogg" ||
mime_type == "audio/ogg") && title.endsWith(".opus")) {
tag.reset(new TagLib::Ogg::Opus::File(
stream,
true,
TagLib::AudioProperties::Accurate));
}
#endif
else if (mime_type == "application/x-flac" ||
else if (mime_type == "application/ogg" ||
mime_type == "audio/ogg") {
tag.reset(new TagLib::Ogg::Vorbis::File(
stream,
true,
TagLib::AudioProperties::Accurate));
} else if (mime_type == "application/x-flac" ||
mime_type == "audio/flac" ||
mime_type == "audio/x-flac") {
tag.reset(new TagLib::FLAC::File(

View File

@ -593,7 +593,7 @@ normalize (gdouble *vals, guint numvals)
delta = 1.f;
for (i = 0; i < numvals; i++)
vals[i] = finite (vals[i]) ? MIN(1.f, MAX(0.f, (vals[i] - mini) / delta))
vals[i] = isfinite (vals[i]) ? MIN(1.f, MAX(0.f, (vals[i] - mini) / delta))
: 0.f;
}

View File

@ -134,6 +134,7 @@ set(SOURCES
devices/deviceproperties.cpp
devices/devicestatefiltermodel.cpp
devices/deviceview.cpp
devices/deviceviewcontainer.cpp
devices/filesystemdevice.cpp
engines/enginebase.cpp
@ -308,6 +309,7 @@ set(SOURCES
songinfo/songkickconcerts.cpp
songinfo/songkickconcertwidget.cpp
songinfo/songplaystats.cpp
songinfo/ultimatelyricslyric.cpp
songinfo/ultimatelyricsprovider.cpp
songinfo/ultimatelyricsreader.cpp
@ -445,6 +447,7 @@ set(HEADERS
devices/deviceproperties.h
devices/devicestatefiltermodel.h
devices/deviceview.h
devices/deviceviewcontainer.h
devices/filesystemdevice.h
engines/enginebase.h
@ -592,6 +595,7 @@ set(HEADERS
songinfo/songkickconcerts.h
songinfo/songkickconcertwidget.h
songinfo/songplaystats.h
songinfo/ultimatelyricslyric.h
songinfo/ultimatelyricsprovider.h
songinfo/ultimatelyricsreader.h
@ -670,6 +674,7 @@ set(UI
covers/coversearchstatisticsdialog.ui
devices/deviceproperties.ui
devices/deviceviewcontainer.ui
globalsearch/globalsearchsettingspage.ui
globalsearch/globalsearchview.ui
@ -1022,9 +1027,13 @@ optional_source(HAVE_AUDIOCD
SOURCES
devices/cddadevice.cpp
devices/cddalister.cpp
ui/ripcd.cpp
HEADERS
devices/cddadevice.h
devices/cddalister.h
ui/ripcd.h
UI
ui/ripcd.ui
)
# mtp device
@ -1190,6 +1199,7 @@ target_link_libraries(clementine_lib
${QTSINGLECOREAPPLICATION_LIBRARIES}
${QTIOCOMPRESSOR_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
${SQLITE_LIBRARIES}
z
Qocoa
)
@ -1263,11 +1273,8 @@ else (APPLE)
target_link_libraries(clementine_lib ${QXT_LIBRARIES})
endif (APPLE)
# Link against the qsqlite plugin on windows and mac
if(HAVE_STATIC_SQLITE)
set(3RDPARTY_SQLITE_LIBRARY qsqlite)
target_link_libraries(clementine_lib qsqlite)
endif(HAVE_STATIC_SQLITE)
set(3RDPARTY_SQLITE_LIBRARY qsqlite)
target_link_libraries(clementine_lib qsqlite)
if (WIN32)
target_link_libraries(clementine_lib
@ -1355,6 +1362,21 @@ if (APPLE)
install(DIRECTORY "${GROWL}/Versions/Current/Resources"
DESTINATION "${CMAKE_BINARY_DIR}/clementine.app/Contents/Frameworks/Growl.framework")
install(FILES "${QT_QTCORE_LIBRARY}/Contents/Info.plist"
DESTINATION "${CMAKE_BINARY_DIR}/clementine.app/Contents/Frameworks/QtCore.framework/Contents")
install(FILES "${QT_QTGUI_LIBRARY}/Contents/Info.plist"
DESTINATION "${CMAKE_BINARY_DIR}/clementine.app/Contents/Frameworks/QtGui.framework/Contents")
install(FILES "${QT_QTNETWORK_LIBRARY}/Contents/Info.plist"
DESTINATION "${CMAKE_BINARY_DIR}/clementine.app/Contents/Frameworks/QtNetwork.framework/Contents")
install(FILES "${QT_QTOPENGL_LIBRARY}/Contents/Info.plist"
DESTINATION "${CMAKE_BINARY_DIR}/clementine.app/Contents/Frameworks/QtOpenGL.framework/Contents")
install(FILES "${QT_QTSQL_LIBRARY}/Contents/Info.plist"
DESTINATION "${CMAKE_BINARY_DIR}/clementine.app/Contents/Frameworks/QtSql.framework/Contents")
install(FILES "${QT_QTSVG_LIBRARY}/Contents/Info.plist"
DESTINATION "${CMAKE_BINARY_DIR}/clementine.app/Contents/Frameworks/QtSvg.framework/Contents")
install(FILES "${QT_QTXML_LIBRARY}/Contents/Info.plist"
DESTINATION "${CMAKE_BINARY_DIR}/clementine.app/Contents/Frameworks/QtXml.framework/Contents")
if (HAVE_BREAKPAD)
install(DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/google-breakpad/client/mac/build/Release/Breakpad.framework
@ -1388,7 +1410,7 @@ if (APPLE)
add_custom_target(
sign
COMMAND
codesign -s ${APPLE_DEVELOPER_ID} -fv ${PROJECT_BINARY_DIR}/clementine.app
${PROJECT_SOURCE_DIR}/dist/codesign.py ${APPLE_DEVELOPER_ID} ${PROJECT_BINARY_DIR}/clementine.app
DEPENDS clementine
VERBATIM
)

View File

@ -40,7 +40,6 @@
#cmakedefine HAVE_SKYDRIVE
#cmakedefine HAVE_SPARKLE
#cmakedefine HAVE_SPOTIFY_DOWNLOADER
#cmakedefine HAVE_STATIC_SQLITE
#cmakedefine HAVE_UBUNTU_ONE
#cmakedefine HAVE_WIIMOTEDEV
#cmakedefine IMOBILEDEVICE_USES_UDIDS

View File

@ -44,6 +44,8 @@
# include "moodbar/moodbarloader.h"
#endif
bool Application::kIsPortable = false;
Application::Application(QObject* parent)
: QObject(parent),
tag_reader_client_(NULL),

View File

@ -52,6 +52,8 @@ class Application : public QObject {
Q_OBJECT
public:
static bool kIsPortable;
Application(QObject* parent = NULL);
~Application();

View File

@ -25,6 +25,8 @@
#include <boost/scope_exit.hpp>
#include <sqlite3.h>
#include <QCoreApplication>
#include <QDir>
#include <QLibrary>
@ -86,25 +88,6 @@ struct sqlite3_tokenizer_cursor {
/* Tokenizer implementations will typically add additional fields */
};
int (*Database::_sqlite3_value_type) (sqlite3_value*) = NULL;
sqlite_int64 (*Database::_sqlite3_value_int64) (sqlite3_value*) = NULL;
const uchar* (*Database::_sqlite3_value_text) (sqlite3_value*) = NULL;
void (*Database::_sqlite3_result_int64) (sqlite3_context*, sqlite_int64) = NULL;
void* (*Database::_sqlite3_user_data) (sqlite3_context*) = NULL;
int (*Database::_sqlite3_open) (const char*, sqlite3**) = NULL;
const char* (*Database::_sqlite3_errmsg) (sqlite3*) = NULL;
int (*Database::_sqlite3_close) (sqlite3*) = NULL;
sqlite3_backup* (*Database::_sqlite3_backup_init) (
sqlite3*, const char*, sqlite3*, const char*) = NULL;
int (*Database::_sqlite3_backup_step) (sqlite3_backup*, int) = NULL;
int (*Database::_sqlite3_backup_finish) (sqlite3_backup*) = NULL;
int (*Database::_sqlite3_backup_pagecount) (sqlite3_backup*) = NULL;
int (*Database::_sqlite3_backup_remaining) (sqlite3_backup*) = NULL;
bool Database::sStaticInitDone = false;
bool Database::sLoadedSqliteSymbols = false;
sqlite3_tokenizer_module* Database::sFTSTokenizer = NULL;
@ -218,11 +201,6 @@ int Database::FTSNext(
void Database::StaticInit() {
if (sStaticInitDone) {
return;
}
sStaticInitDone = true;
sFTSTokenizer = new sqlite3_tokenizer_module;
sFTSTokenizer->iVersion = 0;
sFTSTokenizer->xCreate = &Database::FTSCreate;
@ -230,86 +208,7 @@ void Database::StaticInit() {
sFTSTokenizer->xOpen = &Database::FTSOpen;
sFTSTokenizer->xNext = &Database::FTSNext;
sFTSTokenizer->xClose = &Database::FTSClose;
#ifdef HAVE_STATIC_SQLITE
// We statically link libqsqlite.dll on windows and mac so these symbols are already
// available
_sqlite3_value_type = sqlite3_value_type;
_sqlite3_value_int64 = sqlite3_value_int64;
_sqlite3_value_text = sqlite3_value_text;
_sqlite3_result_int64 = sqlite3_result_int64;
_sqlite3_user_data = sqlite3_user_data;
_sqlite3_open = sqlite3_open;
_sqlite3_errmsg = sqlite3_errmsg;
_sqlite3_close = sqlite3_close;
_sqlite3_backup_init = sqlite3_backup_init;
_sqlite3_backup_step = sqlite3_backup_step;
_sqlite3_backup_finish = sqlite3_backup_finish;
_sqlite3_backup_pagecount = sqlite3_backup_pagecount;
_sqlite3_backup_remaining = sqlite3_backup_remaining;
sLoadedSqliteSymbols = true;
return;
#else // HAVE_STATIC_SQLITE
QString plugin_path = QLibraryInfo::location(QLibraryInfo::PluginsPath) +
"/sqldrivers/libqsqlite";
QLibrary library(plugin_path);
if (!library.load()) {
qLog(Error) << "QLibrary::load() failed for " << plugin_path;
return;
}
_sqlite3_value_type = reinterpret_cast<int (*) (sqlite3_value*)>(
library.resolve("sqlite3_value_type"));
_sqlite3_value_int64 = reinterpret_cast<sqlite_int64 (*) (sqlite3_value*)>(
library.resolve("sqlite3_value_int64"));
_sqlite3_value_text = reinterpret_cast<const uchar* (*) (sqlite3_value*)>(
library.resolve("sqlite3_value_text"));
_sqlite3_result_int64 = reinterpret_cast<void (*) (sqlite3_context*, sqlite_int64)>(
library.resolve("sqlite3_result_int64"));
_sqlite3_user_data = reinterpret_cast<void* (*) (sqlite3_context*)>(
library.resolve("sqlite3_user_data"));
_sqlite3_open = reinterpret_cast<int (*) (const char*, sqlite3**)>(
library.resolve("sqlite3_open"));
_sqlite3_errmsg = reinterpret_cast<const char* (*) (sqlite3*)>(
library.resolve("sqlite3_errmsg"));
_sqlite3_close = reinterpret_cast<int (*) (sqlite3*)>(
library.resolve("sqlite3_close"));
_sqlite3_backup_init = reinterpret_cast<
sqlite3_backup* (*) (sqlite3*, const char*, sqlite3*, const char*)>(
library.resolve("sqlite3_backup_init"));
_sqlite3_backup_step = reinterpret_cast<int (*) (sqlite3_backup*, int)>(
library.resolve("sqlite3_backup_step"));
_sqlite3_backup_finish = reinterpret_cast<int (*) (sqlite3_backup*)>(
library.resolve("sqlite3_backup_finish"));
_sqlite3_backup_pagecount = reinterpret_cast<int (*) (sqlite3_backup*)>(
library.resolve("sqlite3_backup_pagecount"));
_sqlite3_backup_remaining = reinterpret_cast<int (*) (sqlite3_backup*)>(
library.resolve("sqlite3_backup_remaining"));
if (!_sqlite3_value_type ||
!_sqlite3_value_int64 ||
!_sqlite3_value_text ||
!_sqlite3_result_int64 ||
!_sqlite3_user_data ||
!_sqlite3_open ||
!_sqlite3_errmsg ||
!_sqlite3_close ||
!_sqlite3_backup_init ||
!_sqlite3_backup_step ||
!_sqlite3_backup_finish ||
!_sqlite3_backup_pagecount ||
!_sqlite3_backup_remaining) {
qLog(Fatal) << "Couldn't resolve sqlite symbols. Please compile "
"Clementine with -DSTATIC_SQLITE=ON.";
sLoadedSqliteSymbols = false;
} else {
sLoadedSqliteSymbols = true;
}
#endif
}
Database::Database(Application* app, QObject* parent, const QString& database_name)
@ -488,6 +387,20 @@ void Database::AttachDatabase(const QString& database_name,
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("ATTACH DATABASE :filename AS :alias", db);
q.bindValue(":filename", database.filename_);
q.bindValue(":alias", database_name);
if (!q.exec()) {
qFatal("Couldn't attach external database '%s'", database_name.toAscii().constData());
}
}
void Database::DetachDatabase(const QString& database_name) {
QMutexLocker l(&mutex_);
{
@ -713,10 +626,10 @@ void Database::DoBackup() {
}
bool Database::OpenDatabase(const QString& filename, sqlite3** connection) const {
int ret = _sqlite3_open(filename.toUtf8(), connection);
int ret = sqlite3_open(filename.toUtf8(), connection);
if (ret != 0) {
if (*connection) {
const char* error_message = _sqlite3_errmsg(*connection);
const char* error_message = sqlite3_errmsg(*connection);
qLog(Error) << "Failed to open database for backup:"
<< filename
<< error_message;
@ -739,8 +652,8 @@ void Database::BackupFile(const QString& filename) {
BOOST_SCOPE_EXIT((source_connection)(dest_connection)(task_id)(app_)) {
// Harmless to call sqlite3_close() with a NULL pointer.
_sqlite3_close(source_connection);
_sqlite3_close(dest_connection);
sqlite3_close(source_connection);
sqlite3_close(dest_connection);
app_->task_manager()->SetTaskFinished(task_id);
} BOOST_SCOPE_EXIT_END
@ -754,26 +667,26 @@ void Database::BackupFile(const QString& filename) {
return;
}
sqlite3_backup* backup = _sqlite3_backup_init(
sqlite3_backup* backup = sqlite3_backup_init(
dest_connection, "main",
source_connection, "main");
if (!backup) {
const char* error_message = _sqlite3_errmsg(dest_connection);
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);
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);
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);
sqlite3_backup_finish(backup);
}

View File

@ -74,6 +74,9 @@ class Database : public QObject {
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:
@ -140,27 +143,6 @@ class Database : public QObject {
void (*) (sqlite3_context*, int, sqlite3_value**),
void (*) (sqlite3_context*));
// Sqlite3 functions. These will be loaded from the sqlite3 plugin.
static int (*_sqlite3_value_type) (sqlite3_value*);
static sqlite_int64 (*_sqlite3_value_int64) (sqlite3_value*);
static const uchar* (*_sqlite3_value_text) (sqlite3_value*);
static void (*_sqlite3_result_int64) (sqlite3_context*, sqlite_int64);
static void* (*_sqlite3_user_data) (sqlite3_context*);
// These are necessary for SQLite backups.
static int (*_sqlite3_open) (const char*, sqlite3**);
static const char* (*_sqlite3_errmsg) (sqlite3*);
static int (*_sqlite3_close) (sqlite3*);
static sqlite3_backup* (*_sqlite3_backup_init) (
sqlite3*, const char*, sqlite3*, const char*);
static int (*_sqlite3_backup_step) (sqlite3_backup*, int);
static int (*_sqlite3_backup_finish) (sqlite3_backup*);
static int (*_sqlite3_backup_pagecount) (sqlite3_backup*);
static int (*_sqlite3_backup_remaining) (sqlite3_backup*);
static bool sStaticInitDone;
static bool sLoadedSqliteSymbols;
static sqlite3_tokenizer_module* sFTSTokenizer;
static int FTSCreate(int argc, const char* const* argv, sqlite3_tokenizer** tokenizer);

View File

@ -22,9 +22,10 @@
#include "mac_startup.h"
#include <QtDebug>
#include <QAction>
#include <QShortcut>
#include <QSignalMapper>
#include <QtDebug>
#ifdef QT_DBUS_LIB
# include <QtDBus>
@ -32,8 +33,8 @@
const char* GlobalShortcuts::kSettingsGroup = "Shortcuts";
GlobalShortcuts::GlobalShortcuts(QObject *parent)
: QObject(parent),
GlobalShortcuts::GlobalShortcuts(QWidget *parent)
: QWidget(parent),
gnome_backend_(NULL),
system_backend_(NULL),
use_gnome_(false),
@ -101,11 +102,17 @@ GlobalShortcuts::Shortcut GlobalShortcuts::AddShortcut(const QString& id, const
const QKeySequence& default_key) {
Shortcut shortcut;
shortcut.action = new QAction(name, this);
shortcut.action->setShortcut(QKeySequence::fromString(
settings_.value(id, default_key.toString()).toString()));
QKeySequence key_sequence = QKeySequence::fromString(
settings_.value(id, default_key.toString()).toString());
shortcut.action->setShortcut(key_sequence);
shortcut.id = id;
shortcut.default_key = default_key;
// Create application wide QShortcut to hide keyevents mapped to global
// shortcuts from widgets.
shortcut.shortcut = new QShortcut(key_sequence, this);
shortcut.shortcut->setContext(Qt::ApplicationShortcut);
shortcuts_[id] = shortcut;
return shortcut;

View File

@ -18,21 +18,22 @@
#ifndef GLOBALSHORTCUTS_H
#define GLOBALSHORTCUTS_H
#include <QObject>
#include <QKeySequence>
#include <QMap>
#include <QSettings>
#include <QWidget>
class QAction;
class QShortcut;
class GlobalShortcutBackend;
class QSignalMapper;
class GlobalShortcuts : public QObject {
class GlobalShortcuts : public QWidget {
Q_OBJECT
public:
GlobalShortcuts(QObject* parent = 0);
GlobalShortcuts(QWidget* parent = 0);
static const char* kSettingsGroup;
@ -40,6 +41,7 @@ public:
QString id;
QKeySequence default_key;
QAction* action;
QShortcut* shortcut;
};
QMap<QString, Shortcut> shortcuts() const { return shortcuts_; }

View File

@ -451,4 +451,12 @@ void EnableFullScreen(const QWidget& main_window) {
[window setCollectionBehavior: kFullScreenPrimary];
}
float GetDevicePixelRatio(QWidget* widget) {
NSView* view = reinterpret_cast<NSView*>(widget->winId());
if ([[view window] respondsToSelector: @selector(backingScaleFactor)]) {
return [[view window] backingScaleFactor];
}
return 1.0f;
}
} // namespace mac

View File

@ -29,5 +29,6 @@ namespace mac {
QKeySequence KeySequenceFromNSEvent(NSEvent* event);
void DumpDictionary(CFDictionaryRef dict);
float GetDevicePixelRatio(QWidget* widget);
}

View File

@ -104,6 +104,7 @@ Mpris2::Mpris2(Application* app, Mpris1* mpris1, QObject* parent)
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
@ -136,12 +137,16 @@ void Mpris2::RepeatModeChanged() {
}
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()
<< "org.mpris.MediaPlayer2.Player"
<< mprisEntity
<< map
<< QStringList();
msg.setArguments(args);
@ -543,6 +548,10 @@ void Mpris2::ActivatePlaylist(const QDBusObjectPath& playlist_id) {
if (!ok) {
return;
}
if (!app_->playlist_manager()->IsPlaylistOpen(p)) {
qLog(Error) << "Playlist isn't opened!";
return;
}
app_->playlist_manager()->SetActivePlaylist(p);
app_->player()->Next();
}
@ -572,4 +581,8 @@ void Mpris2::PlaylistChanged(Playlist* playlist) {
emit PlaylistChanged(mpris_playlist);
}
void Mpris2::PlaylistCollectionChanged(Playlist* playlist) {
EmitNotification("PlaylistCount", "", "org.mpris.MediaPlayer2.Playlists");
}
} // namespace mpris

View File

@ -202,10 +202,12 @@ private slots:
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;

View File

@ -69,7 +69,7 @@ void Player::Init() {
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)));
SLOT(EngineMetadataReceived(Engine::SimpleMetaBundle)));
engine_->SetVolume(settings_.value("volume", 50).toInt());

View File

@ -19,6 +19,8 @@
#include <algorithm>
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QLatin1Literal>
@ -48,6 +50,7 @@
# include <libmtp.h>
#endif
#include "core/application.h"
#include "core/logging.h"
#include "core/messagehandler.h"
#include "core/mpris_common.h"
@ -314,7 +317,16 @@ void Song::set_score(int v) { d->score_ = qBound(0, v, 100); }
void Song::set_cue_path(const QString& v) { d->cue_path_ = v; }
void Song::set_unavailable(bool v) { d->unavailable_ = v; }
void Song::set_etag(const QString& etag) { d->etag_ = etag; }
void Song::set_url(const QUrl& v) { d->url_ = v; }
void Song::set_url(const QUrl& v) {
if (Application::kIsPortable) {
QUrl base = QUrl::fromLocalFile(QCoreApplication::applicationDirPath() + "/");
d->url_ = base.resolved(v);
} else {
d->url_ = v;
}
}
void Song::set_basefilename(const QString& v) { d->basefilename_ = v; }
void Song::set_directory_id(int v) { d->directory_id_ = v; }
@ -414,7 +426,7 @@ void Song::InitFromProtobuf(const pb::tagreader::SongMetadata& pb) {
set_length_nanosec(pb.length_nanosec());
d->bitrate_ = pb.bitrate();
d->samplerate_ = pb.samplerate();
d->url_ = QUrl::fromEncoded(QByteArray(pb.url().data(), pb.url().size()));
set_url(QUrl::fromEncoded(QByteArray(pb.url().data(), pb.url().size())));
d->basefilename_ = QStringFromStdString(pb.basefilename());
d->mtime_ = pb.mtime();
d->ctime_ = pb.ctime();
@ -430,6 +442,8 @@ void Song::InitFromProtobuf(const pb::tagreader::SongMetadata& pb) {
if (pb.has_rating()) {
d->rating_ = pb.rating();
}
InitArtManual();
}
void Song::ToProtobuf(pb::tagreader::SongMetadata* pb) const {
@ -473,7 +487,6 @@ void Song::InitFromQuery(const SqlRow& q, bool reliable_metadata, int col) {
d->init_from_file_ = reliable_metadata;
#define tostr(n) (q.value(n).isNull() ? QString::null : q.value(n).toString())
#define tobytearray(n)(q.value(n).isNull() ? QByteArray() : q.value(n).toByteArray())
#define toint(n) (q.value(n).isNull() ? -1 : q.value(n).toInt())
#define tolonglong(n) (q.value(n).isNull() ? -1 : q.value(n).toLongLong())
#define tofloat(n) (q.value(n).isNull() ? -1 : q.value(n).toDouble())
@ -496,7 +509,7 @@ void Song::InitFromQuery(const SqlRow& q, bool reliable_metadata, int col) {
d->samplerate_ = toint(col + 14);
d->directory_id_ = toint(col + 15);
d->url_ = QUrl::fromEncoded(tobytearray(col + 16));
set_url(QUrl::fromEncoded(tostr(col + 16).toUtf8()));
d->basefilename_ = QFileInfo(d->url_.toLocalFile()).fileName();
d->mtime_ = toint(col + 17);
d->ctime_ = toint(col + 18);
@ -534,6 +547,8 @@ void Song::InitFromQuery(const SqlRow& q, bool reliable_metadata, int col) {
d->performer_ = tostr(col + 38);
d->grouping_ = tostr(col + 39);
InitArtManual();
#undef tostr
#undef toint
#undef tolonglong
@ -541,7 +556,7 @@ void Song::InitFromQuery(const SqlRow& q, bool reliable_metadata, int col) {
}
void Song::InitFromFilePartial(const QString& filename) {
d->url_ = QUrl::fromLocalFile(filename);
set_url(QUrl::fromLocalFile(filename));
// We currently rely on filename suffix to know if it's a music file or not.
// TODO: I know this is not satisfying, but currently, we rely on TagLib
// which seems to have the behavior (filename checks). Someday, it would be
@ -558,6 +573,17 @@ void Song::InitFromFilePartial(const QString& filename) {
}
}
void Song::InitArtManual() {
// If we don't have an art, check if we have one in the cache
if (d->art_manual_.isEmpty() && d->art_automatic_.isEmpty()) {
QString filename(Utilities::Sha1CoverHash(d->artist_, d->album_).toHex() + ".jpg");
QString path(AlbumCoverLoader::ImageCacheDir() + "/" + filename);
if (QFile::exists(path)) {
d->art_manual_ = path;
}
}
}
#ifdef HAVE_LIBLASTFM
void Song::InitFromLastFM(const lastfm::Track& track) {
d->valid_ = true;
@ -605,9 +631,9 @@ void Song::InitFromLastFM(const lastfm::Track& track) {
filename.replace(':', '/');
if (prefix.contains("://")) {
d->url_ = QUrl(prefix + filename);
set_url(QUrl(prefix + filename));
} else {
d->url_ = QUrl::fromLocalFile(prefix + filename);
set_url(QUrl::fromLocalFile(prefix + filename));
}
d->basefilename_ = QFileInfo(filename).fileName();
@ -766,7 +792,14 @@ void Song::BindToQuery(QSqlQuery *query) const {
query->bindValue(":samplerate", intval(d->samplerate_));
query->bindValue(":directory", notnullintval(d->directory_id_));
query->bindValue(":filename", d->url_.toEncoded());
if (Application::kIsPortable
&& Utilities::UrlOnSameDriveAsClementine(d->url_)) {
query->bindValue(":filename", Utilities::GetRelativePathToClementineBin(d->url_));
} else {
query->bindValue(":filename", d->url_.toEncoded());
}
query->bindValue(":mtime", notnullintval(d->mtime_));
query->bindValue(":ctime", notnullintval(d->ctime_));
query->bindValue(":filesize", notnullintval(d->filesize_));

View File

@ -106,6 +106,7 @@ class Song {
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

View File

@ -33,16 +33,17 @@
#include "config.h"
#include "core/concurrentrun.h"
#include "core/logging.h"
#include "core/song.h"
#include "core/player.h"
#include "core/signalchecker.h"
#include "core/song.h"
#include "core/tagreaderclient.h"
#include "core/timeconstants.h"
#include "internet/fixlastfm.h"
#include "internet/internetmodel.h"
#include "library/librarybackend.h"
#include "library/sqlrow.h"
#include "playlistparsers/parserbase.h"
#include "playlistparsers/cueparser.h"
#include "playlistparsers/parserbase.h"
#include "playlistparsers/playlistparser.h"
#include "podcasts/podcastparser.h"
#include "podcasts/podcastservice.h"
@ -52,7 +53,9 @@
QSet<QString> SongLoader::sRawUriSchemes;
const int SongLoader::kDefaultTimeout = 5000;
SongLoader::SongLoader(LibraryBackendInterface* library, QObject *parent)
SongLoader::SongLoader(LibraryBackendInterface* library,
const Player* player,
QObject *parent)
: QObject(parent),
timeout_timer_(new QTimer(this)),
playlist_parser_(new PlaylistParser(library, this)),
@ -63,7 +66,8 @@ SongLoader::SongLoader(LibraryBackendInterface* library, QObject *parent)
success_(false),
parser_(NULL),
is_podcast_(false),
library_(library)
library_(library),
player_(player)
{
if (sRawUriSchemes.isEmpty()) {
sRawUriSchemes << "udp" << "mms" << "mmsh" << "mmst" << "mmsu" << "rtsp"
@ -91,9 +95,10 @@ SongLoader::Result SongLoader::Load(const QUrl& url) {
return LoadLocal(url_.toLocalFile());
}
if (sRawUriSchemes.contains(url_.scheme())) {
// The URI scheme indicates that it can't possibly be a playlist, so add
// it as a raw stream.
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;
}

View File

@ -33,13 +33,15 @@
class CueParser;
class LibraryBackendInterface;
class ParserBase;
class Player;
class PlaylistParser;
class PodcastParser;
class SongLoader : public QObject {
Q_OBJECT
public:
SongLoader(LibraryBackendInterface* library, QObject* parent = 0);
SongLoader(LibraryBackendInterface* library, const Player* player,
QObject* parent = 0);
~SongLoader();
enum Result {
@ -130,6 +132,7 @@ private:
bool is_podcast_;
QByteArray buffer_;
LibraryBackendInterface* library_;
const Player* player_;
boost::shared_ptr<GstElement> pipeline_;

View File

@ -25,6 +25,7 @@
#include <QDateTime>
#include <QDesktopServices>
#include <QDir>
#include <QFile>
#include <QIODevice>
#include <QMetaEnum>
#include <QMouseEvent>
@ -37,6 +38,7 @@
#include <QWidget>
#include <QXmlStreamReader>
#include "core/application.h"
#include "core/logging.h"
#include "timeconstants.h"
@ -298,6 +300,9 @@ QString ColorToRgba(const QColor& c) {
QString GetConfigPath(ConfigPath config) {
switch (config) {
case Path_Root: {
if (Application::kIsPortable) {
return QString("%1/data").arg(QCoreApplication::applicationDirPath());
}
#ifdef Q_OS_DARWIN
return mac::GetApplicationSupportPath() + "/" + QCoreApplication::organizationName();
#else
@ -307,6 +312,9 @@ QString GetConfigPath(ConfigPath config) {
break;
case Path_CacheRoot: {
if (Application::kIsPortable) {
return GetConfigPath(Path_Root) + "/cache";
}
#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
char* xdg = getenv("XDG_CACHE_HOME");
if (!xdg || !*xdg) {
@ -448,6 +456,31 @@ QByteArray Sha256(const QByteArray& data) {
return ret;
}
// File must not be open and will be closed afterwards!
QByteArray Sha1File(QFile &file) {
file.open(QIODevice::ReadOnly);
QCryptographicHash hash(QCryptographicHash::Sha1);
QByteArray data;
while(!file.atEnd()) {
data = file.read(1000000); // 1 mib
hash.addData(data.data(), data.length());
data.clear();
}
file.close();
return hash.result();
}
QByteArray Sha1CoverHash(const QString& artist, const QString& album) {
QCryptographicHash hash(QCryptographicHash::Sha1);
hash.addData(artist.toLower().toUtf8().constData());
hash.addData(album.toLower().toUtf8().constData());
return hash.result();
}
QString PrettySize(const QSize& size) {
return QString::number(size.width()) + "x" +
QString::number(size.height());
@ -613,6 +646,27 @@ QString SystemLanguageName() {
return system_language;
}
bool UrlOnSameDriveAsClementine(const QUrl &url) {
if (url.scheme() != "file")
return false;
#ifdef Q_OS_WIN
QUrl appUrl = QUrl::fromLocalFile(QCoreApplication::applicationDirPath());
if (url.toLocalFile().left(1) == appUrl.toLocalFile().left(1))
return true;
else
return false;
#else
// Non windows systems have always a / in the path
return true;
#endif
}
QUrl GetRelativePathToClementineBin(const QUrl& url) {
QDir appPath(QCoreApplication::applicationDirPath());
return QUrl::fromLocalFile(appPath.relativeFilePath(url.toLocalFile()));
}
} // namespace Utilities

View File

@ -19,6 +19,7 @@
#define UTILITIES_H
#include <QColor>
#include <QFile>
#include <QLocale>
#include <QCryptographicHash>
#include <QSize>
@ -66,6 +67,8 @@ namespace Utilities {
QByteArray HmacSha256(const QByteArray& key, const QByteArray& data);
QByteArray HmacSha1(const QByteArray& key, const QByteArray& data);
QByteArray Sha256(const QByteArray& data);
QByteArray Sha1File(QFile& file);
QByteArray Sha1CoverHash(const QString& artist, const QString& album);
// Picks an unused ephemeral port number. Doesn't hold the port open so
@ -104,6 +107,12 @@ namespace Utilities {
QStringList Prepend(const QString& text, const QStringList& list);
QStringList Updateify(const QStringList& list);
// Check if two urls are on the same drive (mainly for windows)
bool UrlOnSameDriveAsClementine(const QUrl& url);
// Get relative path to clementine binary
QUrl GetRelativePathToClementineBin(const QUrl& url);
enum ConfigPath {
Path_Root,

View File

@ -67,9 +67,9 @@ ConnectedDevice::~ConnectedDevice() {
void ConnectedDevice::InitBackendDirectory(
const QString& mount_point, bool first_time, bool rewrite_path) {
if (first_time)
if (first_time || backend_->GetAllDirectories().isEmpty()) {
backend_->AddDirectory(mount_point);
else {
} else {
if (rewrite_path) {
// This is a bit of a hack. The device might not be mounted at the same
// path each time, so if it's different we have to munge all the paths in

View File

@ -0,0 +1,56 @@
/* This file is part of Clementine.
Copyright 2012, 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 "deviceviewcontainer.h"
#include "ui_deviceviewcontainer.h"
#include "ui/iconloader.h"
DeviceViewContainer::DeviceViewContainer(QWidget *parent)
: QWidget(parent),
ui_(new Ui::DeviceViewContainer),
loaded_icons_(false) {
ui_->setupUi(this);
QPalette palette(ui_->windows_is_broken_frame->palette());
palette.setColor(QPalette::Background, QColor(255, 255, 222));
ui_->windows_is_broken_frame->setPalette(palette);
#ifdef Q_OS_WIN
ui_->windows_is_broken_frame->show();
#else
ui_->windows_is_broken_frame->hide();
#endif
}
DeviceViewContainer::~DeviceViewContainer() {
delete ui_;
}
void DeviceViewContainer::showEvent(QShowEvent* e) {
if (!loaded_icons_) {
loaded_icons_ = true;
ui_->close_frame_button->setIcon(IconLoader::Load("edit-delete"));
ui_->warning_icon->setPixmap(IconLoader::Load("dialog-warning").pixmap(22));
}
QWidget::showEvent(e);
}
DeviceView* DeviceViewContainer::view() const {
return ui_->view;
}

View File

@ -0,0 +1,46 @@
/* This file is part of Clementine.
Copyright 2012, 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 DEVICEVIEWCONTAINER_H
#define DEVICEVIEWCONTAINER_H
#include <QWidget>
namespace Ui {
class DeviceViewContainer;
}
class DeviceView;
class DeviceViewContainer : public QWidget {
Q_OBJECT
public:
explicit DeviceViewContainer(QWidget* parent = 0);
~DeviceViewContainer();
DeviceView* view() const;
protected:
void showEvent(QShowEvent*);
private:
Ui::DeviceViewContainer* ui_;
bool loaded_icons_;
};
#endif // DEVICEVIEWCONTAINER_H

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DeviceViewContainer</class>
<widget class="QWidget" name="DeviceViewContainer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>391</width>
<height>396</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="windows_is_broken_frame">
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="warning_icon"/>
</item>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>iPods and USB devices currently don't work on Windows. Sorry!</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="close_frame_button"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="DeviceView" name="view" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>DeviceView</class>
<extends>QWidget</extends>
<header>devices/deviceview.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>close_frame_button</sender>
<signal>clicked()</signal>
<receiver>windows_is_broken_frame</receiver>
<slot>hide()</slot>
<hints>
<hint type="sourcelabel">
<x>362</x>
<y>31</y>
</hint>
<hint type="destinationlabel">
<x>369</x>
<y>40</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -145,8 +145,14 @@ QStandardItem* GlobalSearchModel::BuildContainers(
sort_text = display_text;
break;
case LibraryModel::GroupBy_Bitrate:
display_text = QString(s.bitrate(), 1);
sort_text = display_text;
break;
case LibraryModel::GroupBy_None:
return parent;
}
// Find a container for this level

View File

@ -72,7 +72,7 @@ void BoxService::EnsureConnected() {
void BoxService::Connect() {
OAuthenticator* oauth = new OAuthenticator(
kClientId, kClientSecret, OAuthenticator::RedirectStyle::LOCALHOST, this);
kClientId, kClientSecret, OAuthenticator::RedirectStyle::REMOTE, this);
if (!refresh_token().isEmpty()) {
oauth->RefreshAuthorisation(
kOAuthTokenEndpoint, refresh_token());

View File

@ -188,7 +188,7 @@ QString CloudFileService::GuessMimeTypeForFile(const QString& filename) const {
return "audio/mpeg";
} else if (filename.endsWith(".m4a")) {
return "audio/mpeg";
} else if (filename.endsWith(".ogg")) {
} else if (filename.endsWith(".ogg") || filename.endsWith(".opus")) {
return "application/ogg";
} else if (filename.endsWith(".flac")) {
return "application/x-flac";

View File

@ -1,6 +1,7 @@
#include "dropboxservice.h"
#include <QFileInfo>
#include <QTimer>
#include <qjson/parser.h>
@ -27,6 +28,8 @@ static const char* kMediaEndpoint =
"https://api.dropbox.com/1/media/dropbox/";
static const char* kDeltaEndpoint =
"https://api.dropbox.com/1/delta";
static const char* kLongPollEndpoint =
"https://api-notify.dropbox.com/1/longpoll_delta";
} // namespace
@ -136,6 +139,12 @@ void DropboxService::RequestFileListFinished(QNetworkReply* reply) {
continue;
}
// Workaround: Since Dropbox doesn't recognize Opus files and thus treats them
// as application/octet-stream, we overwrite the mime type here
if (metadata["mime_type"].toString() == "application/octet-stream" &&
url.toString().endsWith(".opus"))
metadata["mime_type"] = GuessMimeTypeForFile(url.toString());
if (ShouldIndexFile(url, metadata["mime_type"].toString())) {
QNetworkReply* reply = FetchContentUrl(url);
NewClosure(reply, SIGNAL(finished()),
@ -146,6 +155,41 @@ void DropboxService::RequestFileListFinished(QNetworkReply* reply) {
if (response.contains("has_more") && response["has_more"].toBool()) {
RequestFileList();
} else {
// Long-poll wait for changes.
LongPollDelta();
}
}
void DropboxService::LongPollDelta() {
QSettings s;
s.beginGroup(kSettingsGroup);
QUrl request_url = QUrl(QString(kLongPollEndpoint));
if (s.contains("cursor")) {
request_url.addQueryItem("cursor", s.value("cursor").toString());
}
QNetworkRequest request(request_url);
request.setRawHeader("Authorization", GenerateAuthorisationHeader());
QNetworkReply* reply = network_->get(request);
NewClosure(reply, SIGNAL(finished()),
this, SLOT(LongPollFinished(QNetworkReply*)), reply);
}
void DropboxService::LongPollFinished(QNetworkReply* reply) {
reply->deleteLater();
QJson::Parser parser;
QVariantMap response = parser.parse(reply).toMap();
if (response["changes"].toBool()) {
// New changes, we should request deltas again.
qLog(Debug) << "Detected new dropbox changes; fetching...";
RequestFileList();
} else {
bool ok = false;
int backoff_secs = response["backoff"].toInt(&ok);
backoff_secs = ok ? backoff_secs : 0;
QTimer::singleShot(backoff_secs * 1000, this, SLOT(LongPollDelta()));
}
}

View File

@ -31,6 +31,8 @@ class DropboxService : public CloudFileService {
private slots:
void RequestFileListFinished(QNetworkReply* reply);
void FetchContentUrlFinished(QNetworkReply* reply, const QVariantMap& file);
void LongPollFinished(QNetworkReply* reply);
void LongPollDelta();
private:
void RequestFileList();

View File

@ -1651,10 +1651,11 @@ QVariantMap GroovesharkService::ExtractResult(QNetworkReply* reply) {
QVariantList::iterator it;
for (it = errors.begin(); it != errors.end(); ++it) {
QVariantMap error = (*it).toMap();
qLog(Error) << "Grooveshark error: " << error["message"].toString();
qLog(Error) << "Grooveshark error: (" << error["code"].toInt() <<") " << error["message"].toString();
switch (error["code"].toInt()) {
case 100: // User auth required
case 102: // User premium required
case 300: // Session required
// These errors can happen if session_id is obsolete (e.g. we haven't use
// it for more than two weeks): force the user to login again
Logout();
@ -1678,7 +1679,12 @@ Song GroovesharkService::ExtractSong(const QVariantMap& result_song) {
Song song;
if (!result_song.isEmpty()) {
int song_id = result_song["SongID"].toInt();
QString song_name = result_song["SongName"].toString();
QString song_name;
if (result_song.contains("SongName")) {
song_name = result_song["SongName"].toString();
} else {
song_name = result_song["Name"].toString();
}
int artist_id = result_song["ArtistID"].toInt();
QString artist_name = result_song["ArtistName"].toString();
int album_id = result_song["AlbumID"].toInt();

View File

@ -36,7 +36,7 @@ void OAuthenticator::StartAuthorisation(
if (redirect_style_ == RedirectStyle::REMOTE) {
const int port = server->url().port();
redirect_url = QUrl(
QString("http://data.clementine-player.org/skydrive?port=%1").arg(port));
QString("https://clementine-data.appspot.com/skydrive?port=%1").arg(port));
} else {
redirect_url = server->url();
}

View File

@ -17,8 +17,8 @@ static const char* kServiceName = "Skydrive";
static const char* kServiceId = "skydrive";
static const char* kSettingsGroup = "Skydrive";
static const char* kClientId = "00000000400E7C78";
static const char* kClientSecret = "B0KLZjEgC5SpW0KknrsBFwlaKmGThaAk";
static const char* kClientId = "0000000040111F16";
static const char* kClientSecret = "w2ClguSX0jG56cBl1CeUniypTBRjXt2Z";
static const char* kOAuthEndpoint =
"https://login.live.com/oauth20_authorize.srf";

View File

@ -36,8 +36,9 @@ GroupByDialog::GroupByDialog(QWidget *parent)
mapping_.insert(Mapping(LibraryModel::GroupBy_Genre, 6));
mapping_.insert(Mapping(LibraryModel::GroupBy_Year, 7));
mapping_.insert(Mapping(LibraryModel::GroupBy_YearAlbum, 8));
mapping_.insert(Mapping(LibraryModel::GroupBy_Performer, 9));
mapping_.insert(Mapping(LibraryModel::GroupBy_Grouping, 10));
mapping_.insert(Mapping(LibraryModel::GroupBy_Bitrate, 9));
mapping_.insert(Mapping(LibraryModel::GroupBy_Performer, 10));
mapping_.insert(Mapping(LibraryModel::GroupBy_Grouping, 11));
connect(ui_->button_box->button(QDialogButtonBox::Reset), SIGNAL(clicked()),
SLOT(Reset()));

View File

@ -88,6 +88,11 @@
<string>Year - Album</string>
</property>
</item>
<item>
<property name="text">
<string>Bitrate</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
@ -144,6 +149,11 @@
<string>Year - Album</string>
</property>
</item>
<item>
<property name="text">
<string>Bitrate</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
@ -200,6 +210,11 @@
<string>Year - Album</string>
</property>
</item>
<item>
<property name="text">
<string>Bitrate</string>
</property>
</item>
</widget>
</item>
</layout>

View File

@ -18,9 +18,11 @@
#include "librarybackend.h"
#include "libraryquery.h"
#include "sqlrow.h"
#include "core/application.h"
#include "core/database.h"
#include "core/scopedtransaction.h"
#include "core/tagreaderclient.h"
#include "core/utilities.h"
#include "smartplaylists/search.h"
#include <QCoreApplication>
@ -191,13 +193,20 @@ void LibraryBackend::UpdateTotalSongCount() {
void LibraryBackend::AddDirectory(const QString& path) {
QString canonical_path = QFileInfo(path).canonicalFilePath();
QString db_path = canonical_path;
if (Application::kIsPortable
&& Utilities::UrlOnSameDriveAsClementine(QUrl::fromLocalFile(canonical_path))) {
db_path = Utilities::GetRelativePathToClementineBin(QUrl::fromLocalFile(db_path)).toLocalFile();
qLog(Debug) << "db_path" << db_path;
}
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
QSqlQuery q(QString("INSERT INTO %1 (path, subdirs)"
" VALUES (:path, 1)").arg(dirs_table_), db);
q.bindValue(":path", canonical_path);
q.bindValue(":path", db_path);
q.exec();
if (db_->CheckErrors(q)) return;

View File

@ -17,6 +17,7 @@
#include "librarydirectorymodel.h"
#include "librarybackend.h"
#include "core/application.h"
#include "core/filesystemmusicstorage.h"
#include "core/musicstorage.h"
#include "core/utilities.h"
@ -35,7 +36,14 @@ LibraryDirectoryModel::~LibraryDirectoryModel() {
}
void LibraryDirectoryModel::DirectoryDiscovered(const Directory &dir) {
QStandardItem* item = new QStandardItem(dir.path);
QStandardItem* item;
if (Application::kIsPortable
&& Utilities::UrlOnSameDriveAsClementine(QUrl::fromLocalFile(dir.path))) {
item = new QStandardItem(Utilities::GetRelativePathToClementineBin(
QUrl::fromLocalFile(dir.path)).toLocalFile());
} else {
item = new QStandardItem(dir.path);
}
item->setData(dir.id, kIdRole);
item->setIcon(dir_icon_);
storage_ << boost::shared_ptr<MusicStorage>(new FilesystemMusicStorage(dir.path));

View File

@ -212,6 +212,10 @@ void LibraryFilterWidget::SetQueryMode(QueryOptions::QueryMode query_mode) {
model_->SetFilterQueryMode(query_mode);
}
void LibraryFilterWidget::ShowInLibrary(const QString& search) {
ui_->filter->setText(search);
}
void LibraryFilterWidget::SetAgeFilterEnabled(bool enabled) {
filter_age_menu_->setEnabled(enabled);
}

View File

@ -56,6 +56,7 @@ class LibraryFilterWidget : public QWidget {
void SetDelayBehaviour(DelayBehaviour behaviour) { delay_behaviour_ = behaviour; }
void SetAgeFilterEnabled(bool enabled);
void SetGroupByEnabled(bool enabled);
void ShowInLibrary(const QString& search);
QMenu* menu() const { return library_menu_; }
void AddMenuAction(QAction* action);

View File

@ -190,6 +190,7 @@ void LibraryModel::SongsDiscovered(const SongList& songs) {
case GroupBy_YearAlbum:
key = PrettyYearAlbum(qMax(0, song.year()), song.album()); break;
case GroupBy_FileType: key = song.filetype(); break;
case GroupBy_Bitrate: key = song.bitrate(); break;
case GroupBy_None:
qLog(Error) << "GroupBy_None";
break;
@ -280,6 +281,9 @@ QString LibraryModel::DividerKey(GroupBy type, LibraryItem* item) const {
case GroupBy_YearAlbum:
return SortTextForYear(item->metadata.year());
case GroupBy_Bitrate:
return SortTextForBitrate(item->metadata.bitrate());
case GroupBy_None:
return QString();
}
@ -313,6 +317,11 @@ QString LibraryModel::DividerDisplayText(GroupBy type, const QString& key) const
return tr("Unknown");
return QString::number(key.toInt()); // To remove leading 0s
case GroupBy_Bitrate:
if (key == "000")
return tr("Unknown");
return QString::number(key.toInt()); // To remove leading 0s
case GroupBy_None:
// fallthrough
;
@ -739,6 +748,9 @@ void LibraryModel::InitQuery(GroupBy type, LibraryQuery* q) {
case GroupBy_AlbumArtist:
q->SetColumnSpec("DISTINCT effective_albumartist");
break;
case GroupBy_Bitrate:
q->SetColumnSpec("DISTINCT bitrate");
break;
case GroupBy_None:
q->SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
break;
@ -796,6 +808,9 @@ void LibraryModel::FilterQuery(GroupBy type, LibraryItem* item, LibraryQuery* q)
case GroupBy_FileType:
q->AddWhere("filetype", item->metadata.filetype());
break;
case GroupBy_Bitrate:
q->AddWhere("bitrate", item->key);
break;
case GroupBy_None:
qLog(Error) << "Unknown GroupBy type" << type << "used in filter";
break;
@ -825,6 +840,7 @@ LibraryItem* LibraryModel::ItemFromQuery(GroupBy type,
int container_level) {
LibraryItem* item = InitItem(type, signal, parent, container_level);
int year = 0;
int bitrate = 0;
switch (type) {
case GroupBy_Artist:
@ -863,6 +879,12 @@ LibraryItem* LibraryModel::ItemFromQuery(GroupBy type,
item->key = item->metadata.TextForFiletype();
break;
case GroupBy_Bitrate:
bitrate = qMax(0, row.value(0).toInt());
item->key = QString::number(bitrate);
item->sort_text = SortTextForBitrate(bitrate) + " ";
break;
case GroupBy_None:
item->metadata.InitFromQuery(row, true);
item->key = item->metadata.title();
@ -881,6 +903,7 @@ LibraryItem* LibraryModel::ItemFromSong(GroupBy type,
int container_level) {
LibraryItem* item = InitItem(type, signal, parent, container_level);
int year = 0;
int bitrate = 0;
switch (type) {
case GroupBy_Artist:
@ -918,6 +941,12 @@ LibraryItem* LibraryModel::ItemFromSong(GroupBy type,
item->key = s.TextForFiletype();
break;
case GroupBy_Bitrate:
bitrate = qMax(0, s.bitrate());
item->key = QString::number(bitrate);
item->sort_text = SortTextForBitrate(bitrate) + " ";
break;
case GroupBy_None:
item->metadata = s;
item->key = s.title();
@ -1004,6 +1033,12 @@ QString LibraryModel::SortTextForYear(int year) {
return QString("0").repeated(qMax(0, 4 - str.length())) + str;
}
QString LibraryModel::SortTextForBitrate(int bitrate) {
QString str = QString::number(bitrate);
return QString("0").repeated(qMax(0, 3 - str.length())) + str;
}
QString LibraryModel::SortTextForSong(const Song& song) {
QString ret = QString::number(qMax(0, song.disc()) * 1000 + qMax(0, song.track()));
ret.prepend(QString("0").repeated(6 - ret.length()));

View File

@ -82,6 +82,7 @@ class LibraryModel : public SimpleTreeModel<LibraryItem> {
GroupBy_FileType = 8,
GroupBy_Performer = 9,
GroupBy_Grouping = 10,
GroupBy_Bitrate = 11,
};
struct Grouping {
@ -159,6 +160,7 @@ class LibraryModel : public SimpleTreeModel<LibraryItem> {
static QString SortText(QString text);
static QString SortTextForArtist(QString artist);
static QString SortTextForYear(int year);
static QString SortTextForBitrate(int bitrate);
static QString SortTextForSong(const Song& song);
signals:

View File

@ -39,19 +39,37 @@ LibraryQuery::LibraryQuery(const QueryOptions& options)
// expected with sqlite's FTS3:
// 1) Append * to all tokens.
// 2) Prefix "fts" to column names.
// 3) Remove colons which don't correspond to column names.
// Split on whitespace
QStringList tokens(options.filter().split(QRegExp("\\s+")));
QStringList tokens(options.filter().split(
QRegExp("\\s+"), QString::SkipEmptyParts));
QString query;
foreach (QString token, tokens) {
token.remove('(');
token.remove(')');
token.remove('"');
token.replace('-', ' ');
if (token.contains(':'))
query += "fts" + token + "* ";
else
if (token.contains(':')) {
// Only prefix fts if the token is a valid column name.
if (Song::kFtsColumns.contains("fts" + token.section(':', 0, 0),
Qt::CaseInsensitive)) {
// Account for multiple colons.
QString columntoken = token.section(
':', 0, 0, QString::SectionIncludeTrailingSep);
QString subtoken = token.section(':', 1, -1);
subtoken.replace(":", " ");
subtoken = subtoken.trimmed();
query += "fts" + columntoken + subtoken + "* ";
} else {
token.replace(":", " ");
token = token.trimmed();
query += token + "* ";
}
} else {
query += token + "* ";
}
}
where_clauses_ << "fts.%fts_table_noprefix MATCH ?";

View File

@ -55,11 +55,13 @@
#include "qtsinglecoreapplication.h"
#include <QDir>
#include <QFont>
#include <QLibraryInfo>
#include <QNetworkProxyFactory>
#include <QSslSocket>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSysInfo>
#include <QTextCodec>
#include <QTranslator>
#include <QtConcurrentRun>
@ -104,10 +106,8 @@ using boost::scoped_ptr;
#endif
// Load sqlite plugin on windows and mac.
#ifdef HAVE_STATIC_SQLITE
# include <QtPlugin>
Q_IMPORT_PLUGIN(qsqlite)
#endif
#include <QtPlugin>
Q_IMPORT_PLUGIN(qsqlite)
void LoadTranslation(const QString& prefix, const QString& path,
const QString& language) {
@ -225,6 +225,18 @@ void ParseAProto() {
message.ParseFromArray(data.constData(), data.size());
}
void CheckPortable() {
QFile f(QApplication::applicationDirPath() + QDir::separator() + "data");
qLog(Debug) << f.fileName();
if (f.exists()) {
// We are portable. Set the bool and change the qsettings path
Application::kIsPortable = true;
QSettings::setDefaultFormat(QSettings::IniFormat);
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, f.fileName());
}
}
int main(int argc, char *argv[]) {
if (CrashReporting::SendCrashReport(argc, argv)) {
return 0;
@ -236,6 +248,12 @@ int main(int argc, char *argv[]) {
// 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
QCoreApplication::setApplicationName("Clementine");
@ -243,11 +261,6 @@ int main(int argc, char *argv[]) {
QCoreApplication::setOrganizationName("Clementine");
QCoreApplication::setOrganizationDomain("clementine-player.org");
#ifdef Q_OS_DARWIN
// Must happen after QCoreApplication::setOrganizationName().
setenv("XDG_CONFIG_HOME", Utilities::GetConfigPath(Utilities::Path_Root).toLocal8Bit().constData(), 1);
#endif
// 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
@ -256,12 +269,6 @@ int main(int argc, char *argv[]) {
RegisterMetaTypes();
#ifdef HAVE_LIBLASTFM
lastfm::ws::ApiKey = LastFMService::kApiKey;
lastfm::ws::SharedSecret = LastFMService::kSecret;
lastfm::setNetworkAccessManager(new NetworkAccessManager);
#endif
CommandlineOptions options(argc, argv);
{
@ -270,6 +277,7 @@ int main(int argc, char *argv[]) {
// This MUST be done before parsing the commandline options so QTextCodec
// gets the right system locale for filenames.
QtSingleCoreApplication a(argc, argv);
CheckPortable();
crash_reporting.SetApplicationPath(a.applicationFilePath());
// Parse commandline options - need to do this before starting the
@ -288,6 +296,17 @@ int main(int argc, char *argv[]) {
}
}
#ifdef Q_OS_DARWIN
// Must happen after QCoreApplication::setOrganizationName().
setenv("XDG_CONFIG_HOME", Utilities::GetConfigPath(Utilities::Path_Root).toLocal8Bit().constData(), 1);
#endif
#ifdef HAVE_LIBLASTFM
lastfm::ws::ApiKey = LastFMService::kApiKey;
lastfm::ws::SharedSecret = LastFMService::kSecret;
lastfm::setNetworkAccessManager(new NetworkAccessManager);
#endif
// Initialise logging
logging::Init();
logging::SetLevels(options.log_levels());

View File

@ -192,6 +192,9 @@ void Chromaprinter::ReportError(GstMessage* msg) {
gboolean Chromaprinter::BusCallback(GstBus*, GstMessage* msg, gpointer data) {
Chromaprinter* instance = reinterpret_cast<Chromaprinter*>(data);
if (instance->finishing_) {
return GST_BUS_DROP;
}
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR:
@ -207,6 +210,9 @@ gboolean Chromaprinter::BusCallback(GstBus*, GstMessage* msg, gpointer data) {
GstBusSyncReply Chromaprinter::BusCallbackSync(GstBus*, GstMessage* msg, gpointer data) {
Chromaprinter* instance = reinterpret_cast<Chromaprinter*>(data);
if (instance->finishing_) {
return GST_BUS_PASS;
}
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:

View File

@ -249,7 +249,8 @@ void IncomingDataParser::InsertUrls(const pb::remote::Message& msg) {
// Extract urls
QList<QUrl> urls;
for (auto it = request.urls().begin(); it != request.urls().end(); ++it) {
urls << QUrl(QString::fromStdString(*it));
std::string s = *it;
urls << QUrl(QStringFromStdString(s));
}
// Insert the urls
@ -258,14 +259,16 @@ void IncomingDataParser::InsertUrls(const pb::remote::Message& msg) {
}
void IncomingDataParser::RemoveSongs(const pb::remote::Message& msg) {
const pb::remote::RequestRemoveSongs& request = msg.request_remove_songs();
const pb::remote::RequestRemoveSongs& request = msg.request_remove_songs();
// Extract urls
QList<int> songs;
std::copy(request.songs().begin(), request.songs().end(), songs.begin());
// Extract urls
QList<int> songs;
for (int i = 0; i<request.songs().size();i++) {
songs.append(request.songs(i));
}
// Insert the urls
emit RemoveSongs(request.playlist_id(), songs);
// Insert the urls
emit RemoveSongs(request.playlist_id(), songs);
}
void IncomingDataParser::ClientConnect(const pb::remote::Message& msg) {

View File

@ -217,3 +217,13 @@ void NetworkRemote::CreateRemoteClient(QTcpSocket* client_socket) {
incoming_data_parser_.get(), SLOT(Parse(pb::remote::Message)));
}
}
void NetworkRemote::EnableKittens(bool aww) {
if (outgoing_data_creator_.get())
outgoing_data_creator_->EnableKittens(aww);
}
void NetworkRemote::SendKitten(quint64 id, const QImage &kitten) {
if (outgoing_data_creator_.get())
outgoing_data_creator_->SendKitten(kitten);
}

View File

@ -26,6 +26,8 @@ public slots:
void StartServer();
void ReloadSettings();
void AcceptConnection();
void EnableKittens(bool aww);
void SendKitten(quint64 id, const QImage& kitten);
private:
boost::scoped_ptr<QTcpServer> server_;

View File

@ -33,6 +33,7 @@ const quint32 OutgoingDataCreator::kFileChunkSize = 100000; // in Bytes
OutgoingDataCreator::OutgoingDataCreator(Application* app)
: app_(app),
aww_(false),
ultimate_reader_(new UltimateLyricsReader(this)),
fetcher_(new SongInfoFetcher(this))
{
@ -314,21 +315,26 @@ void OutgoingDataCreator::SendFirstData(bool send_playlist_songs) {
void OutgoingDataCreator::CurrentSongChanged(const Song& song, const QString& uri, const QImage& img) {
current_song_ = song;
current_uri_ = uri;
current_image_ = img;
if (!clients_->empty()) {
// Create the message
pb::remote::Message msg;
msg.set_type(pb::remote::CURRENT_METAINFO);
// If there is no song, create an empty node, otherwise fill it with data
int i = app_->playlist_manager()->active()->current_row();
CreateSong(
current_song_, img, i,
msg.mutable_response_current_metadata()->mutable_song_metadata());
SendDataToClients(&msg);
if (!aww_) {
current_image_ = img;
}
SendSongMetadata();
}
void OutgoingDataCreator::SendSongMetadata() {
// Create the message
pb::remote::Message msg;
msg.set_type(pb::remote::CURRENT_METAINFO);
// If there is no song, create an empty node, otherwise fill it with data
int i = app_->playlist_manager()->active()->current_row();
CreateSong(
current_song_, current_image_, i,
msg.mutable_response_current_metadata()->mutable_song_metadata());
SendDataToClients(&msg);
}
void OutgoingDataCreator::CreateSong(
@ -509,11 +515,16 @@ void OutgoingDataCreator::UpdateTrackPosition() {
pb::remote::Message msg;
msg.set_type(pb::remote::UPDATE_TRACK_POSITION);
const int position = std::floor(
int position = std::floor(
float(app_->player()->engine()->position_nanosec()) / kNsecPerSec + 0.5);
if (app_->player()->engine()->position_nanosec() > current_song_.length_nanosec())
position = last_track_position_;
msg.mutable_response_update_track_position()->set_position(position);
last_track_position_ = position;
SendDataToClients(&msg);
}
@ -535,7 +546,7 @@ void OutgoingDataCreator::SendLyrics(int id, const SongInfoFetcher::Result& resu
foreach (const CollapsibleInfoPane::Data& data, result.info_) {
// If the size is zero, do not send the provider
SongInfoTextView* editor = qobject_cast<SongInfoTextView*>(data.contents_);
UltimateLyricsLyric* editor = qobject_cast<UltimateLyricsLyric*>(data.content_object_);
if (editor->toPlainText().length() == 0)
continue;
@ -628,12 +639,13 @@ void OutgoingDataCreator::SendSingleSong(RemoteClient* client, const Song &song,
if (!(song.url().scheme() == "file"))
return;
// Calculate the number of chunks
int chunk_count = qRound((song.filesize() / kFileChunkSize) + 0.5);
int chunk_number = 1;
// Open the file
QFile file(song.url().toLocalFile());
// Get sha1 for file
QByteArray sha1 = Utilities::Sha1File(file).toHex();
qLog(Debug) << "sha1 for file" << song.url().toLocalFile() << "=" << sha1;
file.open(QIODevice::ReadOnly);
QByteArray data;
@ -643,6 +655,10 @@ void OutgoingDataCreator::SendSingleSong(RemoteClient* client, const Song &song,
QImage null_image;
// Calculate the number of chunks
int chunk_count = qRound((file.size() / kFileChunkSize) + 0.5);
int chunk_number = 1;
while (!file.atEnd()) {
// Read file chunk
data = file.read(kFileChunkSize);
@ -654,6 +670,7 @@ void OutgoingDataCreator::SendSingleSong(RemoteClient* client, const Song &song,
chunk->set_file_number(song_no);
chunk->set_size(file.size());
chunk->set_data(data.data(), data.size());
chunk->set_file_hash(sha1.data(), sha1.size());
// On the first chunk send the metadata, so the client knows
// what file it receives.
@ -721,12 +738,12 @@ void OutgoingDataCreator::SendLibrary(RemoteClient *client) {
// Attach this file to the database
Database::AttachedDatabase adb(temp_file_name, "", true);
app_->database()->AttachDatabase("songs_export", adb);
QSqlDatabase db(app_->database()->Connect());
app_->database()->AttachDatabaseOnDbConnection("songs_export", adb, db);
// Copy the content of the song table to this temporary database
QSqlQuery q(QString("create table songs_export.songs as SELECT * FROM songs;"), db);
q.exec();
QSqlQuery q(QString("create table songs_export.songs as SELECT * FROM songs where unavailable = 0;"), db);
if (app_->database()->CheckErrors(q)) return;
@ -735,6 +752,11 @@ void OutgoingDataCreator::SendLibrary(RemoteClient *client) {
// Open the file
QFile file(temp_file_name);
// Get the sha1 hash
QByteArray sha1 = Utilities::Sha1File(file).toHex();
qLog(Debug) << "Library sha1" << sha1;
file.open(QIODevice::ReadOnly);
QByteArray data;
@ -755,6 +777,7 @@ void OutgoingDataCreator::SendLibrary(RemoteClient *client) {
chunk->set_chunk_number(chunk_number);
chunk->set_size(file.size());
chunk->set_data(data.data(), data.size());
chunk->set_file_hash(sha1.data(), sha1.size());
// Send data directly to the client
client->SendData(&msg);
@ -769,3 +792,15 @@ void OutgoingDataCreator::SendLibrary(RemoteClient *client) {
// Remove temporary file
file.remove();
}
void OutgoingDataCreator::EnableKittens(bool aww) {
aww_ = aww;
}
void OutgoingDataCreator::SendKitten(const QImage& kitten) {
if (aww_) {
current_image_ = kitten;
SendSongMetadata();
}
}

View File

@ -18,8 +18,8 @@
#include "songinfo/collapsibleinfopane.h"
#include "songinfo/songinfofetcher.h"
#include "songinfo/songinfoprovider.h"
#include "songinfo/songinfotextview.h"
#include "songinfo/songinfoview.h"
#include "songinfo/ultimatelyricslyric.h"
#include "songinfo/ultimatelyricsprovider.h"
#include "songinfo/ultimatelyricsreader.h"
#include "remotecontrolmessages.pb.h"
@ -62,6 +62,7 @@ public slots:
void PlaylistRenamed(int id, const QString& new_name);
void ActiveChanged(Playlist*);
void CurrentSongChanged(const Song& song, const QString& uri, const QImage& img);
void SendSongMetadata();
void StateChanged(Engine::State);
void SendKeepAlive();
void SendRepeatMode(PlaylistSequence::RepeatMode mode);
@ -73,6 +74,8 @@ public slots:
void SendSongs(const pb::remote::RequestDownloadSongs& request, RemoteClient* client);
void ResponseSongOffer(RemoteClient* client, bool accepted);
void SendLibrary(RemoteClient* client);
void EnableKittens(bool aww);
void SendKitten(const QImage& kitten);
private:
Application* app_;
@ -85,6 +88,8 @@ private:
QTimer* track_position_timer_;
int keep_alive_timeout_;
QMap<RemoteClient*, QQueue<DownloadItem> > download_queue_;
int last_track_position_;
bool aww_;
boost::scoped_ptr<UltimateLyricsReader> ultimate_reader_;
ProviderList provider_list_;

View File

@ -50,7 +50,6 @@ RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
authenticated_ = !use_auth_code_;
}
RemoteClient::~RemoteClient() {
client_->close();
if (client_->state() == QAbstractSocket::ConnectedState)

View File

@ -25,6 +25,7 @@
#include "songloaderinserter.h"
#include "songmimedata.h"
#include "songplaylistitem.h"
#include "core/application.h"
#include "core/closure.h"
#include "core/logging.h"
#include "core/modelfuturewatcher.h"
@ -761,7 +762,8 @@ bool Playlist::dropMimeData(const QMimeData* data, Qt::DropAction action, int ro
}
}
} else if (data->hasFormat(kCddaMimeType)) {
SongLoaderInserter* inserter = new SongLoaderInserter(task_manager_, library_);
SongLoaderInserter* inserter = new SongLoaderInserter(
task_manager_, library_, backend_->app()->player());
connect(inserter, SIGNAL(Error(QString)), SIGNAL(LoadTracksError(QString)));
inserter->LoadAudioCD(this, row, play_now, enqueue_now);
} else if (data->hasUrls()) {
@ -773,7 +775,8 @@ bool Playlist::dropMimeData(const QMimeData* data, Qt::DropAction action, int ro
}
void Playlist::InsertUrls(const QList<QUrl> &urls, int pos, bool play_now, bool enqueue) {
SongLoaderInserter* inserter = new SongLoaderInserter(task_manager_, library_);
SongLoaderInserter* inserter = new SongLoaderInserter(
task_manager_, library_, backend_->app()->player());
connect(inserter, SIGNAL(Error(QString)), SIGNAL(LoadTracksError(QString)));
inserter->Load(this, pos, play_now, enqueue, urls);

View File

@ -78,6 +78,8 @@ class PlaylistBackend : public QObject {
void FavoritePlaylist(int id, bool is_favorite);
void RemovePlaylist(int id);
Application* app() const { return app_; }
public slots:
void SavePlaylist(int playlist, const PlaylistItemList& items,
int last_played, smart_playlists::GeneratorPtr dynamic);

View File

@ -16,13 +16,6 @@
*/
#include "playlistdelegates.h"
#include "queue.h"
#include "core/logging.h"
#include "core/player.h"
#include "core/utilities.h"
#include "library/librarybackend.h"
#include "widgets/trackslider.h"
#include "ui/iconloader.h"
#include <QDateTime>
#include <QDir>
@ -39,6 +32,18 @@
#include <QWhatsThis>
#include <QtConcurrentRun>
#include "queue.h"
#include "core/logging.h"
#include "core/player.h"
#include "core/utilities.h"
#include "library/librarybackend.h"
#include "widgets/trackslider.h"
#include "ui/iconloader.h"
#ifdef Q_OS_DARWIN
#include "core/mac_utilities.h"
#endif // Q_OS_DARWIN
const int QueuedItemDelegate::kQueueBoxBorder = 1;
const int QueuedItemDelegate::kQueueBoxCornerRadius = 3;
const int QueuedItemDelegate::kQueueBoxLength = 30;
@ -492,8 +497,14 @@ void SongSourceDelegate::paint(
const QUrl& url = index.data().toUrl();
QPixmap pixmap = LookupPixmap(url, option_copy.decorationSize);
float device_pixel_ratio = 1.0f;
#ifdef Q_OS_DARWIN
QWidget* parent_widget = reinterpret_cast<QWidget*>(parent());
device_pixel_ratio = mac::GetDevicePixelRatio(parent_widget);
#endif
// Draw the pixmap in the middle of the rectangle
QRect draw_rect(QPoint(0, 0), option_copy.decorationSize);
QRect draw_rect(QPoint(0, 0), option_copy.decorationSize / device_pixel_ratio);
draw_rect.moveCenter(option_copy.rect.center());
painter->drawPixmap(draw_rect, pixmap);

View File

@ -101,6 +101,7 @@ PlaylistListContainer::~PlaylistListContainer() {
void PlaylistListContainer::showEvent(QShowEvent* e) {
// Loading icons is expensive so only do it when the view is first opened
if (loaded_icons_) {
QWidget::showEvent(e);
return;
}
loaded_icons_ = true;
@ -268,7 +269,11 @@ void PlaylistListContainer::CurrentChanged(Playlist* new_playlist) {
void PlaylistListContainer::PlaylistPathChanged(int id, const QString& new_path) {
// Update the path in the database
app_->playlist_backend()->SetPlaylistUiPath(id, new_path);
app_->playlist_manager()->playlist(id)->set_ui_path(new_path);
Playlist* playlist = app_->playlist_manager()->playlist(id);
// Check the playlist exists (if it's not opened it's not in the manager)
if (playlist) {
playlist->set_ui_path(new_path);
}
}
void PlaylistListContainer::ItemDoubleClicked(const QModelIndex& proxy_index) {

View File

@ -65,22 +65,28 @@ void PlaylistListModel::AddRowMappings(const QModelIndex& begin,
for (int i=begin.row() ; i<=end.row() ; ++i) {
const QModelIndex index = begin.sibling(i, 0);
QStandardItem* item = itemFromIndex(index);
AddRowItem(item, parent_path);
}
}
switch (index.data(Role_Type).toInt()) {
case Type_Playlist: {
const int id = index.data(Role_PlaylistId).toInt();
playlists_by_id_[id] = item;
if (dropping_rows_) {
emit PlaylistPathChanged(id, parent_path);
}
break;
void PlaylistListModel::AddRowItem(QStandardItem* item, const QString& parent_path) {
switch (item->data(Role_Type).toInt()) {
case Type_Playlist: {
const int id = item->data(Role_PlaylistId).toInt();
playlists_by_id_[id] = item;
if (dropping_rows_) {
emit PlaylistPathChanged(id, parent_path);
}
case Type_Folder:
break;
break;
}
case Type_Folder:
for (int j=0; j<item->rowCount(); ++j) {
QStandardItem* child_item = item->child(j);
AddRowItem(child_item, parent_path);
}
break;
}
}

View File

@ -61,6 +61,7 @@ private slots:
private:
void AddRowMappings(const QModelIndex& begin, const QModelIndex& end);
void AddRowItem(QStandardItem* item, const QString& parent_path);
void UpdatePathsRecursive(const QModelIndex& parent);
private:

View File

@ -156,7 +156,7 @@ void PlaylistManager::New(const QString& name, const SongList& songs,
void PlaylistManager::Load(const QString& filename) {
QUrl url = QUrl::fromLocalFile(filename);
SongLoader* loader = new SongLoader(library_backend_, this);
SongLoader* loader = new SongLoader(library_backend_, app_->player(), this);
connect(loader, SIGNAL(LoadFinished(bool)), SLOT(LoadFinished(bool)));
SongLoader::Result result = loader->Load(url);
QFileInfo info(filename);

View File

@ -1254,3 +1254,24 @@ void PlaylistView::FadePreviousBackgroundImage(qreal value) {
void PlaylistView::PlayerStopped() {
CurrentSongChanged(Song(), QString(), QImage());
}
void PlaylistView::focusInEvent(QFocusEvent* event) {
QTreeView::focusInEvent(event);
if (event->reason() == Qt::TabFocusReason ||
event->reason() == Qt::BacktabFocusReason) {
// If there's a current item but no selection it probably means the list was
// filtered, and the selected item does not match the filter. If there's
// only 1 item in the view it is now impossible to select that item without
// using the mouse.
const QModelIndex& current = selectionModel()->currentIndex();
if (current.isValid() &&
selectionModel()->selectedIndexes().isEmpty()) {
QItemSelection new_selection(
current.sibling(current.row(), 0),
current.sibling(current.row(),
current.model()->columnCount(current.parent()) - 1));
selectionModel()->select(new_selection, QItemSelectionModel::Select);
}
}
}

View File

@ -134,6 +134,7 @@ class PlaylistView : public QTreeView {
void dropEvent(QDropEvent *event);
void resizeEvent(QResizeEvent* event);
bool eventFilter(QObject* object, QEvent* event);
void focusInEvent(QFocusEvent* event);
// QAbstractScrollArea
void scrollContentsBy(int dx, int dy);

View File

@ -23,8 +23,9 @@
#include "core/songloader.h"
#include "core/taskmanager.h"
SongLoaderInserter::SongLoaderInserter(
TaskManager* task_manager, LibraryBackendInterface* library)
SongLoaderInserter::SongLoaderInserter(TaskManager* task_manager,
LibraryBackendInterface* library,
const Player* player)
: task_manager_(task_manager),
destination_(NULL),
row_(-1),
@ -32,7 +33,8 @@ SongLoaderInserter::SongLoaderInserter(
enqueue_(false),
async_load_id_(0),
async_progress_(0),
library_(library) {
library_(library),
player_(player) {
}
SongLoaderInserter::~SongLoaderInserter() {
@ -53,7 +55,7 @@ void SongLoaderInserter::Load(Playlist *destination,
destination, SLOT(UpdateItems(const SongList&)));
foreach (const QUrl& url, urls) {
SongLoader* loader = new SongLoader(library_, this);
SongLoader* loader = new SongLoader(library_, player_, this);
// we're connecting this before we're even sure if this is an async load
// to avoid race conditions (signal emission before we're listening to it)
@ -92,7 +94,7 @@ void SongLoaderInserter::LoadAudioCD(Playlist *destination,
play_now_ = play_now;
enqueue_ = enqueue;
SongLoader *loader = new SongLoader(library_, this);
SongLoader* loader = new SongLoader(library_, player_, this);
connect(loader, SIGNAL(LoadFinished(bool)), SLOT(AudioCDTagsLoaded(bool)));
qLog(Info) << "Loading audio CD...";
SongLoader::Result ret = loader->LoadAudioCD();

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