Merge branch 'master' into gstreamer-1.2
This commit is contained in:
commit
5ac83bd463
|
@ -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
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <math.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <iterator>
|
||||
|
||||
|
|
|
@ -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}
|
||||
)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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\"")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
/*!
|
||||
|
|
|
@ -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.
|
||||
|
|
11
Changelog
11
Changelog
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
|
|
@ -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)
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</extract>
|
||||
<extract>
|
||||
<item begin="<h3><a name="{track}">{track}. {Title2}</a></h3><br />" end="<div class="thanks">"/>
|
||||
</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}&bandName={artist}&ExactBandMatch=1">
|
||||
<extract>
|
||||
<item url="http://www.metal-archives.com/release/ajax-view-lyrics/id/{id}"/>
|
||||
<item begin="id=\"lyricsLink_" end="""/>
|
||||
</extract>
|
||||
<invalidIndicator value=""iTotalRecords": 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}&artista={artist}">
|
||||
<urlFormat replace="_@,;&\/"" with="_"/>
|
||||
<urlFormat replace=" " with="+"/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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:
|
||||
|
|
|
@ -108,7 +108,6 @@ QT_PLUGINS = [
|
|||
'imageformats/libqjpeg.dylib',
|
||||
'imageformats/libqmng.dylib',
|
||||
'imageformats/libqsvg.dylib',
|
||||
'imageformats/libqtiff.dylib',
|
||||
]
|
||||
QT_PLUGINS_SEARCH_PATH=[
|
||||
'/target/plugins',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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...));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -44,6 +44,8 @@
|
|||
# include "moodbar/moodbarloader.h"
|
||||
#endif
|
||||
|
||||
bool Application::kIsPortable = false;
|
||||
|
||||
Application::Application(QObject* parent)
|
||||
: QObject(parent),
|
||||
tag_reader_client_(NULL),
|
||||
|
|
|
@ -52,6 +52,8 @@ class Application : public QObject {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static bool kIsPortable;
|
||||
|
||||
Application(QObject* parent = NULL);
|
||||
~Application();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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_; }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -29,5 +29,6 @@ namespace mac {
|
|||
|
||||
QKeySequence KeySequenceFromNSEvent(NSEvent* event);
|
||||
void DumpDictionary(CFDictionaryRef dict);
|
||||
float GetDevicePixelRatio(QWidget* widget);
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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_));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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_;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 ?";
|
||||
|
|
49
src/main.cpp
49
src/main.cpp
|
@ -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());
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -50,7 +50,6 @@ RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
|
|||
authenticated_ = !use_auth_code_;
|
||||
}
|
||||
|
||||
|
||||
RemoteClient::~RemoteClient() {
|
||||
client_->close();
|
||||
if (client_->state() == QAbstractSocket::ConnectedState)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue