diff --git a/.gitignore b/.gitignore index cb16342d6..7a9999082 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ Makefile* moc_* *~ /tomahawk +thirdparty/qtweetlib/WARNING-twitter-api-keys diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..4b82b11a1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "thirdparty/jreen"] + path = thirdparty/jreen + url = git://gitorious.org/jreen/jreen.git diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..c07550f06 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,16 @@ +Tomahawk is primarily authored by: + +* Christian Muehlhaeuser + +Contributors include: + +* Leo Franchi +* Dominik Schmidt +* Jeff Mitchell +* J Herskowitz +* Alejandro Wainzinger + +Thanks to: + +* Harald Sitter +* Steve Robertson diff --git a/CMakeLists.txt b/CMakeLists.txt index 1405e1844..f848d45ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,28 +1,109 @@ PROJECT( tomahawk ) CMAKE_MINIMUM_REQUIRED( VERSION 2.8 ) -SET( CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules" ) +### +### Tomahawk application info +### +SET( ORGANIZATION_NAME "Tomahawk" ) +SET( ORGANIZATION_DOMAIN "tomahawk-player.org" ) +SET( APPLICATION_NAME "Tomahawk" ) +SET( VERSION "0.0.2" ) + + +# set paths +SET( CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_SOURCE_DIR}/CMakeModules" ) +SET( THIRDPARTY_DIR ${CMAKE_SOURCE_DIR}/thirdparty ) # Check if we need qtgui: IF( "${gui}" STREQUAL "no" ) ADD_DEFINITIONS( -DENABLE_HEADLESS ) MESSAGE( STATUS "Building in HEADLESS mode ***" ) - FIND_PACKAGE( Qt4 4.6.0 COMPONENTS QtCore QtXml QtNetwork REQUIRED ) + FIND_PACKAGE( Qt4 4.7.0 COMPONENTS QtCore QtXml QtNetwork REQUIRED ) ELSE() MESSAGE( STATUS "Building full GUI version ***" ) - FIND_PACKAGE( Qt4 4.6.0 COMPONENTS QtGui QtCore QtXml QtNetwork REQUIRED ) + FIND_PACKAGE( Qt4 4.7.0 COMPONENTS QtGui QtCore QtXml QtNetwork REQUIRED ) ENDIF() -FIND_PACKAGE( Taglib 1.6.0 REQUIRED ) -FIND_PACKAGE( LibLastFm REQUIRED ) +#deps +INCLUDE( MacroOptionalFindPackage ) +INCLUDE( MacroLogFeature ) -IF( UNIX AND NOT APPLE ) - ADD_SUBDIRECTORY( alsa-playback ) -ELSE() - ADD_SUBDIRECTORY( rtaudio ) -ENDIF( UNIX AND NOT APPLE ) +# required +#While we distribute our own liblastfm2, don't need to look for it +#macro_optional_find_package(LibLastFm 0.3.3) +#macro_log_feature(LIBLASTFM_FOUND "LastFm" "Qt library for the Last.fm webservices" "https://github.com/mxcl/liblastfm" FALSE "" "liblastfm is needed for scrobbling tracks to Last.fm and fetching cover artwork") +set(LIBLASTFM_FOUND true) -ADD_SUBDIRECTORY( libportfwd ) -ADD_SUBDIRECTORY( qxt ) +macro_optional_find_package(LibEchonest 1.1.1) +macro_log_feature(LIBECHONEST_FOUND "Echonest" "Qt library for communicating with The Echo Nest" "http://projects.kde.org/libechonest" TRUE "" "libechonest is needed for dynamic playlists and the infosystem") + +macro_optional_find_package(CLucene 0.9.23) +macro_log_feature(CLucene_FOUND "CLucene" "The open-source, C++ search engine" "http://clucene.sf.net" TRUE "" "CLucene is used for indexing the collection") + +macro_optional_find_package(QJSON) +macro_log_feature(QJSON_FOUND "QJson" "Qt library that maps JSON data to QVariant objects" "http://qjson.sf.net" TRUE "" "libqjson is used for encoding communication between Tomahawk instances") + +macro_optional_find_package(Taglib 1.6.0) +macro_log_feature(TAGLIB_FOUND "TagLib" "Audio Meta-Data Library" "http://developer.kde.org/~wheeler/taglib.html" TRUE "" "taglib is needed for reading meta data from audio files") + +# we need pthreads too +find_package(Threads) + +include( CheckTagLibFileName ) +check_taglib_filename( COMPLEX_TAGLIB_FILENAME ) + +# optional +macro_optional_find_package(Jreen) +IF( ENABLE_JREEN AND NOT LIBJREEN_FOUND ) + ADD_SUBDIRECTORY( thirdparty/jreen ) + SET( LIBJREEN_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/include ) + IF( UNIX AND NOT APPLE ) + SET( LIBJREEN_LIBRARY ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/libjreen.so ) + ENDIF( UNIX AND NOT APPLE ) + IF( WIN32 ) + SET( LIBJREEN_LIBRARY ${CMAKE_CURRENT_BINARY_DIR}/thirdparty/jreen/libjreen.dll ) + ENDIF( WIN32 ) + SET( LIBJREEN_FOUND true ) + MESSAGE(STATUS "Internal libjreen: ${LIBJREEN_INCLUDE_DIR}, ${LIBJREEN_LIBRARY}") +ENDIF( ENABLE_JREEN AND NOT LIBJREEN_FOUND ) + +IF( WIN32 ) + find_library(QTSPARKLE_LIBRARIES qtsparkle) +ENDIF( WIN32 ) + +macro_log_feature(JREEN_FOUND "Jreen" "Qt XMPP library" "http://gitorious.org/jreen" FALSE "" "Jreen is needed for the alternative/new Jabber SIP plugin. Built automatically inside Tomahawk, if not installed systemwide and ENABLE_JREEN is true") + +macro_optional_find_package(Gloox 1.0) +IF( ENABLE_JREEN ) + set( GLOOX_FOUND false ) +ENDIF( ENABLE_JREEN) +macro_log_feature(GLOOX_FOUND "Gloox" "A portable high-level Jabber/XMPP library for C++" "http://camaya.net/gloox" FALSE "" "Gloox is needed for the Jabber SIP plugin and the XMPP-Bot") +#show dep log +macro_display_feature_log() +MESSAGE("WARNING!") +MESSAGE("Phonon is going to replace our own audio abstraction layer soon so we didn't") +MESSAGE("add checks for libmad, libvorbis and libflac. Make sure they are installed!") +MESSAGE("") +MESSAGE("-----------------------------------------------------------------------------") + +SET( INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" ) + +# make uninstall support +CONFIGURE_FILE( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + +ADD_CUSTOM_TARGET(uninstall + "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") + + +IF( NOT APPLE ) + # Make linking as strict on linux as it is on osx. Then we don't break linking on mac so often + SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-undefined" ) +ENDIF( NOT APPLE ) + +ADD_SUBDIRECTORY( thirdparty ) +ADD_SUBDIRECTORY( src/libtomahawk ) ADD_SUBDIRECTORY( src ) - +ADD_SUBDIRECTORY( admin ) diff --git a/CMakeModules/CheckTagLibFileName.cmake b/CMakeModules/CheckTagLibFileName.cmake new file mode 100644 index 000000000..e5b3706c9 --- /dev/null +++ b/CMakeModules/CheckTagLibFileName.cmake @@ -0,0 +1,15 @@ +# taglib changed filenames to be a char/wchar struct on some platforms, need to check for it +macro (CHECK_TAGLIB_FILENAME TAGLIB_FILENAME_COMPLEX) + include (CheckCXXSourceCompiles) + set (CMAKE_REQUIRED_FLAGS ${TAGLIB_CFLAGS}) + set (CMAKE_REQUIRED_INCLUDES ${TAGLIB_INCLUDES}) + set (CMAKE_REQUIRED_LIBRARIES ${TAGLIB_LIBRARIES}) + check_cxx_source_compiles( + "#include + int main() + { + TagLib::FileName fileName1(\"char\"); + TagLib::FileName fileName2(L\"wchar\"); + return 0; + }" ${TAGLIB_FILENAME_COMPLEX}) +endmacro (CHECK_TAGLIB_FILENAME) diff --git a/CMakeModules/FindCLucene.cmake b/CMakeModules/FindCLucene.cmake new file mode 100644 index 000000000..52116d01f --- /dev/null +++ b/CMakeModules/FindCLucene.cmake @@ -0,0 +1,114 @@ +# +# This module looks for clucene (http://clucene.sf.net) support +# It will define the following values +# +# CLUCENE_INCLUDE_DIR = where CLucene/StdHeader.h can be found +# CLUCENE_LIBRARY_DIR = where CLucene/clucene-config.h can be found +# CLUCENE_LIBRARIES = the libraries to link against CLucene +# CLUCENE_VERSION = The CLucene version string +# CLucene_FOUND = set to 1 if clucene is found +# + +INCLUDE(CheckSymbolExists) +INCLUDE(FindLibraryWithDebug) + +if(NOT CLUCENE_MIN_VERSION) + set(CLUCENE_MIN_VERSION "0.9.23") +endif(NOT CLUCENE_MIN_VERSION) + +IF(EXISTS ${PROJECT_CMAKE}/CLuceneConfig.cmake) + INCLUDE(${PROJECT_CMAKE}/CLuceneConfig.cmake) +ENDIF(EXISTS ${PROJECT_CMAKE}/CLuceneConfig.cmake) + +SET(TRIAL_LIBRARY_PATHS + $ENV{CLUCENE_HOME}/lib${LIB_SUFFIX} + ${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX} + /usr/local/lib${LIB_SUFFIX} + /usr/lib${LIB_SUFFIX} + /sw/lib${LIB_SUFFIX} + /usr/pkg/lib${LIB_SUFFIX} + /usr/lib64 + ) +SET(TRIAL_INCLUDE_PATHS + $ENV{CLUCENE_HOME}/include + ${CMAKE_INSTALL_PREFIX}/include + /usr/local/include + /usr/include + /sw/include + /usr/pkg/include + ) +FIND_LIBRARY_WITH_DEBUG(CLUCENE_CORE_LIBRARY + WIN32_DEBUG_POSTFIX d + NAMES clucene-core + PATHS ${TRIAL_LIBRARY_PATHS}) +IF (CLUCENE_CORE_LIBRARY) + MESSAGE(STATUS "Found CLucene core library: ${CLUCENE_CORE_LIBRARY}") +ENDIF (CLUCENE_CORE_LIBRARY) +FIND_LIBRARY_WITH_DEBUG(CLUCENE_SHARED_LIBRARY + WIN32_DEBUG_POSTFIX d + NAMES clucene-shared + PATHS ${TRIAL_LIBRARY_PATHS}) +IF (CLUCENE_SHARED_LIBRARY) + MESSAGE(STATUS "Found CLucene shared library: ${CLUCENE_SHARED_LIBRARY}") +ENDIF (CLUCENE_SHARED_LIBRARY) + +IF(CLUCENE_CORE_LIBRARY AND CLUCENE_SHARED_LIBRARY) + SET(CLUCENE_LIBRARIES ${CLUCENE_CORE_LIBRARY} ${CLUCENE_SHARED_LIBRARY}) +ENDIF(CLUCENE_CORE_LIBRARY AND CLUCENE_SHARED_LIBRARY) + +FIND_PATH(CLUCENE_INCLUDE_DIR + NAMES CLucene.h + PATHS ${TRIAL_INCLUDE_PATHS}) + +IF (CLUCENE_INCLUDE_DIR) + MESSAGE(STATUS "Found CLucene include dir: ${CLUCENE_INCLUDE_DIR}") +ENDIF (CLUCENE_INCLUDE_DIR) + +IF(WIN32) + SET(TRIAL_LIBRARY_PATHS ${CLUCENE_INCLUDE_DIR}) +ENDIF(WIN32) + +SET(CLUCENE_GOOD_VERSION TRUE) + +FIND_PATH(CLUCENE_LIBRARY_DIR + NAMES CLuceneConfig.cmake/CLuceneConfig.cmake CLucene/CLuceneConfig.cmake + PATHS ${TRIAL_LIBRARY_PATHS} ${TRIAL_INCLUDE_PATHS} NO_DEFAULT_PATH) +IF (CLUCENE_LIBRARY_DIR) + MESSAGE(STATUS "Found CLucene library dir: ${CLUCENE_LIBRARY_DIR}") + # include CLuceneConfig/CLuceneConfig.cmake + IF(EXISTS ${CLUCENE_LIBRARY_DIR}/CLuceneConfig.cmake/CLuceneConfig.cmake) + INCLUDE(${CLUCENE_LIBRARY_DIR}/CLuceneConfig.cmake/CLuceneConfig.cmake) + ENDIF(EXISTS ${CLUCENE_LIBRARY_DIR}/CLuceneConfig.cmake/CLuceneConfig.cmake) + # include CLucene/CLuceneConfig.cmake + IF(EXISTS ${CLUCENE_LIBRARY_DIR}/CLucene/CLuceneConfig.cmake) + INCLUDE(${CLUCENE_LIBRARY_DIR}/CLucene/CLuceneConfig.cmake) + ENDIF(EXISTS ${CLUCENE_LIBRARY_DIR}/CLucene/CLuceneConfig.cmake) + IF (CLUCENE_VERSION STRLESS "${CLUCENE_MIN_VERSION}") + MESSAGE(ERROR " CLucene version ${CLUCENE_VERSION} is less than the required minimum ${CLUCENE_MIN_VERSION}") + SET(CLUCENE_GOOD_VERSION FALSE) + ENDIF (CLUCENE_VERSION STRLESS "${CLUCENE_MIN_VERSION}") + IF (CLUCENE_VERSION STREQUAL "0.9.17") + MESSAGE(ERROR "CLucene version 0.9.17 is not supported.") + SET(CLUCENE_GOOD_VERSION FALSE) + ENDIF (CLUCENE_VERSION STREQUAL "0.9.17") +ENDIF (CLUCENE_LIBRARY_DIR) + +IF(CLUCENE_INCLUDE_DIR AND CLUCENE_LIBRARIES AND CLUCENE_LIBRARY_DIR AND CLUCENE_GOOD_VERSION) + SET(CLucene_FOUND TRUE) +ENDIF(CLUCENE_INCLUDE_DIR AND CLUCENE_LIBRARIES AND CLUCENE_LIBRARY_DIR AND CLUCENE_GOOD_VERSION) + +IF(CLucene_FOUND) + IF(NOT CLucene_FIND_QUIETLY) + MESSAGE(STATUS "Found CLucene: ${CLUCENE_LIBRARIES} version ${CLUCENE_VERSION}") + ENDIF(NOT CLucene_FIND_QUIETLY) +ELSE(CLucene_FOUND) + IF(CLucene_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find CLucene.") + ENDIF(CLucene_FIND_REQUIRED) +ENDIF(CLucene_FOUND) + +MARK_AS_ADVANCED( + CLUCENE_INCLUDE_DIR + CLUCENE_LIBRARY_DIR + CLUCENE_LIBRARIES + ) diff --git a/CMakeModules/FindGloox.cmake b/CMakeModules/FindGloox.cmake new file mode 100644 index 000000000..ccdbdcfbf --- /dev/null +++ b/CMakeModules/FindGloox.cmake @@ -0,0 +1,23 @@ +# - Try to find GLOOX +# Find GLOOX headers, libraries and the answer to all questions. +# +# GLOOX_FOUND True if gloox got found +# GLOOX_INCLUDE_DIR Location of gloox headers +# GLOOX_LIBRARIES List of libaries to use gloox +# +# Copyright (c) 2009 Nigmatullin Ruslan +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +FIND_PATH( GLOOX_INCLUDE_DIR "gloox/gloox.h" ) +FIND_LIBRARY( GLOOX_LIBRARIES gloox ) + +if( GLOOX_LIBRARIES AND GLOOX_INCLUDE_DIR ) + message( STATUS "Found gloox: ${GLOOX_LIBRARIES}" ) + set( GLOOX_FOUND 1 ) +else( GLOOX_LIBRARIES AND GLOOX_INCLUDE_DIR ) + message( STATUS "Could NOT find gloox" ) +endif( GLOOX_LIBRARIES AND GLOOX_INCLUDE_DIR ) diff --git a/CMakeModules/FindJreen.cmake b/CMakeModules/FindJreen.cmake new file mode 100644 index 000000000..301eb24e7 --- /dev/null +++ b/CMakeModules/FindJreen.cmake @@ -0,0 +1,42 @@ +# - Find libjreen +# Find the libjreen includes and the libjreen libraries +# This module defines +# LIBJREEN_INCLUDE_DIR, root jreen include dir. Include jreen includes with jreen/foo.h +# LIBJREEN_LIBRARY, the path to libjreen +# LIBJREEN_FOUND, whether libjreen was found + + +find_path(LIBJREEN_INCLUDE_DIR NAMES jreen.h + HINTS + ~/usr/include + /opt/local/include + /usr/include + /usr/local/include + /opt/kde4/include + ${KDE4_INCLUDE_DIR} + PATH_SUFFIXES jreen +) + +find_library( LIBJREEN_LIBRARY NAMES jreen + PATHS + ~/usr/lib + /opt/local/lib + /usr/lib + /usr/lib64 + /usr/local/lib + /opt/kde4/lib + ${KDE4_LIB_DIR} +) + + +if(LIBJREEN_INCLUDE_DIR AND LIBJREEN_LIBRARY) + set(LIBJREEN_FOUND TRUE) + message(STATUS "Found libjreen: ${LIBJREEN_INCLUDE_DIR}, ${LIBJREEN_LIBRARY}") +else(LIBJREEN_INCLUDE_DIR AND LIBJREEN_LIBRARY) + set(LIBJREEN_FOUND FALSE) + if (LIBJREEN_FIND_REQUIRED) + message(FATAL_ERROR "Could NOT find required package libjreen") + endif(LIBJREEN_FIND_REQUIRED) +endif(LIBJREEN_INCLUDE_DIR AND LIBJREEN_LIBRARY) + +mark_as_advanced(LIBJREEN_INCLUDE_DIR LIBJREEN_LIBRARY) diff --git a/CMakeModules/FindLibEchonest.cmake b/CMakeModules/FindLibEchonest.cmake new file mode 100644 index 000000000..9c78462f4 --- /dev/null +++ b/CMakeModules/FindLibEchonest.cmake @@ -0,0 +1,42 @@ +# - Find libechonest +# Find the libechonest includes and the libechonest libraries +# This module defines +# LIBECHONEST_INCLUDE_DIR, root echonest include dir. Include echonest includes with echonest/foo.h +# LIBECHONEST_LIBRARY, the path to libechonest +# LIBECHONEST_FOUND, whether libechonest was found + + +find_path(LIBECHONEST_INCLUDE_DIR NAMES echonest_export.h + HINTS + ~/usr/include + /opt/local/include + /usr/include + /usr/local/include + /opt/kde4/include + ${KDE4_INCLUDE_DIR} + PATH_SUFFIXES echonest +) + +find_library( LIBECHONEST_LIBRARY NAMES echonest + PATHS + ~/usr/lib + /opt/local/lib + /usr/lib + /usr/lib64 + /usr/local/lib + /opt/kde4/lib + ${KDE4_LIB_DIR} +) + + +if(LIBECHONEST_INCLUDE_DIR AND LIBECHONEST_LIBRARY) + set(LIBECHONEST_FOUND TRUE) + message(STATUS "Found libechonest: ${LIBECHONEST_INCLUDE_DIR}, ${LIBECHONEST_LIBRARY}") +else(LIBECHONEST_INCLUDE_DIR AND LIBECHONEST_LIBRARY) + set(LIBECHONEST_FOUND FALSE) + if (LIBECHONEST_FIND_REQUIRED) + message(FATAL_ERROR "Could NOT find required package libechonest") + endif(LIBECHONEST_FIND_REQUIRED) +endif(LIBECHONEST_INCLUDE_DIR AND LIBECHONEST_LIBRARY) + +mark_as_advanced(LIBECHONEST_INCLUDE_DIR LIBECHONEST_LIBRARY) diff --git a/CMakeModules/FindLibLastFm.cmake b/CMakeModules/FindLibLastFm.cmake index 1d863b084..c73038a7d 100644 --- a/CMakeModules/FindLibLastFm.cmake +++ b/CMakeModules/FindLibLastFm.cmake @@ -10,8 +10,8 @@ find_path(LIBLASTFM_INCLUDE_DIR NAMES Audioscrobbler HINTS ~/usr/include /opt/local/include - /usr/include /usr/local/include + /usr/include /opt/kde4/include ${KDE4_INCLUDE_DIR} PATH_SUFFIXES lastfm @@ -21,9 +21,9 @@ find_library( LIBLASTFM_LIBRARY NAMES lastfm PATHS ~/usr/lib /opt/local/lib + /usr/local/lib /usr/lib /usr/lib64 - /usr/local/lib /opt/kde4/lib ${KDE4_LIB_DIR} ) diff --git a/CMakeModules/FindQJSON.cmake b/CMakeModules/FindQJSON.cmake new file mode 100644 index 000000000..32e294ffd --- /dev/null +++ b/CMakeModules/FindQJSON.cmake @@ -0,0 +1,46 @@ +# Find QJSON - JSON handling library for Qt +# +# This module defines +# QJSON_FOUND - whether the qsjon library was found +# QJSON_LIBRARIES - the qjson library +# QJSON_INCLUDE_DIR - the include path of the qjson library +# + +if (QJSON_INCLUDE_DIR AND QJSON_LIBRARIES) + + # Already in cache + set (QJSON_FOUND TRUE) + +else (QJSON_INCLUDE_DIR AND QJSON_LIBRARIES) + + if (NOT WIN32) + # use pkg-config to get the values of QJSON_INCLUDE_DIRS + # and QJSON_LIBRARY_DIRS to add as hints to the find commands. + include (FindPkgConfig) + pkg_check_modules (QJSON REQUIRED QJson>=0.5) + endif (NOT WIN32) + + find_library (QJSON_LIBRARIES + NAMES + qjson + PATHS + ${QJSON_LIBRARY_DIRS} + ${LIB_INSTALL_DIR} + ${KDE4_LIB_DIR} + ) + + find_path (QJSON_INCLUDE_DIR + NAMES + parser.h + PATH_SUFFIXES + qjson + PATHS + ${QJSON_INCLUDE_DIRS} + ${INCLUDE_INSTALL_DIR} + ${KDE4_INCLUDE_DIR} + ) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(QJSON DEFAULT_MSG QJSON_LIBRARIES QJSON_INCLUDE_DIR) + +endif (QJSON_INCLUDE_DIR AND QJSON_LIBRARIES) diff --git a/CMakeModules/MacroLogFeature.cmake b/CMakeModules/MacroLogFeature.cmake new file mode 100644 index 000000000..45e27b6df --- /dev/null +++ b/CMakeModules/MacroLogFeature.cmake @@ -0,0 +1,157 @@ +# This file defines the Feature Logging macros. +# +# MACRO_LOG_FEATURE(VAR FEATURE DESCRIPTION URL [REQUIRED [MIN_VERSION [COMMENTS]]]) +# Logs the information so that it can be displayed at the end +# of the configure run +# VAR : TRUE or FALSE, indicating whether the feature is supported +# FEATURE: name of the feature, e.g. "libjpeg" +# DESCRIPTION: description what this feature provides +# URL: home page +# REQUIRED: TRUE or FALSE, indicating whether the featue is required +# MIN_VERSION: minimum version number. empty string if unneeded +# COMMENTS: More info you may want to provide. empty string if unnecessary +# +# MACRO_DISPLAY_FEATURE_LOG() +# Call this to display the collected results. +# Exits CMake with a FATAL error message if a required feature is missing +# +# Example: +# +# INCLUDE(MacroLogFeature) +# +# FIND_PACKAGE(JPEG) +# MACRO_LOG_FEATURE(JPEG_FOUND "libjpeg" "Support JPEG images" "http://www.ijg.org" TRUE "3.2a" "") +# ... +# MACRO_DISPLAY_FEATURE_LOG() + +# Copyright (c) 2006, Alexander Neundorf, +# Copyright (c) 2006, Allen Winter, +# Copyright (c) 2009, Sebastian Trueg, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +IF (NOT _macroLogFeatureAlreadyIncluded) + SET(_file ${CMAKE_BINARY_DIR}/MissingRequirements.txt) + IF (EXISTS ${_file}) + FILE(REMOVE ${_file}) + ENDIF (EXISTS ${_file}) + + SET(_file ${CMAKE_BINARY_DIR}/EnabledFeatures.txt) + IF (EXISTS ${_file}) + FILE(REMOVE ${_file}) + ENDIF (EXISTS ${_file}) + + SET(_file ${CMAKE_BINARY_DIR}/DisabledFeatures.txt) + IF (EXISTS ${_file}) + FILE(REMOVE ${_file}) + ENDIF (EXISTS ${_file}) + + SET(_macroLogFeatureAlreadyIncluded TRUE) + + INCLUDE(FeatureSummary) + +ENDIF (NOT _macroLogFeatureAlreadyIncluded) + + +MACRO(MACRO_LOG_FEATURE _var _package _description _url ) # _required _minvers _comments) + + STRING(TOUPPER "${ARGV4}" _required) + SET(_minvers "${ARGV5}") + SET(_comments "${ARGV6}") + + IF (${_var}) + SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/EnabledFeatures.txt) + ELSE (${_var}) + IF ("${_required}" STREQUAL "TRUE") + SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/MissingRequirements.txt) + ELSE ("${_required}" STREQUAL "TRUE") + SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/DisabledFeatures.txt) + ENDIF ("${_required}" STREQUAL "TRUE") + ENDIF (${_var}) + + SET(_logtext " * ${_package}") + + IF (NOT ${_var}) + IF (${_minvers} MATCHES ".*") + SET(_logtext "${_logtext} (${_minvers} or higher)") + ENDIF (${_minvers} MATCHES ".*") + SET(_logtext "${_logtext} <${_url}>\n ") + ELSE (NOT ${_var}) + SET(_logtext "${_logtext} - ") + ENDIF (NOT ${_var}) + + SET(_logtext "${_logtext}${_description}") + + IF (NOT ${_var}) + IF (${_comments} MATCHES ".*") + SET(_logtext "${_logtext}\n ${_comments}") + ENDIF (${_comments} MATCHES ".*") +# SET(_logtext "${_logtext}\n") #double-space missing features? + ENDIF (NOT ${_var}) + + FILE(APPEND "${_LOGFILENAME}" "${_logtext}\n") + + IF(COMMAND SET_PACKAGE_INFO) # in FeatureSummary.cmake since CMake 2.8.3 + SET_PACKAGE_INFO("${_package}" "\"${_description}\"" "${_url}" "\"${_comments}\"") + ENDIF(COMMAND SET_PACKAGE_INFO) + +ENDMACRO(MACRO_LOG_FEATURE) + + +MACRO(MACRO_DISPLAY_FEATURE_LOG) + IF(COMMAND FEATURE_SUMMARY) # in FeatureSummary.cmake since CMake 2.8.3 + FEATURE_SUMMARY(FILENAME ${CMAKE_CURRENT_BINARY_DIR}/FindPackageLog.txt + WHAT ALL) + ENDIF(COMMAND FEATURE_SUMMARY) + + SET(_missingFile ${CMAKE_BINARY_DIR}/MissingRequirements.txt) + SET(_enabledFile ${CMAKE_BINARY_DIR}/EnabledFeatures.txt) + SET(_disabledFile ${CMAKE_BINARY_DIR}/DisabledFeatures.txt) + + IF (EXISTS ${_missingFile} OR EXISTS ${_enabledFile} OR EXISTS ${_disabledFile}) + SET(_printSummary TRUE) + ENDIF (EXISTS ${_missingFile} OR EXISTS ${_enabledFile} OR EXISTS ${_disabledFile}) + + IF(_printSummary) + SET(_missingDeps 0) + IF (EXISTS ${_enabledFile}) + FILE(READ ${_enabledFile} _enabled) + FILE(REMOVE ${_enabledFile}) + SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following external packages were located on your system.\n-- This installation will have the extra features provided by these packages.\n-----------------------------------------------------------------------------\n${_enabled}") + ENDIF (EXISTS ${_enabledFile}) + + + IF (EXISTS ${_disabledFile}) + SET(_missingDeps 1) + FILE(READ ${_disabledFile} _disabled) + FILE(REMOVE ${_disabledFile}) + SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following OPTIONAL packages could NOT be located on your system.\n-- Consider installing them to enable more features from this software.\n-----------------------------------------------------------------------------\n${_disabled}") + ENDIF (EXISTS ${_disabledFile}) + + + IF (EXISTS ${_missingFile}) + SET(_missingDeps 1) + FILE(READ ${_missingFile} _requirements) + SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following REQUIRED packages could NOT be located on your system.\n-- You must install these packages before continuing.\n-----------------------------------------------------------------------------\n${_requirements}") + FILE(REMOVE ${_missingFile}) + SET(_haveMissingReq 1) + ENDIF (EXISTS ${_missingFile}) + + + IF (NOT ${_missingDeps}) + SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- Congratulations! All external packages have been found.") + ENDIF (NOT ${_missingDeps}) + + + MESSAGE(${_summary}) + MESSAGE("-----------------------------------------------------------------------------\n") + + + IF(_haveMissingReq) + MESSAGE(FATAL_ERROR "Exiting: Missing Requirements") + ENDIF(_haveMissingReq) + + ENDIF(_printSummary) + +ENDMACRO(MACRO_DISPLAY_FEATURE_LOG) diff --git a/CMakeModules/MacroOptionalFindPackage.cmake b/CMakeModules/MacroOptionalFindPackage.cmake new file mode 100644 index 000000000..d4ed48e33 --- /dev/null +++ b/CMakeModules/MacroOptionalFindPackage.cmake @@ -0,0 +1,48 @@ +# - MACRO_OPTIONAL_FIND_PACKAGE() combines FIND_PACKAGE() with an OPTION() +# MACRO_OPTIONAL_FIND_PACKAGE( [QUIT] ) +# This macro is a combination of OPTION() and FIND_PACKAGE(), it +# works like FIND_PACKAGE(), but additionally it automatically creates +# an option name WITH_, which can be disabled via the cmake GUI. +# or via -DWITH_=OFF +# The standard _FOUND variables can be used in the same way +# as when using the normal FIND_PACKAGE() + +# Copyright (c) 2006-2010 Alexander Neundorf, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +# This is just a helper macro to set a bunch of variables empty. +# We don't know whether the package uses UPPERCASENAME or CamelCaseName, so we try both: +macro(_MOFP_SET_EMPTY_IF_DEFINED _name _var) + if(DEFINED ${_name}_${_var}) + set(${_name}_${_var} "") + endif(DEFINED ${_name}_${_var}) + + string(TOUPPER ${_name} _nameUpper) + if(DEFINED ${_nameUpper}_${_var}) + set(${_nameUpper}_${_var} "") + endif(DEFINED ${_nameUpper}_${_var}) +endmacro(_MOFP_SET_EMPTY_IF_DEFINED _package _var) + + +macro (MACRO_OPTIONAL_FIND_PACKAGE _name ) + option(WITH_${_name} "Search for ${_name} package" ON) + if (WITH_${_name}) + find_package(${_name} ${ARGN}) + else (WITH_${_name}) + string(TOUPPER ${_name} _nameUpper) + set(${_name}_FOUND FALSE) + set(${_nameUpper}_FOUND FALSE) + + _mofp_set_empty_if_defined(${_name} INCLUDE_DIRS) + _mofp_set_empty_if_defined(${_name} INCLUDE_DIR) + _mofp_set_empty_if_defined(${_name} INCLUDES) + _mofp_set_empty_if_defined(${_name} LIBRARY) + _mofp_set_empty_if_defined(${_name} LIBRARIES) + _mofp_set_empty_if_defined(${_name} LIBS) + _mofp_set_empty_if_defined(${_name} FLAGS) + _mofp_set_empty_if_defined(${_name} DEFINITIONS) + endif (WITH_${_name}) +endmacro (MACRO_OPTIONAL_FIND_PACKAGE) + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 000000000..89a441c90 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,24 @@ +Version 0.0.3: + * Properly honor the chosen port number if a static host and port are + marked as preferred. + +Version 0.0.2: + * Don't reconnect to Jabber if the settings dialog is closed successfully + but the Jabber settings haven't changed. + * Don't run a rescan of the local collection if the settings dialog is + closed successfully but the path hasn't changed. + * Don't attempt to connect to unavailable Twitter peers over and over. + * Find Twitter peers if the peer's Got Tomahawk? tweet is not their latest + tweet. + * Got Tomahawk? tweets can now be sent directly to specific users or in + private direct messages. + * Display a helpful message when someone sends a normal instant message to + the Tomahawk XMPP presence. + * Incompatible change: Twitter SIP protocol has changed slightly. 0.0.1 + clients will not be able to talk to newer clients. + * Hopefully fixed crashes during Twitter authentication. + * Don't let long playlist or summary names force a large Tomahawk window. + * Tomahawk now asks you to authorize new contacts. + +Version 0.0.1: + * First public release. diff --git a/README b/README index 0bd86608d..f33c4f948 100644 --- a/README +++ b/README @@ -1,53 +1,82 @@ Quickstart on Ubuntu -------------------- - sudo apt-get install build-essential cmake libtag1c2a libtag1-dev liblastfm-dev \ - libqt4-dev libqt4-sql-sqlite libvorbis-dev libmad0-dev \ - libasound2-dev libboost-dev zlib1g-dev libgnutls-dev pkg-config + $ sudo apt-get install build-essential cmake libtag1c2a libtag1-dev libqt4-dev libqt4-sql-sqlite \ + libvorbis-dev libmad0-dev libflac++-dev libasound2-dev libboost-dev zlib1g-dev \ + libgnutls-dev pkg-config Gloox 1.0 (XMPP library) ------------------------ - On Ubuntu 10.10: - $ sudo apt-get install libgloox-dev + On Ubuntu 10.10 (and higher): + $ sudo apt-get install libgloox-dev Otherwise see: http://camaya.net/glooxdownload - You need to build gloox 1.0 from source, Ubuntu 10.04 only packages v0.9. + You need to build gloox 1.0 from source, Ubuntu 10.04 only packages version 0.9. - $ # Download and unpack tarball - $ ./configure --without-openssl --with-gnutls --without-libidn --with-zlib --without-examples --without-tests - $ CXXFLAGS=-fPIC make - $ sudo make install + Download and unpack tarball: + $ ./configure --without-openssl --with-gnutls --without-libidn --with-zlib --without-examples --without-tests + $ CXXFLAGS=-fPIC make + $ sudo make install QJson (Qt JSON library) ----------------------- - On Ubuntu 10.04: - $ sudo apt-get install libqjson-dev + On Ubuntu 10.04 (and higher): + $ sudo apt-get install libqjson-dev - Otherwise see: http://sourceforge.net/projects/qjson/files/ (developed using 0.7.1) + Otherwise see: http://sourceforge.net/projects/qjson/files/ (developed using version 0.7.1) - $ # Download and unpack tarball - $ ./configure && make - $ sudo make install + Download and unpack tarball: + $ ./configure && make + $ sudo make install -libEchonest 0.1 +libEchonest 1.1.4 --------------- See: http://projects.kde.org/projects/playground/libs/libechonest/ - $ git clone git://git.kde.org/libechonest.git - $ cd libechonest - $ mkdir build && cd build - $ cmake .. - $ make - $ sudo make install + Download and unpack tarball: + $ mkdir build && cd build + $ cmake .. + $ make + $ sudo make install + +CLucene 0.9.23 +--------------- + See: http://clucene.sourceforge.net/download.shtml + + Clone from git and build CLucene: + $ git clone git://clucene.git.sourceforge.net/gitroot/clucene/clucene + $ cd clucene && mkdir build && cd build + $ cmake .. + $ make + $ sudo make install + + +Quickstart on OS X +------------------ + + Install homebrew + $ ruby -e "$(curl -fsSL https://gist.github.com/raw/323731/install_homebrew.rb)" + $ brew install cmake qt qjson gloox libmad libvorbis flac taglib boost + + Install libEchnoest & CLucene as per the above instructions. + + If liblastfm gives problems, do the below: + $ brew edit liblastfm + Change the url to https://github.com/davidsansome/liblastfm/tarball/0.3.1 + $ brew install liblastfm + Copy the md5 hash it returns. + $ brew edit liblastfm + Replace the md5 hash with the new one you copied. + $ brew install liblastfm + Now compile Tomahawk -------------------- - $ sudo ldconfig -v | grep -Ei 'qjson|gloox|echonest' - $ mkdir build && cd build - $ cmake .. - $ make - $ ./tomahawk + $ mkdir build && cd build + $ cmake .. + $ make + $ ./tomahawk Dependencies @@ -60,38 +89,36 @@ Dependencies SQLite 3.6.22 http://www.sqlite.org/ TagLib 1.6.2 http://developer.kde.org/~wheeler/taglib.html Boost 1.3x http://www.boost.org/ - - Unless you enable the headless mode (no GUI), we also require the following libraries: - + CLucene 0.9.23 (0.9.21 will fail) http://clucene.sourceforge.net/download.shtml libmad 0.15.1b http://www.underbit.com/products/mad/ libvorbis 1.2.3 http://xiph.org/vorbis/ libogg 1.1.4 http://xiph.org/ogg/ - liblastfm 0.3.3 http://github.com/mxcl/liblastfm/ - libechonest 0.1.2 http://projects.kde.org/projects/playground/libs/libechonest/ + libflac++ 1.2.0 http://flac.sourceforge.net/ + libechonest 1.1.4 http://projects.kde.org/projects/playground/libs/libechonest/ Third party libraries that we ship with our source: RtAudio 4.0.7 http://www.music.mcgill.ca/~gary/rtaudio/ MiniUPnP http://miniupnp.free.fr/ + liblastfm 0.4.0 http://github.com/jonocole/liblastfm/ To build the app: ----------------- - $ mkdir build - $ cd build - - (Pick one of the following two choices. If unsure pick the second one, you probably want a GUI) - $ cmake -Dgui=no .. # enables headless mode, build without GUI - $ cmake .. # normal build including GUI - - $ make + $ mkdir build && cd build + $ cmake .. + $ make To run the app: --------------- - (Only run the next two commands if you installed any of the dependencies from source on Linux) - $ export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH - $ sudo ldconfig -v + Only run the next two commands if you installed any of the dependencies from source on Linux. + $ export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH + $ sudo ldconfig -v - $ ./tomahawk + Start the application on Linux: + $ ./tomahawk + + Start the application on OS X: + $ open tomahawk.app Enjoy! diff --git a/admin/CMakeLists.txt b/admin/CMakeLists.txt new file mode 100644 index 000000000..48db60b20 --- /dev/null +++ b/admin/CMakeLists.txt @@ -0,0 +1,3 @@ +IF(WIN32) + INSTALL(DIRECTORY win DESTINATION share/tomahawk/admin ) +ENDIF(WIN32) \ No newline at end of file diff --git a/gen_resources.sh b/admin/gen_resources.sh similarity index 100% rename from gen_resources.sh rename to admin/gen_resources.sh diff --git a/admin/mac/DS_Store.in b/admin/mac/DS_Store.in new file mode 100644 index 000000000..bea3a4bcf Binary files /dev/null and b/admin/mac/DS_Store.in differ diff --git a/admin/mac/Info.plist b/admin/mac/Info.plist index 3187e4438..9acc67a0c 100644 --- a/admin/mac/Info.plist +++ b/admin/mac/Info.plist @@ -5,24 +5,58 @@ CFBundleDevelopmentRegion English CFBundleExecutable - tomahawk + Tomahawk CFBundleIdentifier - org.tomahawk.Tomahawk + org.tomahawk-player.Tomahawk CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType APPL CFBundleVersion - 0.0.1.0 + 0.0.2.0 CFBundleShortVersionString - 0.0.1 + 0.0.2 CFBundleSignature tomahawk CFBundleIconFile - tomahawk.icns + Tomahawk.icns CFBundleName Tomahawk LSMinimumSystemVersion - 10.5.0 + 10.5.0 + SUFeedURL + http://download.tomahawk-player.org/sparkle + SUPublicDSAKeyFile + sparkle_pub.pem + CFBundleURLTypes + + + CFBundleURLName + Tomahawk URL + CFBundleURLSchemes + + tomahawk + + + + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + xspf + + CFBundleTypeIconFile + Generic.icns + CFBundleTypeMIMETypes + + application/xspf+xml + + CFBundleTypeName + XSPF Playlist + CFBundleTypeRole + Viewer + + diff --git a/admin/mac/add-Qt-to-bundle.sh b/admin/mac/add-Qt-to-bundle.sh index 74d062dac..fee822d38 100755 --- a/admin/mac/add-Qt-to-bundle.sh +++ b/admin/mac/add-Qt-to-bundle.sh @@ -9,6 +9,8 @@ # sqldrivers/libsqlite.dylib ################################################################################ +QT_FRAMEWORKS_DIR="$QTDIR/lib" +QT_PLUGINS_DIR="$QTDIR/plugins" if [[ ! -d "$QTDIR/lib/QtCore.framework" ]] then @@ -18,12 +20,10 @@ then QT_FRAMEWORKS_DIR=/Library/Frameworks QT_PLUGINS_DIR=/Developer/Applications/Qt/plugins fi -elif [[ $QTDIR ]] -then - QT_FRAMEWORKS_DIR="$QTDIR/lib" - QT_PLUGINS_DIR="$QTDIR/plugins" fi +echo "Plugins go to: $QT_PLUGINS_DIR" + if [ -z $QTDIR ] then echo QTDIR must be set, or install the official Qt dmg @@ -38,26 +38,18 @@ for x in $1 do echo "C $x" cp -R $QT_FRAMEWORKS_DIR/$x.framework Contents/Frameworks/ + chmod -R u+rw Contents/Frameworks/ done #plugins shift mkdir -p Contents/MacOS -while (( "$#" )) -do - echo "C $1" +mkdir -p Contents/MacOS/sqldrivers +mkdir -p Contents/MacOS/imageformats - if [[ -d $QT_PLUGINS_DIR/$1 ]] - then - cp -R $QT_PLUGINS_DIR/$1 Contents/MacOS - else - dir=$(basename $(dirname $1)) - mkdir Contents/MacOS/$dir - cp $QT_PLUGINS_DIR/$1 Contents/MacOS/$dir - fi - - shift -done +cp -R $QT_PLUGINS_DIR/sqldrivers/libqsqlite.dylib Contents/MacOS/sqldrivers/ +cp -R $QT_PLUGINS_DIR/imageformats/libqgif.dylib Contents/MacOS/imageformats/ +cp -R $QT_PLUGINS_DIR/imageformats/libqjpeg.dylib Contents/MacOS/imageformats/ #cleanup find Contents/Frameworks -name Headers -o -name \*.prl -o -name \*_debug | xargs rm -rf diff --git a/admin/mac/build-release-osx.sh b/admin/mac/build-release-osx.sh index ebf2b780e..b283a2e44 100755 --- a/admin/mac/build-release-osx.sh +++ b/admin/mac/build-release-osx.sh @@ -1,8 +1,7 @@ #!/bin/bash # -# Usage: dist/build-relese-osx.sh [-j] [--no-clean] +# Usage: ./admin/mac/build-release-osx.sh [--no-clean] # -# Adding the -j parameter results in building a japanese version. ################################################################################ @@ -17,17 +16,27 @@ function die { } ################################################################################ +if [ -z $1 ] +then + echo This script expects the version number as a parameter, e.g. 1.0.0 + exit 1 +fi ROOT=`pwd` QTDIR=`which qmake` +LINKDIR=`readlink $QTDIR` QTDIR=`dirname $QTDIR` +QTDIR=$QTDIR/`dirname $LINKDIR` QTDIR=`dirname $QTDIR` test -L "$QTDIR" && QTDIR=`readlink $QTDIR` +echo "Goes here: $QTDIR" + export QMAKESPEC='macx-g++' export QTDIR export VERSION +export QTVERSION='4.7.2' ################################################################################ @@ -35,14 +44,34 @@ CLEAN='1' BUILD='1' NOTQUICK='1' CREATEDMG='1' +VERSION=$1 - header addQt + header "Adding Qt to app bundle" cd tomahawk.app -# $ROOT/admin/mac/add-Qt-to-bundle.sh \ -# 'QtCore QtGui QtXml QtNetwork QtSql' + $ROOT/../admin/mac/add-Qt-to-bundle.sh \ + 'QtCore QtGui QtXml QtNetwork QtSql QtXmlPatterns QtWebKit phonon' - header deposx - $ROOT/admin/mac/deposx.sh + header "Running install_name_tool" + $ROOT/../admin/mac/deposx.sh + + header "Renaming files" + mv Contents/Resources/tomahawkSources.icns Contents/Resources/Tomahawk.icns + mv Contents/MacOS/tomahawk Contents/MacOS/Tomahawk +# cp $ROOT/../admin/mac/Info.plist Contents/Info.plist + + header "Copying Sparkle pubkey & framework, and qt.conf" + cp $ROOT/../admin/mac/sparkle_pub.pem Contents/Resources + cp -R /Library/Frameworks/Sparkle.framework Contents/Frameworks + cp $ROOT/../admin/mac/qt.conf Contents/Resources + + header "Creating DMG" + cd .. + mv tomahawk.app Tomahawk.app + $ROOT/../admin/mac/create-dmg.sh Tomahawk.app + mv Tomahawk.dmg Tomahawk-$VERSION.dmg - header Done! + header "Creating signed Sparkle update" + $ROOT/../admin/mac/sign_bundle.rb $VERSION ~/tomahawk_sparkle_privkey.pem + mv Tomahawk.app tomahawk.app + header "Done!" diff --git a/admin/mac/create-dmg.sh b/admin/mac/create-dmg.sh new file mode 100755 index 000000000..74c42ddc0 --- /dev/null +++ b/admin/mac/create-dmg.sh @@ -0,0 +1,59 @@ +#!/bin/sh +# author: max@last.fm, muesli@tomahawk-player.org +# brief: Produces a compressed DMG from a bundle directory +# usage: Pass the bundle directory as the only parameter +# note: This script depends on the Tomahawk build system, and must be run from +# the build directory +################################################################################ + + +#if [ -z $VERSION ] +#then +# echo VERSION must be set +# exit 2 +#fi + +if [ -z "$1" ] +then + echo "Please pass the bundle.app directory as the first parameter." + exit 3 +fi +################################################################################ + + +NAME=$(basename "$1" | perl -pe 's/(.*).app/\1/') +IN="$1" +TMP="dmg/$NAME" +OUT="$NAME.dmg" +mkdir -p "$TMP" +################################################################################ + + +# clean up +rm -rf "$TMP" +rm -f "$OUT" + +# create DMG contents and copy files +mkdir -p "$TMP/.background" +cp ../admin/mac/dmg_background.png "$TMP/.background/background.png" +cp ../admin/mac/DS_Store.in "$TMP/.DS_Store" +chmod go-rwx "$TMP/.DS_Store" +ln -s /Applications "$TMP/Applications" +# copies the prepared bundle into the dir that will become the DMG +cp -R "$IN" "$TMP" + +# create +hdiutil makehybrid -hfs -hfs-volume-name Tomahawk -hfs-openfolder "$TMP" "$TMP" -o tmp.dmg +hdiutil convert -format UDZO -imagekey zlib-level=9 tmp.dmg -o "$OUT" + +# cleanup +rm tmp.dmg + +#hdiutil create -srcfolder "$TMP" \ +# -format UDZO -imagekey zlib-level=9 \ +# -scrub \ +# "$OUT" \ +# || die "Error creating DMG :(" + +# done ! +echo 'DMG size:' `du -hs "$OUT" | awk '{print $1}'` diff --git a/admin/mac/deposx.sh b/admin/mac/deposx.sh index 802576a34..8a8332936 100755 --- a/admin/mac/deposx.sh +++ b/admin/mac/deposx.sh @@ -9,6 +9,16 @@ then exit 1 fi +if [ -z $QTVERSION ] +then + echo QTVERSION must be set + exit 1 +fi + +cd .. +ORIGROOT=`pwd` +cd - + cd Contents QTLIBS=`ls Frameworks | cut -d. -f1` @@ -16,19 +26,27 @@ LIBS=`cd MacOS && ls -fR1 | grep dylib` ################################################################################ +function import_lib +{ + echo "L \`$1'" + cp -R -L $1 MacOS/`basename $1` + chmod u+rw MacOS/`basename $1` + deplib_change MacOS/`basename $1` + deposx_change MacOS/`basename $1` +} + function deposx_change { echo "D \`$1'" echo $QTDIR - for y in $QTLIBS do install_name_tool -change $QTDIR/lib/$y.framework/Versions/4/$y \ @executable_path/../Frameworks/$y.framework/Versions/4/$y \ "$1" - install_name_tool -change $QTDIR/Cellar/qt/4.6.2/lib/$y.framework/Versions/4/$y \ + install_name_tool -change /usr/local/Cellar/qt/$QTVERSION/lib/$y.framework/Versions/4/$y \ @executable_path/../Frameworks/$y.framework/Versions/4/$y \ "$1" done @@ -40,6 +58,35 @@ function deposx_change "$1" done } + +function deplib_change +{ + install_name_tool -change /usr/local/Cellar/liblastfm/0.3.3/lib/liblastfm.0.dylib @executable_path/liblastfm.0.dylib $1 + install_name_tool -change /usr/local/Cellar/qjson/0.7.1/lib/libqjson.0.7.1.dylib @executable_path/libqjson.0.7.1.dylib $1 + install_name_tool -change /usr/local/lib/libechonest.1.1.dylib @executable_path/libechonest.1.1.dylib $1 + install_name_tool -change /usr/local/lib/libclucene-core.0.9.23.dylib @executable_path/libclucene-core.0.9.23.dylib $1 + install_name_tool -change /usr/local/lib/libclucene-shared.0.9.23.dylib @executable_path/libclucene-shared.0.9.23.dylib $1 + install_name_tool -change /usr/local/Cellar/gloox/1.0/lib/libgloox.8.dylib @executable_path/libgloox.8.dylib $1 + install_name_tool -change /usr/local/Cellar/taglib/1.6.3/lib/libtag.1.dylib @executable_path/libtag.1.dylib $1 + install_name_tool -change /usr/local/Cellar/libogg/1.2.0/lib/libogg.0.dylib @executable_path/libogg.0.dylib $1 + install_name_tool -change /usr/local/Cellar/libvorbis/1.3.1/lib/libvorbis.0.dylib @executable_path/libvorbis.0.dylib $1 + install_name_tool -change /usr/local/Cellar/libvorbis/1.3.1/lib/libvorbisfile.3.dylib @executable_path/libvorbisfile.3.dylib $1 + install_name_tool -change /usr/local/Cellar/mad/0.15.1b/lib/libmad.0.dylib @executable_path/libmad.0.dylib $1 + install_name_tool -change /usr/local/Cellar/flac/1.2.1/lib/libFLAC++.6.dylib @executable_path/libFLAC++.6.dylib $1 + install_name_tool -change /usr/local/Cellar/flac/1.2.1/lib/libFLAC.8.dylib @executable_path/libFLAC.8.dylib $1 + install_name_tool -change $ORIGROOT/src/libtomahawk/libtomahawklib.dylib @executable_path/libtomahawklib.dylib $1 + install_name_tool -change $ORIGROOT/libtomahawk_sipjabber.dylib @executable_path/libtomahawk_sipjabber.dylib $1 + install_name_tool -change $ORIGROOT/libtomahawk_siptwitter.dylib @executable_path/libtomahawk_siptwitter.dylib $1 + install_name_tool -change $ORIGROOT/libtomahawk_sipzeroconf.dylib @executable_path/libtomahawk_sipzeroconf.dylib $1 + install_name_tool -change $ORIGROOT/thirdparty/jdns/libtomahawk_jdns.dylib @executable_path/libtomahawk_jdns.dylib $1 + install_name_tool -change $ORIGROOT/thirdparty/qtweetlib/libtomahawk_qtweetlib.dylib @executable_path/libtomahawk_qtweetlib.dylib $1 + + install_name_tool -change libqjson.0.7.1.dylib @executable_path/libqjson.0.7.1.dylib $1 + install_name_tool -change libechonest.1.1.dylib @executable_path/libechonest.1.1.dylib $1 + install_name_tool -change libclucene-core.0.9.23.dylib @executable_path/libclucene-core.0.9.23.dylib $1 + install_name_tool -change libclucene-shared.0.9.23.dylib @executable_path/libclucene-shared.0.9.23.dylib $1 +} + ################################################################################ @@ -48,19 +95,30 @@ find MacOS -type f -a -perm -100 | while read x do echo $x y=$(file "$x" | grep 'Mach-O') - test -n "$y" && deposx_change "$x" - - install_name_tool -change liblastfm.0.dylib @executable_path/liblastfm.0.dylib $x - install_name_tool -change /usr/local/Cellar/gloox/1.0/lib/libgloox.8.dylib @executable_path/libgloox.8.dylib $x - install_name_tool -change /usr/local/lib/libgloox.8.dylib @executable_path/libgloox.8.dylib $x - install_name_tool -change /usr/local/Cellar/taglib/1.6/lib/libtag.1.dylib @executable_path/libtag.1.dylib $x - install_name_tool -change /usr/local/Cellar/libogg/1.2.0/lib/libogg.0.dylib @executable_path/libogg.0.dylib $x - install_name_tool -change /usr/local/Cellar/libvorbis/1.3.1/lib/libvorbisfile.3.dylib @executable_path/libvorbisfile.3.dylib $x - install_name_tool -change /usr/local/Cellar/mad/0.15.1b/lib/libmad.0.dylib @executable_path/libmad.0.dylib $x + deposx_change "$x" + deplib_change "$x" done -deposx_change MacOS/libqjson.0.7.1.dylib -deposx_change MacOS/liblastfm.0.dylib +import_lib /usr/local/Cellar/qjson/0.7.1/lib/libqjson.0.7.1.dylib +import_lib /usr/local/Cellar/liblastfm/0.3.3/lib/liblastfm.0.dylib +import_lib /usr/local/Cellar/gloox/1.0/lib/libgloox.8.dylib +import_lib /usr/local/Cellar/taglib/1.6.3/lib/libtag.1.dylib +import_lib /usr/local/Cellar/libogg/1.2.0/lib/libogg.0.dylib +import_lib /usr/local/Cellar/libvorbis/1.3.1/lib/libvorbis.0.dylib +import_lib /usr/local/Cellar/libvorbis/1.3.1/lib/libvorbisfile.3.dylib +import_lib /usr/local/Cellar/mad/0.15.1b/lib/libmad.0.dylib +import_lib /usr/local/Cellar/flac/1.2.1/lib/libFLAC++.6.dylib +import_lib /usr/local/Cellar/flac/1.2.1/lib/libFLAC.8.dylib +import_lib /usr/local/lib/libechonest.1.1.dylib +import_lib /usr/local/lib/libclucene-core.0.9.23.dylib +import_lib /usr/local/lib/libclucene-shared.0.9.23.dylib + +import_lib ../../libtomahawk_sipjabber.dylib +import_lib ../../libtomahawk_siptwitter.dylib +import_lib ../../libtomahawk_sipzeroconf.dylib +import_lib ../../src/libtomahawk/libtomahawklib.dylib +import_lib ../../thirdparty/jdns/libtomahawk_jdns.dylib +import_lib ../../thirdparty/qtweetlib/libtomahawk_qtweetlib.dylib # now Qt for x in $QTLIBS diff --git a/admin/mac/dmg_background.png b/admin/mac/dmg_background.png new file mode 100644 index 000000000..a516605d9 Binary files /dev/null and b/admin/mac/dmg_background.png differ diff --git a/admin/mac/qt.conf b/admin/mac/qt.conf new file mode 100644 index 000000000..64d729736 --- /dev/null +++ b/admin/mac/qt.conf @@ -0,0 +1,2 @@ +[Paths] +Plugins = PlugIns diff --git a/admin/mac/sign_bundle.rb b/admin/mac/sign_bundle.rb new file mode 100755 index 000000000..aaebb9c0c --- /dev/null +++ b/admin/mac/sign_bundle.rb @@ -0,0 +1,14 @@ +#!/usr/bin/ruby +if ARGV.length < 2 + puts "Usage: ruby sign_update.rb version private_key_file" + puts "\nCall this from the build directory." + puts "If you don't have the tomahawk private key and you think you should, ask leo :)" + exit +end + +tarball = "tomahawk-#{ARGV[0]}.tar.bz2" +puts "Zipping: #{tarball}..." +`tar jcvf "#{tarball}" Tomahawk.app` + +puts "Signing..." +puts `openssl dgst -sha1 -binary < "#{tarball}" | openssl dgst -dss1 -sign "#{ARGV[1]}" | openssl enc -base64` diff --git a/admin/mac/sparkle-beta.rss b/admin/mac/sparkle-beta.rss new file mode 100755 index 000000000..124746590 --- /dev/null +++ b/admin/mac/sparkle-beta.rss @@ -0,0 +1,25 @@ + + + + Tomahawk Player Changelog + http://www.gettomahawk.com + Tomahawk Player Beta + en + + Version 0.0.1 (Tomahawk Player Beta - It Lives?) + + https://github.com/tomahawk-player/tomahawk/raw/0.0.1/ChangeLog + + Fri, 25 Mar 2011 00:00:01 +0100 + + + + Version 0.0.2 (Tomahawk Player Beta - It Lives?) + + https://github.com/tomahawk-player/tomahawk/raw/stable/ChangeLog + + Mon, 28 Mar 2011 06:13:01 +0100 + + + + diff --git a/admin/mac/sparkle.rss b/admin/mac/sparkle.rss new file mode 100755 index 000000000..7e98e4568 --- /dev/null +++ b/admin/mac/sparkle.rss @@ -0,0 +1,25 @@ + + + + Tomahawk Player Changelog + http://www.gettomahawk.com + Tomahawk Player + en + + Version 0.0.1 (Tomahawk Player - It Lives!) + + https://github.com/tomahawk-player/tomahawk/raw/0.0.1/ChangeLog + + Fri, 25 Mar 2011 00:00:01 +0100 + + + + Version 0.0.2 (Tomahawk Player - It Lives!) + + https://github.com/tomahawk-player/tomahawk/raw/stable/ChangeLog + + Mon, 28 Mar 2011 06:13:01 +0100 + + + + diff --git a/admin/mac/sparkle_pub.pem b/admin/mac/sparkle_pub.pem new file mode 100644 index 000000000..5e9606170 --- /dev/null +++ b/admin/mac/sparkle_pub.pem @@ -0,0 +1,20 @@ +-----BEGIN PUBLIC KEY----- +MIIDOzCCAi0GByqGSM44BAEwggIgAoIBAQDRltnNbKWFroVCsG1nTSdlTDmo7fjl +tgOuQ0YB2s0a1bcqgQ5YJRE59pFvF/z2pkHEHdyBA6USd9N7/T9lolwNcJoByJpO +MobUNs04elqZXliriaAdoSb2g6ZpxiedppbbyNP/BlK6o+zpyn0LVYXDI/OwJFzS +xjGXM+rBEWdUJnogZxV31gF9W3yD1Quz6icBulT9V/Soo6me9Mc60ooKSYj4Zgqd +3ln8tG90RFnWfbb0nbrITvR3ll6XXLfn081tjhymcXqHcgvaaqcmpKWL6ZWwX1mH +3t1pImnif/tSSZPG21KGE3FtuQ/+YFo19apQ6U6l8kaSFxqcDLAYzBy9AhUA/QfN +8WEIvzOEZ9uSWT7lYy64mUkCggEABsUmcs3kwjrmszIAAmPIowA0DBrxWZL03JBV +bDKT6tNHZaFFlCufVSjiL1EFZjRARC16OWYaDcElUsZYFMcsNIIa8LyDQaq6+SSm +quhMO5heeJiYPrutDiJzbJr0+HoY77Ll+Q4/cEkl0UAN4Ovp18WKwaq6GpHAvBnv +71LunLGAKsVb5joXBQ8In6zQkibJhgiBJwzLK90/j0OTiDaaOwM3PsAegORBVlVE +TAk4AQmawmF8nBGLzTyKXl83J571ku1Mm2JTl16jMYziKARKXYBmkcP1at0YddVK +WWpAwRKSxOucVJYfV58JqmjZqst8BBeH6esQKr5dklUvvDMaEwOCAQYAAoIBAQCw +5mo+8/R3S9cNYg9o8JNJGdSbMhSkurILHh9WNElsIC3RNtPcpijmAnWtXTVDhe6w +77wLj37tUuFGbsu2qPXtZoup35emf9DDshZ5w5UOclPaZ9HYjlC1H64c6d66Rllk +fY6FRDv9qVfjT84APbvMDrk6csJ5YHxFPDaqeQaFB0nxFiCMVwjEx+ZSvQNK1jJ2 +o2gtuOvSPVSphsMeJ72DDNxO+SRRVnOmWaxg9rlmFuGle6Z+UJ2FItfmPEvhSBMY +hzndUbC7Wi4sIpBzbm9O5MiPYMv0VmN+0t1156EiC9uR4f7AKH2S94dnQob/YeY0 +jMH+XxU/wzGUCmsOx1lx +-----END PUBLIC KEY----- diff --git a/admin/unix/tomahawk.protocol b/admin/unix/tomahawk.protocol new file mode 100644 index 000000000..3a393aa61 --- /dev/null +++ b/admin/unix/tomahawk.protocol @@ -0,0 +1,12 @@ +[Protocol] +exec=/home/leo/kde/tomahawk/build/tomahawk "%u" +protocol=tomahawk +input=none +output=none +helper=true +listing= +reading=false +writing=false +makedir=false +deleting=false + diff --git a/admin/win/README.txt b/admin/win/README.txt index ceb98bd3d..e69de29bb 100755 --- a/admin/win/README.txt +++ b/admin/win/README.txt @@ -1,3 +0,0 @@ -# windres.exe tomahawk.rx -O coff -o tomahawk.res - -# SEE: http://stackoverflow.com/questions/708238/how-do-i-add-an-icon-to-a-mingw-gcc-compiled-executable \ No newline at end of file diff --git a/admin/win/RELEASE_NOTES.txt b/admin/win/RELEASE_NOTES.txt deleted file mode 100755 index fd3e8f620..000000000 --- a/admin/win/RELEASE_NOTES.txt +++ /dev/null @@ -1 +0,0 @@ -TO DO \ No newline at end of file diff --git a/admin/win/Toolchain-mingw32-openSUSE.cmake b/admin/win/Toolchain-mingw32-openSUSE.cmake new file mode 100644 index 000000000..80a22964d --- /dev/null +++ b/admin/win/Toolchain-mingw32-openSUSE.cmake @@ -0,0 +1,20 @@ +# this one is important +SET(CMAKE_SYSTEM_NAME Windows) + +# specify the cross compiler +SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc) +SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) + +# where is the target environment containing libraries +SET(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32/sys-root/mingw) +SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + +# windres executable for application icon support +SET(WINDRES_EXECUTABLE /usr/bin/i686-w64-mingw32-windres) + +# libs with broken find modules +SET(TAGLIB_FOUND true) +SET(TAGLIB_LIBRARIES ${CMAKE_FIND_ROOT_PATH}/lib/libtag.dll.a) +SET(TAGLIB_INCLUDES ${CMAKE_FIND_ROOT_PATH}/include/taglib) diff --git a/admin/win/nsi/RELEASE_NOTES.txt b/admin/win/nsi/RELEASE_NOTES.txt new file mode 100755 index 000000000..adbeaae55 --- /dev/null +++ b/admin/win/nsi/RELEASE_NOTES.txt @@ -0,0 +1 @@ +See http://github.com/tomahawk-player/tomahawk/blob/stable/ChangeLog diff --git a/admin/win/installer.ico b/admin/win/nsi/installer.ico similarity index 100% rename from admin/win/installer.ico rename to admin/win/nsi/installer.ico diff --git a/admin/win/nsis_uac/History.txt b/admin/win/nsi/nsis_uac/History.txt similarity index 100% rename from admin/win/nsis_uac/History.txt rename to admin/win/nsi/nsis_uac/History.txt diff --git a/admin/win/nsis_uac/License.txt b/admin/win/nsi/nsis_uac/License.txt similarity index 100% rename from admin/win/nsis_uac/License.txt rename to admin/win/nsi/nsis_uac/License.txt diff --git a/admin/win/nsis_uac/NSISUtil.h b/admin/win/nsi/nsis_uac/NSISUtil.h similarity index 100% rename from admin/win/nsis_uac/NSISUtil.h rename to admin/win/nsi/nsis_uac/NSISUtil.h diff --git a/admin/win/nsis_uac/Release/A/UAC.dll b/admin/win/nsi/nsis_uac/Release/A/UAC.dll similarity index 100% rename from admin/win/nsis_uac/Release/A/UAC.dll rename to admin/win/nsi/nsis_uac/Release/A/UAC.dll diff --git a/admin/win/nsis_uac/Release/U/UAC.dll b/admin/win/nsi/nsis_uac/Release/U/UAC.dll similarity index 100% rename from admin/win/nsis_uac/Release/U/UAC.dll rename to admin/win/nsi/nsis_uac/Release/U/UAC.dll diff --git a/admin/win/nsis_uac/RunAs.cpp b/admin/win/nsi/nsis_uac/RunAs.cpp similarity index 100% rename from admin/win/nsis_uac/RunAs.cpp rename to admin/win/nsi/nsis_uac/RunAs.cpp diff --git a/admin/win/nsis_uac/UAC Readme.html b/admin/win/nsi/nsis_uac/UAC Readme.html similarity index 100% rename from admin/win/nsis_uac/UAC Readme.html rename to admin/win/nsi/nsis_uac/UAC Readme.html diff --git a/admin/win/nsis_uac/UAC.nsh b/admin/win/nsi/nsis_uac/UAC.nsh similarity index 100% rename from admin/win/nsis_uac/UAC.nsh rename to admin/win/nsi/nsis_uac/UAC.nsh diff --git a/admin/win/nsis_uac/UAC_AdminOnly.nsi b/admin/win/nsi/nsis_uac/UAC_AdminOnly.nsi similarity index 100% rename from admin/win/nsis_uac/UAC_AdminOnly.nsi rename to admin/win/nsi/nsis_uac/UAC_AdminOnly.nsi diff --git a/admin/win/nsis_uac/UAC_AllowLUA.nsi b/admin/win/nsi/nsis_uac/UAC_AllowLUA.nsi similarity index 100% rename from admin/win/nsis_uac/UAC_AllowLUA.nsi rename to admin/win/nsi/nsis_uac/UAC_AllowLUA.nsi diff --git a/admin/win/nsis_uac/UAC_GetUserShellFolderPath.nsi b/admin/win/nsi/nsis_uac/UAC_GetUserShellFolderPath.nsi similarity index 100% rename from admin/win/nsis_uac/UAC_GetUserShellFolderPath.nsi rename to admin/win/nsi/nsis_uac/UAC_GetUserShellFolderPath.nsi diff --git a/admin/win/nsis_uac/UAC_RealWorldFullyLoadedDualModeExample.nsi b/admin/win/nsi/nsis_uac/UAC_RealWorldFullyLoadedDualModeExample.nsi similarity index 100% rename from admin/win/nsis_uac/UAC_RealWorldFullyLoadedDualModeExample.nsi rename to admin/win/nsi/nsis_uac/UAC_RealWorldFullyLoadedDualModeExample.nsi diff --git a/admin/win/nsis_uac/UAC_Uninstaller.nsi b/admin/win/nsi/nsis_uac/UAC_Uninstaller.nsi similarity index 100% rename from admin/win/nsis_uac/UAC_Uninstaller.nsi rename to admin/win/nsi/nsis_uac/UAC_Uninstaller.nsi diff --git a/admin/win/nsis_uac/resource.h b/admin/win/nsi/nsis_uac/resource.h similarity index 100% rename from admin/win/nsis_uac/resource.h rename to admin/win/nsi/nsis_uac/resource.h diff --git a/admin/win/nsis_uac/resource.rc b/admin/win/nsi/nsis_uac/resource.rc similarity index 100% rename from admin/win/nsis_uac/resource.rc rename to admin/win/nsi/nsis_uac/resource.rc diff --git a/admin/win/nsis_uac/uac.cpp b/admin/win/nsi/nsis_uac/uac.cpp similarity index 100% rename from admin/win/nsis_uac/uac.cpp rename to admin/win/nsi/nsis_uac/uac.cpp diff --git a/admin/win/nsis_uac/uac.h b/admin/win/nsi/nsis_uac/uac.h similarity index 100% rename from admin/win/nsis_uac/uac.h rename to admin/win/nsi/nsis_uac/uac.h diff --git a/admin/win/nsi/page_header.bmp b/admin/win/nsi/page_header.bmp new file mode 100755 index 000000000..c50025a22 Binary files /dev/null and b/admin/win/nsi/page_header.bmp differ diff --git a/admin/win/nsi/revision.txt b/admin/win/nsi/revision.txt new file mode 100644 index 000000000..56749c830 --- /dev/null +++ b/admin/win/nsi/revision.txt @@ -0,0 +1 @@ +96 \ No newline at end of file diff --git a/admin/win/tomahawk.ini b/admin/win/nsi/tomahawk.ini similarity index 100% rename from admin/win/tomahawk.ini rename to admin/win/nsi/tomahawk.ini diff --git a/admin/win/tomahawk.nsi b/admin/win/nsi/tomahawk.nsi similarity index 90% rename from admin/win/tomahawk.nsi rename to admin/win/nsi/tomahawk.nsi index 144979e24..f6e49cfd3 100755 --- a/admin/win/tomahawk.nsi +++ b/admin/win/nsi/tomahawk.nsi @@ -15,15 +15,15 @@ ;----------------------------------------------------------------------------- ; Some paths. ;----------------------------------------------------------------------------- -!define MING_PATH "/usr/i686-pc-mingw32/sys-root/mingw" +!define MING_PATH "/usr/i686-w64-mingw32/sys-root/mingw" !define MING_BIN "${MING_PATH}/bin" !define MING_DLL_PATH "${MING_BIN}" !define MING_LIB "${MING_PATH}/lib" -!define ROOT_PATH "..\.." ; assuming the script is in ROOT/admin/win/ +!define ROOT_PATH "..\..\.." ; assuming the script is in ROOT/admin/win/nsi +!define BUILD_PATH "${ROOT_PATH}\build" !define QT_DLL_PATH "${MING_BIN}" !define SQLITE_DLL_PATH "${MING_LIB}/qt4/plugins/sqldrivers" -!define RTAUDIO_DLL_PATH "../../rtaudio/" -!define QXTWEB_DLL_PATH "../../qxt/qxtweb-standalone/" +!define IMAGEFORMATS_DLL_PATH "${MING_LIB}/qt4/plugins/imageformats" ;----------------------------------------------------------------------------- ; Increment installer revision number as part of this script. @@ -35,7 +35,7 @@ !define VER_MAJOR "0" !define VER_MINOR "0" -!define VER_BUILD "0" +!define VER_BUILD "2" !define VERSION "${VER_MAJOR}.${VER_MINOR}.${VER_BUILD}" @@ -250,8 +250,32 @@ Section "Tomahawk Player" SEC_TOMAHAWK_PLAYER SetDetailsPrint listonly SetOutPath "$INSTDIR" - ;Main executable. - File "${ROOT_PATH}\build\tomahawk.exe" + !ifdef INSTALL_PATH + ;Main executable. + File "${INSTALL_PATH}\bin\tomahawk.exe" + + File "${INSTALL_PATH}\lib\librtaudio.dll" + File "${INSTALL_PATH}\lib\libqxtweb-standalone.dll" + File "${INSTALL_PATH}\lib\libtomahawk_jdns.dll" + File "${INSTALL_PATH}\lib\libtomahawk_qtweetlib.dll" + File "${INSTALL_PATH}\lib\libtomahawklib.dll" + File "${INSTALL_PATH}\lib\libtomahawk_sipjabber.dll" + File "${INSTALL_PATH}\lib\libtomahawk_siptwitter.dll" + File "${INSTALL_PATH}\lib\libtomahawk_sipzeroconf.dll" + !endif + !ifndef INSTALL_PATH + ;Main executable. + File "${BUILD_PATH}\tomahawk.exe" + + File "${BUILD_PATH}\thirdparty\rtaudio\librtaudio.dll" + File "${BUILD_PATH}\thirdparty\qxt\qxtweb-standalone\libqxtweb-standalone.dll" + File "${BUILD_PATH}\thirdparty\jdns\libtomahawk_jdns.dll" + File "${BUILD_PATH}\thirdparty\qtweetlib\libtomahawk_qtweetlib.dll" + File "${BUILD_PATH}\src\libtomahawk\libtomahawklib.dll" + File "${BUILD_PATH}\libtomahawk_sipjabber.dll" + File "${BUILD_PATH}\libtomahawk_siptwitter.dll" + File "${BUILD_PATH}\libtomahawk_sipzeroconf.dll" + !endif ;License & release notes. File "${ROOT_PATH}\LICENSE.txt" @@ -263,11 +287,18 @@ Section "Tomahawk Player" SEC_TOMAHAWK_PLAYER File "${QT_DLL_PATH}\QtNetwork4.dll" File "${QT_DLL_PATH}\QtSql4.dll" File "${QT_DLL_PATH}\QtXml4.dll" + File "${QT_DLL_PATH}\QtWebKit4.dll" ;SQLite driver SetOutPath "$INSTDIR\sqldrivers" File "${SQLITE_DLL_PATH}\qsqlite4.dll" SetOutPath "$INSTDIR" + + ;Image plugins + SetOutPath "$INSTDIR\imageformats" + File "${IMAGEFORMATS_DLL_PATH}\qgif4.dll" + File "${IMAGEFORMATS_DLL_PATH}\qjpeg4.dll" + SetOutPath "$INSTDIR" ;Cygwin/c++ stuff ;File "${MING_DLL_PATH}\cygmad-0.dll" @@ -277,24 +308,28 @@ Section "Tomahawk Player" SEC_TOMAHAWK_PLAYER File "${MING_DLL_PATH}\libstdc++-6.dll" ;Audio stuff - ;File "${MING_DLL_PATH}\libmad.dll" - ;File "${MING_DLL_PATH}\librtaudio.dll" - File "${RTAUDIO_DLL_PATH}\librtaudio.dll" + File "${MING_DLL_PATH}\libmad-0.dll" File "${MING_DLL_PATH}\libogg-0.dll" File "${MING_DLL_PATH}\libvorbisfile-3.dll" File "${MING_DLL_PATH}\libvorbis-0.dll" + File "${MING_DLL_PATH}\libFLAC-8.dll" + File "${MING_DLL_PATH}\libFLAC++-6.dll" ;Other File "${MING_DLL_PATH}\libqjson.dll" File "${MING_DLL_PATH}\libtag.dll" File "${MING_DLL_PATH}\libgloox-8.dll" - File "${MING_DLL_PATH}\libpng14-14.dll" + File "${MING_DLL_PATH}\libpng15-15.dll" + File "${MING_DLL_PATH}\libjpeg-8.dll" File "${MING_DLL_PATH}\zlib1.dll" File "${MING_DLL_PATH}\libechonest.dll" File "${MING_DLL_PATH}\liblastfm.dll" - File "${QXTWEB_DLL_PATH}\libqxtweb-standalone.dll" + File "${MING_LIB}\libclucene-core.dll" + File "${MING_LIB}\libclucene-shared.dll" + + File "${MING_BIN}\libqtsparkle.dll" SectionEnd SectionGroup "Shortcuts" @@ -519,6 +554,13 @@ Function .onInit StrCmp $R0 0 +3 MessageBox MB_OK|MB_ICONEXCLAMATION "The installer is already running." Abort + + ;Use available InstallLocation when possible. This is useful in the uninstaller + ;via re-install, which would otherwise use a default location - a bug. + ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Tomahawk" "InstallLocation" + StrCmp $R0 "" SkipSetInstDir + StrCpy $INSTDIR $R0 + SkipSetInstDir: FunctionEnd Function .onInstSuccess diff --git a/admin/win/nsi/welcome.bmp b/admin/win/nsi/welcome.bmp new file mode 100755 index 000000000..580504f50 Binary files /dev/null and b/admin/win/nsi/welcome.bmp differ diff --git a/admin/win/revision.txt b/admin/win/revision.txt deleted file mode 100644 index 7c091989d..000000000 --- a/admin/win/revision.txt +++ /dev/null @@ -1 +0,0 @@ -37 \ No newline at end of file diff --git a/admin/win/sparklewin-beta.rss b/admin/win/sparklewin-beta.rss new file mode 100644 index 000000000..14d33d797 --- /dev/null +++ b/admin/win/sparklewin-beta.rss @@ -0,0 +1,17 @@ + + + + Tomahawk Player + http://www.gettomahawk.com + Tomahawk Player Beta + en + + Version 0.0.1 (Tomahawk Player Beta - It Lives?) + + https://github.com/tomahawk-player/tomahawk/raw/stable/ChangeLog + + Fri, 25 Mar 2011 00:00:01 +0100 + + + + diff --git a/admin/win/sparklewin.rss b/admin/win/sparklewin.rss new file mode 100644 index 000000000..aa9f8f4fa --- /dev/null +++ b/admin/win/sparklewin.rss @@ -0,0 +1,17 @@ + + + + Tomahawk Player + http://www.gettomahawk.com + Tomahawk Player + en + + Version 0.0.1 (Tomahawk Player - It Lives!) + + https://github.com/tomahawk-player/tomahawk/raw/0.0.1/ChangeLog + + Fri, 25 Mar 2011 00:00:01 +0100 + + + + diff --git a/admin/win/tomahawk.rc b/admin/win/tomahawk.rc deleted file mode 100644 index 1cd4630ac..000000000 --- a/admin/win/tomahawk.rc +++ /dev/null @@ -1 +0,0 @@ -ID ICON "data/tomahawk_logo_32x32.ico" diff --git a/cmake_uninstall.cmake.in b/cmake_uninstall.cmake.in new file mode 100644 index 000000000..df95fb9d8 --- /dev/null +++ b/cmake_uninstall.cmake.in @@ -0,0 +1,21 @@ +IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +STRING(REGEX REPLACE "\n" ";" files "${files}") +FOREACH(file ${files}) + MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + IF(EXISTS "$ENV{DESTDIR}${file}") + EXEC_PROGRAM( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + IF(NOT "${rm_retval}" STREQUAL 0) + MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + ENDIF(NOT "${rm_retval}" STREQUAL 0) + ELSE(EXISTS "$ENV{DESTDIR}${file}") + MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") + ENDIF(EXISTS "$ENV{DESTDIR}${file}") +ENDFOREACH(file) diff --git a/data/icons/tomahawk-icon.svg b/data/icons/tomahawk-icon.svg new file mode 100644 index 000000000..b1ceb1bc3 --- /dev/null +++ b/data/icons/tomahawk-icon.svg @@ -0,0 +1,302 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/images/arrow-down-double.png b/data/images/arrow-down-double.png new file mode 100644 index 000000000..aadd380ad Binary files /dev/null and b/data/images/arrow-down-double.png differ diff --git a/data/images/arrow-right-double.png b/data/images/arrow-right-double.png new file mode 100644 index 000000000..2702a3ddd Binary files /dev/null and b/data/images/arrow-right-double.png differ diff --git a/data/images/arrow-up-double.png b/data/images/arrow-up-double.png new file mode 100644 index 000000000..6f54166fe Binary files /dev/null and b/data/images/arrow-up-double.png differ diff --git a/data/images/back.png b/data/images/back.png new file mode 100644 index 000000000..3c9be3ebe Binary files /dev/null and b/data/images/back.png differ diff --git a/data/images/cover-shadow.png b/data/images/cover-shadow.png new file mode 100644 index 000000000..e362118c6 Binary files /dev/null and b/data/images/cover-shadow.png differ diff --git a/data/images/echonest_logo.png b/data/images/echonest_logo.png new file mode 100644 index 000000000..0c587bdfe Binary files /dev/null and b/data/images/echonest_logo.png differ diff --git a/data/images/forward.png b/data/images/forward.png new file mode 100644 index 000000000..4b9ede5c5 Binary files /dev/null and b/data/images/forward.png differ diff --git a/data/images/home.png b/data/images/home.png new file mode 100644 index 000000000..5f86690c9 Binary files /dev/null and b/data/images/home.png differ diff --git a/data/images/list-add.png b/data/images/list-add.png new file mode 100644 index 000000000..1e03be9bc Binary files /dev/null and b/data/images/list-add.png differ diff --git a/data/images/list-remove.png b/data/images/list-remove.png new file mode 100644 index 000000000..5a4d15b7b Binary files /dev/null and b/data/images/list-remove.png differ diff --git a/data/images/loading-animation.gif b/data/images/loading-animation.gif new file mode 100644 index 000000000..0be11d9d2 Binary files /dev/null and b/data/images/loading-animation.gif differ diff --git a/data/images/music-icon.png b/data/images/music-icon.png new file mode 100644 index 000000000..67e75a0b0 Binary files /dev/null and b/data/images/music-icon.png differ diff --git a/data/images/playlist-icon.png b/data/images/playlist-icon.png index 99ac953b1..658b5bc51 100644 Binary files a/data/images/playlist-icon.png and b/data/images/playlist-icon.png differ diff --git a/data/images/user-avatar.png b/data/images/user-avatar.png index 5be7b700f..0cbd4d6ce 100644 Binary files a/data/images/user-avatar.png and b/data/images/user-avatar.png differ diff --git a/data/images/view-refresh.png b/data/images/view-refresh.png new file mode 100644 index 000000000..86b6f82c1 Binary files /dev/null and b/data/images/view-refresh.png differ diff --git a/data/www/auth.html b/data/www/auth.html new file mode 100644 index 000000000..cdfba330d --- /dev/null +++ b/data/www/auth.html @@ -0,0 +1,64 @@ + + + + + Allow Tomahawk Access + + + + + Tomahawk - Powered by Playdar + + +
+
+

Allow access to Tomahawk from <%NAME%>

+

+ + +

+ + + + +
+
+ + diff --git a/data/www/auth.na.html b/data/www/auth.na.html new file mode 100644 index 000000000..d1b816572 --- /dev/null +++ b/data/www/auth.na.html @@ -0,0 +1,44 @@ + + + + + Allow Tomahawk Access + + + + + Tomahawk - Powered by Playdar + + +
+

You have allowed access to Tomahawk from <%NAME%>

+

Copy and paste this authentication token into the status bar then close this window.

+

Token: + + diff --git a/data/www/playdar_auth_logo.gif b/data/www/playdar_auth_logo.gif new file mode 100644 index 000000000..22b06bfa3 Binary files /dev/null and b/data/www/playdar_auth_logo.gif differ diff --git a/data/www/tomahawk_banner_small.png b/data/www/tomahawk_banner_small.png new file mode 100644 index 000000000..83a818d17 Binary files /dev/null and b/data/www/tomahawk_banner_small.png differ diff --git a/include/tomahawk/album.h b/include/tomahawk/album.h deleted file mode 100644 index f140a0fcc..000000000 --- a/include/tomahawk/album.h +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef TOMAHAWKALBUM_H -#define TOMAHAWKALBUM_H - -#include -#include - -#include "tomahawk/typedefs.h" -#include "tomahawk/artist.h" -#include "tomahawk/collection.h" - -#include "tomahawk/playlistinterface.h" - -namespace Tomahawk -{ - -class Album : public QObject, public PlaylistInterface -{ -Q_OBJECT - -public: - static album_ptr get( unsigned int id, const QString& name, const Tomahawk::artist_ptr& artist, const Tomahawk::collection_ptr& collection ); - - Album( unsigned int id, const QString& name, const Tomahawk::artist_ptr& artist, const Tomahawk::collection_ptr& collection ); - - unsigned int id() const { return m_id; } - QString name() const { return m_name; } - artist_ptr artist() const { return m_artist; } - - Tomahawk::collection_ptr collection() const { return m_collection; } - QList tracks(); - - virtual int trackCount() const { return m_queries.count(); } - virtual Tomahawk::result_ptr siblingItem( int itemsAway ); - - virtual PlaylistInterface::RepeatMode repeatMode() const { return PlaylistInterface::NoRepeat; } - virtual bool shuffled() const { return false; } - - virtual void setRepeatMode( PlaylistInterface::RepeatMode ) {} - virtual void setShuffled( bool ) {} - -signals: - void repeatModeChanged( PlaylistInterface::RepeatMode mode ); - void shuffleModeChanged( bool enabled ); - - void tracksAdded( const QList& tracks, const Tomahawk::collection_ptr& ); - void trackCountChanged( unsigned int tracks ); - -private slots: - void onTracksAdded( const QList& tracks, const Tomahawk::collection_ptr& collection ); - -private: - unsigned int m_id; - QString m_name; - - artist_ptr m_artist; - QList m_queries; - Tomahawk::collection_ptr m_collection; - - unsigned int m_currentTrack; -}; - -}; // ns - -#endif diff --git a/include/tomahawk/artist.h b/include/tomahawk/artist.h deleted file mode 100644 index 9ea130954..000000000 --- a/include/tomahawk/artist.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef TOMAHAWKARTIST_H -#define TOMAHAWKARTIST_H - -#include -#include - -namespace Tomahawk -{ - -class Artist : public QObject -{ -Q_OBJECT - -public: - Artist( const QString& name ) - : m_name( name ) - {}; - - QString name() const { return m_name; } - -private: - QString m_name; -}; - -}; // ns - -#endif diff --git a/include/tomahawk/collection.h b/include/tomahawk/collection.h deleted file mode 100644 index af1bc7dc4..000000000 --- a/include/tomahawk/collection.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - The collection - acts as container for someones music library - load() -> async populate by calling addArtists etc, - then finishedLoading() is emitted. - then use artists() etc to get the data. -*/ - -#ifndef TOMAHAWK_COLLECTION_H -#define TOMAHAWK_COLLECTION_H - -#include -#include -#include -#include - -#include "tomahawk/functimeout.h" -#include "tomahawk/playlist.h" -#include "tomahawk/source.h" -#include "tomahawk/typedefs.h" - -namespace Tomahawk -{ - -class Collection : public QObject -{ -Q_OBJECT - -public: - Collection( const source_ptr& source, const QString& name, QObject* parent = 0 ); - virtual ~Collection(); - - virtual QString name() const; - - virtual void loadPlaylists() { qDebug() << Q_FUNC_INFO; } - virtual void loadTracks() { qDebug() << Q_FUNC_INFO; } - - virtual Tomahawk::playlist_ptr playlist( const QString& guid ); - virtual void addPlaylist( const Tomahawk::playlist_ptr& p ); - virtual void deletePlaylist( const Tomahawk::playlist_ptr& p ); - - virtual QList< Tomahawk::playlist_ptr > playlists() { return m_playlists; } - virtual QList< Tomahawk::query_ptr > tracks() { return m_tracks; } - - const source_ptr& source() const { return m_source; } - unsigned int lastmodified() const { return m_lastmodified; } - -signals: - void tracksAdded( const QList& tracks, const Tomahawk::collection_ptr& ); - void tracksRemoved( const QList&, const Tomahawk::collection_ptr& ); - void tracksFinished( const Tomahawk::collection_ptr& ); - - void playlistsAdded( const QList& ); - void playlistsDeleted( const QList& ); - -public slots: - virtual void addTracks( const QList &newitems ) = 0; - virtual void removeTracks( const QList &olditems ) = 0; - - void setPlaylists( const QList& plists ); - void setTracks( const QList& tracks, Tomahawk::collection_ptr collection ); - -protected: - QString m_name; - unsigned int m_lastmodified; // unix time of last change to collection - -private: - source_ptr m_source; - QList< Tomahawk::playlist_ptr > m_playlists; - QList< Tomahawk::query_ptr > m_tracks; -}; - -}; // ns - -inline uint qHash( const QSharedPointer& key ) -{ - return qHash( (void *)key.data() ); -} - -#endif // TOMAHAWK_COLLECTION_H diff --git a/include/tomahawk/functimeout.h b/include/tomahawk/functimeout.h deleted file mode 100644 index 5da67a526..000000000 --- a/include/tomahawk/functimeout.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef FUNCTIMEOUT_H -#define FUNCTIMEOUT_H - -#include -#include -#include - -#include "boost/function.hpp" -#include "boost/bind.hpp" - -/* - I want to do: - QTimer::singleShot(1000, this, SLOT(doSomething(x))); - instead, I'm doing: - new FuncTimeout(1000, boost::bind(&MyClass::doSomething, this, x)); - - */ -namespace Tomahawk -{ - -class FuncTimeout : public QObject -{ -Q_OBJECT - -public: - FuncTimeout( int ms, boost::function func ) - : m_func( func ) - { - //qDebug() << Q_FUNC_INFO; - QTimer::singleShot( ms, this, SLOT(exec() ) ); - }; - - ~FuncTimeout() - { - //qDebug() << Q_FUNC_INFO; - }; - -public slots: - void exec() - { - m_func(); - this->deleteLater(); - }; - -private: - boost::function m_func; -}; - -}; // ns - -#endif // FUNCTIMEOUT_H diff --git a/include/tomahawk/infosystem.h b/include/tomahawk/infosystem.h index 983305c47..93a9b1745 100644 --- a/include/tomahawk/infosystem.h +++ b/include/tomahawk/infosystem.h @@ -1,19 +1,39 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef TOMAHAWK_INFOSYSTEM_H #define TOMAHAWK_INFOSYSTEM_H #include #include -#include -#include -#include -#include -#include - +#include +#include +#include +#include +#include +#include namespace Tomahawk { namespace InfoSystem { +class InfoSystemCache; + enum InfoType { InfoTrackID, InfoTrackArtist, @@ -60,37 +80,42 @@ enum InfoType { InfoAlbumDate, InfoAlbumGenre, InfoAlbumComposer, + InfoAlbumCoverArt, + InfoMiscTopHotttness, InfoMiscTopTerms, + InfoMiscSubmitNowPlaying, + InfoMiscSubmitScrobble, + InfoNoInfo }; typedef QMap< InfoType, QVariant > InfoMap; typedef QMap< QString, QMap< QString, QString > > InfoGenericMap; -typedef QHash InfoCustomDataHash; -typedef QHash MusixMatchHash; +typedef QHash< QString, QVariant > InfoCustomDataHash; class InfoPlugin : public QObject { Q_OBJECT public: - InfoPlugin(QObject *parent) - :QObject(parent) - { - qDebug() << Q_FUNC_INFO; - } - ~InfoPlugin() + InfoPlugin( QObject *parent ); + + virtual ~InfoPlugin() { qDebug() << Q_FUNC_INFO; } - virtual void getInfo(const QString &caller, const InfoType type, const QVariant &data, Tomahawk::InfoSystem::InfoCustomDataHash customData) = 0; + virtual void getInfo( const QString &caller, const InfoType type, const QVariant &data, Tomahawk::InfoSystem::InfoCustomDataHash customData ) = 0; signals: - void info(QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData); - void finished(QString, Tomahawk::InfoSystem::InfoType); + void info( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void getCachedInfo( QHash< QString, QString > criteria, QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void finished( QString, Tomahawk::InfoSystem::InfoType ); + +//public slots: + //void notInCacheSlot( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ) = 0; protected: InfoType m_type; @@ -103,46 +128,44 @@ class InfoSystem : public QObject Q_OBJECT public: + + InfoSystem( QObject *parent ); + ~InfoSystem(); + void registerInfoTypes( const InfoPluginPtr &plugin, const QSet< InfoType > &types ); - InfoSystem(QObject *parent); - ~InfoSystem() - { - qDebug() << Q_FUNC_INFO; - } + void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomDataHash customData ); + void getInfo( const QString &caller, const InfoMap &input, InfoCustomDataHash customData ); - void registerInfoTypes(const InfoPluginPtr &plugin, const QSet< InfoType > &types); - - void getInfo(const QString &caller, const InfoType type, const QVariant &data, InfoCustomDataHash customData); - void getInfo(const QString &caller, const InfoMap &input, InfoCustomDataHash customData); + InfoSystemCache* getCache() { return m_cache; } signals: - void info(QString caller, Tomahawk::InfoSystem::InfoType, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData); - void finished(QString target); + void info( QString caller, Tomahawk::InfoSystem::InfoType, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void finished( QString target ); public slots: - void infoSlot(QString target, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData); - void finishedSlot(QString target,Tomahawk::InfoSystem::InfoType type); + void infoSlot( QString target, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void finishedSlot( QString target,Tomahawk::InfoSystem::InfoType type); private: - - QLinkedList< InfoPluginPtr > determineOrderedMatches(const InfoType type) const; + QLinkedList< InfoPluginPtr > determineOrderedMatches( const InfoType type ) const; - QMap< InfoType, QLinkedList > m_infoMap; + QMap< InfoType, QLinkedList< InfoPluginPtr > > m_infoMap; // For now, statically instantiate plugins; this is just somewhere to keep them - QLinkedList m_plugins; + QLinkedList< InfoPluginPtr > m_plugins; QHash< QString, QHash< Tomahawk::InfoSystem::InfoType, int > > m_dataTracker; + InfoSystemCache* m_cache; + QThread* m_infoSystemCacheThreadController; }; } } -Q_DECLARE_METATYPE(Tomahawk::InfoSystem::InfoGenericMap) -Q_DECLARE_METATYPE(Tomahawk::InfoSystem::InfoCustomDataHash); -Q_DECLARE_METATYPE(Tomahawk::InfoSystem::MusixMatchHash) +Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoGenericMap ) +Q_DECLARE_METATYPE( Tomahawk::InfoSystem::InfoCustomDataHash ); #endif // TOMAHAWK_INFOSYSTEM_H diff --git a/include/tomahawk/pipeline.h b/include/tomahawk/pipeline.h deleted file mode 100644 index 17dce1895..000000000 --- a/include/tomahawk/pipeline.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef PIPELINE_H -#define PIPELINE_H - -#include -#include -#include -#include - -#include "tomahawk/typedefs.h" -#include "tomahawk/query.h" -#include "tomahawk/result.h" -#include "tomahawk/resolver.h" - -namespace Tomahawk -{ - -class Resolver; - -class Pipeline : public QObject -{ -Q_OBJECT - -public: - explicit Pipeline( QObject* parent = 0 ); - -// const query_ptr& query( QID qid ) const; -// result_ptr result( RID rid ) const; - - void reportResults( QID qid, const QList< result_ptr >& results ); - - /// sorter to rank resolver priority - static bool resolverSorter( const Resolver* left, const Resolver* right ); - - void addResolver( Resolver* r, bool sort = true ); - void removeResolver( Resolver* r ); - - query_ptr query( const QID& qid ) const - { - return m_qids.value( qid ); - } - - result_ptr result( const RID& rid ) const - { - return m_rids.value( rid ); - } - -public slots: - void add( const query_ptr& q ); - void add( const QList& qlist ); - void databaseReady(); - -private slots: - void shunt( const query_ptr& q ); - void indexReady(); - -private: - QList< Resolver* > m_resolvers; - QMap< QID, query_ptr > m_qids; - QMap< RID, result_ptr > m_rids; - - QMutex m_mut; // for m_qids, m_rids - - // store queries here until DB index is loaded, then shunt them all - QList< query_ptr > m_queries_pending; - bool m_index_ready; -}; - -}; //ns - -#endif // PIPELINE_H diff --git a/include/tomahawk/playlistinterface.h b/include/tomahawk/playlistinterface.h deleted file mode 100644 index 54f22d047..000000000 --- a/include/tomahawk/playlistinterface.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef PLAYLISTINTERFACE_H -#define PLAYLISTINTERFACE_H - -#include - -#include "tomahawk/typedefs.h" - -class PlaylistInterface -{ -public: - enum RepeatMode { NoRepeat, RepeatOne, RepeatAll }; - - PlaylistInterface() {} - virtual ~PlaylistInterface() {} - - virtual int trackCount() const = 0; - - virtual Tomahawk::result_ptr previousItem() { return siblingItem( -1 ); } - virtual Tomahawk::result_ptr nextItem() { return siblingItem( 1 ); } - virtual Tomahawk::result_ptr siblingItem( int itemsAway ) = 0; - - virtual PlaylistInterface::RepeatMode repeatMode() const = 0; - virtual bool shuffled() const = 0; - -public slots: - virtual void setRepeatMode( RepeatMode mode ) = 0; - virtual void setShuffled( bool enabled ) = 0; - -signals: - virtual void repeatModeChanged( PlaylistInterface::RepeatMode mode ) = 0; - virtual void shuffleModeChanged( bool enabled ) = 0; - virtual void trackCountChanged( unsigned int tracks ) = 0; -}; - -#endif // PLAYLISTINTERFACE_H diff --git a/include/tomahawk/plugin_includes.h b/include/tomahawk/plugin_includes.h index 77c472446..f8844e05f 100644 --- a/include/tomahawk/plugin_includes.h +++ b/include/tomahawk/plugin_includes.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef PLUGIN_INCLUDES_H #define PLUGIN_INCLUDES_H diff --git a/include/tomahawk/pluginapi.h b/include/tomahawk/pluginapi.h deleted file mode 100644 index 3f6ab07fa..000000000 --- a/include/tomahawk/pluginapi.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef PLUGINAPI_H -#define PLUGINAPI_H - -#include -#include - -#include "tomahawk/collection.h" -#include "tomahawk/source.h" - -/* - This is the only API plugins have access to. - This class must proxy calls to internal functions, because plugins can't - get a pointer to any old object and start calling methods on it. -*/ - -namespace Tomahawk -{ -class Resolver; -class Pipeline; - -class PluginAPI : public QObject -{ -Q_OBJECT - -public: - explicit PluginAPI( Pipeline * p ); - - /// call every time new results are available for a running query -// void reportResults( const QString& qid, const QList& results ); - - /// add/remove sources (which have collections) - void addSource( source_ptr s ); - void removeSource( source_ptr s ); - - /// register object capable of searching - void addResolver( Resolver * r ); - - Pipeline * pipeline() const { return m_pipeline; } - -private: - Pipeline * m_pipeline; -}; - - -}; //ns - -#endif // PLUGINAPI_H diff --git a/include/tomahawk/query.h b/include/tomahawk/query.h deleted file mode 100644 index 3ce6064eb..000000000 --- a/include/tomahawk/query.h +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef QUERY_H -#define QUERY_H - -#include -#include -#include -#include - -#include "tomahawk/collection.h" -#include "tomahawk/result.h" -#include "tomahawk/typedefs.h" - -namespace Tomahawk -{ - -class Query : public QObject -{ -Q_OBJECT - -public: - explicit Query( const QVariant& v ); - - QVariant toVariant() const { return m_v; } - - /// returns list of all results so far - QList< result_ptr > results() const; - - /// how many results found so far? - unsigned int numResults() const; - - QID id() const; - - /// sorter for list of results - static bool resultSorter( const result_ptr &left, const result_ptr& right ); - - /// solved=true when a perfect result has been found (score of 1.0) - bool solved() const { return m_solved; } - - unsigned int lastPipelineWeight() const { return m_lastpipelineweight; } - void setLastPipelineWeight( unsigned int w ) { m_lastpipelineweight = w;} - - /// for debug output: - QString toString() const - { - return QString( "Query(%1, %2 - %3)" ).arg( id() ).arg( artist() ).arg( track() ); - } - - QString artist() const { return m_artist; } - QString album() const { return m_album; } - QString track() const { return m_track; } - -signals: - void resultsAdded( const QList& ); - void resultsRemoved( const Tomahawk::result_ptr& ); - void solvedStateChanged( bool state ); - -public slots: - /// (indirectly) called by resolver plugins when results are found - void addResults( const QList< Tomahawk::result_ptr >& ); - void removeResult( const Tomahawk::result_ptr& ); - -private slots: - void resultUnavailable(); - -private: - mutable QMutex m_mut; - mutable QVariant m_v; - QList< Tomahawk::result_ptr > m_results; - bool m_solved; - mutable QID m_qid; - unsigned int m_lastpipelineweight; - - QString m_artist; - QString m_album; - QString m_track; -}; - -}; //ns - -#endif // QUERY_H diff --git a/include/tomahawk/resolver.h b/include/tomahawk/resolver.h deleted file mode 100644 index 16e55bda2..000000000 --- a/include/tomahawk/resolver.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef RESOLVER_H -#define RESOLVER_H - -#include - -#include "tomahawk/pluginapi.h" - -// implement this if you can resolve queries to content - -/* - Weight: 1-100, 100 being the best - Timeout: some millisecond value, after which we try the next highest - weighted resolver - -*/ -namespace Tomahawk -{ -class PluginAPI; - -class Resolver : public QObject -{ -Q_OBJECT - -public: - Resolver() {}; - - virtual QString name() const = 0; - virtual unsigned int weight() const = 0; - virtual unsigned int preference() const { return 100; }; - virtual unsigned int timeout() const = 0; - virtual void resolve( const QVariant& ) = 0; - - //virtual QWidget * configUI() { return 0; }; - //etc - - PluginAPI * api() const { return m_api; } - -private: - PluginAPI * m_api; -}; - -}; //ns - -#endif // RESOLVER_H diff --git a/include/tomahawk/result.h b/include/tomahawk/result.h deleted file mode 100644 index 1d36a8e79..000000000 --- a/include/tomahawk/result.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef RESULT_H -#define RESULT_H - -#include - -#include "tomahawk/typedefs.h" -#include "collection.h" -#include "artist.h" -#include "album.h" - -namespace Tomahawk -{ - -class Result : public QObject -{ -Q_OBJECT - -public: - explicit Result( const QVariant& v, const collection_ptr& collection ); - QVariant toVariant() const { return m_v; } - - float score() const; - RID id() const; - collection_ptr collection() const { return m_collection; } - - Tomahawk::artist_ptr artist() const { return m_artist; } - Tomahawk::album_ptr album() const { return m_album; } - QString track() const { return m_track; } - QString url() const { return m_url; } - QString mimetype() const { return m_mimetype; } - - unsigned int duration() const { return m_duration; } - unsigned int bitrate() const { return m_bitrate; } - unsigned int size() const { return m_size; } - unsigned int albumpos() const { return m_albumpos; } - unsigned int modificationTime() const { return m_modtime; } - - unsigned int dbid() const { return m_id; } - - // for debug output: - QString toString() const; - -signals: - // emitted when the collection this result comes from is going offline: - void becomingUnavailable(); - -private: - QVariant m_v; - mutable RID m_rid; - collection_ptr m_collection; - - Tomahawk::artist_ptr m_artist; - Tomahawk::album_ptr m_album; - QString m_track; - QString m_url; - QString m_mimetype; - - unsigned int m_duration; - unsigned int m_bitrate; - unsigned int m_size; - unsigned int m_albumpos; - unsigned int m_modtime; - - unsigned int m_id; -}; - -}; //ns - -#endif // RESULT_H diff --git a/include/tomahawk/source.h b/include/tomahawk/source.h deleted file mode 100644 index 6889f3cb4..000000000 --- a/include/tomahawk/source.h +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef SOURCE_H -#define SOURCE_H - -#include -#include -#include - -#include "dbsyncconnection.h" -#include "collection.h" -#include "typedefs.h" - -class ControlConnection; - -namespace Tomahawk -{ - -class Source : public QObject -{ -Q_OBJECT - -public: - explicit Source( const QString& username, ControlConnection* cc ); - explicit Source( const QString& username ); - virtual ~Source(); - - bool isLocal() const { return m_isLocal; } - bool isOnline() const { return m_online; } - - const QString& userName() const { return m_username; } - QString friendlyName() const; - void setFriendlyName( const QString& fname ) { m_friendlyname = fname; } - - collection_ptr collection() const; - void addCollection( QSharedPointer c ); - void removeCollection( QSharedPointer c ); - - unsigned int id() const { return m_id; } - ControlConnection* controlConnection() const { return m_cc; } - - void scanningProgress( unsigned int files ) { emit loadingStateChanged( DBSyncConnection::SCANNING, DBSyncConnection::UNKNOWN, QString::number( files ) ); } - -signals: - void syncedWithDatabase(); - void online(); - void offline(); - - void collectionAdded( QSharedPointer ); - void collectionRemoved( QSharedPointer ); - - void stats( const QVariantMap& ); - void usernameChanged( const QString& ); - - // this signal is emitted from DBSyncConnection: - void loadingStateChanged( DBSyncConnection::State newstate, DBSyncConnection::State oldstate, const QString& info ); - -public slots: - void doDBSync(); - void setStats( const QVariantMap& m ); - -protected: - void setOffline(); - void setOnline(); - -private slots: - void dbLoaded( unsigned int id, const QString& fname ); - void remove(); - -private: - bool m_isLocal; - bool m_online; - QString m_username, m_friendlyname; - unsigned int m_id; - QList< QSharedPointer > m_collections; - ControlConnection* m_cc; - QVariantMap m_stats; -}; - -}; - -#endif // SOURCE_H diff --git a/include/tomahawk/sourcelist.h b/include/tomahawk/sourcelist.h deleted file mode 100644 index ef94be5d8..000000000 --- a/include/tomahawk/sourcelist.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef SOURCELIST_H -#define SOURCELIST_H - -#include -#include -#include - -#include "tomahawk/source.h" -#include "tomahawk/typedefs.h" - -class SourceList : public QObject -{ -Q_OBJECT - -public: - explicit SourceList( QObject* parent = 0 ); - - const Tomahawk::source_ptr& getLocal(); - void add( const Tomahawk::source_ptr& s ); - void remove( const Tomahawk::source_ptr& s ); - void remove( Tomahawk::Source* s ); - - QList sources() const; - Tomahawk::source_ptr lookup( const QString& username ) const; - Tomahawk::source_ptr lookup( unsigned int id ) const; - unsigned int count() const; - -signals: - void sourceAdded( const Tomahawk::source_ptr& ); - void sourceRemoved( const Tomahawk::source_ptr& ); - -private: - QMap m_sources; - QMap m_sources_id2name; - - Tomahawk::source_ptr m_local; - mutable QMutex m_mut; // mutable so const methods can use a lock -}; - -#endif // SOURCELIST_H diff --git a/include/tomahawk/tomahawkapp.h b/include/tomahawk/tomahawkapp.h index b0a277462..bf83f6e86 100644 --- a/include/tomahawk/tomahawkapp.h +++ b/include/tomahawk/tomahawkapp.h @@ -1,63 +1,75 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef TOMAHAWKAPP_H #define TOMAHAWKAPP_H #define APP TomahawkApp::instance() -#define RESPATH ":/data/" - #include "headlesscheck.h" +#include "config.h" + +#include "mac/tomahawkapp_mac.h" // for PlatforInterface #include #include #include -#include #include #include "QxtHttpServerConnector" #include "QxtHttpSessionManager" -#include "tomahawk/functimeout.h" -#include "tomahawk/typedefs.h" -#include "tomahawk/tomahawkplugin.h" -#include "tomahawk/playlist.h" -#include "tomahawk/pipeline.h" +#include "typedefs.h" +#include "playlist.h" +#include "resolver.h" +#include "network/servent.h" #include "utils/tomahawkutils.h" -#include "sourcelist.h" -#include "servent.h" - class AudioEngine; class Database; -class Jabber; -class XMPPBot; -class TomahawkZeroconf; +class SipHandler; class TomahawkSettings; +class XMPPBot; namespace Tomahawk { + class ShortcutHandler; namespace InfoSystem { class InfoSystem; } } -#ifndef NO_LIBLASTFM +#ifdef LIBLASTFM_FOUND #include #include "scrobbler.h" #endif #ifndef TOMAHAWK_HEADLESS class TomahawkWindow; -class PlaylistManager; -#include #endif // this also acts as a a container for important top-level objects // that other parts of the app need to find // (eg, library, pipeline, friends list) -class TomahawkApp : public TOMAHAWK_APPLICATION +class TomahawkApp : public TOMAHAWK_APPLICATION, public Tomahawk::PlatformInterface { Q_OBJECT @@ -67,45 +79,28 @@ public: static TomahawkApp* instance(); - Tomahawk::Pipeline* pipeline() { return &m_pipeline; } - AudioEngine* audioEngine() { return m_audioEngine; } - Database* database() { return m_db; } - SourceList& sourcelist() { return m_sources; } - Servent& servent() { return m_servent; } - QNetworkAccessManager* nam() { return m_nam; } - QNetworkProxy* proxy() { return m_proxy; } + SipHandler* sipHandler() { return m_sipHandler; } Tomahawk::InfoSystem::InfoSystem* infoSystem() { return m_infoSystem; } XMPPBot* xmppBot() { return m_xmppBot; } - const QString& nodeID() const; - #ifndef TOMAHAWK_HEADLESS AudioControls* audioControls(); - PlaylistManager* playlistManager(); + TomahawkWindow* mainWindow() const { return m_mainwindow; } #endif - void registerIODeviceFactory( const QString &proto, boost::function(Tomahawk::result_ptr)> fac ); - QSharedPointer localFileIODeviceFactory( const Tomahawk::result_ptr& result ); - QSharedPointer httpIODeviceFactory( const Tomahawk::result_ptr& result ); + void addScriptResolver( const QString& scriptPath ); + void removeScriptResolver( const QString& scriptPath ); - TomahawkSettings* settings() { return m_settings; } + // PlatformInterface + virtual void activate(); + virtual bool loadUrl( const QString& url ); -signals: - void settingsChanged(); + // because QApplication::arguments() is expensive + bool scrubFriendlyName() const { return m_scrubFriendlyName; } -public slots: - QSharedPointer getIODeviceForUrl( const Tomahawk::result_ptr& result ); - void reconnectJabber(); - private slots: - void jabberMessage( const QString&, const QString& ); - void jabberPeerOffline( const QString& ); - void jabberPeerOnline( const QString& ); - void jabberAuthError( int code, const QString& msg ); - void jabberDisconnected(); - void jabberConnected(); - - void lanHostFound( const QString&, int, const QString&, const QString& ); + void setupSIP(); + void messageReceived( const QString& ); private: void initLocalCollection(); @@ -113,23 +108,21 @@ private: void registerMetaTypes(); void startServent(); void setupDatabase(); - void setupJabber(); void setupPipeline(); void startHTTP(); QList m_collections; - QList m_plugins; + QList m_scriptResolvers; - Tomahawk::Pipeline m_pipeline; + Database* m_database; AudioEngine* m_audioEngine; - Database* m_db; - Servent m_servent; - SourceList m_sources; - TomahawkZeroconf* m_zeroconf; - QSharedPointer m_jabber; + SipHandler* m_sipHandler; + Servent* m_servent; XMPPBot* m_xmppBot; - -#ifndef NO_LIBLASTFM + Tomahawk::ShortcutHandler* m_shortcutHandler; + bool m_scrubFriendlyName; + +#ifdef LIBLASTFM_FOUND Scrobbler* m_scrobbler; #endif @@ -137,13 +130,7 @@ private: TomahawkWindow* m_mainwindow; #endif - QMap< QString,boost::function(Tomahawk::result_ptr)> > m_iofactories; - bool m_headless; - TomahawkSettings* m_settings; - - QNetworkAccessManager* m_nam; - QNetworkProxy* m_proxy; Tomahawk::InfoSystem::InfoSystem* m_infoSystem; diff --git a/include/tomahawk/tomahawkplugin.h b/include/tomahawk/tomahawkplugin.h deleted file mode 100644 index f27e2d5ea..000000000 --- a/include/tomahawk/tomahawkplugin.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef TOMAHAWK_PLUGIN_H -#define TOMAHAWK_PLUGIN_H -#include -#include - -#include "tomahawk/pluginapi.h" - -class TomahawkPlugin -{ -public: - TomahawkPlugin(){}; - TomahawkPlugin(Tomahawk::PluginAPI * api) - : m_api(api) {}; - - virtual TomahawkPlugin * factory(Tomahawk::PluginAPI * api) = 0; - - virtual QString name() const = 0; - virtual QString description() const = 0; - -protected: - Tomahawk::PluginAPI * api() const { return m_api; }; - -private: - Tomahawk::PluginAPI * m_api; - -}; - -Q_DECLARE_INTERFACE(TomahawkPlugin, "org.tomahawk.TomahawkPlugin/1.0") - -#endif diff --git a/include/tomahawk/track.h b/include/tomahawk/track.h deleted file mode 100644 index f5967619c..000000000 --- a/include/tomahawk/track.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef TOMAHAWKTRACK_H -#define TOMAHAWKTRACK_H - -#include -#include - -#include "tomahawk/artist.h" -#include "tomahawk/typedefs.h" - -namespace Tomahawk -{ - -class Track : public QObject -{ -Q_OBJECT - -public: - Track( Tomahawk::artist_ptr artist, const QString& name ) - : m_name( name ) - , m_artist( artist ) - {} - - const QString& name() const { return m_name; } - const artist_ptr artist() const { return m_artist; } - -private: - QString m_name; - artist_ptr m_artist; -}; - -}; // ns - -#endif diff --git a/include/tomahawk/typedefs.h b/include/tomahawk/typedefs.h deleted file mode 100644 index 0140306bd..000000000 --- a/include/tomahawk/typedefs.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef TYPEDEFS_H -#define TYPEDEFS_H - -#include -#include - -namespace Tomahawk -{ - class Artist; - class Album; - class Collection; - class Playlist; - class PlaylistEntry; - class Query; - class Result; - class Source; - - typedef QSharedPointer collection_ptr; - typedef QSharedPointer playlist_ptr; - typedef QSharedPointer plentry_ptr; - typedef QSharedPointer query_ptr; - typedef QSharedPointer result_ptr; - typedef QSharedPointer source_ptr; - typedef QSharedPointer artist_ptr; - typedef QSharedPointer album_ptr; - - // let's keep these typesafe, they are different kinds of GUID: - typedef QString QID; //query id - typedef QString RID; //result id - -}; // ns - -typedef int AudioErrorCode; - -// creates 36char ascii guid without {} around it -inline static QString uuid() -{ - // kinda lame, but - QString q = QUuid::createUuid(); - q.remove( 0, 1 ); - q.chop( 1 ); - return q; -} - -#endif // TYPEDEFS_H diff --git a/resources.qrc b/resources.qrc index 870d293c4..ddbc46b96 100644 --- a/resources.qrc +++ b/resources.qrc @@ -6,6 +6,7 @@ ./data/images/avatar-dude.png ./data/images/back-pressed.png ./data/images/back-rest.png +./data/images/cover-shadow.png ./data/images/magnifying-glass.png ./data/images/no-album-art-placeholder.png ./data/images/now-playing-panel.png @@ -57,10 +58,22 @@ ./data/images/view-toggle-pressed-centre.png ./data/images/view-toggle-pressed-left.png ./data/images/view-toggle-pressed-right.png +./data/images/list-add.png +./data/images/list-remove.png +./data/images/arrow-up-double.png +./data/images/arrow-down-double.png ./data/images/volume-icon-full.png +./data/images/arrow-right-double.png +./data/images/view-refresh.png ./data/images/volume-icon-muted.png ./data/images/volume-slider-bkg.png ./data/images/volume-slider-level.png +./data/images/echonest_logo.png +./data/images/loading-animation.gif +./data/images/home.png +./data/images/back.png +./data/images/forward.png +./data/images/music-icon.png ./data/topbar-radiobuttons.css ./data/icons/tomahawk-icon-16x16.png ./data/icons/tomahawk-icon-32x32.png @@ -71,5 +84,8 @@ ./data/icons/audio-x-generic-22x22.png ./data/icons/audio-x-generic-32x32.png ./data/icons/audio-x-generic-16x16.png +./data/www/auth.html +./data/www/auth.na.html +./data/www/tomahawk_banner_small.png diff --git a/src/CMakeLists.linux.txt b/src/CMakeLists.linux.txt index edbcafd3a..54a3ac268 100644 --- a/src/CMakeLists.linux.txt +++ b/src/CMakeLists.linux.txt @@ -1,6 +1,7 @@ SET( OS_SPECIFIC_LINK_LIBRARIES ${OS_SPECIFIC_LINK_LIBRARIES} alsaplayback + tomahawklib ) IF( "${gui}" STREQUAL "no" ) @@ -10,5 +11,3 @@ ELSE() gnutls ) ENDIF() - -#include( "CPack.txt" ) diff --git a/src/CMakeLists.osx.txt b/src/CMakeLists.osx.txt index 7db1f0f95..6d3ded108 100644 --- a/src/CMakeLists.osx.txt +++ b/src/CMakeLists.osx.txt @@ -1,13 +1,49 @@ -SET( tomahawkSourcesGui ${tomahawkSourcesGui} audio/rtaudiooutput.cpp ) -SET( tomahawkHeadersGui ${tomahawkHeadersGui} audio/rtaudiooutput.h ) +SET( TOMAHAWK_LIBRARIES tomahawklib ) -FIND_LIBRARY( COREAUDIO_LIBRARY CoreAudio ) -FIND_LIBRARY( COREFOUNDATION_LIBRARY CoreFoundation ) -MARK_AS_ADVANCED( COREAUDIO_LIBRARY COREFOUNDATION_LIBRARY ) +SET( OS_SPECIFIC_LINK_LIBRARIES + ${OS_SPECIFIC_LINK_LIBRARIES} + ${COREAUDIO_LIBRARY} + ${COREFOUNDATION_LIBRARY} -SET( OS_SPECIFIC_LINK_LIBRARIES - ${OS_SPECIFIC_LINK_LIBRARIES} - ${COREAUDIO_LIBRARY} - ${COREFOUNDATION_LIBRARY} - rtaudio + /System/Library/Frameworks/AppKit.framework + /System/Library/Frameworks/Carbon.framework + /System/Library/Frameworks/DiskArbitration.framework + /System/Library/Frameworks/Foundation.framework + /System/Library/Frameworks/IOKit.framework + + rtaudio ) + + +if (APPLE) +# find_library(GROWL Growl) + option(ENABLE_SPARKLE "Sparkle updating" ON) + find_library(SPARKLE Sparkle) + if (ENABLE_SPARKLE AND SPARKLE) + set(HAVE_SPARKLE ON) + set( OS_SPECIFIC_LINK_LIBRARIES ${OS_SPECIFIC_LINK_LIBRARIES} ${SPARKLE} ) + endif(ENABLE_SPARKLE AND SPARKLE) + # Uses Darwin kernel version. + # 9.8.0 -> 10.5/Leopard + # 10.4.0 -> 10.6/Snow Leopard + string(REGEX MATCH "[0-9]+" DARWIN_VERSION ${CMAKE_HOST_SYSTEM_VERSION}) + if (DARWIN_VERSION GREATER 9) + SET(SNOW_LEOPARD 1) + elseif (DARWIN_VERSION GREATER 8) + SET(LEOPARD 1) + endif (DARWIN_VERSION GREATER 9) + +# Use two different sparkle update tracks for debug and release +# We have to change the URL in the Info.plist file :-/ + IF( NOT CMAKE_BUILD_TYPE STREQUAL "Release" ) + FILE(READ ${CMAKE_SOURCE_DIR}/admin/mac/Info.plist plist) + STRING( REPLACE "http://download.tomahawk-player.org/sparkle" # match this + "http://download.tomahawk-player.org/sparkle-debug" #replace with debug url + edited_plist # save in this variable + "${plist}" # from the contents of this var + ) + FILE( WRITE ${CMAKE_BINARY_DIR}/Info.plist "${edited_plist}" ) + ELSE() # Just copy the release one + FILE( COPY ${CMAKE_SOURCE_DIR}/admin/mac/Info.plist DESTINATION ${CMAKE_BINARY_DIR} ) + ENDIF() +endif (APPLE) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 18e5c2b46..259756b05 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,16 +7,21 @@ ENDIF() SET( QT_USE_QTSQL TRUE ) SET( QT_USE_QTNETWORK TRUE ) SET( QT_USE_QTXML TRUE ) -INCLUDE( ${QT_USE_FILE} ) +SET( QT_USE_QTWEBKIT TRUE ) +INCLUDE( ${QT_USE_FILE} ) INCLUDE( ${CMAKE_MODULE_PATH}/AddAppIconMacro.cmake ) -SET( CMAKE_BUILD_TYPE "debugfull" ) +#SET( CMAKE_BUILD_TYPE "Release" ) SET( CMAKE_VERBOSE_MAKEFILE ON ) SET( CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" ) SET( CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" ) SET( CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" ) -SET( TOMAHAWK_INC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../include/" ) +SET( TOMAHAWK_INC_DIR "${CMAKE_SOURCE_DIR}/include/" ) +IF( NOT CMAKE_BUILD_TYPE STREQUAL "Release" ) + MESSAGE( "Building in debug mode, enabling all debug updates" ) + SET( DEBUG_BUILD ON ) +ENDIF() # build plugins # use glob, but hardcoded list for now: @@ -27,245 +32,86 @@ SET( TOMAHAWK_INC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../include/" ) #ENDFOREACH( moddir ) SET( tomahawkSources ${tomahawkSources} - album.cpp - pipeline.cpp - playlist.cpp - pluginapi.cpp - query.cpp - result.cpp - source.cpp - sourcelist.cpp - - audio/madtranscode.cpp - audio/vorbistranscode.cpp - audio/audioengine.cpp - - utils/tomahawkutils.cpp - jabber/jabber_p.cpp + sip/SipHandler.cpp + infosystem/infosystemcache.cpp infosystem/infosystem.cpp infosystem/infoplugins/echonestplugin.cpp + infosystem/infoplugins/lastfmplugin.cpp infosystem/infoplugins/musixmatchplugin.cpp - bufferiodevice.cpp - msgprocessor.cpp - collection.cpp - musicscanner.cpp - scriptresolver.cpp - - network/connection.cpp - network/controlconnection.cpp - network/filetransferconnection.cpp - network/dbsyncconnection.cpp - network/remotecollection.cpp - network/servent.cpp - - database/fuzzyindex.cpp - database/database.cpp - database/databaseworker.cpp - database/databaseimpl.cpp - database/databaseresolver.cpp - database/databasecommand.cpp - database/databasecommandloggable.cpp - database/databasecommand_resolve.cpp - database/databasecommand_allalbums.cpp - database/databasecommand_alltracks.cpp - database/databasecommand_addfiles.cpp - database/databasecommand_dirmtimes.cpp - database/databasecommand_loadfile.cpp - database/databasecommand_addsource.cpp - database/databasecommand_sourceoffline.cpp - database/databasecommand_collectionstats.cpp - database/databasecommand_loadplaylistentries.cpp - database/databasecommand_modifyplaylist.cpp - database/databasecommand_setplaylistrevision.cpp - database/databasecommand_loadallplaylists.cpp - database/databasecommand_createplaylist.cpp - database/databasecommand_deleteplaylist.cpp - database/databasecommand_renameplaylist.cpp - database/databasecommand_loadops.cpp - database/databasecommand_updatesearchindex.cpp - database/databasecollection.cpp - - scrobbler.cpp - - xmppbot/xmppbot.cpp web/api_v1.cpp - tomahawksettings.cpp + resolvers/scriptresolver.cpp + resolvers/qtscriptresolver.cpp + + musicscanner.cpp + shortcuthandler.cpp + scanmanager.cpp tomahawkapp.cpp main.cpp ) +IF(LIBLASTFM_FOUND) + SET(tomahawkSources ${tomahawkSources} + scrobbler.cpp + ) +ENDIF(LIBLASTFM_FOUND) + SET( tomahawkSourcesGui ${tomahawkSourcesGui} - xspfloader.cpp - - utils/elidedlabel.cpp - utils/imagebutton.cpp - utils/progresstreeview.cpp - utils/proxystyle.cpp - utils/widgetdragfilter.cpp - - playlist/collectionmodel.cpp - playlist/collectionproxymodel.cpp - playlist/collectionflatmodel.cpp - playlist/collectionview.cpp - playlist/playlistmanager.cpp - playlist/plitem.cpp - playlist/playlistmodel.cpp - playlist/playlistproxymodel.cpp - playlist/playlistview.cpp - playlist/playlistitemdelegate.cpp - playlist/trackmodel.cpp - playlist/trackproxymodel.cpp - playlist/trackview.cpp - playlist/albumitem.cpp - playlist/albummodel.cpp - playlist/albumproxymodel.cpp - playlist/albumview.cpp - sourcetree/sourcesmodel.cpp + sourcetree/sourcesproxymodel.cpp sourcetree/sourcetreeitem.cpp sourcetree/sourcetreeitemwidget.cpp sourcetree/sourcetreeview.cpp - topbar/topbar.cpp - topbar/clearbutton.cpp - topbar/searchlineedit.cpp - topbar/lineedit.cpp - topbar/searchbutton.cpp - - infowidgets/sourceinfowidget.cpp - - tomahawkwindow.cpp + transferview.cpp + tomahawktrayicon.cpp audiocontrols.cpp settingsdialog.cpp + tomahawkwindow.cpp ) SET( tomahawkHeaders ${tomahawkHeaders} "${TOMAHAWK_INC_DIR}/tomahawk/tomahawkapp.h" - - "${TOMAHAWK_INC_DIR}/tomahawk/collection.h" - "${TOMAHAWK_INC_DIR}/tomahawk/pipeline.h" - "${TOMAHAWK_INC_DIR}/tomahawk/pluginapi.h" - "${TOMAHAWK_INC_DIR}/tomahawk/query.h" - "${TOMAHAWK_INC_DIR}/tomahawk/resolver.h" - "${TOMAHAWK_INC_DIR}/tomahawk/result.h" - "${TOMAHAWK_INC_DIR}/tomahawk/source.h" - "${TOMAHAWK_INC_DIR}/tomahawk/sourcelist.h" - - "${TOMAHAWK_INC_DIR}/tomahawk/artist.h" - "${TOMAHAWK_INC_DIR}/tomahawk/album.h" - "${TOMAHAWK_INC_DIR}/tomahawk/track.h" - "${TOMAHAWK_INC_DIR}/tomahawk/playlist.h" - "${TOMAHAWK_INC_DIR}/tomahawk/infosystem.h" - "${TOMAHAWK_INC_DIR}/tomahawk/functimeout.h" -# "${TOMAHAWK_INC_DIR}/tomahawk/tomahawkplugin.h" - - audio/transcodeinterface.h - audio/madtranscode.h - audio/vorbistranscode.h - audio/audioengine.h - - database/fuzzyindex.h - database/database.h - database/databaseworker.h - database/databaseimpl.h - database/databaseresolver.h - database/databasecommand.h - database/databasecommandloggable.h - database/databasecommand_resolve.h - database/databasecommand_allalbums.h - database/databasecommand_alltracks.h - database/databasecommand_addfiles.h - database/databasecommand_dirmtimes.h - database/databasecommand_loadfile.h - database/databasecommand_addsource.h - database/databasecommand_sourceoffline.h - database/databasecommand_collectionstats.h - database/databasecommand_loadplaylistentries.h - database/databasecommand_modifyplaylist.h - database/databasecommand_setplaylistrevision.h - database/databasecommand_loadallplaylists.h - database/databasecommand_createplaylist.h - database/databasecommand_deleteplaylist.h - database/databasecommand_renameplaylist.h - database/databasecommand_loadops.h - database/databasecommand_updatesearchindex.h - database/databasecollection.h - - jabber/jabber.h - jabber/jabber_p.h + sip/SipHandler.h + infosystem/infosystemcache.h infosystem/infoplugins/echonestplugin.h + infosystem/infoplugins/lastfmplugin.h infosystem/infoplugins/musixmatchplugin.h - bufferiodevice.h - msgprocessor.h - musicscanner.h - scriptresolver.h - tomahawksettings.h - tomahawkzeroconf.h - - network/remotecollection.h - network/servent.h - network/connection.h - network/controlconnection.h - network/filetransferconnection.h - network/dbsyncconnection.h - - scrobbler.h - - xmppbot/xmppbot.h web/api_v1.h + + resolvers/scriptresolver.h + resolvers/qtscriptresolver.h + + musicscanner.h + scanmanager.h + shortcuthandler.h ) +IF(LIBLASTFM_FOUND) + SET(tomahawkHeaders ${tomahawkHeaders} + scrobbler.h + ) +ENDIF(LIBLASTFM_FOUND) + + SET( tomahawkHeadersGui ${tomahawkHeadersGui} - xspfloader.h - - utils/elidedlabel.h - utils/animatedcounterlabel.h - utils/imagebutton.h - utils/progresstreeview.h - utils/widgetdragfilter.h - - playlist/collectionmodel.h - playlist/collectionproxymodel.h - playlist/collectionflatmodel.h - playlist/collectionview.h - playlist/playlistmanager.h - playlist/plitem.h - playlist/playlistmodel.h - playlist/playlistproxymodel.h - playlist/playlistview.h - playlist/playlistitemdelegate.h - playlist/trackmodel.h - playlist/trackproxymodel.h - playlist/trackview.h - playlist/albumitem.h - playlist/albummodel.h - playlist/albumproxymodel.h - playlist/albumview.h - sourcetree/sourcesmodel.h + sourcetree/sourcesproxymodel.h sourcetree/sourcetreeitem.h sourcetree/sourcetreeitemwidget.h sourcetree/sourcetreeview.h - topbar/topbar.h - topbar/clearbutton.h - topbar/searchlineedit.h - topbar/lineedit.h - topbar/lineedit_p.h - topbar/searchbutton.h - - infowidgets/sourceinfowidget.h - - tomahawkwindow.h + transferview.h + tomahawktrayicon.h audiocontrols.h settingsdialog.h + tomahawkwindow.h ) SET( tomahawkUI ${tomahawkUI} @@ -275,14 +121,13 @@ SET( tomahawkUI ${tomahawkUI} audiocontrols.ui sourcetree/sourcetreeitemwidget.ui - topbar/topbar.ui - infowidgets/sourceinfowidget.ui ) INCLUDE_DIRECTORIES( . ${TOMAHAWK_INC_DIR} ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR}/thirdparty/liblastfm2/src audio database @@ -291,17 +136,21 @@ INCLUDE_DIRECTORIES( sourcetree topbar utils + libtomahawk + libtomahawk/utils + infosystem + mac - ../rtaudio - ../alsa-playback - ../libportfwd/include - ../qxt/qxtweb-standalone/qxtweb + ${THIRDPARTY_DIR}/alsa-playback + ${THIRDPARTY_DIR}/rtaudio + ${THIRDPARTY_DIR}/qxt/qxtweb-standalone/qxtweb + ${THIRDPARTY_DIR}/qtweetlib/qtweetlib/src + ${THIRDPARTY_DIR}/qtweetlib/tomahawk-custom - /usr/include/taglib - /usr/local/include/taglib - /usr/include/echonest - /usr/local/include/echonest - /usr/local/include + ${TAGLIB_INCLUDES} + ${QJSON_INCLUDE_DIR} + ${LIBECHONEST_INCLUDE_DIR} + ${LIBECHONEST_INCLUDE_DIR}/.. ) SET( OS_SPECIFIC_LINK_LIBRARIES "" ) @@ -312,24 +161,38 @@ ENDIF( WIN32 ) IF( UNIX ) INCLUDE( "CMakeLists.unix.txt" ) ENDIF( UNIX ) -IF( APPLE ) - INCLUDE( "CMakeLists.osx.txt" ) -ENDIF( APPLE ) -IF( UNIX AND NOT APPLE ) - INCLUDE( "CMakeLists.linux.txt" ) -ENDIF( UNIX AND NOT APPLE ) -kde4_add_app_icon( tomahawkSources "${CMAKE_CURRENT_SOURCE_DIR}/../data/icons/tomahawk-icon-*.png" ) +IF( APPLE ) + SET( tomahawkHeaders ${tomahawkHeaders} mac/tomahawkapp_mac.h mac/macshortcuthandler.h ) + SET( tomahawkSources ${tomahawkSources} mac/tomahawkapp_mac.mm mac/macshortcuthandler.cpp ) + + IF(HAVE_SPARKLE) + SET( tomahawkHeaders ${tomahawkHeaders} ${SPARKLE}/Headers ) + ENDIF(HAVE_SPARKLE) + +ENDIF( APPLE ) + +IF(GLOOX_FOUND) + INCLUDE_DIRECTORIES( ${GLOOX_INCLUDE_DIR} ) + SET( tomahawkHeaders ${tomahawkHeaders} xmppbot/xmppbot.h ) + SET( tomahawkSources ${tomahawkSources} xmppbot/xmppbot.cpp ) +ENDIF(GLOOX_FOUND) +ADD_SUBDIRECTORY( sip ) + +kde4_add_app_icon( tomahawkSources "${CMAKE_SOURCE_DIR}/data/icons/tomahawk-icon-*.png" ) qt4_add_resources( RC_SRCS "../resources.qrc" ) qt4_wrap_cpp( tomahawkMoc ${tomahawkHeaders} ) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/config.h) + SET( final_src ${final_src} ${tomahawkMoc} ${tomahawkSources} ${tomahawkHeaders} ) IF( "${gui}" STREQUAL "no" ) ELSE() - qt4_wrap_ui( tomahawkUI_H ${tomahawkUI} ) - qt4_wrap_cpp( tomahawkMocGui ${tomahawkHeadersGui} ) - SET( final_src ${final_src} ${tomahawkUI_H} ${tomahawkMocGui} ${tomahawkSourcesGui} ${RC_SRCS} ) + qt4_wrap_ui( tomahawkUI_H ${tomahawkUI} ) + qt4_wrap_cpp( tomahawkMocGui ${tomahawkHeadersGui} ) + SET( final_src ${final_src} ${tomahawkUI_H} ${tomahawkMocGui} ${tomahawkSourcesGui} ${RC_SRCS} ) ENDIF() IF( UNIX AND NOT APPLE ) @@ -337,21 +200,47 @@ IF( UNIX AND NOT APPLE ) ENDIF( UNIX AND NOT APPLE ) IF( APPLE ) ADD_EXECUTABLE( tomahawk MACOSX_BUNDLE ${final_src} ) + SET_TARGET_PROPERTIES(tomahawk PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_BINARY_DIR}/Info.plist" +) ENDIF( APPLE ) IF( WIN32 ) - ADD_EXECUTABLE( tomahawk ${final_src} ) + ADD_EXECUTABLE( tomahawk WIN32 ${final_src} ) ENDIF( WIN32 ) MESSAGE( STATUS "OS_SPECIFIC_LINK_LIBRARIES: ${OS_SPECIFIC_LINK_LIBRARIES}" ) +SET(LINK_LIBRARIES "") +IF(LIBLASTFM_FOUND) + SET(LINK_LIBRARIES ${LINK_LIBRARIES} tomahawk_lastfm2 ) +ENDIF(LIBLASTFM_FOUND) +IF(GLOOX_FOUND) + SET(LINK_LIBRARIES ${LINK_LIBRARIES} ${GLOOX_LIBRARIES} ) +ENDIF(GLOOX_FOUND) + TARGET_LINK_LIBRARIES( tomahawk + ${LINK_LIBRARIES} + ${TOMAHAWK_LIBRARIES} + ${OS_SPECIFIC_LINK_LIBRARIES} ${QT_LIBRARIES} ${MAC_EXTRA_LIBS} - portfwd - ${OS_SPECIFIC_LINK_LIBRARIES} - echonest - qjson - tag + ${LIBECHONEST_LIBRARY} + ${QXTWEB_LIBRARIES} + ${QTWEETLIB_LIBRARIES} + ${QJSON_LIBRARIES} + ${TAGLIB_LIBRARIES} + ${QTWEETLIB_LIBRARIES} + ${CLUCENE_LIBRARIES} ) -INCLUDE( "CPack.txt" ) + +IF( APPLE ) + IF(HAVE_SPARKLE) + MESSAGE("Sparkle Found, installing framekwork in bundle") + INSTALL(DIRECTORY "${SPARKLE}/Versions/Current/Resources" DESTINATION "${CMAKE_BINARY_DIR}/tomahawk.app/Contents/Frameworks/Sparkle.framework") + + ENDIF(HAVE_SPARKLE) +ENDIF( APPLE ) + +INSTALL( TARGETS tomahawk BUNDLE DESTINATION . RUNTIME DESTINATION bin ) + +#INCLUDE( "CPack.txt" ) diff --git a/src/CMakeLists.unix.txt b/src/CMakeLists.unix.txt index f42700174..cce2e5cd5 100644 --- a/src/CMakeLists.unix.txt +++ b/src/CMakeLists.unix.txt @@ -4,17 +4,12 @@ ADD_DEFINITIONS( -g ) ADD_DEFINITIONS( -fno-operator-names ) ADD_DEFINITIONS( -fPIC ) -SET( GLOOX_LIBS ${GLOOX_LIBS} resolv gloox ) +SET( QXTWEB_LIBRARIES qxtweb-standalone ) -SET( OS_SPECIFIC_LINK_LIBRARIES - ${LIBLASTFM_LIBRARY} - ${GLOOX_LIBS} - qxtweb-standalone -) +IF( APPLE ) + INCLUDE( "CMakeLists.osx.txt" ) +ENDIF( APPLE ) -SET( OS_SPECIFIC_LINK_LIBRARIES - ${OS_SPECIFIC_LINK_LIBRARIES} - mad - vorbisfile - ogg -) +IF( UNIX AND NOT APPLE ) + INCLUDE( "CMakeLists.linux.txt" ) +ENDIF( UNIX AND NOT APPLE ) diff --git a/src/CMakeLists.win32.txt b/src/CMakeLists.win32.txt index 5652d47a8..1557dbfa3 100644 --- a/src/CMakeLists.win32.txt +++ b/src/CMakeLists.win32.txt @@ -1,47 +1,29 @@ +SET( CMAKE_BUILD_TYPE "Release" ) + ADD_DEFINITIONS( /DNOMINMAX ) ADD_DEFINITIONS( /DWIN32_LEAN_AND_MEAN ) ADD_DEFINITIONS( -static-libgcc ) -#ADD_DEFINITIONS( /DNO_LIBLASTFM ) -#ADD_DEFINITIONS( -DNO_OGG ) -# Add manual locations to stuff: -INCLUDE_DIRECTORIES( - ../../libmad-0.15.1b - ../../boost_1_43_0 - ../../gloox-1.0 - ../../qjson - ../../liblastfm/_include - ../../taglib-1.6.3/ - ../../taglib-1.6.3/build - ../../taglib-1.6.3/taglib - ../../taglib-1.6.3/taglib/toolkit + +SET( TOMAHAWK_LIBRARIES ${CMAKE_BINARY_DIR}/src/libtomahawk/libtomahawklib.dll ) +SET( QTWEETLIB_LIBRARIES ${CMAKE_BINARY_DIR}/thirdparty/qtweetlib/libtomahawk_qtweetlib.dll ) +SET( QXTWEB_LIBRARIES ${CMAKE_BINARY_DIR}/thirdparty/qxt/qxtweb-standalone/libqxtweb-standalone.dll ) + +SET( OS_SPECIFIC_LINK_LIBRARIES + ${OS_SPECIFIC_LINK_LIBRARIES} + + ${QTSPARKLE_LIBRARIES} + +# third party shipped with tomahawk + ${CMAKE_BINARY_DIR}/thirdparty/rtaudio/librtaudio.dll + +# system libs + "secur32.dll" + "crypt32.dll" + "iphlpapi.a" + "ws2_32.dll" + "dnsapi.dll" + "dsound.dll" + "winmm.dll" + "advapi32.dll" ) - -SET( OS_SPECIFIC_LINK_LIBRARIES - ${LIBLASTFM_LIBRARY} - gloox -) - -SET( OS_SPECIFIC_LINK_LIBRARIES - ${OS_SPECIFIC_LINK_LIBRARIES} - "secur32.dll" - "crypt32.dll" - "ws2_32.dll" - "dnsapi.dll" - "${CMAKE_CURRENT_SOURCE_DIR}/../qxt/qxtweb-standalone/libqxtweb-standalone.dll" -) - -SET( tomahawkSourcesGui ${tomahawkSourcesGui} audio/rtaudiooutput.cpp ) -SET( tomahawkHeadersGui ${tomahawkHeadersGui} audio/rtaudiooutput.h ) - -SET( OS_SPECIFIC_LINK_LIBRARIES - ${OS_SPECIFIC_LINK_LIBRARIES} - "dsound.dll" - "winmm.dll" - "iphlpapi.a" - "mad" - "vorbisfile" - "ogg" - "${CMAKE_CURRENT_SOURCE_DIR}/../rtaudio/librtaudio.dll" -) - diff --git a/src/CPack.txt b/src/CPack.txt index 46174a970..37c2e1cc6 100644 --- a/src/CPack.txt +++ b/src/CPack.txt @@ -58,8 +58,8 @@ SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libqtgui4 (>=4:4.7.0-0ubuntu1), libtag1c2a (>= INSTALL( TARGETS tomahawk DESTINATION bin RUNTIME DESTINATION bin -# LIBRARY DESTINATION lib -# ARCHIVE DESTINATION lib +# LIBRARY DESTINATION lib${LIB_SUFFIX} +# ARCHIVE DESTINATION lib${LIB_SUFFIX} ) INSTALL( diff --git a/src/album.cpp b/src/album.cpp deleted file mode 100644 index 25c0a7507..000000000 --- a/src/album.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "tomahawk/album.h" - -#include - -#include "tomahawk/collection.h" -#include "tomahawk/tomahawkapp.h" -#include "database/database.h" -#include "database/databasecommand_alltracks.h" - -using namespace Tomahawk; - - -album_ptr -Album::get( unsigned int id, const QString& name, const Tomahawk::artist_ptr& artist, const Tomahawk::collection_ptr& collection ) -{ - static QHash< unsigned int, album_ptr > s_albums; - - if ( s_albums.contains( id ) ) - { - return s_albums.value( id ); - } - - album_ptr a = album_ptr( new Album( id, name, artist, collection ) ); - s_albums.insert( id, a ); - - return a; -} - - -Album::Album( unsigned int id, const QString& name, const Tomahawk::artist_ptr& artist, const Tomahawk::collection_ptr& collection ) - : m_id( id ) - , m_name( name ) - , m_artist( artist ) - , m_collection( collection ) - , m_currentTrack( 0 ) -{ -} - - -void -Album::onTracksAdded( const QList& tracks, const Tomahawk::collection_ptr& collection ) -{ - qDebug() << Q_FUNC_INFO; - - m_queries << tracks; - emit tracksAdded( tracks, collection ); -} - - -Tomahawk::result_ptr -Album::siblingItem( int itemsAway ) -{ - int p = m_currentTrack; - p += itemsAway; - - if ( p < 0 ) - return Tomahawk::result_ptr(); - - if ( p >= m_queries.count() ) - return Tomahawk::result_ptr(); - - m_currentTrack = p; - return m_queries.at( p )->results().first(); -} - - -QList -Album::tracks() -{ - if ( m_queries.isEmpty() ) - { - DatabaseCommand_AllTracks* cmd = new DatabaseCommand_AllTracks( m_collection ); - cmd->setAlbum( this ); - cmd->setSortOrder( DatabaseCommand_AllTracks::AlbumPosition ); - - connect( cmd, SIGNAL( tracks( QList, Tomahawk::collection_ptr ) ), - SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ) ); - - APP->database()->enqueue( QSharedPointer( cmd ) ); - } - - return m_queries; -} diff --git a/src/audio/transcodeinterface.h b/src/audio/transcodeinterface.h deleted file mode 100644 index 1209cbfe6..000000000 --- a/src/audio/transcodeinterface.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef TRANSCODEINTERFACE_H -#define TRANSCODEINTERFACE_H - -#include -#include -#include -#include - -class TranscodeInterface : public QObject -{ - Q_OBJECT - - public: - virtual ~TranscodeInterface() {} - - virtual const QStringList supportedTypes() const = 0; - - virtual int needData() = 0; - virtual bool haveData() = 0; - - virtual unsigned int preferredDataSize() = 0; - - virtual QByteArray data() = 0; - -// virtual void setBufferCapacity( int bytes ) = 0; -// virtual int bufferSize() = 0; - - public slots: - virtual void clearBuffers() = 0; - virtual void onSeek( int seconds ) = 0; - virtual void processData( const QByteArray& data, bool finish ) = 0; -}; - -#endif diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index 770450511..cd6f7682e 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -1,18 +1,37 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "audiocontrols.h" #include "ui_audiocontrols.h" #include #include "tomahawk/tomahawkapp.h" -#include "tomahawk/album.h" +#include "audio/audioengine.h" +#include "playlist/playlistmanager.h" +#include "utils/imagebutton.h" #include "utils/tomahawkutils.h" -#include "audioengine.h" -#include "imagebutton.h" -#include "playlist/playlistmanager.h" +#include "album.h" #define LASTFM_DEFAULT_COVER "http://cdn.last.fm/flatness/catalogue/noimage" +static QString s_infoIdentifier = QString("AUDIOCONTROLS"); AudioControls::AudioControls( QWidget* parent ) : QWidget( parent ) @@ -23,17 +42,21 @@ AudioControls::AudioControls( QWidget* parent ) ui->setupUi( this ); ui->buttonAreaLayout->setSpacing( 2 ); - ui->trackLabelLayout->setSpacing( 3 ); QFont font( ui->artistTrackLabel->font() ); font.setPixelSize( 12 ); - ui->artistTrackLabel->setMinimumSize( ui->artistTrackLabel->minimumSizeHint() ); - ui->albumLabel->setMinimumSize( ui->albumLabel->minimumSizeHint() ); - + +#ifdef Q_WS_MAC + font.setPointSize( font.pointSize() - 2 ); +#endif + ui->artistTrackLabel->setFont( font ); ui->artistTrackLabel->setElideMode( Qt::ElideMiddle ); + ui->artistTrackLabel->setType( QueryLabel::ArtistAndTrack ); ui->albumLabel->setFont( font ); + ui->albumLabel->setType( QueryLabel::Album ); + ui->timeLabel->setFont( font ); ui->timeLeftLabel->setFont( font ); @@ -56,7 +79,7 @@ AudioControls::AudioControls( QWidget* parent ) ui->volumeHighButton->setPixmap( RESPATH "images/volume-icon-full.png" ); ui->ownerLabel->setForegroundRole( QPalette::Dark ); - ui->metadataArea->setStyleSheet( "QWidget#metadataArea {\nborder-width: 4px;\nborder-image: url(" RESPATH "images/now-playing-panel.png) 4 4 4 4 stretch stretch; }" ); + ui->metaDataArea->setStyleSheet( "QWidget#metaDataArea {\nborder-width: 4px;\nborder-image: url(" RESPATH "images/now-playing-panel.png) 4 4 4 4 stretch stretch; }" ); ui->seekSlider->setFixedHeight( 20 ); ui->seekSlider->setEnabled( false ); @@ -83,7 +106,7 @@ AudioControls::AudioControls( QWidget* parent ) ui->volumeSlider->setFixedHeight( 20 ); ui->volumeSlider->setRange( 0, 100 ); - ui->volumeSlider->setValue( APP->audioEngine()->volume() ); + ui->volumeSlider->setValue( AudioEngine::instance()->volume() ); ui->volumeSlider->setStyleSheet( "QSlider::groove::horizontal {" "margin: 5px; border-width: 3px;" "border-image: url(" RESPATH "images/volume-slider-bkg.png) 3 3 3 3 stretch stretch;" @@ -108,37 +131,48 @@ AudioControls::AudioControls( QWidget* parent ) m_prevAction = new QAction( this ); m_nextAction = new QAction( this ); - connect( m_playAction, SIGNAL( triggered() ), (QObject*)TomahawkApp::instance()->audioEngine(), SLOT( play() ) ); - connect( m_pauseAction, SIGNAL( triggered() ), (QObject*)TomahawkApp::instance()->audioEngine(), SLOT( pause() ) ); - connect( m_prevAction, SIGNAL( triggered() ), (QObject*)TomahawkApp::instance()->audioEngine(), SLOT( previous() ) ); - connect( m_nextAction, SIGNAL( triggered() ), (QObject*)TomahawkApp::instance()->audioEngine(), SLOT( next() ) ); */ + connect( m_playAction, SIGNAL( triggered() ), (QObject*)APP->audioEngine(), SLOT( play() ) ); + connect( m_pauseAction, SIGNAL( triggered() ), (QObject*)APP->audioEngine(), SLOT( pause() ) ); + connect( m_prevAction, SIGNAL( triggered() ), (QObject*)APP->audioEngine(), SLOT( previous() ) ); + connect( m_nextAction, SIGNAL( triggered() ), (QObject*)APP->audioEngine(), SLOT( next() ) ); */ - connect( ui->volumeSlider, SIGNAL( valueChanged( int ) ), (QObject*)TomahawkApp::instance()->audioEngine(), SLOT( setVolume( int ) ) ); - connect( ui->prevButton, SIGNAL( clicked() ), (QObject*)TomahawkApp::instance()->audioEngine(), SLOT( previous() ) ); - connect( ui->playPauseButton, SIGNAL( clicked() ), (QObject*)TomahawkApp::instance()->audioEngine(), SLOT( play() ) ); - connect( ui->pauseButton, SIGNAL( clicked() ), (QObject*)TomahawkApp::instance()->audioEngine(), SLOT( pause() ) ); - connect( ui->nextButton, SIGNAL( clicked() ), (QObject*)TomahawkApp::instance()->audioEngine(), SLOT( next() ) ); - connect( ui->volumeLowButton, SIGNAL( clicked() ), (QObject*)TomahawkApp::instance()->audioEngine(), SLOT( lowerVolume() ) ); - connect( ui->volumeHighButton, SIGNAL( clicked() ), (QObject*)TomahawkApp::instance()->audioEngine(), SLOT( raiseVolume() ) ); + connect( ui->volumeSlider, SIGNAL( valueChanged( int ) ), AudioEngine::instance(), SLOT( setVolume( int ) ) ); + connect( ui->prevButton, SIGNAL( clicked() ), AudioEngine::instance(), SLOT( previous() ) ); + connect( ui->playPauseButton, SIGNAL( clicked() ), AudioEngine::instance(), SLOT( play() ) ); + connect( ui->pauseButton, SIGNAL( clicked() ), AudioEngine::instance(), SLOT( pause() ) ); + connect( ui->nextButton, SIGNAL( clicked() ), AudioEngine::instance(), SLOT( next() ) ); + connect( ui->volumeLowButton, SIGNAL( clicked() ), AudioEngine::instance(), SLOT( lowerVolume() ) ); + connect( ui->volumeHighButton, SIGNAL( clicked() ), AudioEngine::instance(), SLOT( raiseVolume() ) ); + + connect( ui->playPauseButton, SIGNAL( clicked() ), this, SIGNAL( playPressed() ) ); + connect( ui->pauseButton, SIGNAL( clicked() ), this, SIGNAL( pausePressed() ) ); + connect( ui->repeatButton, SIGNAL( clicked() ), SLOT( onRepeatClicked() ) ); connect( ui->shuffleButton, SIGNAL( clicked() ), SLOT( onShuffleClicked() ) ); - connect( ui->artistTrackLabel, SIGNAL( clicked() ), SLOT( onTrackClicked() ) ); - connect( ui->albumLabel, SIGNAL( clicked() ), SLOT( onAlbumClicked() ) ); + connect( ui->artistTrackLabel, SIGNAL( clickedArtist() ), SLOT( onArtistClicked() ) ); + connect( ui->artistTrackLabel, SIGNAL( clickedTrack() ), SLOT( onTrackClicked() ) ); + connect( ui->albumLabel, SIGNAL( clickedAlbum() ), SLOT( onAlbumClicked() ) ); // - connect( (QObject*)TomahawkApp::instance()->audioEngine(), SIGNAL( loading( const Tomahawk::result_ptr& ) ), SLOT( onPlaybackLoading( const Tomahawk::result_ptr& ) ) ); - connect( (QObject*)TomahawkApp::instance()->audioEngine(), SIGNAL( started( const Tomahawk::result_ptr& ) ), SLOT( onPlaybackStarted( const Tomahawk::result_ptr& ) ) ); - connect( (QObject*)TomahawkApp::instance()->audioEngine(), SIGNAL( paused() ), SLOT( onPlaybackPaused() ) ); - connect( (QObject*)TomahawkApp::instance()->audioEngine(), SIGNAL( resumed() ), SLOT( onPlaybackResumed() ) ); - connect( (QObject*)TomahawkApp::instance()->audioEngine(), SIGNAL( stopped() ), SLOT( onPlaybackStopped() ) ); - connect( (QObject*)TomahawkApp::instance()->audioEngine(), SIGNAL( timerSeconds( unsigned int ) ), SLOT( onPlaybackTimer( unsigned int ) ) ); - connect( (QObject*)TomahawkApp::instance()->audioEngine(), SIGNAL( volumeChanged( int ) ), SLOT( onVolumeChanged( int ) ) ); + connect( AudioEngine::instance(), SIGNAL( loading( Tomahawk::result_ptr ) ), SLOT( onPlaybackLoading( Tomahawk::result_ptr ) ) ); + connect( AudioEngine::instance(), SIGNAL( started( Tomahawk::result_ptr ) ), SLOT( onPlaybackStarted( Tomahawk::result_ptr ) ) ); + connect( AudioEngine::instance(), SIGNAL( paused() ), SLOT( onPlaybackPaused() ) ); + connect( AudioEngine::instance(), SIGNAL( resumed() ), SLOT( onPlaybackResumed() ) ); + connect( AudioEngine::instance(), SIGNAL( stopped() ), SLOT( onPlaybackStopped() ) ); + connect( AudioEngine::instance(), SIGNAL( timerSeconds( unsigned int ) ), SLOT( onPlaybackTimer( unsigned int ) ) ); + connect( AudioEngine::instance(), SIGNAL( volumeChanged( int ) ), SLOT( onVolumeChanged( int ) ) ); m_defaultCover = QPixmap( RESPATH "images/no-album-art-placeholder.png" ) .scaled( ui->coverImage->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); + connect( TomahawkApp::instance()->infoSystem(), + SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ), + SLOT( infoSystemInfo( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ) ); + + connect( TomahawkApp::instance()->infoSystem(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) ); + onPlaybackStopped(); // initial state } @@ -200,7 +234,7 @@ AudioControls::onCoverArtDownloaded() { // Follow HTTP redirect QNetworkRequest req( redir ); - QNetworkReply* reply = APP->nam()->get( req ); + QNetworkReply* reply = TomahawkUtils::nam()->get( req ); connect( reply, SIGNAL( finished() ), SLOT( onCoverArtDownloaded() ) ); } @@ -215,12 +249,59 @@ AudioControls::onPlaybackStarted( const Tomahawk::result_ptr& result ) onPlaybackLoading( result ); - QString imgurl = "http://ws.audioscrobbler.com/2.0/?method=album.imageredirect&artist=%1&album=%2&size=medium&api_key=7a90f6672a04b809ee309af169f34b8b"; - QNetworkRequest req( imgurl.arg( result->artist()->name() ).arg( result->album()->name() ) ); - QNetworkReply* reply = APP->nam()->get( req ); - connect( reply, SIGNAL( finished() ), SLOT( onCoverArtDownloaded() ) ); + QString artistName = result->artist()->name(); + QString albumName = result->album()->name(); + + Tomahawk::InfoSystem::InfoCustomDataHash trackInfo; + + trackInfo["artist"] = QVariant::fromValue< QString >( result->artist()->name() ); + trackInfo["album"] = QVariant::fromValue< QString >( result->album()->name() ); + TomahawkApp::instance()->infoSystem()->getInfo( + s_infoIdentifier, Tomahawk::InfoSystem::InfoAlbumCoverArt, + QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomDataHash >( trackInfo ), Tomahawk::InfoSystem::InfoCustomDataHash() ); } +void +AudioControls::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ) +{ + qDebug() << Q_FUNC_INFO; + if ( caller != s_infoIdentifier || type != Tomahawk::InfoSystem::InfoAlbumCoverArt ) + { + qDebug() << "info of wrong type or not with our identifier"; + return; + } + + if ( m_currentTrack.isNull() ) + { + qDebug() << "Current track is null when trying to apply fetched cover art"; + return; + } + + if ( !output.canConvert< Tomahawk::InfoSystem::InfoCustomDataHash >() ) + { + qDebug() << "Cannot convert fetched art from a QByteArray"; + return; + } + + Tomahawk::InfoSystem::InfoCustomDataHash returnedData = output.value< Tomahawk::InfoSystem::InfoCustomDataHash >(); + const QByteArray ba = returnedData["imgbytes"].toByteArray(); + if ( ba.length() ) + { + QPixmap pm; + pm.loadFromData( ba ); + + if ( pm.isNull() || returnedData["url"].toString().startsWith( LASTFM_DEFAULT_COVER ) ) + ui->coverImage->setPixmap( m_defaultCover ); + else + ui->coverImage->setPixmap( pm.scaled( ui->coverImage->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ); + } +} + +void +AudioControls::infoSystemFinished( QString target ) +{ + qDebug() << Q_FUNC_INFO; +} void AudioControls::onPlaybackLoading( const Tomahawk::result_ptr& result ) @@ -229,9 +310,9 @@ AudioControls::onPlaybackLoading( const Tomahawk::result_ptr& result ) m_currentTrack = result; - ui->artistTrackLabel->setText( QString( "%1 - %2" ).arg( result->artist()->name() ).arg( result->track() ) ); - ui->albumLabel->setText( result->album()->name() ); - ui->ownerLabel->setText( result->collection()->source()->friendlyName() ); + ui->artistTrackLabel->setResult( result ); + ui->albumLabel->setResult( result ); + ui->ownerLabel->setText( result->friendlySource() ); ui->coverImage->setPixmap( m_defaultCover ); if ( ui->timeLabel->text().isEmpty() ) @@ -359,21 +440,21 @@ AudioControls::onRepeatClicked() case PlaylistInterface::NoRepeat: { // switch to RepeatOne - APP->playlistManager()->setRepeatMode( PlaylistInterface::RepeatOne ); + PlaylistManager::instance()->setRepeatMode( PlaylistInterface::RepeatOne ); } break; case PlaylistInterface::RepeatOne: { // switch to RepeatAll - APP->playlistManager()->setRepeatMode( PlaylistInterface::RepeatAll ); + PlaylistManager::instance()->setRepeatMode( PlaylistInterface::RepeatAll ); } break; case PlaylistInterface::RepeatAll: { // switch to NoRepeat - APP->playlistManager()->setRepeatMode( PlaylistInterface::NoRepeat ); + PlaylistManager::instance()->setRepeatMode( PlaylistInterface::NoRepeat ); } break; @@ -408,19 +489,26 @@ AudioControls::onShuffleModeChanged( bool enabled ) void AudioControls::onShuffleClicked() { - APP->playlistManager()->setShuffled( m_shuffled ^ true ); + PlaylistManager::instance()->setShuffled( m_shuffled ^ true ); } void -AudioControls::onTrackClicked() +AudioControls::onArtistClicked() { - APP->playlistManager()->showCurrentTrack(); + PlaylistManager::instance()->show( m_currentTrack->artist() ); } void AudioControls::onAlbumClicked() { - APP->playlistManager()->show( m_currentTrack->album() ); + PlaylistManager::instance()->show( m_currentTrack->album() ); +} + + +void +AudioControls::onTrackClicked() +{ + PlaylistManager::instance()->showCurrentTrack(); } diff --git a/src/audiocontrols.h b/src/audiocontrols.h index df8feb795..0491745c3 100644 --- a/src/audiocontrols.h +++ b/src/audiocontrols.h @@ -1,10 +1,29 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef AUDIOCONTROLS_H #define AUDIOCONTROLS_H #include -#include "tomahawk/result.h" -#include "tomahawk/playlistinterface.h" +#include "result.h" +#include "playlistinterface.h" +#include "tomahawk/infosystem.h" namespace Ui { @@ -19,9 +38,15 @@ public: AudioControls( QWidget* parent = 0 ); ~AudioControls(); +signals: + void playPressed(); + void pausePressed(); + public slots: void onRepeatModeChanged( PlaylistInterface::RepeatMode mode ); void onShuffleModeChanged( bool enabled ); + void infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void infoSystemFinished( QString target ); protected: void changeEvent( QEvent* e ); @@ -38,8 +63,10 @@ private slots: void onRepeatClicked(); void onShuffleClicked(); - void onTrackClicked(); + + void onArtistClicked(); void onAlbumClicked(); + void onTrackClicked(); void onCoverArtDownloaded(); diff --git a/src/audiocontrols.ui b/src/audiocontrols.ui index 6d1e54611..31ac8f656 100644 --- a/src/audiocontrols.ui +++ b/src/audiocontrols.ui @@ -6,8 +6,8 @@ 0 0 - 706 - 70 + 939 + 80 @@ -19,28 +19,22 @@ 0 - 70 + 80 16777215 - 70 + 80 Form - - - 1 - + 0 - - 1 - 0 @@ -123,25 +117,22 @@ - + 16777215 - 66 + 74 - - - - 12 + 10 0 - 8 + 12 0 @@ -156,155 +147,182 @@ - 49 - 48 + 58 + 58 - 49 - 48 + 58 + 58 - Cover - - - false + TextLabel Qt::AlignCenter - - 0 - - - -1 - - 8 + 4 - 0 + 6 - 0 + 2 - - - - 0 - - - 6 - - - 4 - - - 0 - - - - - - - PointingHandCursor - - - Artist - - - - - - - PointingHandCursor - - - Album - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 7 - - - - Owner - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - 0 - - - - - + + + 0 + + + 0 + + + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 16 + + + + PointingHandCursor + + + Artist + + + + + + + + 0 + 0 + + + + + 0 + 16 + + + + PointingHandCursor + + + Album + + + + + + + + + Qt::Horizontal + + + + 4 + 8 + + + + + + + + + 7 + + + + Owner + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + 0 + + + + - - - - 0 - - - 0 - - - 6 - - - 0 - - - - - Time - - - - - - - Qt::Horizontal - - - - - - - Time Left - - - - - + + + Qt::Vertical + + + + 20 + 4 + + + + + + + + 4 + + + + + Time + + + + + + + + 0 + 20 + + + + + 16777215 + 20 + + + + Qt::Horizontal + + + + + + + Time Left + + + + @@ -336,13 +354,13 @@ 2 - 1 + 0 2 - 1 + 0 @@ -432,6 +450,18 @@ + + + 0 + 20 + + + + + 16777215 + 20 + + Qt::Horizontal @@ -462,12 +492,12 @@ ImageButton QPushButton -

imagebutton.h
+
utils/imagebutton.h
- ElidedLabel + QueryLabel QLabel -
elidedlabel.h
+
utils/querylabel.h
diff --git a/src/bufferiodevice.cpp b/src/bufferiodevice.cpp deleted file mode 100644 index b4e533643..000000000 --- a/src/bufferiodevice.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include -#include "bufferiodevice.h" - - -BufferIODevice::BufferIODevice( unsigned int size, QObject *parent ) : - QIODevice( parent ), - m_size( size ), - m_received( 0 ) -{ -} - - -bool -BufferIODevice::open( OpenMode mode ) -{ - QMutexLocker lock( &m_mut ); - - qDebug() << Q_FUNC_INFO; - QIODevice::open( QIODevice::ReadWrite ); // FIXME? - return true; -} - - -void -BufferIODevice::close() -{ - QMutexLocker lock( &m_mut ); - - qDebug() << Q_FUNC_INFO; - QIODevice::close(); -} - - -void -BufferIODevice::inputComplete( const QString& errmsg ) -{ - qDebug() << Q_FUNC_INFO; - setErrorString( errmsg ); - emit readChannelFinished(); -} - - -void -BufferIODevice::addData( QByteArray ba ) -{ - writeData( ba.data(), ba.length() ); -} - - -qint64 -BufferIODevice::bytesAvailable() const -{ - QMutexLocker lock( &m_mut ); - return m_buffer.length(); -} - - -qint64 -BufferIODevice::readData( char * data, qint64 maxSize ) -{ - //qDebug() << Q_FUNC_INFO << maxSize; - QMutexLocker lock( &m_mut ); - - qint64 size = maxSize; - if ( m_buffer.length() < maxSize ) - size = m_buffer.length(); - - memcpy( data, m_buffer.data(), size ); - m_buffer.remove( 0, size ); - - //qDebug() << "readData ends, bufersize:" << m_buffer.length(); - return size; -} - - -qint64 BufferIODevice::writeData( const char * data, qint64 maxSize ) -{ - { - QMutexLocker lock( &m_mut ); - m_buffer.append( data, maxSize ); - m_received += maxSize; - } - - emit bytesWritten( maxSize ); - emit readyRead(); - return maxSize; -} - - -qint64 BufferIODevice::size() const -{ - return m_size; -} - - -bool BufferIODevice::atEnd() const -{ - QMutexLocker lock( &m_mut ); - return ( m_size == m_received && m_buffer.length() == 0 ); -} - - -void -BufferIODevice::clear() -{ - QMutexLocker lock( &m_mut ); - m_buffer.clear(); -} diff --git a/src/bufferiodevice.h b/src/bufferiodevice.h deleted file mode 100644 index 69a661d76..000000000 --- a/src/bufferiodevice.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef BUFFERIODEVICE_H -#define BUFFERIODEVICE_H - -#include -#include -#include - -class BufferIODevice : public QIODevice -{ -Q_OBJECT -public: - explicit BufferIODevice( unsigned int size = 0, QObject *parent = 0 ); - - virtual bool open( OpenMode mode ); - virtual void close(); - - virtual qint64 bytesAvailable() const; - virtual qint64 size() const; - virtual bool atEnd() const; - - void addData( QByteArray ba ); - void clear(); - - OpenMode openMode() const { qDebug() << "openMode"; return QIODevice::ReadWrite; } - - void inputComplete( const QString& errmsg = "" ); - -protected: - virtual qint64 readData( char * data, qint64 maxSize ); - virtual qint64 writeData( const char * data, qint64 maxSize ); - -private: - QByteArray m_buffer; - mutable QMutex m_mut; //const methods need to lock - unsigned int m_size, m_received; -}; - -#endif // BUFFERIODEVICE_H diff --git a/src/collection.cpp b/src/collection.cpp deleted file mode 100644 index 18b7c7a5b..000000000 --- a/src/collection.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include "tomahawk/collection.h" - -#include -#include - -#include "tomahawk/playlist.h" - -using namespace Tomahawk; - - -Collection::Collection( const source_ptr& source, const QString& name, QObject* parent ) - : QObject( parent ) - , m_name( name ) - , m_lastmodified( 0 ) - , m_source( source ) -{ -// qDebug() << Q_FUNC_INFO; -} - - -Collection::~Collection() -{ - qDebug() << Q_FUNC_INFO; -} - - -QString -Collection::name() const -{ - return m_name; -} - - -void -Collection::addPlaylist( const Tomahawk::playlist_ptr& p ) -{ - qDebug() << Q_FUNC_INFO; - QList toadd; - toadd << p; - m_playlists.append( toadd ); - - qDebug() << Q_FUNC_INFO << "Collection name" << name() - << "from source id" << source()->id() - << "numplaylists:" << m_playlists.length(); - emit playlistsAdded( toadd ); -} - - -void -Collection::deletePlaylist( const Tomahawk::playlist_ptr& p ) -{ - qDebug() << Q_FUNC_INFO; - QList todelete; - todelete << p; - m_playlists.removeAll( p ); - - qDebug() << Q_FUNC_INFO << "Collection name" << name() - << "from source id" << source()->id() - << "numplaylists:" << m_playlists.length(); - emit playlistsDeleted( todelete ); -} - - -playlist_ptr -Collection::playlist( const QString& guid ) -{ - foreach( const playlist_ptr& pp, m_playlists ) - { - if( pp->guid() == guid ) - return pp; - } - - return playlist_ptr(); -} - - -void -Collection::setPlaylists( const QList& plists ) -{ - qDebug() << Q_FUNC_INFO << plists.count(); - - m_playlists.append( plists ); - emit playlistsAdded( plists ); -} - - -void -Collection::setTracks( const QList& tracks, Tomahawk::collection_ptr collection ) -{ - qDebug() << Q_FUNC_INFO << tracks.count() << collection->name(); - - m_tracks << tracks; - emit tracksAdded( tracks, collection ); -} diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 000000000..f8246a54d --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,20 @@ +#ifndef CONFIG_H_IN +#define CONFIG_H_IN + +#cmakedefine ORGANIZATION_NAME "${ORGANIZATION_NAME}" +#cmakedefine ORGANIZATION_DOMAIN "${ORGANIZATION_DOMAIN}" +#cmakedefine APPLICATION_NAME "${APPLICATION_NAME}" +#cmakedefine VERSION "${VERSION}" + +#cmakedefine DEBUG_BUILD + +#define CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" + +#cmakedefine SNOW_LEOPARD +#cmakedefine LEOPARD +#cmakedefine HAVE_SPARKLE + +#cmakedefine LIBLASTFM_FOUND +#cmakedefine GLOOX_FOUND + +#endif // CONFIG_H_IN diff --git a/src/database/database.cpp b/src/database/database.cpp deleted file mode 100644 index e415e1650..000000000 --- a/src/database/database.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "database.h" - - -Database::Database( const QString& dbname, QObject* parent ) - : QObject( parent ) - , m_impl( new DatabaseImpl( dbname, this ) ) - , m_workerRO( new DatabaseWorker( m_impl, this, false ) ) - , m_workerRW( new DatabaseWorker( m_impl, this, true ) ) -{ - m_workerRO->start(); - m_workerRW->start(); -} - - -Database::~Database() -{ - qDebug() << Q_FUNC_INFO; - - delete m_workerRW; - delete m_workerRO; - delete m_impl; -} - - -void -Database::loadIndex() -{ - m_impl->loadIndex(); -} - - -void -Database::enqueue( QSharedPointer lc ) -{ - if( lc->doesMutates() ) - { - //qDebug() << Q_FUNC_INFO << "RW" << lc->commandname(); - emit newJobRO( lc ); - } - else - { - //qDebug() << Q_FUNC_INFO << "RO" << lc->commandname(); - emit newJobRW( lc ); - } -} - - -const QString& -Database::dbid() const -{ - return m_impl->dbid(); -} diff --git a/src/database/database.h b/src/database/database.h deleted file mode 100644 index 4b388f223..000000000 --- a/src/database/database.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef DATABASE_H -#define DATABASE_H - -#include -#include - -#include "databaseimpl.h" -#include "databasecommand.h" -#include "databaseworker.h" - -/* - This class is really a firewall/pimpl - the public functions of LibraryImpl - are the ones that operate on the database, without any locks. - - HOWEVER, we're using the command pattern to serialize access to the database - and provide an async api. You create a DatabaseCommand object, and add it to - the queue of work. There is a single thread responsible for exec'ing all - the commands, so sqlite only does one thing at a time. - - Update: 1 thread for mutates, one for readonly queries. -*/ -class Database : public QObject -{ -Q_OBJECT -public: - explicit Database( const QString& dbname, QObject* parent = 0 ); - ~Database(); - - const QString& dbid() const; - const bool indexReady() const { return m_indexReady; } - - void loadIndex(); - -signals: - void indexReady(); // search index - void newJobRO( QSharedPointer ); - void newJobRW( QSharedPointer ); - -public slots: - void enqueue( QSharedPointer lc ); - -private: - DatabaseImpl* m_impl; - DatabaseWorker *m_workerRO, *m_workerRW; - bool m_indexReady; -}; - -#endif // DATABASE_H diff --git a/src/database/databasecollection.cpp b/src/database/databasecollection.cpp deleted file mode 100644 index 771291096..000000000 --- a/src/database/databasecollection.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include "databasecollection.h" - -#include "tomahawk/tomahawkapp.h" -#include "database.h" -#include "databasecommand_alltracks.h" -#include "databasecommand_addfiles.h" -#include "databasecommand_loadallplaylists.h" - -using namespace Tomahawk; - - -DatabaseCollection::DatabaseCollection( const source_ptr& src, QObject* parent ) - : Collection( src, QString( "dbcollection:%1" ).arg( src->userName() ), parent ) -{ -} - - -void -DatabaseCollection::loadPlaylists() -{ - qDebug() << Q_FUNC_INFO; - DatabaseCommand_LoadAllPlaylists* cmd = new DatabaseCommand_LoadAllPlaylists( source() ); - - connect( cmd, SIGNAL( done( const QList& ) ), - SLOT( setPlaylists( const QList& ) ) ); - - TomahawkApp::instance()->database()->enqueue( QSharedPointer( cmd ) ); -} - - -void -DatabaseCollection::loadTracks() -{ - qDebug() << Q_FUNC_INFO << source()->userName(); - DatabaseCommand_AllTracks* cmd = new DatabaseCommand_AllTracks( source()->collection() ); - - connect( cmd, SIGNAL( tracks( QList, Tomahawk::collection_ptr ) ), - SLOT( setTracks( QList, Tomahawk::collection_ptr ) ) ); -/* connect( cmd, SIGNAL( done( Tomahawk::collection_ptr ) ), - SIGNAL( tracksFinished( Tomahawk::collection_ptr ) ) );*/ - - TomahawkApp::instance()->database()->enqueue( QSharedPointer( cmd ) ); -} - - -void -DatabaseCollection::addTracks( const QList &newitems ) -{ - qDebug() << Q_FUNC_INFO << newitems.length(); - DatabaseCommand_AddFiles* cmd = new DatabaseCommand_AddFiles( newitems, source() ); - - TomahawkApp::instance()->database()->enqueue( QSharedPointer( cmd ) ); -} - - -void -DatabaseCollection::removeTracks( const QList &olditems ) -{ - // FIXME - Q_ASSERT( false ); - - // TODO RemoveTracks cmd, probably builds a temp table of all the URLs in - // olditems, then joins on that to batch-delete. -} - - -QList< Tomahawk::playlist_ptr > -DatabaseCollection::playlists() -{ - qDebug() << Q_FUNC_INFO; - - if ( Collection::playlists().isEmpty() ) - { - loadPlaylists(); - } - - return Collection::playlists(); -} - - -QList< Tomahawk::query_ptr > -DatabaseCollection::tracks() -{ - qDebug() << Q_FUNC_INFO; - - if ( Collection::tracks().isEmpty() ) - { - loadTracks(); - } - - return Collection::tracks(); -} diff --git a/src/database/databasecollection.h b/src/database/databasecollection.h deleted file mode 100644 index 37b658bed..000000000 --- a/src/database/databasecollection.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef DATABASECOLLECTION_H -#define DATABASECOLLECTION_H - -#include "tomahawk/collection.h" -#include "tomahawk/typedefs.h" - -class DatabaseCollection : public Tomahawk::Collection -{ -Q_OBJECT - -public: - explicit DatabaseCollection( const Tomahawk::source_ptr& source, QObject* parent = 0 ); - ~DatabaseCollection() - { - qDebug() << Q_FUNC_INFO; - } - - virtual void loadTracks(); - virtual void loadPlaylists(); - - virtual QList< Tomahawk::playlist_ptr > playlists(); - virtual QList< Tomahawk::query_ptr > tracks(); - -public slots: - virtual void addTracks( const QList &newitems ); - virtual void removeTracks( const QList &olditems ); -}; - -#endif // DATABASECOLLECTION_H diff --git a/src/database/databasecommand.cpp b/src/database/databasecommand.cpp deleted file mode 100644 index dc021bd22..000000000 --- a/src/database/databasecommand.cpp +++ /dev/null @@ -1,91 +0,0 @@ -#include "databasecommand.h" - -#include - -#include "databasecommand_addfiles.h" -#include "databasecommand_createplaylist.h" -#include "databasecommand_deleteplaylist.h" -#include "databasecommand_renameplaylist.h" -#include "databasecommand_setplaylistrevision.h" - - -DatabaseCommand::DatabaseCommand( QObject* parent ) - : QObject( parent ) - , m_state( PENDING ) -{ - //qDebug() << Q_FUNC_INFO; -} - - -DatabaseCommand::DatabaseCommand( const source_ptr& src, QObject* parent ) - : QObject( parent ) - , m_state( PENDING ) - , m_source( src ) -{ - //qDebug() << Q_FUNC_INFO; -} - - -DatabaseCommand::~DatabaseCommand() -{ - //qDebug() << Q_FUNC_INFO; -} - - -void -DatabaseCommand::_exec( DatabaseImpl* lib ) -{ - //qDebug() << "RUNNING" << thread(); - m_state = RUNNING; - emit running(); - exec( lib ); - m_state = FINISHED; - //qDebug() << "FINISHED" << thread(); -} - - -DatabaseCommand* -DatabaseCommand::factory( const QVariant& op, const source_ptr& source ) -{ - const QString name = op.toMap().value( "command" ).toString(); - - if( name == "addfiles" ) - { - DatabaseCommand_AddFiles * cmd = new DatabaseCommand_AddFiles; - cmd->setSource( source ); - QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd ); - return cmd; - } - else if( name == "createplaylist" ) - { - DatabaseCommand_CreatePlaylist * cmd = new DatabaseCommand_CreatePlaylist; - cmd->setSource( source ); - QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd ); - return cmd; - } - else if( name == "deleteplaylist" ) - { - DatabaseCommand_DeletePlaylist * cmd = new DatabaseCommand_DeletePlaylist; - cmd->setSource( source ); - QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd ); - return cmd; - } - else if( name == "renameplaylist" ) - { - DatabaseCommand_RenamePlaylist * cmd = new DatabaseCommand_RenamePlaylist; - cmd->setSource( source ); - QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd ); - return cmd; - } - else if( name == "setplaylistrevision" ) - { - DatabaseCommand_SetPlaylistRevision * cmd = new DatabaseCommand_SetPlaylistRevision; - cmd->setSource( source ); - QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd ); - return cmd; - } - - qDebug() << "ERRROR in" << Q_FUNC_INFO << name; -// Q_ASSERT( false ); - return NULL; -} diff --git a/src/database/databasecommand_addfiles.cpp b/src/database/databasecommand_addfiles.cpp deleted file mode 100644 index 62e271ae6..000000000 --- a/src/database/databasecommand_addfiles.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#include "databasecommand_addfiles.h" - -#include - -#include "tomahawk/collection.h" -#include "tomahawk/tomahawkapp.h" -#include "database.h" -#include "databasecommand_collectionstats.h" -#include "databaseimpl.h" -#include "controlconnection.h" - -using namespace Tomahawk; - - -// remove file paths when making oplog/for network transmission -QVariantList -DatabaseCommand_AddFiles::files() const -{ - QVariantList list; - foreach( const QVariant& v, m_files ) - { - // replace url with the id, we don't leak file paths over the network. - QVariantMap m = v.toMap(); - m.remove( "url" ); - m.insert( "url", QString::number( m.value( "id" ).toInt() ) ); - list.append( m ); - } - return list; -} - - -// After changing a collection, we need to tell other bits of the system: -void -DatabaseCommand_AddFiles::postCommitHook() -{ - qDebug() << Q_FUNC_INFO; - - // make the collection object emit its tracksAdded signal, so the - // collection browser will update/fade in etc. - Collection* coll = source()->collection().data(); - - connect( this, SIGNAL( notify( const QList&, Tomahawk::collection_ptr ) ), - coll, SIGNAL( setTracks( const QList&, Tomahawk::collection_ptr ) ), - Qt::QueuedConnection ); - // do it like this so it gets called in the right thread: - emit notify( m_files, source()->collection() ); - - // also re-calc the collection stats, to updates the "X tracks" in the sidebar etc: - DatabaseCommand_CollectionStats* cmd = new DatabaseCommand_CollectionStats( source() ); - connect( cmd, SIGNAL( done( const QVariantMap& ) ), - source().data(), SLOT( setStats( const QVariantMap& ) ), Qt::QueuedConnection ); - APP->database()->enqueue( QSharedPointer( cmd ) ); - - if( source()->isLocal() ) - APP->servent().triggerDBSync(); -} - - -void -DatabaseCommand_AddFiles::exec( DatabaseImpl* dbi ) -{ - qDebug() << Q_FUNC_INFO; - Q_ASSERT( !source().isNull() ); - - TomahawkSqlQuery query_file = dbi->newquery(); - TomahawkSqlQuery query_filejoin = dbi->newquery(); - TomahawkSqlQuery query_file_del = dbi->newquery(); - - query_file.prepare( "INSERT INTO file(source, url, size, mtime, md5, mimetype, duration, bitrate) " - "VALUES (?, ?, ?, ?, ?, ?, ?, ?)" ); - query_filejoin.prepare( "INSERT INTO file_join(file, artist ,album, track, albumpos) " - "VALUES (?,?,?,?,?)" ); - query_file_del.prepare( QString( "DELETE FROM file WHERE source %1 AND url = ?" ) - .arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) - ) ); - - int maxart, maxalb, maxtrk; // store max id, so we can index new ones after - maxart = maxalb = maxtrk = 0; - - int added = 0; - QVariant srcid = source()->isLocal() ? - QVariant( QVariant::Int ) : source()->id(); - - qDebug() << "Adding" << m_files.length() << "files to db for source" << srcid; - - QList::iterator it; - for( it = m_files.begin(); it != m_files.end(); ++it ) - { - QVariant& v = *it; - QVariantMap m = v.toMap(); - - QString url = m.value( "url" ).toString(); - int mtime = m.value( "lastmodified" ).toInt(); - int size = m.value( "size" ).toInt(); - QString hash = m.value( "hash" ).toString(); - QString mimetype = m.value( "mimetype" ).toString(); - int duration = m.value( "duration" ).toInt(); - int bitrate = m.value( "bitrate" ).toInt(); - QString artist = m.value( "artist" ).toString(); - QString album = m.value( "album" ).toString(); - QString track = m.value( "track" ).toString(); - int albumpos = m.value( "albumpos" ).toInt(); - - int fileid = 0; - query_file_del.bindValue( 0, url ); - query_file_del.exec(); - - query_file.bindValue( 0, srcid ); - query_file.bindValue( 1, url ); - query_file.bindValue( 2, size ); - query_file.bindValue( 3, mtime ); - query_file.bindValue( 4, hash ); - query_file.bindValue( 5, mimetype ); - query_file.bindValue( 6, duration ); - query_file.bindValue( 7, bitrate ); - if( !query_file.exec() ) - { - qDebug() << "Failed to insert to file:" - << query_file.lastError().databaseText() - << query_file.lastError().driverText() - << query_file.boundValues(); - continue; - } - else - { - if( added % 100 == 0 ) qDebug() << "Inserted" << added; - } - // get internal IDs for art/alb/trk - fileid = query_file.lastInsertId().toInt(); - - // insert the new fileid, set the url for our use: - m.insert( "id", fileid ); - if( !source()->isLocal() ) m["url"] = QString( "servent://%1\t%2" ) - .arg( source()->userName() ) - .arg( fileid ); - v = m; - - bool isnew; - int artid = dbi->artistId( artist, isnew ); - if( artid < 1 ) continue; - if( isnew && maxart == 0 ) maxart = artid; - - int trkid = dbi->trackId( artid, track, isnew ); - if( trkid < 1 ) continue; - if( isnew && maxtrk == 0 ) maxtrk = trkid; - - int albid = dbi->albumId( artid, album, isnew ); - if( albid > 0 && isnew && maxalb == 0 ) maxalb = albid; - - // Now add the association - query_filejoin.bindValue( 0, fileid ); - query_filejoin.bindValue( 1, artid ); - query_filejoin.bindValue( 2, albid > 0 ? albid : QVariant( QVariant::Int ) ); - query_filejoin.bindValue( 3, trkid ); - query_filejoin.bindValue( 4, albumpos ); - if( !query_filejoin.exec() ) - { - qDebug() << "Error inserting into file_join table"; - continue; - } - added++; - } - qDebug() << "Inserted" << added; - - // TODO building the index could be a separate job, outside this transaction - if(maxart) dbi->updateSearchIndex( "artist", maxart ); - if(maxalb) dbi->updateSearchIndex( "album", maxalb ); - if(maxtrk) dbi->updateSearchIndex( "track", maxtrk ); - - qDebug() << "Committing" << added << "tracks..."; - qDebug() << "Done."; - emit done( m_files, source()->collection() ); -} diff --git a/src/database/databasecommand_addfiles.h b/src/database/databasecommand_addfiles.h deleted file mode 100644 index bfd6a368e..000000000 --- a/src/database/databasecommand_addfiles.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef DATABASECOMMAND_ADDFILES_H -#define DATABASECOMMAND_ADDFILES_H - -#include -#include - -#include "database/databasecommandloggable.h" -#include "tomahawk/typedefs.h" - -class DatabaseCommand_AddFiles : public DatabaseCommandLoggable -{ -Q_OBJECT -Q_PROPERTY( QVariantList files READ files WRITE setFiles ) - -public: - explicit DatabaseCommand_AddFiles( QObject* parent = 0 ) - : DatabaseCommandLoggable( parent ) - {} - - explicit DatabaseCommand_AddFiles( const QList& files, const Tomahawk::source_ptr& source, QObject* parent = 0 ) - : DatabaseCommandLoggable( parent ), m_files( files ) - { - setSource( source ); - } - - virtual QString commandname() const { return "addfiles"; } - - virtual void exec( DatabaseImpl* ); - virtual bool doesMutates() const { return true; } - virtual void postCommitHook(); - - QVariantList files() const; - void setFiles( const QVariantList& f ) { m_files = f; } - -signals: - void done( const QList&, Tomahawk::collection_ptr ); - void notify( const QList&, Tomahawk::collection_ptr ); - -private: - QVariantList m_files; -}; - -#endif // DATABASECOMMAND_ADDFILES_H diff --git a/src/database/databasecommand_addsource.h b/src/database/databasecommand_addsource.h deleted file mode 100644 index a45fcfa4e..000000000 --- a/src/database/databasecommand_addsource.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef DATABASECOMMAND_ADDSOURCE_H -#define DATABASECOMMAND_ADDSOURCE_H - -#include -#include - -#include "databasecommand.h" - -class DatabaseCommand_addSource : public DatabaseCommand -{ -Q_OBJECT - -public: - explicit DatabaseCommand_addSource( const QString& username, const QString& fname, QObject* parent = 0 ); - virtual void exec( DatabaseImpl* lib ); - virtual bool doesMutates() const { return true; } - virtual QString commandname() const { return "addsource"; } -signals: - void done( unsigned int, const QString& ); - -private: - QString m_username, m_fname; -}; - -#endif // DATABASECOMMAND_ADDSOURCE_H diff --git a/src/database/databasecommand_alltracks.cpp b/src/database/databasecommand_alltracks.cpp deleted file mode 100644 index ed0a6fa3f..000000000 --- a/src/database/databasecommand_alltracks.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "databasecommand_alltracks.h" - -#include - -#include "databaseimpl.h" - -void -DatabaseCommand_AllTracks::exec( DatabaseImpl* dbi ) -{ - Q_ASSERT( !m_collection->source().isNull() ); - - TomahawkSqlQuery query = dbi->newquery(); - QList ql; - QString m_orderToken; - - switch ( m_sortOrder ) - { - case 0: - break; - - case ModificationTime: - m_orderToken = "file.mtime"; - break; - - case AlbumPosition: - m_orderToken = "file_join.albumpos"; - break; - } - - QString sql = QString( - "SELECT file.id, artist.name, album.name, track.name, file.size, " - "file.duration, file.bitrate, file.url, file.source, file.mtime, file.mimetype, file_join.albumpos, artist.id, album.id " - "FROM file, artist, track, file_join " - "LEFT OUTER JOIN album " - "ON file_join.album = album.id " - "WHERE file.id = file_join.file " - "AND file_join.artist = artist.id " - "AND file_join.track = track.id " - "AND file.source %1 " - "%2 " - "%3 %4 %5" - ).arg( m_collection->source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( m_collection->source()->id() ) ) - .arg( !m_album ? QString() : QString( "AND album.id = %1" ).arg( m_album->id() ) ) - .arg( m_sortOrder > 0 ? QString( "ORDER BY %1" ).arg( m_orderToken ) : QString() ) - .arg( m_sortDescending ? "DESC" : QString() ) - .arg( m_amount > 0 ? QString( "LIMIT 0, %1" ).arg( m_amount ) : QString() ); - - query.prepare( sql ); - query.exec(); - - int i = 0; - while( query.next() ) - { - QVariantMap t; - QString url; - url = query.value( 7 ).toString(); - if( m_collection->source()->isLocal() ) - t["url"] = url; - else - t["url"] = QString( "servent://%1\t%2" ).arg( m_collection->source()->userName() ).arg( url ); - - t["id"] = QString( "%1" ).arg( query.value( 0 ).toInt() ); - t["artist"] = query.value( 1 ).toString(); - t["artistid"] = query.value( 12 ).toUInt(); - t["album"] = query.value( 2 ).toString(); - t["albumid"] = query.value( 13 ).toUInt(); - t["track"] = query.value( 3 ).toString(); - t["size"] = query.value( 4 ).toInt(); - t["duration"] = query.value( 5 ).toInt(); - t["bitrate"] = query.value( 6 ).toInt(); - t["mtime"] = query.value( 9 ).toInt(); - t["mimetype"] = query.value( 10 ).toString(); - t["albumpos"] = query.value( 11 ).toUInt(); - - Tomahawk::query_ptr query = Tomahawk::query_ptr( new Tomahawk::Query( t ) ); - t["score"] = 1.0; - - QList results; - Tomahawk::result_ptr result = Tomahawk::result_ptr( new Tomahawk::Result( t, m_collection ) ); - results << result; - query->addResults( results ); - - ql << query; - - if ( ++i % 1000 == 0 ) - { - emit tracks( ql, m_collection ); - ql.clear(); - } - } - - qDebug() << Q_FUNC_INFO << ql.length(); - - if ( ql.count() ) - emit tracks( ql, m_collection ); - emit done( m_collection ); -} diff --git a/src/database/databasecommand_alltracks.h b/src/database/databasecommand_alltracks.h deleted file mode 100644 index 80ec99361..000000000 --- a/src/database/databasecommand_alltracks.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef DATABASECOMMAND_ALLTRACKS_H -#define DATABASECOMMAND_ALLTRACKS_H - -#include -#include - -#include "databasecommand.h" -#include "tomahawk/album.h" -#include "tomahawk/collection.h" -#include "tomahawk/typedefs.h" - -class DatabaseCommand_AllTracks : public DatabaseCommand -{ -Q_OBJECT -public: - enum SortOrder { - None = 0, - ModificationTime = 1, - AlbumPosition = 2 - }; - - explicit DatabaseCommand_AllTracks( const Tomahawk::collection_ptr& collection, QObject* parent = 0 ) - : DatabaseCommand( parent ) - , m_collection( collection ) - , m_album( 0 ) - , m_amount( 0 ) - , m_sortOrder( DatabaseCommand_AllTracks::None ) - , m_sortDescending( false ) - {} - - virtual void exec( DatabaseImpl* ); - - virtual bool doesMutates() const { return false; } - virtual QString commandname() const { return "alltracks"; } - - void setAlbum( Tomahawk::Album* album ) { m_album = album; } - void setLimit( unsigned int amount ) { m_amount = amount; } - void setSortOrder( DatabaseCommand_AllTracks::SortOrder order ) { m_sortOrder = order; } - void setSortDescending( bool descending ) { m_sortDescending = descending; } - -signals: - void tracks( const QList&, const Tomahawk::collection_ptr& ); - void done( const Tomahawk::collection_ptr& ); - -private: - Tomahawk::collection_ptr m_collection; - Tomahawk::Album* m_album; - unsigned int m_amount; - DatabaseCommand_AllTracks::SortOrder m_sortOrder; - bool m_sortDescending; -}; - -#endif // DATABASECOMMAND_ALLTRACKS_H diff --git a/src/database/databasecommand_collectionstats.cpp b/src/database/databasecommand_collectionstats.cpp deleted file mode 100644 index 4d2ce5ccf..000000000 --- a/src/database/databasecommand_collectionstats.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "databasecommand_collectionstats.h" - -#include "databaseimpl.h" - -using namespace Tomahawk; - - -DatabaseCommand_CollectionStats::DatabaseCommand_CollectionStats( const source_ptr& source, QObject* parent ) - : DatabaseCommand( source, parent ) -{ -} - - -void -DatabaseCommand_CollectionStats::exec( DatabaseImpl* dbi ) -{ - //qDebug() << Q_FUNC_INFO; - Q_ASSERT( !source().isNull() ); - TomahawkSqlQuery query = dbi->newquery(); - - Q_ASSERT( source()->isLocal() || source()->id() >= 1 ); - - if( source()->isLocal() ) - { - query.exec("SELECT count(*), max(mtime), (SELECT guid FROM oplog WHERE source IS NULL ORDER BY id DESC LIMIT 1) " - "FROM file " - "WHERE source IS NULL"); - } - else - { - query.prepare("SELECT count(*), max(mtime), " - " (SELECT lastop FROM source WHERE id = ?) " - "FROM file " - "WHERE source = ?" - ); - query.addBindValue( source()->id() ); - query.addBindValue( source()->id() ); - } - if( !query.exec() ) - { - qDebug() << "Failed to get collection stats:" << query.boundValues(); - throw "failed to get collection stats"; - } - - QVariantMap m; - if( query.next() ) - { - m.insert( "numfiles", query.value( 0 ).toInt() ); - m.insert( "lastmodified", query.value( 1 ).toInt() ); - m.insert( "lastop", query.value( 2 ).toString() ); - } - - //qDebug() << "Loaded collection stats for" - // << (source()->isLocal() ? "LOCAL" : source()->username()) - // << m; - emit done( m ); -} diff --git a/src/database/databasecommand_collectionstats.h b/src/database/databasecommand_collectionstats.h deleted file mode 100644 index 50dcee941..000000000 --- a/src/database/databasecommand_collectionstats.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef DATABASECOMMAND_COLLECTIONSTATS_H -#define DATABASECOMMAND_COLLECTIONSTATS_H - -#include - -#include "databasecommand.h" -#include "tomahawk/source.h" -#include "tomahawk/typedefs.h" - -class DatabaseCommand_CollectionStats : public DatabaseCommand -{ -Q_OBJECT - -public: - explicit DatabaseCommand_CollectionStats( const Tomahawk::source_ptr& source, QObject* parent = 0 ); - virtual void exec( DatabaseImpl* lib ); - virtual bool doesMutates() const { return false; } - virtual QString commandname() const { return "collectionstats"; } - -signals: - void done( const QVariantMap& ); -}; - -#endif // DATABASECOMMAND_COLLECTIONSTATS_H diff --git a/src/database/databasecommand_createplaylist.cpp b/src/database/databasecommand_createplaylist.cpp deleted file mode 100644 index 13d35144e..000000000 --- a/src/database/databasecommand_createplaylist.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include "databasecommand_createplaylist.h" - -#include - -#include "tomahawk/tomahawkapp.h" - -using namespace Tomahawk; - - -DatabaseCommand_CreatePlaylist::DatabaseCommand_CreatePlaylist( QObject* parent ) - : DatabaseCommandLoggable( parent ) - , m_report( true ) -{ - qDebug() << Q_FUNC_INFO << "def"; -} - - -DatabaseCommand_CreatePlaylist::DatabaseCommand_CreatePlaylist( const source_ptr& author, - const playlist_ptr& playlist ) - : DatabaseCommandLoggable( author ) - , m_playlist( playlist ) - , m_report( false ) //this ctor used when creating locally, reporting done elsewhere -{ - qDebug() << Q_FUNC_INFO; -} - - -void -DatabaseCommand_CreatePlaylist::exec( DatabaseImpl* lib ) -{ - qDebug() << Q_FUNC_INFO; - Q_ASSERT( !m_playlist.isNull() ); - Q_ASSERT( !source().isNull() ); - - TomahawkSqlQuery cre = lib->newquery(); - cre.prepare( "INSERT INTO playlist( guid, source, shared, title, info, creator, lastmodified) " - "VALUES( :guid, :source, :shared, :title, :info, :creator, :lastmodified )" ); - cre.bindValue( ":guid", m_playlist->guid() ); - cre.bindValue( ":source", source()->isLocal() ? QVariant(QVariant::Int) : source()->id() ); - cre.bindValue( ":shared", m_playlist->shared() ); - cre.bindValue( ":title", m_playlist->title() ); - cre.bindValue( ":info", m_playlist->info() ); - cre.bindValue( ":creator", m_playlist->creator() ); - cre.bindValue( ":lastmodified", m_playlist->lastmodified() ); - - qDebug() << "CREATE PLAYLIST:" << cre.boundValues(); - - cre.exec(); -} - - -void -DatabaseCommand_CreatePlaylist::postCommitHook() -{ - qDebug() << Q_FUNC_INFO; - if( m_report == false ) - return; - - qDebug() << Q_FUNC_INFO << "..reporting.."; - m_playlist->reportCreated( m_playlist ); - - if( source()->isLocal() ) - APP->servent().triggerDBSync(); -} diff --git a/src/database/databasecommand_createplaylist.h b/src/database/databasecommand_createplaylist.h deleted file mode 100644 index bd259eece..000000000 --- a/src/database/databasecommand_createplaylist.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef DATABASECOMMAND_CREATEPLAYLIST_H -#define DATABASECOMMAND_CREATEPLAYLIST_H - -#include "databaseimpl.h" -#include "databasecommandloggable.h" -#include "tomahawk/playlist.h" -#include "tomahawk/typedefs.h" - -class DatabaseCommand_CreatePlaylist : public DatabaseCommandLoggable -{ -Q_OBJECT -Q_PROPERTY( QVariant playlist READ playlistV WRITE setPlaylistV ) - -public: - explicit DatabaseCommand_CreatePlaylist( QObject* parent = 0 ); - explicit DatabaseCommand_CreatePlaylist( const Tomahawk::source_ptr& author, const Tomahawk::playlist_ptr& playlist ); - - QString commandname() const { return "createplaylist"; } - - virtual void exec( DatabaseImpl* lib ); - virtual void postCommitHook(); - virtual bool doesMutates() const { return true; } - - QVariant playlistV() const - { - return QJson::QObjectHelper::qobject2qvariant( (QObject*)m_playlist.data() ); - } - - void setPlaylistV( const QVariant& v ) - { - qDebug() << "***********" << Q_FUNC_INFO << v; - using namespace Tomahawk; - - Playlist* p = new Playlist( source() ); - QJson::QObjectHelper::qvariant2qobject( v.toMap(), p ); - m_playlist = playlist_ptr( p ); - } - -private: - Tomahawk::playlist_ptr m_playlist; - bool m_report; // call Playlist::reportCreated? -}; - -#endif // DATABASECOMMAND_CREATEPLAYLIST_H diff --git a/src/database/databasecommand_deleteplaylist.cpp b/src/database/databasecommand_deleteplaylist.cpp deleted file mode 100644 index 840fbb019..000000000 --- a/src/database/databasecommand_deleteplaylist.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "databasecommand_deleteplaylist.h" - -#include - -#include "tomahawk/tomahawkapp.h" - -using namespace Tomahawk; - - -DatabaseCommand_DeletePlaylist::DatabaseCommand_DeletePlaylist( const source_ptr& source, const QString& playlistguid ) - : DatabaseCommandLoggable( source ) -{ - setPlaylistguid( playlistguid ); -} - - -void -DatabaseCommand_DeletePlaylist::exec( DatabaseImpl* lib ) -{ - qDebug() << Q_FUNC_INFO; - - TomahawkSqlQuery cre = lib->newquery(); - - QString sql = QString( "DELETE FROM playlist WHERE guid = :id AND source %1" ) - .arg( source()->isLocal() ? "IS NULL" : QString("= %1").arg( source()->id() ) ); - cre.prepare( sql ); - cre.bindValue( ":id", m_playlistguid ); - - cre.exec(); -} - - -void -DatabaseCommand_DeletePlaylist::postCommitHook() -{ - qDebug() << Q_FUNC_INFO << "..reporting.."; - - playlist_ptr playlist = source()->collection()->playlist( m_playlistguid ); - Q_ASSERT( !playlist.isNull() ); - - playlist->reportDeleted( playlist ); - - if( source()->isLocal() ) - APP->servent().triggerDBSync(); -} diff --git a/src/database/databasecommand_deleteplaylist.h b/src/database/databasecommand_deleteplaylist.h deleted file mode 100644 index 84d069750..000000000 --- a/src/database/databasecommand_deleteplaylist.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef DATABASECOMMAND_DELETEPLAYLIST_H -#define DATABASECOMMAND_DELETEPLAYLIST_H - -#include "databaseimpl.h" -#include "databasecommandloggable.h" -#include "tomahawk/source.h" -#include "tomahawk/typedefs.h" - -class DatabaseCommand_DeletePlaylist : public DatabaseCommandLoggable -{ -Q_OBJECT -Q_PROPERTY( QString playlistguid READ playlistguid WRITE setPlaylistguid ) - -public: - explicit DatabaseCommand_DeletePlaylist( QObject* parent = 0 ) - : DatabaseCommandLoggable( parent ) - {} - - explicit DatabaseCommand_DeletePlaylist( const Tomahawk::source_ptr& source, const QString& playlistguid ); - - QString commandname() const { return "deleteplaylist"; } - - virtual void exec( DatabaseImpl* lib ); - virtual void postCommitHook(); - virtual bool doesMutates() const { return true; } - - QString playlistguid() const { return m_playlistguid; } - void setPlaylistguid( const QString& s ) { m_playlistguid = s; } - -private: - QString m_playlistguid; -}; - -#endif // DATABASECOMMAND_DELETEPLAYLIST_H diff --git a/src/database/databasecommand_importplaylist.h b/src/database/databasecommand_importplaylist.h deleted file mode 100644 index 19832a4d2..000000000 --- a/src/database/databasecommand_importplaylist.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef DATABASECOMMAND_IMPORTPLAYLIST_H -#define DATABASECOMMAND_IMPORTPLAYLIST_H -#include -#include -#include "databasecommand.h" -#include "tomahawk/source.h" - -class Playlist; - -class DatabaseCommand_ImportPlaylist : public DatabaseCommand -{ - Q_OBJECT -public: - explicit DatabaseCommand_ImportPlaylist(Playlist * p, QObject *parent = 0) - : DatabaseCommand(parent), m_playlist(p) - {} - - virtual void exec(DatabaseImpl *); - virtual bool doesMutates() const { return true; } - virtual QString commandname() const { return "importplaylist"; } - -signals: - void done(int id); - -private: - Playlist * m_playlist; -}; - -#endif // DATABASECOMMAND_ADDFILES_H diff --git a/src/database/databasecommand_loadallplaylists.h b/src/database/databasecommand_loadallplaylists.h deleted file mode 100644 index 371651eea..000000000 --- a/src/database/databasecommand_loadallplaylists.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef DATABASECOMMAND_IMPORTALLPLAYLIST_H -#define DATABASECOMMAND_IMPORTALLPLAYLIST_H - -#include -#include - -#include "databasecommand.h" -#include "tomahawk/typedefs.h" - -class DatabaseCommand_LoadAllPlaylists : public DatabaseCommand -{ -Q_OBJECT - -public: - explicit DatabaseCommand_LoadAllPlaylists( const Tomahawk::source_ptr& s, QObject* parent = 0 ) - : DatabaseCommand( s, parent ) - {} - - virtual void exec( DatabaseImpl* ); - virtual bool doesMutates() const { return false; } - virtual QString commandname() const { return "loadallplaylists"; } - -signals: - void done( const QList& playlists ); -}; - -#endif // DATABASECOMMAND_ADDFILES_H diff --git a/src/database/databasecommand_loadfile.cpp b/src/database/databasecommand_loadfile.cpp deleted file mode 100644 index 8a5cfe5c5..000000000 --- a/src/database/databasecommand_loadfile.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "databasecommand_loadfile.h" - -#include "databaseimpl.h" - - -DatabaseCommand_LoadFile::DatabaseCommand_LoadFile( const QString& id, QObject* parent ) - : DatabaseCommand( parent ) - , m_id( id ) -{ -} - - -void -DatabaseCommand_LoadFile::exec(DatabaseImpl* dbi) -{ - QVariantMap r; - // file ids internally are really ints, at least for now: - bool ok; - do - { - unsigned int fid = m_id.toInt( &ok ); - if( !ok ) - break; - - r = dbi->file( fid ); - } while( false ); - - emit result( r ); -} diff --git a/src/database/databasecommand_loadfile.h b/src/database/databasecommand_loadfile.h deleted file mode 100644 index aca186109..000000000 --- a/src/database/databasecommand_loadfile.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef DATABASECOMMAND_LOADFILE_H -#define DATABASECOMMAND_LOADFILE_H - -#include -#include -#include - -#include "databasecommand.h" - -class DatabaseCommand_LoadFile : public DatabaseCommand -{ -Q_OBJECT - -public: - explicit DatabaseCommand_LoadFile( const QString& id, QObject* parent = 0 ); - virtual void exec( DatabaseImpl* ); - virtual bool doesMutates() const { return false; } - virtual QString commandname() const { return "loadfile"; } - -signals: - void result( QVariantMap ); - -private: - QString m_id; -}; - -#endif // DATABASECOMMAND_LOADFILE_H diff --git a/src/database/databasecommand_loadops.cpp b/src/database/databasecommand_loadops.cpp deleted file mode 100644 index 1ecd69815..000000000 --- a/src/database/databasecommand_loadops.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "databasecommand_loadops.h" - - -void -DatabaseCommand_loadOps::exec( DatabaseImpl* dbi ) -{ - QList< dbop_ptr > ops; - - TomahawkSqlQuery query = dbi->newquery(); - query.prepare( QString( - "SELECT guid, command, json, compressed " - "FROM oplog " - "WHERE source %1 " - "AND id > coalesce((SELECT id FROM oplog WHERE guid = ?),0) " - "ORDER BY id ASC" - ).arg( source()->isLocal() ? "IS NULL" : QString("= %1").arg(source()->id()) ) - ); - query.addBindValue( m_since ); - if( !query.exec() ) - { - Q_ASSERT(0); - } - - while( query.next() ) - { - dbop_ptr op( new DBOp ); - op->guid = query.value( 0 ).toString(); - op->command = query.value( 1 ).toString(); - op->payload = query.value( 2 ).toByteArray(); - op->compressed = query.value( 3 ).toBool(); - ops << op; - } - - qDebug() << "Loaded" << ops.length() << "ops from db"; - - emit done( m_since, ops ); -} diff --git a/src/database/databasecommand_loadops.h b/src/database/databasecommand_loadops.h deleted file mode 100644 index f6e11b09a..000000000 --- a/src/database/databasecommand_loadops.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef DATABASECOMMAND_LOADOPS_H -#define DATABASECOMMAND_LOADOPS_H - -#include "tomahawk/typedefs.h" -#include "databasecommand.h" -#include "databaseimpl.h" -#include "op.h" - -class DatabaseCommand_loadOps : public DatabaseCommand -{ -Q_OBJECT -public: - explicit DatabaseCommand_loadOps( const Tomahawk::source_ptr& src, QString since, QObject* parent = 0 ) - : DatabaseCommand( src ), m_since( since ) - {} - - virtual void exec( DatabaseImpl* db ); - virtual bool doesMutates() const { return false; } - virtual QString commandname() const { return "loadops"; } - -signals: - void done( QString lastguid, QList< dbop_ptr > ops ); - -private: - QString m_since; // guid to load from -}; - -#endif // DATABASECOMMAND_LOADOPS_H diff --git a/src/database/databasecommand_loadplaylistentries.h b/src/database/databasecommand_loadplaylistentries.h deleted file mode 100644 index d9333e9eb..000000000 --- a/src/database/databasecommand_loadplaylistentries.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef DATABASECOMMAND_LOADPLAYLIST_H -#define DATABASECOMMAND_LOADPLAYLIST_H - -#include -#include - -#include "databasecommand.h" -#include "tomahawk/playlist.h" - -class DatabaseCommand_LoadPlaylistEntries : public DatabaseCommand -{ -Q_OBJECT - -public: - explicit DatabaseCommand_LoadPlaylistEntries( QString revision_guid, QObject* parent = 0 ) - : DatabaseCommand( parent ), m_guid( revision_guid ) - {} - - virtual void exec( DatabaseImpl* ); - virtual bool doesMutates() const { return false; } - virtual QString commandname() const { return "loadplaylistentries"; } - -signals: - void done( const QString& rev, - const QList& orderedguid, - const QList& oldorderedguid, - bool islatest, - const QMap< QString, Tomahawk::plentry_ptr >& added, - bool applied ); - -private: - QString m_guid; -}; - -#endif diff --git a/src/database/databasecommand_modifyplaylist.cpp b/src/database/databasecommand_modifyplaylist.cpp deleted file mode 100644 index 91a2ae9ce..000000000 --- a/src/database/databasecommand_modifyplaylist.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "databasecommand_modifyplaylist.h" - -using namespace Tomahawk; - - -DatabaseCommand_ModifyPlaylist::DatabaseCommand_ModifyPlaylist( Playlist* playlist, const QList< plentry_ptr >& entries, Mode mode ) - : DatabaseCommand() - , m_playlist( playlist ) - , m_entries( entries ) - , m_mode( mode ) -{ -} - - -void DatabaseCommand_ModifyPlaylist::exec( DatabaseImpl* lib ) -{ -} diff --git a/src/database/databasecommand_modifyplaylist.h b/src/database/databasecommand_modifyplaylist.h deleted file mode 100644 index 9bae6f0d4..000000000 --- a/src/database/databasecommand_modifyplaylist.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef DATABASECOMMAND_MODIFYPLAYLIST_H -#define DATABASECOMMAND_MODIFYPLAYLIST_H - -#include -#include - -#include "databasecommand.h" -#include "tomahawk/source.h" -#include "tomahawk/playlist.h" - -class DatabaseCommand_ModifyPlaylist : public DatabaseCommand -{ -Q_OBJECT -Q_PROPERTY( int mode READ mode WRITE setMode ) - -public: - enum Mode - { - ADD = 1, - REMOVE = 2, - UPDATE = 3 - }; - - explicit DatabaseCommand_ModifyPlaylist( Tomahawk::Playlist* playlist, const QList< Tomahawk::plentry_ptr >& entries, Mode mode ); - - virtual bool doesMutates() const { return true; } - - virtual void exec( DatabaseImpl* lib ); - - int mode() const { return m_mode; } - void setMode( int m ) { m_mode = (Mode)m; } - -private: - Tomahawk::Playlist* m_playlist; - QList< Tomahawk::plentry_ptr > m_entries; - Mode m_mode; -}; - -#endif // DATABASECOMMAND_MODIFYPLAYLIST_H diff --git a/src/database/databasecommand_renameplaylist.cpp b/src/database/databasecommand_renameplaylist.cpp deleted file mode 100644 index 51da0ff6d..000000000 --- a/src/database/databasecommand_renameplaylist.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "databasecommand_renameplaylist.h" - -#include - -#include "tomahawk/tomahawkapp.h" - -using namespace Tomahawk; - - -DatabaseCommand_RenamePlaylist::DatabaseCommand_RenamePlaylist( const source_ptr& source, const QString& playlistguid, const QString& playlistTitle ) - : DatabaseCommandLoggable( source ) -{ - setPlaylistguid( playlistguid ); - setPlaylistTitle( playlistTitle ); -} - - -void -DatabaseCommand_RenamePlaylist::exec( DatabaseImpl* lib ) -{ - qDebug() << Q_FUNC_INFO; - - TomahawkSqlQuery cre = lib->newquery(); - - QString sql = QString( "UPDATE playlist SET title = :title WHERE guid = :id AND source %1" ) - .arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) ); - - cre.prepare( sql ); - cre.bindValue( ":id", m_playlistguid ); - cre.bindValue( ":title", m_playlistTitle ); - - cre.exec(); -} - - -void -DatabaseCommand_RenamePlaylist::postCommitHook() -{ - qDebug() << Q_FUNC_INFO << "..reporting.."; - - playlist_ptr playlist = source()->collection()->playlist( m_playlistguid ); - Q_ASSERT( !playlist.isNull() ); - - playlist->setTitle( m_playlistTitle ); - - if( source()->isLocal() ) - APP->servent().triggerDBSync(); -} diff --git a/src/database/databasecommand_resolve.cpp b/src/database/databasecommand_resolve.cpp deleted file mode 100644 index df09d5bcf..000000000 --- a/src/database/databasecommand_resolve.cpp +++ /dev/null @@ -1,198 +0,0 @@ -#include "databasecommand_resolve.h" - -#include "tomahawk/tomahawkapp.h" -#include "tomahawk/album.h" - -#define MINSCORE 0.5 - -using namespace Tomahawk; - - -DatabaseCommand_Resolve::DatabaseCommand_Resolve( const QVariant& v, bool searchlocal ) - : DatabaseCommand() - , m_v( v ) - , m_searchlocal( searchlocal ) -{ -} - - -void -DatabaseCommand_Resolve::exec( DatabaseImpl* lib ) -{ - const Tomahawk::QID qid = m_v.toMap().value("qid").toString(); - const QString artistname = m_v.toMap().value("artist").toString(); - const QString albumname = m_v.toMap().value("album").toString(); - const QString trackname = m_v.toMap().value("track").toString(); - - //qDebug() << Q_FUNC_INFO << artistname << trackname; - - /* - Resolving is a 2 stage process. - 1) find list of trk/art/alb IDs that are reasonable matches to the metadata given - 2) find files in database by permitted sources and calculate score, ignoring - results that are less than MINSCORE - */ - - typedef QPair scorepair_t; - - // STEP 1 - QList< int > artists = lib->searchTable( "artist", artistname, 10 ); - QList< int > tracks = lib->searchTable( "track", trackname, 10 ); - QList< int > albums = lib->searchTable( "album", albumname, 10 ); - - //qDebug() << "art" << artists.size() << "trk" << tracks.size(); - //qDebug() << "searchTable calls duration:" << timer.elapsed(); - - if( artists.length() == 0 || tracks.length() == 0 ) - { - //qDebug() << "No candidates found in first pass, aborting resolve" << artistname << trackname; - return; - } - - // STEP 2 - TomahawkSqlQuery files_query = lib->newquery(); - - QStringList artsl, trksl; - foreach( int i, artists ) artsl.append( QString::number(i) ); - foreach( int i, tracks ) trksl.append( QString::number(i) ); - - QString sql = QString( "SELECT " - "url, mtime, size, md5, mimetype, duration, bitrate, file_join.artist, file_join.album, file_join.track, " - "artist.name as artname, " - "album.name as albname, " - "track.name as trkname, " - "file.source, " - "file_join.albumpos, " - "artist.id as artid, " - "album.id as albid " - "FROM file, file_join, artist, track " - "LEFT JOIN album ON album.id = file_join.album " - "WHERE " - "artist.id = file_join.artist AND " - "track.id = file_join.track AND " - "file.source %1 AND " - "file.id = file_join.file AND " - "file_join.artist IN (%2) AND " - "file_join.track IN (%3) " - "ORDER by file_join.artist,file_join.track" - ).arg( m_searchlocal ? "IS NULL" : " IN (SELECT id FROM source WHERE isonline = 'true') " ) - .arg( artsl.join(",") ) - .arg( trksl.join(",") ); - - files_query.prepare( sql ); - bool ok = files_query.exec(); - if(!ok) - throw "Error"; - - //qDebug() << "SQL exec() duration, ms, " << timer.elapsed() - // << "numresults" << files_query.numRowsAffected(); - //qDebug() << sql; - - QList res; - - while( files_query.next() ) - { - QVariantMap m; - - m["mtime"] = files_query.value(1).toString(); - m["size"] = files_query.value(2).toInt(); - m["hash"] = files_query.value(3).toString(); - m["mimetype"] = files_query.value(4).toString(); - m["duration"] = files_query.value(5).toInt(); - m["bitrate"] = files_query.value(6).toInt(); - m["artist"] = files_query.value(10).toString(); - m["artistid"] = files_query.value(15).toUInt(); - m["album"] = files_query.value(11).toString(); - m["albumid"] = files_query.value(16).toUInt(); - m["track"] = files_query.value(12).toString(); - m["srcid"] = files_query.value(13).toInt(); - m["albumpos"] = files_query.value(14).toUInt(); - m["sid"] = uuid(); - - collection_ptr coll; - - const QString url_str = files_query.value( 0 ).toString(); - if( m_searchlocal ) - { - coll = APP->sourcelist().getLocal()->collection(); - m["url"] = url_str; - m["source"] = "Local Database"; // TODO - } - else - { - source_ptr s = APP->sourcelist().lookup( files_query.value( 13 ).toUInt() ); - if( s.isNull() ) - { - //qDebug() << "Skipping result for offline sourceid:" << files_query.value(13).toUInt(); - // will happen for valid sources which are offline (and thus not in the sourcelist) - return; - } - - coll = s->collection(); - m.insert( "url", QString( "servent://%1\t%2" ) - .arg( s->userName() ) - .arg( url_str ) ); - m.insert( "source", s->friendlyName() ); - } - - //int artid = files_query.value( 7 ).toInt(); - //int albid = files_query.value( 8 ).toInt(); - //int trkid = files_query.value( 9 ).toInt(); - - float score = how_similar( m_v.toMap(), m ); - //qDebug() << "Score calc:" << timer.elapsed(); - - m["score"] = score; - //qDebug() << "RESULT" << score << m; - - if( score < MINSCORE ) - continue; - - res << Tomahawk::result_ptr( new Tomahawk::Result( m, coll ) ); - } - - // return results, if any found - if( res.length() > 0 ) - { - emit results( qid, res ); - } -} - - -// TODO make clever (ft. featuring live (stuff) etc) -float -DatabaseCommand_Resolve::how_similar( const QVariantMap& q, const QVariantMap& r ) -{ - // query values - const QString qArtistname = DatabaseImpl::sortname( q.value("artist").toString() ); - const QString qAlbumname = DatabaseImpl::sortname( q.value("album").toString() ); - const QString qTrackname = DatabaseImpl::sortname( q.value("track").toString() ); - - // result values - const QString rArtistname = DatabaseImpl::sortname( r.value("artist").toString() ); - const QString rAlbumname = DatabaseImpl::sortname( r.value("album").toString() ); - const QString rTrackname = DatabaseImpl::sortname( r.value("track").toString() ); - - // normal edit distance - int artdist = levenshtein( qArtistname, rArtistname ); - int albdist = levenshtein( qAlbumname, rAlbumname ); - int trkdist = levenshtein( qTrackname, rTrackname ); - - // max length of name - int mlart = qMax( qArtistname.length(), rArtistname.length() ); - int mlalb = qMax( qAlbumname.length(), rAlbumname.length() ); - int mltrk = qMax( qTrackname.length(), rTrackname.length() ); - - // distance scores - float dcart = (float)( mlart - artdist ) / mlart; - float dcalb = (float)( mlalb - albdist ) / mlalb; - float dctrk = (float)( mltrk - trkdist ) / mltrk; - - // don't penalize for missing album name - if( qAlbumname.length() == 0 ) dcalb = 1.0; - - // weighted, so album match is worth less than track title - float combined = ( dcart*4 + dcalb + dctrk*5 ) / 10; - - return combined; -} diff --git a/src/database/databasecommand_resolve.h b/src/database/databasecommand_resolve.h deleted file mode 100644 index 26945698a..000000000 --- a/src/database/databasecommand_resolve.h +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef DATABASECOMMAND_RESOLVE_H -#define DATABASECOMMAND_RESOLVE_H - -#include "databasecommand.h" -#include "databaseimpl.h" -#include "tomahawk/result.h" - -#include - -class DatabaseCommand_Resolve : public DatabaseCommand -{ -Q_OBJECT -public: - explicit DatabaseCommand_Resolve( const QVariant& v, bool searchlocal ); - - virtual QString commandname() const { return "dbresolve"; } - virtual bool doesMutates() const { return false; } - - virtual void exec(DatabaseImpl *lib); - - -signals: - - void results( Tomahawk::QID qid, QList results ); - -public slots: - -private: - QVariant m_v; - bool m_searchlocal; - - float how_similar( const QVariantMap& q, const QVariantMap& r ); - - static int levenshtein(const QString& source, const QString& target) - { - // Step 1 - const int n = source.length(); - const int m = target.length(); - if (n == 0) { - return m; - } - if (m == 0) { - return n; - } - // Good form to declare a TYPEDEF - typedef QVector< QVector > Tmatrix; - Tmatrix matrix; - matrix.resize( n+1 ); - - // Size the vectors in the 2.nd dimension. Unfortunately C++ doesn't - // allow for allocation on declaration of 2.nd dimension of vec of vec - for (int i = 0; i <= n; i++) { - QVector tmp; - tmp.resize( m+1 ); - matrix.insert( i, tmp ); - } - // Step 2 - for (int i = 0; i <= n; i++) { - matrix[i][0]=i; - } - for (int j = 0; j <= m; j++) { - matrix[0][j]=j; - } - // Step 3 - for (int i = 1; i <= n; i++) { - const QChar s_i = source[i-1]; - // Step 4 - for (int j = 1; j <= m; j++) { - const QChar t_j = target[j-1]; - // Step 5 - int cost; - if (s_i == t_j) { - cost = 0; - } - else { - cost = 1; - } - // Step 6 - const int above = matrix[i-1][j]; - const int left = matrix[i][j-1]; - const int diag = matrix[i-1][j-1]; - //int cell = min( above + 1, min(left + 1, diag + cost)); - int cell = (((left+1)>(diag+cost))?diag+cost:left+1); - if(above+1 < cell) cell = above+1; - // Step 6A: Cover transposition, in addition to deletion, - // insertion and substitution. This step is taken from: - // Berghel, Hal ; Roach, David : "An Extension of Ukkonen's - // Enhanced Dynamic Programming ASM Algorithm" - // (http://www.acm.org/~hlb/publications/asm/asm.html) - if (i>2 && j>2) { - int trans=matrix[i-2][j-2]+1; - if (source[i-2]!=t_j) trans++; - if (s_i!=target[j-2]) trans++; - if (cell>trans) cell=trans; - } - matrix[i][j]=cell; - } - } - // Step 7 - return matrix[n][m]; - }; -}; - -#endif // DATABASECOMMAND_RESOLVE_H diff --git a/src/database/databasecommand_sourceoffline.cpp b/src/database/databasecommand_sourceoffline.cpp deleted file mode 100644 index e31e7861e..000000000 --- a/src/database/databasecommand_sourceoffline.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "databasecommand_sourceoffline.h" - - -DatabaseCommand_SourceOffline::DatabaseCommand_SourceOffline( int id ) - : DatabaseCommand() - , m_id( id ) -{ -} - - -void DatabaseCommand_SourceOffline::exec( DatabaseImpl* lib ) -{ - TomahawkSqlQuery q = lib->newquery(); - q.exec( QString( "UPDATE source SET isonline = 'false' WHERE id = %1" ) - .arg( m_id ) ); -} diff --git a/src/database/databasecommand_sourceoffline.h b/src/database/databasecommand_sourceoffline.h deleted file mode 100644 index c5988d378..000000000 --- a/src/database/databasecommand_sourceoffline.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef DATABASECOMMAND_SOURCEOFFLINE_H -#define DATABASECOMMAND_SOURCEOFFLINE_H - -#include "databasecommand.h" -#include "databaseimpl.h" - -class DatabaseCommand_SourceOffline : public DatabaseCommand -{ -Q_OBJECT - -public: - explicit DatabaseCommand_SourceOffline( int id ); - bool doesMutates() const { return true; } - void exec( DatabaseImpl* lib ); - -private: - int m_id; -}; - -#endif // DATABASECOMMAND_SOURCEOFFLINE_H diff --git a/src/database/databasecommand_updatesearchindex.cpp b/src/database/databasecommand_updatesearchindex.cpp deleted file mode 100644 index f4c517ffb..000000000 --- a/src/database/databasecommand_updatesearchindex.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "databasecommand_updatesearchindex.h" - - -DatabaseCommand_UpdateSearchIndex::DatabaseCommand_UpdateSearchIndex( const QString& t, int p ) - : DatabaseCommand() - , table( t ) - , pkey( p ) -{ - if( table != "artist" && table != "track" && table != "album" ) - { - Q_ASSERT(false); - return; - } -} - - -void DatabaseCommand_UpdateSearchIndex::exec(DatabaseImpl *db) -{ - qDebug() << Q_FUNC_INFO; - - if( table != "artist" && table != "track" && table != "album" ) - { - Q_ASSERT(false); - return; - } - - // if pkey is 0, consult DB to see what needs indexing - if( pkey == 0 ) - { - TomahawkSqlQuery q = db->newquery(); - q.exec( QString("SELECT coalesce(max(id),0) from %1_search_index").arg(table) ); - q.next(); - pkey = 1 + q.value(0).toInt(); - qDebug() << "updateSearchIndex" << table << "consulted DB, starting at" << pkey; - } - - TomahawkSqlQuery query = db->newquery(); - qDebug() << "Building index for" << table << ">= id" << pkey; - QString searchtable( table + "_search_index" ); - query.exec(QString( "SELECT id, sortname FROM %1 WHERE id >= %2" ).arg( table ).arg(pkey ) ); - - TomahawkSqlQuery upq = db->newquery(); - TomahawkSqlQuery inq = db->newquery(); - inq.prepare( "INSERT INTO "+ searchtable +" (ngram, id, num) VALUES (?,?,?)" ); - upq.prepare( "UPDATE "+ searchtable +" SET num=num+? WHERE ngram=? AND id=?" ); - - int num_names = 0; - int num_ngrams = 0; - int id; - QString name; - QMap ngrammap; - - // this is the new ngram map we build up, to be merged into the - // main one in FuzzyIndex: - QHash< QString, QMap > idx; - - while( query.next() ) - { - id = query.value( 0 ).toInt(); - name = query.value( 1 ).toString(); - num_names++; - inq.bindValue( 1, id ); // set id - upq.bindValue( 2, id ); // set id - ngrammap = DatabaseImpl::ngrams( name ); - QMapIterator i( ngrammap ); - - while ( i.hasNext() ) - { - i.next(); - num_ngrams++; - upq.bindValue( 0, i.value() ); //num - upq.bindValue( 1, i.key() ); // ngram - upq.exec(); - - if( upq.numRowsAffected() == 0 ) - { - inq.bindValue( 0, i.key() ); //ngram - inq.bindValue( 2, i.value() ); //num - inq.exec(); - if( inq.numRowsAffected() == 0 ) - { - qDebug() << "Error updating search index:" << id << name; - continue; - } - } - - // update ngram cache: - QMapIterator iter( ngrammap ); - while ( iter.hasNext() ) - { - iter.next(); - if( idx.contains( iter.key() ) ) - { - idx[ iter.key() ][ id ] += iter.value(); - } - else - { - QMap tmp; - tmp.insert( id, iter.value() ); - idx.insert( iter.key(), tmp ); - } - } - - - } - } - - // merge in our ngrams into the main index - QMetaObject::invokeMethod( &(db->m_fuzzyIndex), - "mergeIndex", - Qt::QueuedConnection, - Q_ARG( QString, table ), - QGenericArgument( "QHash< QString, QMap >", &idx ) - ); - - qDebug() << "Finished indexing" << num_names <<" names," << num_ngrams << "ngrams."; -} diff --git a/src/database/databasecommand_updatesearchindex.h b/src/database/databasecommand_updatesearchindex.h deleted file mode 100644 index 87d886ff8..000000000 --- a/src/database/databasecommand_updatesearchindex.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef DATABASECOMMAND_UPDATESEARCHINDEX_H -#define DATABASECOMMAND_UPDATESEARCHINDEX_H - -#include "databasecommand.h" -#include "databaseimpl.h" - -class DatabaseCommand_UpdateSearchIndex : public DatabaseCommand -{ -Q_OBJECT -public: - explicit DatabaseCommand_UpdateSearchIndex(const QString& table, int pkey); - - virtual QString commandname() const { return "updatesearchindex"; } - virtual bool doesMutates() const { return true; } - virtual void exec(DatabaseImpl* db); - -signals: - void indexUpdated(); - -public slots: - -private: - QString table; - int pkey; - -}; - -#endif // DATABASECOMMAND_UPDATESEARCHINDEX_H diff --git a/src/database/databasecommandloggable.cpp b/src/database/databasecommandloggable.cpp deleted file mode 100644 index 23f997aae..000000000 --- a/src/database/databasecommandloggable.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "databasecommandloggable.h" - -#include - -#include "database/databasecommand_addfiles.h" -#include "database/databasecommand_setplaylistrevision.h" - - -DatabaseCommandLoggable* -DatabaseCommandLoggable::factory( const QVariantMap& c ) -{ - const QString name = c.value( "command" ).toString(); - //TODO dynamic class loading, factory blah - - if( name == "addfiles" ) - { - DatabaseCommand_AddFiles* cmd = new DatabaseCommand_AddFiles; - QJson::QObjectHelper::qvariant2qobject( c, cmd ); - return cmd; - } - else if( name == "setplaylistrevision" ) - { - DatabaseCommand_SetPlaylistRevision* cmd = new DatabaseCommand_SetPlaylistRevision; - QJson::QObjectHelper::qvariant2qobject( c, cmd ); - return cmd; - } - else - { - qDebug() << "Unhandled command name"; - Q_ASSERT( false ); - return 0; - } -} diff --git a/src/database/databasecommandloggable.h b/src/database/databasecommandloggable.h deleted file mode 100644 index 90dfab1e4..000000000 --- a/src/database/databasecommandloggable.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef DATABASECOMMANDLOGGABLE_H -#define DATABASECOMMANDLOGGABLE_H - -#include "database/databasecommand.h" - -/// A Database Command that will be added to the oplog and sent over the network -/// so peers can sync up and changes to our collection in their cached copy. -class DatabaseCommandLoggable : public DatabaseCommand -{ -Q_OBJECT -Q_PROPERTY(QString command READ commandname) - -public: - - explicit DatabaseCommandLoggable( QObject* parent = 0 ) - : DatabaseCommand( parent ) - {} - - explicit DatabaseCommandLoggable( const Tomahawk::source_ptr& s, QObject* parent = 0 ) - : DatabaseCommand( s, parent ) - {} - - virtual bool loggable() const { return true; } - - static DatabaseCommandLoggable* factory( const QVariantMap& c ); - -}; - -#endif // DATABASECOMMANDLOGGABLE_H diff --git a/src/database/databaseresolver.cpp b/src/database/databaseresolver.cpp deleted file mode 100644 index e0bb8b6d8..000000000 --- a/src/database/databaseresolver.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "databaseresolver.h" - -#include "tomahawk/tomahawkapp.h" -#include "database.h" -#include "database/databasecommand_resolve.h" - -DatabaseResolver::DatabaseResolver( bool searchlocal, int weight ) - : Resolver() - , m_searchlocal( searchlocal ) - , m_weight( weight ) -{ -} - - -void -DatabaseResolver::resolve( const QVariant& v ) -{ - //qDebug() << Q_FUNC_INFO << v; - - if( !m_searchlocal ) - { - if( APP->servent().numConnectedPeers() == 0 ) - return; - } - - DatabaseCommand_Resolve* cmd = new DatabaseCommand_Resolve( v, m_searchlocal ); - - connect( cmd, SIGNAL( results( Tomahawk::QID, QList< Tomahawk::result_ptr> ) ), - SLOT( gotResults( Tomahawk::QID, QList< Tomahawk::result_ptr> ) ), Qt::QueuedConnection ); - - APP->database()->enqueue( QSharedPointer( cmd ) ); - -} - - -void -DatabaseResolver::gotResults( const Tomahawk::QID qid, QList< Tomahawk::result_ptr> results ) -{ -// qDebug() << Q_FUNC_INFO << qid << results.length(); - - APP->pipeline()->reportResults( qid, results ); -} - - -QString -DatabaseResolver::name() const -{ - return QString( "Database (%1)" ).arg( m_searchlocal ? "local" : "remote" ); -} diff --git a/src/database/databaseresolver.h b/src/database/databaseresolver.h deleted file mode 100644 index f9bbd2696..000000000 --- a/src/database/databaseresolver.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef DATABASERESOLVER_H -#define DATABASERESOLVER_H - -#include "tomahawk/resolver.h" -#include "tomahawk/result.h" - -class DatabaseResolver : public Tomahawk::Resolver -{ -Q_OBJECT - -public: - explicit DatabaseResolver( bool searchlocal, int weight ); - - virtual QString name() const; - virtual unsigned int weight() const { return m_weight; } - virtual unsigned int preference() const { return 100; } - virtual unsigned int timeout() const { return 1000; } - - virtual void resolve( const QVariant& v ); - -private slots: - void gotResults( const Tomahawk::QID qid, QList< Tomahawk::result_ptr> results ); - -private: - bool m_searchlocal; - int m_weight; -}; - -#endif // DATABASERESOLVER_H diff --git a/src/database/databaseworker.cpp b/src/database/databaseworker.cpp deleted file mode 100644 index d07296ab6..000000000 --- a/src/database/databaseworker.cpp +++ /dev/null @@ -1,194 +0,0 @@ -#include "databaseworker.h" - -#include -#include -#include - -#include "tomahawk/tomahawkapp.h" -#include "database.h" -#include "database/databasecommandloggable.h" - - -DatabaseWorker::DatabaseWorker( DatabaseImpl* lib, Database* db, bool mutates ) - : QThread() - , m_dbimpl( lib ) - , m_abort( false ) - , m_outstanding( 0 ) -{ - moveToThread( this ); - if( mutates ) - { - connect( db, SIGNAL( newJobRW(QSharedPointer) ), - SLOT( doWork(QSharedPointer) ), - Qt::QueuedConnection ); - } - else - { - connect( db, SIGNAL( newJobRO(QSharedPointer) ), - SLOT( doWork(QSharedPointer) ), - Qt::QueuedConnection ); - } - - qDebug() << "CTOR DatabaseWorker" << this->thread(); -} - - -DatabaseWorker::~DatabaseWorker() -{ - qDebug() << Q_FUNC_INFO; - - quit(); - wait( 5000 ); -} - - -void -DatabaseWorker::run() -{ - exec(); - qDebug() << Q_FUNC_INFO << "DatabaseWorker finishing..."; -} - - -void -DatabaseWorker::doWork( QSharedPointer cmd ) -{ - /* - Run the dbcmd. Only inside a transaction if the cmd does mutates. - - If the cmd is modifying local content (ie source->isLocal()) then - log to the database oplog for replication to peers. - - */ - QTime timer; - timer.start(); - if( cmd->doesMutates() ) - { - bool transok = m_dbimpl->database().transaction(); - Q_ASSERT( transok ); - } - try - { - cmd->_exec( m_dbimpl ); // runs actual SQL stuff - - if( cmd->loggable() ) - { - // We only save our own ops to the oplog, since incoming ops from peers - // are applied immediately. - // - // Crazy idea: if peers had keypairs and could sign ops/msgs, in theory it - // would be safe to sync ops for friend A from friend B's cache, if he saved them, - // which would mean you could get updates even if a peer was offline. - if( cmd->source()->isLocal() ) - { - // save to op-log - DatabaseCommandLoggable* command = (DatabaseCommandLoggable*)cmd.data(); - logOp( command ); - } - else - { - // Make a note of the last guid we applied for this source - // so we can always request just the newer ops in future. - // - qDebug() << "Setting lastop for source" << cmd->source()->id() << "to" << cmd->guid(); - TomahawkSqlQuery query = m_dbimpl->newquery(); - query.prepare( "UPDATE source SET lastop = ? WHERE id = ?" ); - query.addBindValue( cmd->guid() ); - query.addBindValue( cmd->source()->id() ); - if( !query.exec() ) - { - qDebug() << "Failed to set lastop"; - throw "Failed to set lastop"; - } - } - } - - if( cmd->doesMutates() ) - { - qDebug() << "Comitting" << cmd->commandname();; - if( !m_dbimpl->database().commit() ) - { - - qDebug() << "*FAILED TO COMMIT TRANSACTION*"; - throw "commit failed"; - } - else - { - qDebug() << "Committed" << cmd->commandname(); - } - } - - //uint duration = timer.elapsed(); - //qDebug() << "DBCmd Duration:" << duration << "ms, now running postcommit for" << cmd->commandname(); - cmd->postCommit(); - //qDebug() << "Post commit finished for"<< cmd->commandname(); - } - catch( const char * msg ) - { - qDebug() << endl - << "*ERROR* processing databasecommand:" - << cmd->commandname() - << msg - << m_dbimpl->database().lastError().databaseText() - << m_dbimpl->database().lastError().driverText() - << endl; - - if( cmd->doesMutates() ) - m_dbimpl->database().rollback(); - - Q_ASSERT( false ); - } - catch(...) - { - qDebug() << "Uncaught exception processing dbcmd"; - if( cmd->doesMutates() ) - m_dbimpl->database().rollback(); - - Q_ASSERT( false ); - throw; - } - cmd->emitFinished(); -} - - -// this should take a const command, need to check/make json stuff mutable for some objs tho maybe. -void -DatabaseWorker::logOp( DatabaseCommandLoggable* command ) -{ - TomahawkSqlQuery oplogquery = m_dbimpl->newquery(); - oplogquery.prepare( "INSERT INTO oplog(source, guid, command, compressed, json) " - "VALUES(?, ?, ?, ?, ?) "); - - QVariantMap variant = QJson::QObjectHelper::qobject2qvariant( command ); - QByteArray ba = m_serializer.serialize( variant ); - - //qDebug() << "OP JSON:" << ba; // debug - - bool compressed = false; - if( ba.length() >= 512 ) - { - // We need to compress this in this thread, since inserting into the log - // has to happen as part of the same transaction as the dbcmd. - // (we are in a worker thread for RW dbcmds anyway, so it's ok) - //qDebug() << "Compressing DB OP JSON, uncompressed size:" << ba.length(); - ba = qCompress( ba, 9 ); - compressed = true; - //qDebug() << "Compressed DB OP JSON size:" << ba.length(); - } - - qDebug() << "Saving to oplog:" << command->commandname() - << "bytes:" << ba.length() - << "guid:" << command->guid(); - - oplogquery.bindValue( 0, command->source()->isLocal() ? - QVariant(QVariant::Int) : command->source()->id() ); - oplogquery.bindValue( 1, command->guid() ); - oplogquery.bindValue( 2, command->commandname() ); - oplogquery.bindValue( 3, compressed ); - oplogquery.bindValue( 4, ba ); - if( !oplogquery.exec() ) - { - qDebug() << "Error saving to oplog"; - throw "Failed to save to oplog"; - } -} diff --git a/src/database/databaseworker.h b/src/database/databaseworker.h deleted file mode 100644 index 74c0eb22a..000000000 --- a/src/database/databaseworker.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef DATABASEWORKER_H -#define DATABASEWORKER_H - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "databasecommand.h" -#include "databaseimpl.h" - -class Database; -class DatabaseCommandLoggable; - -class DatabaseWorker : public QThread -{ -Q_OBJECT - -public: - DatabaseWorker( DatabaseImpl*, Database*, bool mutates ); - ~DatabaseWorker(); - //void enqueue( QSharedPointer ); - -protected: - void run(); - -public slots: - void doWork( QSharedPointer ); - -private: - void logOp( DatabaseCommandLoggable* command ); - - QMutex m_mut; - DatabaseImpl* m_dbimpl; - QList< QSharedPointer > m_commands; - bool m_abort; - int m_outstanding; - QJson::Serializer m_serializer; -}; - -#endif // DATABASEWORKER_H diff --git a/src/database/fuzzyindex.cpp b/src/database/fuzzyindex.cpp deleted file mode 100644 index 961f3b590..000000000 --- a/src/database/fuzzyindex.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "fuzzyindex.h" - -#include "databaseimpl.h" - -#include - -FuzzyIndex::FuzzyIndex( DatabaseImpl &db ) - : QObject() - , m_db( db ) - , m_loaded( false ) -{ -} - - -void -FuzzyIndex::loadNgramIndex() -{ - // this updates the index in the DB, if needed: - qDebug() << "Checking catalogue is fully indexed.."; - m_db.updateSearchIndex("artist",0); - m_db.updateSearchIndex("album",0); - m_db.updateSearchIndex("track",0); - - // loads index from DB into memory: - qDebug() << "Loading search index for catalogue metadata..." << thread(); - loadNgramIndex_helper( m_artist_ngrams, "artist" ); - loadNgramIndex_helper( m_album_ngrams, "album" ); - loadNgramIndex_helper( m_track_ngrams, "track" ); - m_loaded = true; - emit indexReady(); -} - - -void -FuzzyIndex::loadNgramIndex_helper( QHash< QString, QMap >& idx, const QString& table, unsigned int fromkey ) -{ - QTime t; - t.start(); - TomahawkSqlQuery query = m_db.newquery(); - query.exec( QString( "SELECT ngram, id, num " - "FROM %1_search_index " - "WHERE id >= %2 " - "ORDER BY ngram" ).arg( table ).arg( fromkey ) ); - - QMap ngram_idx; - QString lastngram; - while( query.next() ) - { - if( lastngram.isEmpty() ) - lastngram = query.value(0).toString(); - - if( query.value( 0 ).toString() != lastngram ) - { - idx.insert( lastngram, ngram_idx ); - lastngram = query.value( 0 ).toString(); - ngram_idx.clear(); - } - - ngram_idx.insert( query.value( 1 ).toUInt(), - query.value( 2 ).toUInt() ); - } - - idx.insert( lastngram, ngram_idx ); - qDebug() << "Loaded" << idx.size() - << "ngram entries for" << table - << "in" << t.elapsed(); -} - - -void FuzzyIndex::mergeIndex( const QString& table, QHash< QString, QMap > tomerge ) -{ - qDebug() << Q_FUNC_INFO << table << tomerge.keys().size(); - - QHash< QString, QMap >* idx; - if ( table == "artist" ) idx = &m_artist_ngrams; - else if( table == "album" ) idx = &m_album_ngrams; - else if( table == "track" ) idx = &m_track_ngrams; - else Q_ASSERT(false); - - if( tomerge.size() == 0 ) return; - - if( idx->size() == 0 ) - { - *idx = tomerge; - } - else - { - foreach( const QString& ngram, tomerge.keys() ) - { - - if( idx->contains( ngram ) ) - { - foreach( quint32 id, tomerge[ngram].keys() ) - { - (*idx)[ ngram ][ id ] += tomerge[ngram][id]; - } - } - else - { - idx->insert( ngram, tomerge[ngram] ); - } - } - } - qDebug() << Q_FUNC_INFO << table << "merge complete, num items:" << tomerge.size(); -} - - -QMap< int, float > -FuzzyIndex::search( const QString& table, const QString& name ) -{ - QMap< int, float > resultsmap; - - QHash< QString, QMap >* idx; - if( table == "artist" ) idx = &m_artist_ngrams; - else if( table == "album" ) idx = &m_album_ngrams; - else if( table == "track" ) idx = &m_track_ngrams; - - QMap ngramsmap = DatabaseImpl::ngrams( name ); - foreach( const QString& ngram, ngramsmap.keys() ) - { - if( !idx->contains( ngram ) ) - continue; - //qDebug() << name_orig << "NGRAM:" << ngram << "candidates:" << (*idx)[ngram].size(); - QMapIterator iter( (*idx)[ngram] ); - while( iter.hasNext() ) - { - iter.next(); - resultsmap[ (int) iter.key() ] += (float) iter.value(); - } - } - return resultsmap; -} diff --git a/src/database/fuzzyindex.h b/src/database/fuzzyindex.h deleted file mode 100644 index 4659dbd3c..000000000 --- a/src/database/fuzzyindex.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef FUZZYINDEX_H -#define FUZZYINDEX_H - -#include -#include -#include -#include - -class DatabaseImpl; - - -class FuzzyIndex : public QObject -{ -Q_OBJECT -public: - explicit FuzzyIndex( DatabaseImpl &db ); - -signals: - void indexReady(); - -public slots: - void loadNgramIndex(); - QMap< int, float > search( const QString& table, const QString& name ); - void mergeIndex( const QString& table, QHash< QString, QMap > tomerge ); - -private: - void loadNgramIndex_helper( QHash< QString, QMap >& idx, const QString& table, unsigned int fromkey = 0 ); - - // maps an ngram to {track id, num occurences} - QHash< QString, QMap > m_artist_ngrams, m_album_ngrams, m_track_ngrams; - - DatabaseImpl & m_db; - bool m_loaded; -}; - -#endif // FUZZYINDEX_H diff --git a/src/database/op.h b/src/database/op.h deleted file mode 100644 index a25f6f921..000000000 --- a/src/database/op.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef OP_H -#define OP_H -#include -#include -#include - -struct DBOp -{ - QString guid; - QString command; - QByteArray payload; - bool compressed; -}; - -typedef QSharedPointer dbop_ptr; - -#endif // OP_H diff --git a/src/headlesscheck.h b/src/headlesscheck.h index 9a6e37e4d..189f1127e 100644 --- a/src/headlesscheck.h +++ b/src/headlesscheck.h @@ -1,16 +1,34 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef HEADLESSCHECK #define HEADLESSCHECK #ifdef ENABLE_HEADLESS -#define TOMAHAWK_APPLICATION QCoreApplication +#define TOMAHAWK_APPLICATION QtSingleCoreApplication #define TOMAHAWK_HEADLESS -#include +#include "qtsingleapp/qtsingleapplication.h" #else -#define TOMAHAWK_APPLICATION QApplication -#include +#define TOMAHAWK_APPLICATION QtSingleApplication +#include "qtsingleapp/qtsingleapplication.h" #include "tomahawkwindow.h" #endif diff --git a/src/infosystem/infoplugins/echonestplugin.cpp b/src/infosystem/infoplugins/echonestplugin.cpp index 5f1e47d6a..fab7c9bc4 100644 --- a/src/infosystem/infoplugins/echonestplugin.cpp +++ b/src/infosystem/infoplugins/echonestplugin.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "tomahawk/infosystem.h" #include "tomahawk/tomahawkapp.h" #include "echonestplugin.h" @@ -13,7 +31,6 @@ EchoNestPlugin::EchoNestPlugin(QObject *parent) : InfoPlugin(parent) { qDebug() << Q_FUNC_INFO; - Config::instance()->setAPIKey("JGJCRKWLXLBZIFAZB"); QSet< InfoType > supportedTypes; supportedTypes << Tomahawk::InfoSystem::InfoArtistBiography << Tomahawk::InfoSystem::InfoArtistFamiliarity << Tomahawk::InfoSystem::InfoArtistHotttness << Tomahawk::InfoSystem::InfoArtistTerms << Tomahawk::InfoSystem::InfoMiscTopTerms; qobject_cast< InfoSystem* >(parent)->registerInfoTypes(this, supportedTypes); diff --git a/src/infosystem/infoplugins/echonestplugin.h b/src/infosystem/infoplugins/echonestplugin.h index 8ffb120b2..734ceecd2 100644 --- a/src/infosystem/infoplugins/echonestplugin.h +++ b/src/infosystem/infoplugins/echonestplugin.h @@ -1,7 +1,28 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef ECHONESTPLUGIN_H #define ECHONESTPLUGIN_H + #include "tomahawk/infosystem.h" +#include + class QNetworkReply; namespace Echonest { class Artist; diff --git a/src/infosystem/infoplugins/lastfmplugin.cpp b/src/infosystem/infoplugins/lastfmplugin.cpp new file mode 100644 index 000000000..81272b86f --- /dev/null +++ b/src/infosystem/infoplugins/lastfmplugin.cpp @@ -0,0 +1,318 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "lastfmplugin.h" + +#include +#include +#include + +#include "album.h" +#include "typedefs.h" +#include "audio/audioengine.h" +#include "tomahawksettings.h" +#include "tomahawk/tomahawkapp.h" + +#include +#include + +using namespace Tomahawk::InfoSystem; + +static QString +md5( const QByteArray& src ) +{ + QByteArray const digest = QCryptographicHash::hash( src, QCryptographicHash::Md5 ); + return QString::fromLatin1( digest.toHex() ).rightJustified( 32, '0' ); +} + + +LastFmPlugin::LastFmPlugin( QObject* parent ) + : InfoPlugin(parent) + , m_scrobbler( 0 ) + , m_authJob( 0 ) +{ + QSet< InfoType > supportedTypes; + supportedTypes << InfoMiscSubmitScrobble << InfoMiscSubmitNowPlaying << InfoAlbumCoverArt; + qobject_cast< InfoSystem* >(parent)->registerInfoTypes(this, supportedTypes); + +/* + Your API Key is 7194b85b6d1f424fe1668173a78c0c4a + Your secret is ba80f1df6d27ae63e9cb1d33ccf2052f +*/ + + // Flush session key cache + TomahawkSettings::instance()->setLastFmSessionKey( QByteArray() ); + + lastfm::ws::ApiKey = "7194b85b6d1f424fe1668173a78c0c4a"; + lastfm::ws::SharedSecret = "ba80f1df6d27ae63e9cb1d33ccf2052f"; + lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername(); + + m_pw = TomahawkSettings::instance()->lastFmPassword(); + + if( TomahawkSettings::instance()->scrobblingEnabled() && !lastfm::ws::Username.isEmpty() ) + { + createScrobbler(); + } + + //HACK work around a bug in liblastfm---it doesn't create its config dir, so when it + // tries to write the track cache, it fails silently. until we have a fixed version, do this + // code taken from Amarok (src/services/lastfm/ScrobblerAdapter.cpp) +#ifdef Q_WS_X11 + QString lpath = QDir::home().filePath( ".local/share/Last.fm" ); + QDir ldir = QDir( lpath ); + if( !ldir.exists() ) + { + ldir.mkpath( lpath ); + } +#endif + + connect( TomahawkSettings::instance(), SIGNAL( changed() ), + SLOT( settingsChanged() ), Qt::QueuedConnection ); +} + + +LastFmPlugin::~LastFmPlugin() +{ + delete m_scrobbler; +} + +void +LastFmPlugin::dataError( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ) +{ + emit info( caller, type, data, QVariant(), customData ); + emit finished( caller, type ); + return; +} + +void +LastFmPlugin::getInfo( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash customData ) +{ + qDebug() << Q_FUNC_INFO; + if ( type == InfoMiscSubmitNowPlaying ) + nowPlaying( caller, type, data, customData ); + else if ( type == InfoMiscSubmitScrobble ) + scrobble( caller, type, data, customData ); + else if ( type == InfoAlbumCoverArt ) + fetchCoverArt( caller, type, data, customData ); + else + dataError( caller, type, data, customData ); +} + +void +LastFmPlugin::nowPlaying( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ) +{ + if ( !data.canConvert< Tomahawk::InfoSystem::InfoCustomDataHash >() || !m_scrobbler ) + { + dataError( caller, type, data, customData ); + return; + } + InfoCustomDataHash hash = data.value< Tomahawk::InfoSystem::InfoCustomDataHash >(); + if ( !hash.contains( "title" ) || !hash.contains( "artist" ) || !hash.contains( "album" ) || !hash.contains( "duration" ) ) + { + dataError( caller, type, data, customData ); + return; + } + + m_track = lastfm::MutableTrack(); + m_track.stamp(); + + m_track.setTitle( hash["title"].toString() ); + m_track.setArtist( hash["artist"].toString() ); + m_track.setAlbum( hash["album"].toString() ); + m_track.setDuration( hash["duration"].toUInt() ); + m_track.setSource( lastfm::Track::Player ); + + m_scrobbler->nowPlaying( m_track ); + emit info( caller, type, data, QVariant(), customData ); + emit finished( caller, type ); +} + +void +LastFmPlugin::scrobble( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ) +{ + Q_ASSERT( QThread::currentThread() == thread() ); + + if ( !m_scrobbler || m_track.isNull() ) + { + dataError( caller, type, data, customData ); + return; + } + + qDebug() << Q_FUNC_INFO << m_track.toString(); + m_scrobbler->cache( m_track ); + m_scrobbler->submit(); + + emit info( caller, type, data, QVariant(), customData ); + emit finished( caller, type ); +} + +void +LastFmPlugin::fetchCoverArt( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ) +{ + qDebug() << Q_FUNC_INFO; + if ( !data.canConvert< Tomahawk::InfoSystem::InfoCustomDataHash >() ) + { + dataError( caller, type, data, customData ); + return; + } + InfoCustomDataHash hash = data.value< Tomahawk::InfoSystem::InfoCustomDataHash >(); + if ( !hash.contains( "artist" ) || !hash.contains( "album" ) ) + { + dataError( caller, type, data, customData ); + return; + } + + QString artistName = hash["artist"].toString(); + QString albumName = hash["album"].toString(); + + QString imgurl = "http://ws.audioscrobbler.com/2.0/?method=album.imageredirect&artist=%1&album=%2&size=medium&api_key=7a90f6672a04b809ee309af169f34b8b"; + QNetworkRequest req( imgurl.arg( artistName ).arg( albumName ) ); + QNetworkReply* reply = TomahawkUtils::nam()->get( req ); + reply->setProperty("customData", QVariant::fromValue(customData)); + reply->setProperty("origData", data); + reply->setProperty("caller", caller); + reply->setProperty("type", (uint)(type) ); + + connect( reply, SIGNAL( finished() ), SLOT( coverArtReturned() ) ); +} + +void +LastFmPlugin::coverArtReturned() +{ + qDebug() << Q_FUNC_INFO; + QNetworkReply* reply = qobject_cast( sender() ); + QUrl redir = reply->attribute( QNetworkRequest::RedirectionTargetAttribute ).toUrl(); + if ( redir.isEmpty() ) + { + const QByteArray ba = reply->readAll(); + Tomahawk::InfoSystem::InfoCustomDataHash returnedData; + returnedData["imgbytes"] = ba; + returnedData["url"] = reply->url().toString(); + emit info( + reply->property( "caller" ).toString(), + (Tomahawk::InfoSystem::InfoType)(reply->property( "type" ).toUInt()), + reply->property( "origData" ), + returnedData, + reply->property( "customData" ).value< Tomahawk::InfoSystem::InfoCustomDataHash >() + ); + emit finished( reply->property( "caller" ).toString(), (Tomahawk::InfoSystem::InfoType)(reply->property( "type" ).toUInt()) ); + } + else + { + // Follow HTTP redirect + QNetworkRequest req( redir ); + QNetworkReply* newReply = TomahawkUtils::nam()->get( req ); + newReply->setProperty( "origData", reply->property( "origData" ) ); + newReply->setProperty( "customData", reply->property( "customData" ) ); + newReply->setProperty( "caller", reply->property( "caller" ) ); + newReply->setProperty( "type", reply->property( "type" ) ); + connect( newReply, SIGNAL( finished() ), SLOT( coverArtReturned() ) ); + } + + reply->deleteLater(); +} + +void +LastFmPlugin::settingsChanged() +{ + if( !m_scrobbler && TomahawkSettings::instance()->scrobblingEnabled() ) + { // can simply create the scrobbler + lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername(); + m_pw = TomahawkSettings::instance()->lastFmPassword(); + + createScrobbler(); + } + else if( m_scrobbler && !TomahawkSettings::instance()->scrobblingEnabled() ) + { + delete m_scrobbler; + m_scrobbler = 0; + } + else if( TomahawkSettings::instance()->lastFmUsername() != lastfm::ws::Username || + TomahawkSettings::instance()->lastFmPassword() != m_pw ) + { + lastfm::ws::Username = TomahawkSettings::instance()->lastFmUsername(); + // credentials have changed, have to re-create scrobbler for them to take effect + if( m_scrobbler ) + delete m_scrobbler; + + createScrobbler(); + } +} + + +void +LastFmPlugin::onAuthenticated() +{ + if( !m_authJob ) + { + qDebug() << Q_FUNC_INFO << "Help! No longer got a last.fm auth job!"; + return; + } + + if( m_authJob->error() == QNetworkReply::NoError ) + { + lastfm::XmlQuery lfm = lastfm::XmlQuery( m_authJob->readAll() ); + + if( lfm.children( "error" ).size() > 0 ) + { + qDebug() << "Error from authenticating with Last.fm service:" << lfm.text(); + TomahawkSettings::instance()->setLastFmSessionKey( QByteArray() ); + + } + else + { + lastfm::ws::SessionKey = lfm[ "session" ][ "key" ].text(); + + TomahawkSettings::instance()->setLastFmSessionKey( lastfm::ws::SessionKey.toLatin1() ); + + if( TomahawkSettings::instance()->scrobblingEnabled() ) + m_scrobbler = new lastfm::Audioscrobbler( "thk" ); + } + } + else + { + qDebug() << "Got error in Last.fm authentication job:" << m_authJob->errorString(); + } + + m_authJob->deleteLater(); +} + + +void +LastFmPlugin::createScrobbler() +{ + if( TomahawkSettings::instance()->lastFmSessionKey().isEmpty() ) // no session key, so get one + { + QString authToken = md5( ( lastfm::ws::Username.toLower() + md5( m_pw.toUtf8() ) ).toUtf8() ); + + QMap query; + query[ "method" ] = "auth.getMobileSession"; + query[ "username" ] = lastfm::ws::Username; + query[ "authToken" ] = authToken; + m_authJob = lastfm::ws::post( query ); + + connect( m_authJob, SIGNAL( finished() ), SLOT( onAuthenticated() ) ); + } + else + { + lastfm::ws::SessionKey = TomahawkSettings::instance()->lastFmSessionKey(); + + m_scrobbler = new lastfm::Audioscrobbler( "thk" ); + m_scrobbler->moveToThread( thread() ); + } +} diff --git a/src/infosystem/infoplugins/lastfmplugin.h b/src/infosystem/infoplugins/lastfmplugin.h new file mode 100644 index 000000000..3f396edfb --- /dev/null +++ b/src/infosystem/infoplugins/lastfmplugin.h @@ -0,0 +1,71 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef LASTFMPLUGIN_H +#define LASTFMPLUGIN_H +#include "tomahawk/infosystem.h" +#include "result.h" + +#include +#include +#include + +#include + +class QNetworkReply; + +namespace Tomahawk +{ + +namespace InfoSystem +{ + +class LastFmPlugin : public InfoPlugin +{ + Q_OBJECT + +public: + LastFmPlugin( QObject *parent ); + virtual ~LastFmPlugin(); + + void getInfo( const QString &caller, const InfoType type, const QVariant &data, InfoCustomDataHash customData ); + +public slots: + void settingsChanged(); + void onAuthenticated(); + void coverArtReturned(); + +private: + void fetchCoverArt( const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData ); + void scrobble( const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash &customData ); + void createScrobbler(); + void nowPlaying( const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash &customData ); + void dataError( const QString &caller, const InfoType type, const QVariant& data, InfoCustomDataHash &customData ); + + lastfm::MutableTrack m_track; + lastfm::Audioscrobbler* m_scrobbler; + QString m_pw; + + QNetworkReply* m_authJob; +}; + +} + +} + +#endif // LASTFMPLUGIN_H diff --git a/src/infosystem/infoplugins/musixmatchplugin.cpp b/src/infosystem/infoplugins/musixmatchplugin.cpp index 4e9429e27..0ee319f78 100644 --- a/src/infosystem/infoplugins/musixmatchplugin.cpp +++ b/src/infosystem/infoplugins/musixmatchplugin.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "tomahawk/infosystem.h" #include "tomahawk/tomahawkapp.h" #include "musixmatchplugin.h" @@ -27,11 +45,11 @@ MusixMatchPlugin::~MusixMatchPlugin() void MusixMatchPlugin::getInfo(const QString &caller, const InfoType type, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash customData) { qDebug() << Q_FUNC_INFO; - if( !isValidTrackData(caller, data, customData) || !data.canConvert()) + if( !isValidTrackData(caller, data, customData) || !data.canConvert()) return; - Tomahawk::InfoSystem::MusixMatchHash hash = data.value(); - QString artist = hash["artistName"]; - QString track = hash["trackName"]; + Tomahawk::InfoSystem::InfoCustomDataHash hash = data.value(); + QString artist = hash["artistName"].toString(); + QString track = hash["trackName"].toString(); if( artist.isEmpty() || track.isEmpty() ) { emit info(caller, Tomahawk::InfoSystem::InfoTrackLyrics, data, QVariant(), customData); @@ -44,7 +62,7 @@ void MusixMatchPlugin::getInfo(const QString &caller, const InfoType type, const url.addQueryItem("apikey", m_apiKey); url.addQueryItem("q_artist", artist); url.addQueryItem("q_track", track); - QNetworkReply* reply = TomahawkApp::instance()->nam()->get(QNetworkRequest(url)); + QNetworkReply* reply = TomahawkUtils::nam()->get(QNetworkRequest(url)); reply->setProperty("customData", QVariant::fromValue(customData)); reply->setProperty("origData", data); reply->setProperty("caller", caller); @@ -55,22 +73,22 @@ void MusixMatchPlugin::getInfo(const QString &caller, const InfoType type, const bool MusixMatchPlugin::isValidTrackData(const QString &caller, const QVariant& data, Tomahawk::InfoSystem::InfoCustomDataHash &customData) { qDebug() << Q_FUNC_INFO; - if (data.isNull() || !data.isValid() || !data.canConvert()) + if (data.isNull() || !data.isValid() || !data.canConvert()) { emit info(caller, Tomahawk::InfoSystem::InfoTrackLyrics, data, QVariant(), customData); emit finished(caller, Tomahawk::InfoSystem::InfoTrackLyrics); qDebug() << "MusixMatchPlugin::isValidTrackData: Data null, invalid, or can't convert"; return false; } - MusixMatchHash hash = data.value(); - if (hash["trackName"].isEmpty() ) + InfoCustomDataHash hash = data.value(); + if (hash["trackName"].toString().isEmpty() ) { emit info(caller, Tomahawk::InfoSystem::InfoTrackLyrics, data, QVariant(), customData); emit finished(caller, Tomahawk::InfoSystem::InfoTrackLyrics); qDebug() << "MusixMatchPlugin::isValidTrackData: Track name is empty"; return false; } - if (hash["artistName"].isEmpty() ) + if (hash["artistName"].toString().isEmpty() ) { emit info(caller, Tomahawk::InfoSystem::InfoTrackLyrics, data, QVariant(), customData); emit finished(caller, Tomahawk::InfoSystem::InfoTrackLyrics); @@ -104,7 +122,7 @@ void MusixMatchPlugin::trackSearchSlot() QUrl url(requestString); url.addQueryItem("apikey", m_apiKey); url.addQueryItem("track_id", track_id); - QNetworkReply* newReply = TomahawkApp::instance()->nam()->get(QNetworkRequest(url)); + QNetworkReply* newReply = TomahawkUtils::nam()->get(QNetworkRequest(url)); newReply->setProperty("origData", oldReply->property("origData")); newReply->setProperty("customData", oldReply->property("customData")); newReply->setProperty("caller", oldReply->property("caller")); @@ -133,4 +151,4 @@ void MusixMatchPlugin::trackLyricsSlot() qDebug() << "Emitting lyrics: " << lyrics; emit info(reply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics, reply->property("origData"), QVariant(lyrics), reply->property("customData").value()); emit finished(reply->property("caller").toString(), Tomahawk::InfoSystem::InfoTrackLyrics); -} \ No newline at end of file +} diff --git a/src/infosystem/infoplugins/musixmatchplugin.h b/src/infosystem/infoplugins/musixmatchplugin.h index 63301fa87..284c81515 100644 --- a/src/infosystem/infoplugins/musixmatchplugin.h +++ b/src/infosystem/infoplugins/musixmatchplugin.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef MUSIXMATCHPLUGIN_H #define MUSIXMATCHPLUGIN_H #include "tomahawk/infosystem.h" diff --git a/src/infosystem/infosystem.cpp b/src/infosystem/infosystem.cpp index 66b107d5e..e4a6a6c2a 100644 --- a/src/infosystem/infosystem.cpp +++ b/src/infosystem/infosystem.cpp @@ -1,20 +1,94 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include + #include "tomahawk/infosystem.h" +#include "tomahawkutils.h" +#include "infosystemcache.h" #include "infoplugins/echonestplugin.h" #include "infoplugins/musixmatchplugin.h" +#include "infoplugins/lastfmplugin.h" using namespace Tomahawk::InfoSystem; +InfoPlugin::InfoPlugin(QObject *parent) + :QObject( parent ) + { + qDebug() << Q_FUNC_INFO; + InfoSystem *system = qobject_cast< InfoSystem* >( parent ); + if( system ) + QObject::connect( system->getCache(), + SIGNAL( notInCache( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ), + this, + SLOT( notInCacheSlot( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ) + ); + } + + InfoSystem::InfoSystem(QObject *parent) - : QObject( parent ) + : QObject(parent) { qDebug() << Q_FUNC_INFO; qRegisterMetaType > >("Tomahawk::InfoSystem::InfoGenericMap"); qRegisterMetaType >("Tomahawk::InfoSystem::InfoCustomDataHash"); - qRegisterMetaType >("Tomahawk::InfoSystem::MusixMatchHash"); + + m_infoSystemCacheThreadController = new QThread( this ); + m_cache = new Tomahawk::InfoSystem::InfoSystemCache(); + m_cache->moveToThread( m_infoSystemCacheThreadController ); + m_infoSystemCacheThreadController->start( QThread::IdlePriority ); + InfoPluginPtr enptr(new EchoNestPlugin(this)); m_plugins.append(enptr); InfoPluginPtr mmptr(new MusixMatchPlugin(this)); m_plugins.append(mmptr); + InfoPluginPtr lfmptr(new LastFmPlugin(this)); + m_plugins.append(lfmptr); +} + +InfoSystem::~InfoSystem() +{ + qDebug() << Q_FUNC_INFO; + Q_FOREACH( InfoPluginPtr plugin, m_plugins ) + { + if( plugin ) + delete plugin.data(); + } + + if( m_infoSystemCacheThreadController ) + { + m_infoSystemCacheThreadController->quit(); + + while( !m_infoSystemCacheThreadController->isFinished() ) + { + QCoreApplication::processEvents( QEventLoop::AllEvents, 200 ); + TomahawkUtils::Sleep::msleep( 100 ); + } + + if( m_cache ) + { + delete m_cache; + m_cache = 0; + } + + delete m_infoSystemCacheThreadController; + m_infoSystemCacheThreadController = 0; + } } void InfoSystem::registerInfoTypes(const InfoPluginPtr &plugin, const QSet< InfoType >& types) @@ -41,6 +115,7 @@ void InfoSystem::getInfo(const QString &caller, const InfoType type, const QVari if (providers.isEmpty()) { emit info(QString(), Tomahawk::InfoSystem::InfoNoInfo, QVariant(), QVariant(), customData); + emit finished(caller); return; } @@ -48,6 +123,7 @@ void InfoSystem::getInfo(const QString &caller, const InfoType type, const QVari if (!ptr) { emit info(QString(), Tomahawk::InfoSystem::InfoNoInfo, QVariant(), QVariant(), customData); + emit finished(caller); return; } @@ -93,4 +169,4 @@ void InfoSystem::finishedSlot(QString target, Tomahawk::InfoSystem::InfoType typ } qDebug() << "emitting finished with target" << target; emit finished(target); -} \ No newline at end of file +} diff --git a/src/infosystem/infosystemcache.cpp b/src/infosystem/infosystemcache.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/infosystem/infosystemcache.h b/src/infosystem/infosystemcache.h new file mode 100644 index 000000000..97990b7a1 --- /dev/null +++ b/src/infosystem/infosystemcache.h @@ -0,0 +1,53 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TOMAHAWK_INFOSYSTEMCACHE_H +#define TOMAHAWK_INFOSYSTEMCACHE_H + +#include +#include + +namespace Tomahawk +{ + +namespace InfoSystem +{ + +class InfoSystemCache : public QObject +{ +Q_OBJECT + +public: + InfoSystemCache( QObject *parent = 0 ) + : QObject( parent ) + { + qDebug() << Q_FUNC_INFO; + } + + virtual ~InfoSystemCache() + { + qDebug() << Q_FUNC_INFO; + } + +}; + +} //namespace InfoSystem + +} //namespace Tomahawk + +#endif //TOMAHAWK_INFOSYSTEMCACHE_H \ No newline at end of file diff --git a/src/infowidgets/sourceinfowidget.cpp b/src/infowidgets/sourceinfowidget.cpp deleted file mode 100644 index 6e0ed4852..000000000 --- a/src/infowidgets/sourceinfowidget.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "sourceinfowidget.h" -#include "ui_sourceinfowidget.h" - -#include "tomahawk/tomahawkapp.h" -#include "utils/tomahawkutils.h" - -#include "playlist/playlistmanager.h" -#include "playlist/albummodel.h" -#include "playlist/collectionflatmodel.h" - -#include "database/databasecommand_alltracks.h" -#include "database/databasecommand_allalbums.h" - - -SourceInfoWidget::SourceInfoWidget( const Tomahawk::source_ptr& source, QWidget* parent ) - : QWidget( parent ) - , ui( new Ui::SourceInfoWidget ) -{ - ui->setupUi( this ); - - ui->sourceLabel->setText( source->friendlyName() ); - - m_recentCollectionModel = new CollectionFlatModel( ui->recentCollectionView ); - ui->recentCollectionView->setModel( m_recentCollectionModel ); - m_recentCollectionModel->addFilteredCollection( source->collection(), 250, DatabaseCommand_AllTracks::ModificationTime ); - -// ui->recentCollectionView->setColumnHidden( TrackModel::Bitrate, true ); -// ui->recentCollectionView->setColumnHidden( TrackModel::Origin, true ); - - m_recentAlbumModel = new AlbumModel( ui->recentAlbumView ); - ui->recentAlbumView->setModel( m_recentAlbumModel ); - m_recentAlbumModel->addFilteredCollection( source->collection(), 20, DatabaseCommand_AllAlbums::ModificationTime ); -} - - -SourceInfoWidget::~SourceInfoWidget() -{ - delete ui; -} - - -void -SourceInfoWidget::changeEvent( QEvent* e ) -{ - QWidget::changeEvent( e ); - switch ( e->type() ) - { - case QEvent::LanguageChange: - ui->retranslateUi( this ); - break; - - default: - break; - } -} diff --git a/src/infowidgets/sourceinfowidget.h b/src/infowidgets/sourceinfowidget.h deleted file mode 100644 index c16b55b37..000000000 --- a/src/infowidgets/sourceinfowidget.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef SOURCEINFOWIDGET_H -#define SOURCEINFOWIDGET_H - -#include - -#include "tomahawk/album.h" -#include "tomahawk/result.h" -#include "tomahawk/playlistinterface.h" - -class AlbumModel; -class CollectionFlatModel; - -namespace Ui -{ - class SourceInfoWidget; -} - -class SourceInfoWidget : public QWidget -{ -Q_OBJECT - -public: - SourceInfoWidget( const Tomahawk::source_ptr& source, QWidget* parent = 0 ); - ~SourceInfoWidget(); - -protected: - void changeEvent( QEvent* e ); - -private: - Ui::SourceInfoWidget *ui; - - CollectionFlatModel* m_recentCollectionModel; - AlbumModel* m_recentAlbumModel; -}; - -#endif // SOURCEINFOWIDGET_H diff --git a/src/jabber/jabber.h b/src/jabber/jabber.h deleted file mode 100644 index 1fa66e172..000000000 --- a/src/jabber/jabber.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef JABBER_H -#define JABBER_H -/* - Pimpl of jabber_p, which inherits from a gazillion gloox classes - and it littered with public methods. - */ -#include "jabber_p.h" - -class Jabber : public QObject -{ - Q_OBJECT -public: - - Jabber( const QString &jid, const QString password, const QString server = "", const int port=-1 ) - : p( jid, password, server, port ) - { - } - - ~Jabber() - { - // p.disconnect(); - } - - void setProxy( QNetworkProxy* proxy ) - { - p.setProxy( proxy ); - } - -public slots: - - void start() - { - //connect( &p, SIGNAL(finished()), - // this, SIGNAL(finished()) ); - - connect( &p, SIGNAL(msgReceived(QString,QString)), - this, SIGNAL(msgReceived(QString,QString)) ); - - connect( &p, SIGNAL(peerOnline(QString)), - this, SIGNAL(peerOnline(QString)) ); - - connect( &p, SIGNAL(peerOffline(QString)), - this, SIGNAL(peerOffline(QString)) ); - - connect( &p, SIGNAL(connected()), - this, SIGNAL(connected()) ); - - connect( &p, SIGNAL(disconnected()), - this, SIGNAL(disconnected()) ); - - connect( &p, SIGNAL(jidChanged(QString)), - this, SIGNAL(jidChanged(QString)) ); - - connect( &p, SIGNAL(authError(int,const QString&)), - this, SIGNAL(authError(int,const QString&)) ); - - p.go(); - } - - void disconnect() - { - QMetaObject::invokeMethod( &p, - "disconnect", - Qt::QueuedConnection - ); - } - - void sendMsg(const QString& to, const QString& msg) - { - QMetaObject::invokeMethod( &p, - "sendMsg", - Qt::QueuedConnection, - Q_ARG(const QString, to), - Q_ARG(const QString, msg) - ); - } - - void broadcastMsg(const QString &msg) - { - QMetaObject::invokeMethod( &p, - "broadcastMsg", - Qt::QueuedConnection, - Q_ARG(const QString, msg) - ); - } - -signals: - //void finished(); - - void msgReceived(const QString&, const QString&); //from, msg - void peerOnline(const QString&); - void peerOffline(const QString&); - void connected(); - void disconnected(); - void jidChanged(const QString&); - void authError(int, const QString&); - -private: - Jabber_p p; -}; - -#endif diff --git a/src/junk/remoteioconnection.cpp b/src/junk/remoteioconnection.cpp deleted file mode 100644 index 9bc7cc655..000000000 --- a/src/junk/remoteioconnection.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "remoteioconnection.h" -#include - -RemoteIOConnection::RemoteIOConnection(Servent * s, FileTransferSession * fts) - : Connection(s), m_fts(fts) -{ - qDebug() << "CTOR " << id() ; -} - -RemoteIOConnection::~RemoteIOConnection() -{ - qDebug() << "DTOR " << id() ; -} - -QString RemoteIOConnection::id() const -{ - return QString("RemoteIOConnection[%1]").arg(m_fts->fid()); -} - -void RemoteIOConnection::shutdown(bool wait) -{ - Connection::shutdown(wait); - /*if(!wait) - { - Connection::shutdown(wait); - return; - } - qDebug() << id() << " shutdown requested - waiting until we've received all data TODO"; - */ -} - -void RemoteIOConnection::setup() -{ - if(m_fts->type() == FileTransferSession::RECEIVING) - { - qDebug() << "RemoteIOConnection in RX mode"; - return; - } - - qDebug() << "RemoteIOConnection in TX mode, fid:" << m_fts->fid(); - - QString url("/tmp/test.mp3"); - qDebug() << "TODO map fid to file://, hardcoded for now"; - - qDebug() << "Opening for transmission:" << url; - m_readdev = QSharedPointer(new QFile(url)); - m_readdev->open(QIODevice::ReadOnly); - if(!m_readdev->isOpen()) - { - qDebug() << "WARNING file is not readable: " << url; - shutdown(); - } - // send chunks within our event loop, since we're not in our own thread - sendSome(); -} - -void RemoteIOConnection::handleMsg(QByteArray msg) -{ - Q_ASSERT(m_fts->type() == FileTransferSession::RECEIVING); - m_fts->iodevice()->addData(msg); - if(msg.length()==0) qDebug() << "Got 0len msg. end?"; -} - - -Connection * RemoteIOConnection::clone() -{ - Q_ASSERT(false); return 0; -}; - - -void RemoteIOConnection::sendSome() -{ - Q_ASSERT(m_fts->type() == FileTransferSession::SENDING); - if(m_readdev->atEnd()) - { - qDebug() << "Sent all. DONE"; - shutdown(true); - return; - } - QByteArray ba = m_readdev->read(4096); - //qDebug() << "Sending " << ba.length() << " bytes of audiofile"; - sendMsg(ba); - QTimer::singleShot(0, this, SLOT(sendSome())); -} - - diff --git a/src/junk/remoteioconnection.h b/src/junk/remoteioconnection.h deleted file mode 100644 index 5874f8a4f..000000000 --- a/src/junk/remoteioconnection.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef REMOTEIOCONNECTION_H -#define REMOTEIOCONNECTION_H - -#include -#include -#include -#include -#include - -#include "controlconnection.h" -#include "filetransfersession.h" - -class RemoteIOConnection : public Connection -{ - Q_OBJECT -public: - RemoteIOConnection(Servent * s, FileTransferSession * fts); - ~RemoteIOConnection(); - QString id() const; - - - void shutdown(bool wait = false); - void setup(); - void handleMsg(QByteArray msg); - Connection * clone(); - -signals: - -private slots: - void sendSome(); - -private: - - FileTransferSession * m_fts; - QSharedPointer m_readdev; -}; - -#endif // REMOTEIOCONNECTION_H diff --git a/src/junk/remoteiodevice.cpp b/src/junk/remoteiodevice.cpp deleted file mode 100644 index 92cefbfe9..000000000 --- a/src/junk/remoteiodevice.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include "remoteiodevice.h" - -RemoteIODevice::RemoteIODevice(RemoteIOConnection * c) - : m_eof(false), m_totalAdded(0), m_rioconn(c) -{ - qDebug() << "CTOR RemoteIODevice"; -} - -RemoteIODevice::~RemoteIODevice() -{ - qDebug() << "DTOR RemoteIODevice"; - m_rioconn->shutdown(); -} - -void RemoteIODevice::close() -{ - qDebug() << "RemoteIODevice::close"; - QIODevice::close(); - deleteLater(); -} - -bool RemoteIODevice::open ( OpenMode mode ) -{ - return QIODevice::open(mode & QIODevice::ReadOnly); -} - -qint64 RemoteIODevice::bytesAvailable () const -{ - return m_buffer.length(); -} - -bool RemoteIODevice::isSequential () const -{ - return true; -}; - -bool RemoteIODevice::atEnd() const -{ - return m_eof && m_buffer.length() == 0; -}; - -void RemoteIODevice::addData(QByteArray msg) -{ - m_mut_recv.lock(); - if(msg.length()==0) - { - m_eof=true; - //qDebug() << "addData finished, entire file received. EOF."; - m_mut_recv.unlock(); - m_wait.wakeAll(); - return; - } - else - { - m_buffer.append(msg); - m_totalAdded += msg.length(); - //qDebug() << "RemoteIODevice has seen in total: " << m_totalAdded ; - m_mut_recv.unlock(); - m_wait.wakeAll(); - emit readyRead(); - return; - } -} - -qint64 RemoteIODevice::writeData ( const char * data, qint64 maxSize ) -{ - Q_ASSERT(false); - return 0; -} - -qint64 RemoteIODevice::readData ( char * data, qint64 maxSize ) -{ - //qDebug() << "RemIO::readData, bytes in buffer: " << m_buffer.length(); - m_mut_recv.lock(); - if(m_eof && m_buffer.length() == 0) - { - // eof - qDebug() << "readData called when EOF"; - m_mut_recv.unlock(); - return 0; - } - if(!m_buffer.length())// return 0; - { - //qDebug() << "WARNING readData when buffer is empty"; - m_mut_recv.unlock(); - return 0; - } - int len; - if(maxSize>=m_buffer.length()) // whole buffer - { - len = m_buffer.length(); - memcpy(data, m_buffer.constData(), len); - m_buffer.clear(); - } else { // partial - len = maxSize; - memcpy(data, m_buffer.constData(), len); - m_buffer.remove(0,len); - } - m_mut_recv.unlock(); - return len; -} diff --git a/src/junk/remoteiodevice.h b/src/junk/remoteiodevice.h deleted file mode 100644 index e8ee34fd4..000000000 --- a/src/junk/remoteiodevice.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef REMOTEIODEVICE_H -#define REMOTEIODEVICE_H -#include -#include -#include -#include -#include -#include "remoteioconnection.h" - -class RemoteIOConnection; - -class RemoteIODevice : public QIODevice -{ - Q_OBJECT -public: - - RemoteIODevice(RemoteIOConnection * c); - ~RemoteIODevice(); - virtual void close(); - virtual bool open ( OpenMode mode ); - qint64 bytesAvailable () const; - virtual bool isSequential () const; - virtual bool atEnd() const; - -public slots: - - void addData(QByteArray msg); - -protected: - - virtual qint64 writeData ( const char * data, qint64 maxSize ); - virtual qint64 readData ( char * data, qint64 maxSize ); - -private: - QByteArray m_buffer; - QMutex m_mut_wait, m_mut_recv; - QWaitCondition m_wait; - bool m_eof; - int m_totalAdded; - - RemoteIOConnection * m_rioconn; -}; -#endif // REMOTEIODEVICE_H diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt new file mode 100644 index 000000000..f45c391a3 --- /dev/null +++ b/src/libtomahawk/CMakeLists.txt @@ -0,0 +1,406 @@ +project( tomahawklib ) + +SET( QT_USE_QTGUI TRUE ) +SET( QT_USE_QTSQL TRUE ) +SET( QT_USE_QTNETWORK TRUE ) +SET( QT_USE_QTXML TRUE ) + +include( ${QT_USE_FILE} ) + +add_definitions( ${QT_DEFINITIONS} ) +add_definitions( -DQT_SHARED ) +add_definitions( -DDLLEXPORT_PRO ) + +set( libSources + tomahawksettings.cpp + sourcelist.cpp + pipeline.cpp + + artist.cpp + album.cpp + collection.cpp + playlist.cpp + query.cpp + result.cpp + source.cpp + viewpage.cpp + + sip/SipPlugin.cpp + + audio/madtranscode.cpp + audio/vorbistranscode.cpp + audio/flactranscode.cpp + audio/audioengine.cpp + + database/database.cpp + database/fuzzyindex.cpp + database/databasecollection.cpp + database/databaseworker.cpp + database/databaseimpl.cpp + database/databaseresolver.cpp + database/databasecommand.cpp + database/databasecommandloggable.cpp + database/databasecommand_resolve.cpp + database/databasecommand_allalbums.cpp + database/databasecommand_alltracks.cpp + database/databasecommand_addfiles.cpp + database/databasecommand_deletefiles.cpp + database/databasecommand_dirmtimes.cpp + database/databasecommand_loadfile.cpp + database/databasecommand_logplayback.cpp + database/databasecommand_addsource.cpp + database/databasecommand_sourceoffline.cpp + database/databasecommand_collectionstats.cpp + database/databasecommand_loadplaylistentries.cpp + database/databasecommand_modifyplaylist.cpp + database/databasecommand_playbackhistory.cpp + database/databasecommand_setplaylistrevision.cpp + database/databasecommand_loadallplaylists.cpp + database/databasecommand_loadallsources.cpp + database/databasecommand_createplaylist.cpp + database/databasecommand_deleteplaylist.cpp + database/databasecommand_renameplaylist.cpp + database/databasecommand_loadops.cpp + database/databasecommand_updatesearchindex.cpp + database/databasecollection.cpp + database/databasecommand_setdynamicplaylistrevision.cpp + database/databasecommand_createdynamicplaylist.cpp + database/databasecommand_loaddynamicplaylist.cpp + database/databasecommand_loadalldynamicplaylists.cpp + database/databasecommand_deletedynamicplaylist.cpp + database/databasecommand_addclientauth.cpp + database/databasecommand_clientauthvalid.cpp + database/database.cpp + + playlist/collectionmodel.cpp + playlist/collectionproxymodel.cpp + playlist/collectionflatmodel.cpp + playlist/collectionview.cpp + playlist/playlistmanager.cpp + playlist/plitem.cpp + playlist/playlistmodel.cpp + playlist/playlistproxymodel.cpp + playlist/playlistview.cpp + playlist/playlistitemdelegate.cpp + playlist/queueproxymodel.cpp + playlist/queueview.cpp + playlist/trackmodel.cpp + playlist/trackproxymodel.cpp + playlist/trackview.cpp + playlist/trackheader.cpp + playlist/albumitem.cpp + playlist/albummodel.cpp + playlist/albumproxymodel.cpp + playlist/albumitemdelegate.cpp + playlist/albumview.cpp + + playlist/topbar/topbar.cpp + playlist/topbar/clearbutton.cpp + playlist/topbar/searchlineedit.cpp + playlist/topbar/lineedit.cpp + playlist/topbar/searchbutton.cpp + + playlist/infobar/infobar.cpp + + playlist/dynamic/DynamicPlaylist.cpp + playlist/dynamic/DynamicControl.cpp + playlist/dynamic/GeneratorFactory.cpp + playlist/dynamic/GeneratorInterface.cpp + playlist/dynamic/DynamicView.cpp + playlist/dynamic/DynamicModel.cpp + playlist/dynamic/echonest/EchonestGenerator.cpp + playlist/dynamic/echonest/EchonestControl.cpp + playlist/dynamic/echonest/EchonestSteerer.cpp + playlist/dynamic/widgets/DynamicWidget.cpp + playlist/dynamic/widgets/DynamicControlWrapper.cpp + playlist/dynamic/widgets/DynamicControlList.cpp + playlist/dynamic/widgets/ReadOrWriteWidget.cpp + playlist/dynamic/widgets/MiscControlWidgets.cpp + playlist/dynamic/widgets/CollapsibleControls.cpp + playlist/dynamic/widgets/DynamicSetupWidget.cpp + playlist/dynamic/widgets/LoadingSpinner.cpp + + network/bufferiodevice.cpp + network/msgprocessor.cpp + network/filetransferconnection.cpp + network/dbsyncconnection.cpp + network/remotecollection.cpp + network/portfwdthread.cpp + network/servent.cpp + network/connection.cpp + network/controlconnection.cpp + + utils/tomahawkutils.cpp + utils/querylabel.cpp + utils/elidedlabel.cpp + utils/imagebutton.cpp + utils/progresstreeview.cpp + utils/proxystyle.cpp + utils/widgetdragfilter.cpp + utils/animatedsplitter.cpp + utils/xspfloader.cpp + + widgets/newplaylistwidget.cpp + widgets/welcomewidget.cpp + widgets/overlaywidget.cpp + widgets/infowidgets/sourceinfowidget.cpp + + qtsingleapp/qtlocalpeer.cpp + qtsingleapp/qtsingleapplication.cpp +) + +set( libHeaders + tomahawksettings.h + sourcelist.h + pipeline.h + functimeout.h + + collection.h + query.h + resolver.h + result.h + source.h + viewpage.h + + artist.h + album.h + track.h + playlist.h + + sip/SipPlugin.h + + audio/transcodeinterface.h + audio/madtranscode.h + audio/vorbistranscode.h + audio/flactranscode.h + audio/audioengine.h + + database/database.h + database/fuzzyindex.h + database/databaseworker.h + database/databaseimpl.h + database/databaseresolver.h + database/databasecommand.h + database/databasecommandloggable.h + database/databasecommand_resolve.h + database/databasecommand_allalbums.h + database/databasecommand_alltracks.h + database/databasecommand_addfiles.h + database/databasecommand_deletefiles.h + database/databasecommand_dirmtimes.h + database/databasecommand_loadfile.h + database/databasecommand_logplayback.h + database/databasecommand_addsource.h + database/databasecommand_sourceoffline.h + database/databasecommand_collectionstats.h + database/databasecommand_loadplaylistentries.h + database/databasecommand_modifyplaylist.h + database/databasecommand_playbackhistory.h + database/databasecommand_setplaylistrevision.h + database/databasecommand_loadallplaylists.h + database/databasecommand_loadallsources.h + database/databasecommand_createplaylist.h + database/databasecommand_deleteplaylist.h + database/databasecommand_renameplaylist.h + database/databasecommand_loadops.h + database/databasecommand_updatesearchindex.h + database/databasecollection.h + database/databasecommand_setdynamicplaylistrevision.h + database/databasecommand_createdynamicplaylist.h + database/databasecommand_loaddynamicplaylist.h + database/databasecommand_deletedynamicplaylist.h + database/databasecommand_loadalldynamicplaylists.h + database/databasecommand_addclientauth.h + database/databasecommand_clientauthvalid.h + + network/bufferiodevice.h + network/msgprocessor.h + network/remotecollection.h + network/filetransferconnection.h + network/dbsyncconnection.h + network/servent.h + network/connection.h + network/controlconnection.h + network/portfwdthread.h + + playlist/collectionmodel.h + playlist/collectionproxymodel.h + playlist/collectionflatmodel.h + playlist/collectionview.h + playlist/playlistmanager.h + playlist/plitem.h + playlist/playlistmodel.h + playlist/playlistproxymodel.h + playlist/playlistview.h + playlist/playlistitemdelegate.h + playlist/queueproxymodel.h + playlist/queueview.h + playlist/trackmodel.h + playlist/trackproxymodel.h + playlist/trackview.h + playlist/trackheader.h + playlist/albumitem.h + playlist/albummodel.h + playlist/albumproxymodel.h + playlist/albumitemdelegate.h + playlist/albumview.h + + playlist/topbar/topbar.h + playlist/topbar/clearbutton.h + playlist/topbar/searchlineedit.h + playlist/topbar/lineedit.h + playlist/topbar/lineedit_p.h + playlist/topbar/searchbutton.h + + playlist/infobar/infobar.h + + playlist/dynamic/DynamicPlaylist.h + playlist/dynamic/DynamicControl.h + playlist/dynamic/GeneratorFactory.h + playlist/dynamic/GeneratorInterface.h + playlist/dynamic/DynamicView.h + playlist/dynamic/DynamicModel.h + playlist/dynamic/echonest/EchonestGenerator.h + playlist/dynamic/echonest/EchonestControl.h + playlist/dynamic/echonest/EchonestSteerer.h + playlist/dynamic/widgets/DynamicWidget.h + playlist/dynamic/widgets/DynamicControlWrapper.h + playlist/dynamic/widgets/DynamicControlList.h + playlist/dynamic/widgets/ReadOrWriteWidget.h + playlist/dynamic/widgets/MiscControlWidgets.h + playlist/dynamic/widgets/CollapsibleControls.h + playlist/dynamic/widgets/DynamicSetupWidget.h + playlist/dynamic/widgets/LoadingSpinner.h + + utils/tomahawkutils.h + utils/querylabel.h + utils/elidedlabel.h + utils/animatedcounterlabel.h + utils/imagebutton.h + utils/progresstreeview.h + utils/widgetdragfilter.h + utils/animatedsplitter.h + utils/xspfloader.h + + widgets/newplaylistwidget.h + widgets/welcomewidget.h + widgets/overlaywidget.h + widgets/infowidgets/sourceinfowidget.h + + qtsingleapp/qtlocalpeer.h + qtsingleapp/qtsingleapplication.h +) + +set( libHeaders_NoMOC + playlist/dynamic/GeneratorInterface.h +) +set( libUI ${libUI} + widgets/newplaylistwidget.ui + widgets/welcomewidget.ui + widgets/infowidgets/sourceinfowidget.ui + playlist/topbar/topbar.ui + playlist/infobar/infobar.ui +) + +include_directories( . ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/.. .. + ${CMAKE_CURRENT_SOURCE_DIR} + ${QT_INCLUDE_DIR} + ${QJSON_INCLUDE_DIR} + ${LIBECHONEST_INCLUDE_DIR} + ${LIBECHONEST_INCLUDE_DIR}/.. + ${CLUCENE_INCLUDE_DIR} + ${CLUCENE_LIBRARY_DIR} + + ../../include + ../network + playlist + + ${THIRDPARTY_DIR}/libportfwd/include + ${THIRDPARTY_DIR}/qxt/qxtweb-standalone/qxtweb + ${THIRDPARTY_DIR}/rtaudio + ${THIRDPARTY_DIR}/alsa-playback + ${THIRDPARTY_DIR}/jdns + ${THIRDPARTY_DIR}/jdns/jdns + ${THIRDPARTY_DIR}/jdns/jdnsshared + ${THIRDPARTY_DIR}/qtweetlib/qtweetlib/src +) + + +IF( WIN32 ) + SET( libSources ${libSources} audio/rtaudiooutput.cpp ) + SET( libHeaders ${libHeaders} audio/rtaudiooutput.h ) + + SET( OS_SPECIFIC_LINK_LIBRARIES + ${OS_SPECIFIC_LINK_LIBRARIES} + # Thirdparty + ${CMAKE_BINARY_DIR}/thirdparty/rtaudio/librtaudio.dll + ${QJSON_LDFLAGS} + # System + "iphlpapi.a" + "ws2_32.dll" + "dnsapi.dll" + "dsound.dll" + "winmm.dll" + "advapi32.dll" + ) +ENDIF( WIN32 ) + +IF( APPLE ) + FIND_LIBRARY( COREAUDIO_LIBRARY CoreAudio ) + FIND_LIBRARY( COREFOUNDATION_LIBRARY CoreFoundation ) + MARK_AS_ADVANCED( COREAUDIO_LIBRARY COREFOUNDATION_LIBRARY ) + + SET( libSources ${libSources} audio/rtaudiooutput.cpp ) + SET( libHeaders ${libHeaders} audio/rtaudiooutput.h ) + + SET( OS_SPECIFIC_LINK_LIBRARIES + ${OS_SPECIFIC_LINK_LIBRARIES} + # Thirdparty + ${QJSON_LIBRARIES} + rtaudio + # System + ${COREAUDIO_LIBRARY} + ${COREFOUNDATION_LIBRARY} + ) +ENDIF( APPLE ) + +IF( UNIX AND NOT APPLE ) + SET( OS_SPECIFIC_LINK_LIBRARIES + ${OS_SPECIFIC_LINK_LIBRARIES} + # Thirdparty + alsaplayback + ${QJSON_LDFLAGS} + ) +ENDIF( UNIX AND NOT APPLE ) + +qt4_wrap_ui( libUI_H ${libUI} ) +qt4_wrap_cpp( libMoc ${libHeaders} ) + +SET( libSources ${libSources} ${libUI_H} ${libHeaders_NoMOC} ) + +add_library( tomahawklib SHARED ${libSources} ${libMoc} ) + +target_link_libraries( tomahawklib + # Internal + tomahawk_jdns + tomahawk_qtweetlib + + # Thirdparty shipped with tomahawk + portfwd + + # soon to be removed by phonon-dependency + FLAC++ + ogg + vorbisfile + mad + + # External deps + ${TAGLIB_LIBRARIES} + ${CLUCENE_LIBRARIES} + ${LIBECHONEST_LIBRARY} + ${QT_LIBRARIES} + ${OS_SPECIFIC_LINK_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} +) + +INSTALL( TARGETS tomahawklib DESTINATION lib${LIB_SUFFIX} ) diff --git a/src/libtomahawk/album.cpp b/src/libtomahawk/album.cpp new file mode 100644 index 000000000..5fede95c5 --- /dev/null +++ b/src/libtomahawk/album.cpp @@ -0,0 +1,103 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "album.h" + +#include + +#include "collection.h" +#include "database/database.h" +#include "database/databasecommand_alltracks.h" + +using namespace Tomahawk; + + +album_ptr +Album::get( unsigned int id, const QString& name, const Tomahawk::artist_ptr& artist ) +{ + static QHash< unsigned int, album_ptr > s_albums; + static QMutex s_mutex; + + QMutexLocker lock( &s_mutex ); + if ( s_albums.contains( id ) ) + { + return s_albums.value( id ); + } + + album_ptr a = album_ptr( new Album( id, name, artist ) ); + if ( id > 0 ) + s_albums.insert( id, a ); + + return a; +} + + +Album::Album( unsigned int id, const QString& name, const Tomahawk::artist_ptr& artist ) + : PlaylistInterface( this ) + , m_id( id ) + , m_name( name ) + , m_artist( artist ) + , m_currentTrack( 0 ) +{ +} + + +void +Album::onTracksAdded( const QList& tracks ) +{ + qDebug() << Q_FUNC_INFO; + + m_queries << tracks; + emit tracksAdded( tracks ); +} + + +Tomahawk::result_ptr +Album::siblingItem( int itemsAway ) +{ + int p = m_currentTrack; + p += itemsAway; + + if ( p < 0 ) + return Tomahawk::result_ptr(); + + if ( p >= m_queries.count() ) + return Tomahawk::result_ptr(); + + m_currentTrack = p; + return m_queries.at( p )->results().first(); +} + + +QList +Album::tracks() +{ + if ( m_queries.isEmpty() ) + { + DatabaseCommand_AllTracks* cmd = new DatabaseCommand_AllTracks(); + cmd->setAlbum( this ); + cmd->setSortOrder( DatabaseCommand_AllTracks::AlbumPosition ); + + connect( cmd, SIGNAL( tracks( QList ) ), + SLOT( onTracksAdded( QList ) ) ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); + } + + return m_queries; +} diff --git a/src/libtomahawk/album.h b/src/libtomahawk/album.h new file mode 100644 index 000000000..df8009b27 --- /dev/null +++ b/src/libtomahawk/album.h @@ -0,0 +1,86 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TOMAHAWKALBUM_H +#define TOMAHAWKALBUM_H + +#include +#include + +#include "typedefs.h" +#include "artist.h" +#include "collection.h" +#include "playlistinterface.h" + +#include "dllmacro.h" + +namespace Tomahawk +{ + +class DLLEXPORT Album : public QObject, public PlaylistInterface +{ +Q_OBJECT + +public: + static album_ptr get( unsigned int id, const QString& name, const Tomahawk::artist_ptr& artist ); + + Album( unsigned int id, const QString& name, const Tomahawk::artist_ptr& artist ); + + unsigned int id() const { return m_id; } + QString name() const { return m_name; } + artist_ptr artist() const { return m_artist; } + + QList tracks(); + + virtual int trackCount() const { return m_queries.count(); } + virtual int unfilteredTrackCount() const { return m_queries.count(); } + + virtual Tomahawk::result_ptr siblingItem( int itemsAway ); + + virtual PlaylistInterface::RepeatMode repeatMode() const { return PlaylistInterface::NoRepeat; } + virtual bool shuffled() const { return false; } + + virtual void setRepeatMode( PlaylistInterface::RepeatMode ) {} + virtual void setShuffled( bool ) {} + + virtual void setFilter( const QString& pattern ) {} + +signals: + void repeatModeChanged( PlaylistInterface::RepeatMode mode ); + void shuffleModeChanged( bool enabled ); + + void tracksAdded( const QList& tracks ); + void trackCountChanged( unsigned int tracks ); + void sourceTrackCountChanged( unsigned int tracks ); + +private slots: + void onTracksAdded( const QList& tracks ); + +private: + unsigned int m_id; + QString m_name; + + artist_ptr m_artist; + QList m_queries; + + unsigned int m_currentTrack; +}; + +}; // ns + +#endif diff --git a/src/libtomahawk/artist.cpp b/src/libtomahawk/artist.cpp new file mode 100644 index 000000000..bc130e55f --- /dev/null +++ b/src/libtomahawk/artist.cpp @@ -0,0 +1,105 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "artist.h" + +#include + +#include "collection.h" +#include "database/database.h" +#include "database/databasecommand_alltracks.h" + +using namespace Tomahawk; + +Artist::Artist() {} + +Artist::~Artist() {} + +artist_ptr +Artist::get( unsigned int id, const QString& name ) +{ + static QHash< unsigned int, artist_ptr > s_artists; + static QMutex s_mutex; + + QMutexLocker lock( &s_mutex ); + if ( s_artists.contains( id ) ) + { + return s_artists.value( id ); + } + + artist_ptr a = artist_ptr( new Artist( id, name ) ); + if ( id > 0 ) + s_artists.insert( id, a ); + + return a; +} + + +Artist::Artist( unsigned int id, const QString& name ) + : PlaylistInterface( this ) + , m_id( id ) + , m_name( name ) + , m_currentTrack( 0 ) +{ +} + + +void +Artist::onTracksAdded( const QList& tracks ) +{ + qDebug() << Q_FUNC_INFO; + + m_queries << tracks; + emit tracksAdded( tracks ); +} + + +Tomahawk::result_ptr +Artist::siblingItem( int itemsAway ) +{ + int p = m_currentTrack; + p += itemsAway; + + if ( p < 0 ) + return Tomahawk::result_ptr(); + + if ( p >= m_queries.count() ) + return Tomahawk::result_ptr(); + + m_currentTrack = p; + return m_queries.at( p )->results().first(); +} + + +QList +Artist::tracks() +{ + if ( m_queries.isEmpty() ) + { + DatabaseCommand_AllTracks* cmd = new DatabaseCommand_AllTracks(); + cmd->setArtist( this ); + cmd->setSortOrder( DatabaseCommand_AllTracks::Album ); + + connect( cmd, SIGNAL( tracks( QList ) ), + SLOT( onTracksAdded( QList ) ) ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); + } + + return m_queries; +} diff --git a/src/libtomahawk/artist.h b/src/libtomahawk/artist.h new file mode 100644 index 000000000..d7f5ac7ef --- /dev/null +++ b/src/libtomahawk/artist.h @@ -0,0 +1,84 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TOMAHAWKARTIST_H +#define TOMAHAWKARTIST_H + +#include +#include + +#include "typedefs.h" + +#include "playlistinterface.h" + +#include "dllmacro.h" + +namespace Tomahawk +{ + +class DLLEXPORT Artist : public QObject, public PlaylistInterface +{ +Q_OBJECT + +public: + static artist_ptr get( unsigned int id, const QString& name ); + Artist( unsigned int id, const QString& name ); + + Artist(); + virtual ~Artist(); + + unsigned int id() const { return m_id; } + QString name() const { return m_name; } + + virtual QList tracks(); + + virtual int trackCount() const { return 0; } + virtual int unfilteredTrackCount() const { return m_queries.count(); } + + virtual Tomahawk::result_ptr siblingItem( int itemsAway ); + + virtual PlaylistInterface::RepeatMode repeatMode() const { return PlaylistInterface::NoRepeat; } + virtual bool shuffled() const { return false; } + + virtual void setRepeatMode( PlaylistInterface::RepeatMode ) {} + virtual void setShuffled( bool ) {} + + virtual void setFilter( const QString& pattern ) {} + +signals: + void repeatModeChanged( PlaylistInterface::RepeatMode mode ); + void shuffleModeChanged( bool enabled ); + + void tracksAdded( const QList& tracks ); + void trackCountChanged( unsigned int tracks ); + void sourceTrackCountChanged( unsigned int tracks ); + +private slots: + void onTracksAdded( const QList& tracks ); + +private: + unsigned int m_id; + QString m_name; + + QList m_queries; + unsigned int m_currentTrack; +}; + +}; // ns + +#endif diff --git a/src/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp similarity index 69% rename from src/audio/audioengine.cpp rename to src/libtomahawk/audio/audioengine.cpp index a95cf795f..07c5c3cc1 100644 --- a/src/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -1,22 +1,59 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "audioengine.h" #include #include -#include -#include "tomahawk/playlistinterface.h" +#include "playlistinterface.h" + +#include "database/database.h" +#include "database/databasecommand_logplayback.h" +#include "network/servent.h" #include "madtranscode.h" #ifndef NO_OGG #include "vorbistranscode.h" #endif +#ifndef NO_FLAC +#include "flactranscode.h" +#endif + +AudioEngine* AudioEngine::s_instance = 0; + + +AudioEngine* +AudioEngine::instance() +{ + return s_instance; +} AudioEngine::AudioEngine() : QThread() , m_playlist( 0 ) + , m_currentTrackPlaylist( 0 ) + , m_queue( 0 ) + , m_timeElapsed( 0 ) , m_i( 0 ) { + s_instance = this; qDebug() << "Init AudioEngine"; moveToThread( this ); @@ -35,16 +72,26 @@ AudioEngine::AudioEngine() AudioEngine::~AudioEngine() { - qDebug() << Q_FUNC_INFO << "waiting for event loop to finish.."; + qDebug() << Q_FUNC_INFO << "waiting for event loop to finish..."; quit(); wait( 1000 ); - qDebug() << Q_FUNC_INFO << "ok"; m_input.clear(); delete m_audio; } +void +AudioEngine::playPause() +{ + if( m_audio->isPlaying() ) + pause(); + else + play(); + +} + + void AudioEngine::play() { @@ -89,6 +136,7 @@ AudioEngine::stop() m_audio->stopPlayback(); + setCurrentTrack( Tomahawk::result_ptr() ); emit stopped(); } @@ -97,6 +145,7 @@ void AudioEngine::previous() { qDebug() << Q_FUNC_INFO; + clearBuffers(); loadPreviousTrack(); } @@ -105,6 +154,7 @@ void AudioEngine::next() { qDebug() << Q_FUNC_INFO; + clearBuffers(); loadNextTrack(); } @@ -121,6 +171,13 @@ AudioEngine::setVolume( int percentage ) } +void +AudioEngine::mute() +{ + setVolume( 0 ); +} + + void AudioEngine::onTrackAboutToClose() { @@ -147,9 +204,15 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result ) err = true; else { - m_lastTrack = m_currentTrack; - m_currentTrack = result; - io = TomahawkApp::instance()->getIODeviceForUrl( m_currentTrack ); + setCurrentTrack( result ); + io = Servent::instance()->getIODeviceForUrl( m_currentTrack ); + if ( m_currentTrack->url().startsWith( "http://" ) ) + { + m_readReady = false; + connect( io.data(), SIGNAL( downloadProgress( qint64, qint64 ) ), SLOT( onDownloadProgress( qint64, qint64 ) ) ); + } + else + m_readReady = true; if ( !io || io.isNull() ) { @@ -187,6 +250,12 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result ) { m_transcode = QSharedPointer(new VorbisTranscode()); } +#endif +#ifndef NO_FLAC + else if ( m_currentTrack->mimetype() == "audio/flac" ) + { + m_transcode = QSharedPointer(new FLACTranscode()); + } #endif else qDebug() << "Could NOT find suitable transcoder! Stopping audio."; @@ -247,13 +316,18 @@ AudioEngine::loadNextTrack() { qDebug() << Q_FUNC_INFO; - if ( !m_playlist ) + Tomahawk::result_ptr result; + + if ( m_queue && m_queue->trackCount() ) { - stop(); - return; + result = m_queue->nextItem(); + } + + if ( m_playlist && result.isNull() ) + { + result = m_playlist->nextItem(); } - Tomahawk::result_ptr result = m_playlist->nextItem(); if ( !result.isNull() ) loadTrack( result ); else @@ -266,8 +340,11 @@ AudioEngine::playItem( PlaylistInterface* playlist, const Tomahawk::result_ptr& { qDebug() << Q_FUNC_INFO; - m_playlist = playlist; - m_currentPlaylist = playlist; + clearBuffers(); + + setPlaylist( playlist ); + m_currentTrackPlaylist = playlist; + loadTrack( result ); } @@ -276,10 +353,17 @@ void AudioEngine::setStreamData( long sampleRate, int channels ) { qDebug() << Q_FUNC_INFO << sampleRate << channels << thread(); + + if ( sampleRate < 44100 ) + sampleRate = 44100; + m_audio->initAudio( sampleRate, channels ); if ( m_audio->startPlayback() ) { emit started( m_currentTrack ); + + DatabaseCommand_LogPlayback* cmd = new DatabaseCommand_LogPlayback( m_currentTrack, DatabaseCommand_LogPlayback::Started ); + Database::instance()->enqueue( QSharedPointer(cmd) ); } else { @@ -295,6 +379,7 @@ AudioEngine::setStreamData( long sampleRate, int channels ) void AudioEngine::timerTriggered( unsigned int seconds ) { + m_timeElapsed = seconds; emit timerSeconds( seconds ); if ( m_currentTrack->duration() == 0 ) @@ -308,6 +393,46 @@ AudioEngine::timerTriggered( unsigned int seconds ) } +void +AudioEngine::clearBuffers() +{ + QMutexLocker lock( &m_mutex ); + m_audio->clearBuffers(); +} + + +void +AudioEngine::setPlaylist( PlaylistInterface* playlist ) +{ + m_playlist = playlist; + emit playlistChanged( playlist ); +} + + +void +AudioEngine::setCurrentTrack( const Tomahawk::result_ptr& result ) +{ + m_lastTrack = m_currentTrack; + if ( !m_lastTrack.isNull() ) + { + DatabaseCommand_LogPlayback* cmd = new DatabaseCommand_LogPlayback( m_lastTrack, DatabaseCommand_LogPlayback::Finished, m_timeElapsed ); + Database::instance()->enqueue( QSharedPointer(cmd) ); + + emit finished( m_lastTrack ); + } + + m_currentTrack = result; +} + + +void +AudioEngine::onDownloadProgress( qint64 recv, qint64 total ) +{ + if ( ( recv > 1024 * 32 ) || recv > total ) + m_readReady = true; +} + + void AudioEngine::run() { @@ -373,20 +498,20 @@ AudioEngine::loop() // are we cleanly at the end of a track, and ready for the next one? if ( !m_input.isNull() && m_input->atEnd() && -// m_input->isOpen() && + m_readReady && !m_input->bytesAvailable() && !m_audio->haveData() && !m_audio->isPaused() ) { qDebug() << "Starting next track then"; - next(); + loadNextTrack(); // will need data immediately: nextdelay = 0; } else if ( !m_input.isNull() && !m_input->isOpen() ) { qDebug() << "AudioEngine IODev closed. errorString:" << m_input->errorString(); - next(); + loadNextTrack(); nextdelay = 0; } diff --git a/src/audio/audioengine.h b/src/libtomahawk/audio/audioengine.h similarity index 54% rename from src/audio/audioengine.h rename to src/libtomahawk/audio/audioengine.h index 0489d2b7b..810eb7e95 100644 --- a/src/audio/audioengine.h +++ b/src/libtomahawk/audio/audioengine.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef AUDIOENGINE_H #define AUDIOENGINE_H @@ -5,36 +23,43 @@ #include #include -#include "tomahawk/result.h" -#include "tomahawk/typedefs.h" +#include "result.h" +#include "typedefs.h" #include "rtaudiooutput.h" #include "alsaplayback.h" #include "transcodeinterface.h" +#include "dllmacro.h" + #define AUDIO_VOLUME_STEP 5 class PlaylistInterface; -class AudioEngine : public QThread +class DLLEXPORT AudioEngine : public QThread { Q_OBJECT public: enum AudioErrorCode { StreamReadError, AudioDeviceError, DecodeError }; + static AudioEngine* instance(); + explicit AudioEngine(); ~AudioEngine(); - unsigned int volume() { if ( m_audio ) return m_audio->volume() * 100.0; else return 0; }; // in percent - + unsigned int volume() const { if ( m_audio ) return m_audio->volume() * 100.0; else return 0; }; // in percent + bool isPaused() const { return m_audio->isPaused(); } + bool isPlaying() const { return m_audio->isPlaying(); } + /* Returns the PlaylistInterface of the currently playing track. Note: This might be different to the current playlist! */ - PlaylistInterface* currentPlaylist() const { return m_currentPlaylist; } + PlaylistInterface* currentTrackPlaylist() const { return m_currentTrackPlaylist; } /* Returns the PlaylistInterface of the current playlist. Note: The currently playing track might still be from a different playlist! */ PlaylistInterface* playlist() const { return m_playlist; } public slots: + void playPause(); void play(); void pause(); void stop(); @@ -46,15 +71,18 @@ public slots: void lowerVolume() { setVolume( volume() - AUDIO_VOLUME_STEP ); } void raiseVolume() { setVolume( volume() + AUDIO_VOLUME_STEP ); } void onVolumeChanged( float volume ) { emit volumeChanged( volume * 100 ); } + void mute(); void playItem( PlaylistInterface* playlist, const Tomahawk::result_ptr& result ); - void setPlaylist( PlaylistInterface* playlist ) { m_playlist = playlist; } + void setPlaylist( PlaylistInterface* playlist ); + void setQueue( PlaylistInterface* queue ) { m_queue = queue; } void onTrackAboutToClose(); signals: void loading( const Tomahawk::result_ptr& track ); void started( const Tomahawk::result_ptr& track ); + void finished( const Tomahawk::result_ptr& track ); void stopped(); void paused(); void resumed(); @@ -64,6 +92,8 @@ signals: void timerSeconds( unsigned int secondsElapsed ); void timerPercentage( unsigned int percentage ); + void playlistChanged( PlaylistInterface* playlist ); + void error( AudioErrorCode errorCode ); private slots: @@ -71,14 +101,19 @@ private slots: void loadPreviousTrack(); void loadNextTrack(); + void onDownloadProgress( qint64 recv, qint64 total ); + void setStreamData( long sampleRate, int channels ); void timerTriggered( unsigned int seconds ); void engineLoop(); void loop(); + void setCurrentTrack( const Tomahawk::result_ptr& result ); + private: void run(); + void clearBuffers(); QSharedPointer m_input; QSharedPointer m_transcode; @@ -92,10 +127,15 @@ private: Tomahawk::result_ptr m_currentTrack; Tomahawk::result_ptr m_lastTrack; PlaylistInterface* m_playlist; - PlaylistInterface* m_currentPlaylist; + PlaylistInterface* m_currentTrackPlaylist; + PlaylistInterface* m_queue; QMutex m_mutex; + bool m_readReady; + unsigned int m_timeElapsed; int m_i; + + static AudioEngine* s_instance; }; #endif // AUDIOENGINE_H diff --git a/src/libtomahawk/audio/flactranscode.cpp b/src/libtomahawk/audio/flactranscode.cpp new file mode 100644 index 000000000..aee7299d1 --- /dev/null +++ b/src/libtomahawk/audio/flactranscode.cpp @@ -0,0 +1,164 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "flactranscode.h" + + +FLACTranscode::FLACTranscode() + : m_FLACRunning( false ) + , m_finished( false ) +{ + qDebug() << Q_FUNC_INFO; + + init(); + set_metadata_respond_all(); +} + + +FLACTranscode::~FLACTranscode() +{ + qDebug() << Q_FUNC_INFO; +} + + +void +FLACTranscode::onSeek( int seconds ) +{ + QMutexLocker locker( &m_mutex ); + + m_buffer.clear(); + m_outBuffer.clear(); +} + + +void +FLACTranscode::clearBuffers() +{ + QMutexLocker locker( &m_mutex ); + + m_FLACRunning = false; + m_finished = false; + + m_buffer.clear(); + m_outBuffer.clear(); + + flush(); + reset(); +} + + +void +FLACTranscode::processData( const QByteArray& data, bool finish ) +{ + m_mutex.lock(); + m_buffer.append( data ); + m_mutex.unlock(); + + while ( m_buffer.size() >= FLAC_BUFFER ) + { + process_single(); + } + + m_finished = finish; +} + + +::FLAC__StreamDecoderReadStatus +FLACTranscode::read_callback( FLAC__byte buffer[], size_t *bytes ) +{ + QMutexLocker locker( &m_mutex ); + + if ( *bytes > (unsigned int)m_buffer.size() ) + *bytes = m_buffer.size(); + + memcpy( buffer, (char*)m_buffer.data(), *bytes ); + m_buffer.remove( 0, *bytes ); + + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + + +::FLAC__StreamDecoderWriteStatus +FLACTranscode::write_callback( const ::FLAC__Frame *frame, const FLAC__int32 *const buffer[] ) +{ + union PCMDATA + { + FLAC__int32 i; + unsigned char b[2]; + } pcmDataLeft, pcmDataRight; + + for ( unsigned int sample = 0; sample < frame->header.blocksize; sample++ ) + { + pcmDataLeft.i = buffer[0][sample]; + pcmDataRight.i = buffer[1][sample]; + + m_outBuffer.append( pcmDataLeft.b[0] ); + m_outBuffer.append( pcmDataLeft.b[1] ); + m_outBuffer.append( pcmDataRight.b[0] ); + m_outBuffer.append( pcmDataRight.b[1] ); + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + + +::FLAC__StreamDecoderSeekStatus +FLACTranscode::seek_callback(FLAC__uint64 absolute_byte_offset) +{ + return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; +} + + +void +FLACTranscode::metadata_callback( const ::FLAC__StreamMetadata *metadata ) +{ + qDebug() << Q_FUNC_INFO << metadata->is_last; + + switch ( metadata->type ) + { + case FLAC__METADATA_TYPE_STREAMINFO: + { + FLAC::Metadata::StreamInfo stream_info( (::FLAC__StreamMetadata *)metadata, true ); + + // Try to determine samplerate + qDebug() << "FLACTranscode( BitsPerSample:" << stream_info.get_bits_per_sample() << "Samplerate:" << stream_info.get_sample_rate() << "Channels:" << stream_info.get_channels() << ")"; + emit streamInitialized( stream_info.get_sample_rate(), stream_info.get_channels() ); + + m_FLACRunning = true; + break; + } + + default: + qDebug() << "Not handling type:" << metadata->type; + break; + } +} + + +void +FLACTranscode::error_callback( ::FLAC__StreamDecoderErrorStatus status ) +{ + qDebug() << Q_FUNC_INFO << status; +} + + +bool +FLACTranscode::eof_callback() +{ + return ( m_buffer.isEmpty() && m_finished ); +} diff --git a/src/libtomahawk/audio/flactranscode.h b/src/libtomahawk/audio/flactranscode.h new file mode 100644 index 000000000..2491df3de --- /dev/null +++ b/src/libtomahawk/audio/flactranscode.h @@ -0,0 +1,87 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +/*! \class FLACTranscode + \brief Transcoding plugin for FLAC streams. +*/ + +#ifndef FLAC_TRANSCODE_H +#define FLAC_TRANSCODE_H + +#include "transcodeinterface.h" + +#include +#include +#include + +#include +#include +#include + +#include "dllmacro.h" + +#define FLAC_BUFFER 32768 * 36 +#define FLAC_BUFFER_PREFERRED 32768 + +class DLLEXPORT FLACTranscode : public TranscodeInterface , protected FLAC::Decoder::Stream +{ + Q_OBJECT + + public: + FLACTranscode(); + ~FLACTranscode(); + + const QStringList supportedTypes() const { QStringList l; l << "audio/flac" << "flac"; return l; } + + int needData() { return FLAC_BUFFER - m_buffer.count(); } + bool haveData() { return !m_outBuffer.isEmpty(); } + + unsigned int preferredDataSize() { return FLAC_BUFFER_PREFERRED; } + + QByteArray data() { QByteArray b = m_outBuffer; m_outBuffer.clear(); return b; } + + QMutex* mutex() { return &m_mutex; } + QByteArray* buffer() { return &m_buffer; } + + signals: + void streamInitialized( long sampleRate, int channels ); + + public slots: + void onSeek( int seconds ); + void clearBuffers(); + void processData( const QByteArray& data, bool finish ); + + protected: + virtual ::FLAC__StreamDecoderReadStatus read_callback( FLAC__byte buffer[], size_t *bytes ); + virtual ::FLAC__StreamDecoderWriteStatus write_callback( const ::FLAC__Frame *frame, const FLAC__int32 *const buffer[] ); + virtual ::FLAC__StreamDecoderSeekStatus seek_callback( FLAC__uint64 absolute_byte_offset ); + virtual bool eof_callback(); + virtual void metadata_callback( const ::FLAC__StreamMetadata *metadata ); + void error_callback( ::FLAC__StreamDecoderErrorStatus status ); + + private: + QByteArray m_outBuffer; + + QMutex m_mutex; + QByteArray m_buffer; + + bool m_FLACRunning; + bool m_finished; +}; + +#endif diff --git a/src/audio/madtranscode.cpp b/src/libtomahawk/audio/madtranscode.cpp similarity index 79% rename from src/audio/madtranscode.cpp rename to src/libtomahawk/audio/madtranscode.cpp index 08e7e6b9c..cbd732a28 100644 --- a/src/audio/madtranscode.cpp +++ b/src/libtomahawk/audio/madtranscode.cpp @@ -1,23 +1,20 @@ -/*************************************************************************** - * Copyright (C) 2005 - 2007 by * - * Christian Muehlhaeuser, Last.fm Ltd * - * Erik Jaelevik, Last.fm Ltd * - * * - * This program 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 2 of the License, or * - * (at your option) any later version. * - * * - * This program 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 this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ #include "madtranscode.h" diff --git a/src/audio/madtranscode.h b/src/libtomahawk/audio/madtranscode.h similarity index 58% rename from src/audio/madtranscode.h rename to src/libtomahawk/audio/madtranscode.h index b20f9d529..e90d40ef4 100644 --- a/src/audio/madtranscode.h +++ b/src/libtomahawk/audio/madtranscode.h @@ -1,22 +1,20 @@ -/*************************************************************************** - * Copyright (C) 2005 - 2007 by * - * Christian Muehlhaeuser, Last.fm Ltd * - * * - * This program 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 2 of the License, or * - * (at your option) any later version. * - * * - * This program 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 this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ /*! \class MadTranscode \brief Transcoding plugin for MP3 streams, using libmad. @@ -34,10 +32,12 @@ #include #include +#include "dllmacro.h" + #define MP3_BUFFER 32768 #define MP3_BUFFER_PREFERRED 32768 -class MADTranscode : public TranscodeInterface +class DLLEXPORT MADTranscode : public TranscodeInterface { Q_OBJECT diff --git a/src/audio/rtaudiooutput.cpp b/src/libtomahawk/audio/rtaudiooutput.cpp similarity index 87% rename from src/audio/rtaudiooutput.cpp rename to src/libtomahawk/audio/rtaudiooutput.cpp index b750460a3..667c2613e 100644 --- a/src/audio/rtaudiooutput.cpp +++ b/src/libtomahawk/audio/rtaudiooutput.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include #include #include diff --git a/src/audio/rtaudiooutput.h b/src/libtomahawk/audio/rtaudiooutput.h similarity index 65% rename from src/audio/rtaudiooutput.h rename to src/libtomahawk/audio/rtaudiooutput.h index 1b4089fe3..e13cec279 100644 --- a/src/audio/rtaudiooutput.h +++ b/src/libtomahawk/audio/rtaudiooutput.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef RTAUDIOPLAYBACK_H #define RTAUDIOPLAYBACK_H @@ -20,7 +38,7 @@ class RTAudioOutput : public QObject bool isPaused() { return m_paused; } virtual bool isPlaying() { return m_playing; } - bool haveData() { return m_buffer.length() > 0; } + bool haveData() { return m_buffer.length() > 2048; } bool needData(); void processData( const QByteArray &buffer ); diff --git a/src/libtomahawk/audio/transcodeinterface.h b/src/libtomahawk/audio/transcodeinterface.h new file mode 100644 index 000000000..48ba434df --- /dev/null +++ b/src/libtomahawk/audio/transcodeinterface.h @@ -0,0 +1,54 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TRANSCODEINTERFACE_H +#define TRANSCODEINTERFACE_H + +#include +#include +#include +#include + +#include "dllmacro.h" + +class DLLEXPORT TranscodeInterface : public QObject +{ + Q_OBJECT + + public: + virtual ~TranscodeInterface() {} + + virtual const QStringList supportedTypes() const = 0; + + virtual int needData() = 0; + virtual bool haveData() = 0; + + virtual unsigned int preferredDataSize() = 0; + + virtual QByteArray data() = 0; + +// virtual void setBufferCapacity( int bytes ) = 0; +// virtual int bufferSize() = 0; + + public slots: + virtual void clearBuffers() = 0; + virtual void onSeek( int seconds ) = 0; + virtual void processData( const QByteArray& data, bool finish ) = 0; +}; + +#endif diff --git a/src/audio/vorbistranscode.cpp b/src/libtomahawk/audio/vorbistranscode.cpp similarity index 67% rename from src/audio/vorbistranscode.cpp rename to src/libtomahawk/audio/vorbistranscode.cpp index d7a6bf233..826c0f272 100644 --- a/src/audio/vorbistranscode.cpp +++ b/src/libtomahawk/audio/vorbistranscode.cpp @@ -1,22 +1,20 @@ -/*************************************************************************** - * Copyright (C) 2005 - 2006 by * - * Christian Muehlhaeuser, Last.fm Ltd * - * * - * This program 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 2 of the License, or * - * (at your option) any later version. * - * * - * This program 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 this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ #include "vorbistranscode.h" diff --git a/src/audio/vorbistranscode.h b/src/libtomahawk/audio/vorbistranscode.h similarity index 55% rename from src/audio/vorbistranscode.h rename to src/libtomahawk/audio/vorbistranscode.h index 4c4aa4901..107dfbad3 100644 --- a/src/audio/vorbistranscode.h +++ b/src/libtomahawk/audio/vorbistranscode.h @@ -1,22 +1,20 @@ -/*************************************************************************** - * Copyright (C) 2005 - 2006 by * - * Christian Muehlhaeuser, Last.fm Ltd * - * * - * This program 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 2 of the License, or * - * (at your option) any later version. * - * * - * This program 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 this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. * - ***************************************************************************/ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ /*! \class VorbisTranscode \brief Transcoding plugin for OGG/Vorbis streams. @@ -35,11 +33,13 @@ #include #include +#include "dllmacro.h" + // Must not be smaller than 8500 bytes! #define OGG_BUFFER 8500 #define OGG_BUFFER_PREFERRED 32768 -class VorbisTranscode : public TranscodeInterface +class DLLEXPORT VorbisTranscode : public TranscodeInterface { Q_OBJECT diff --git a/src/libtomahawk/collection.cpp b/src/libtomahawk/collection.cpp new file mode 100644 index 000000000..e02acd260 --- /dev/null +++ b/src/libtomahawk/collection.cpp @@ -0,0 +1,211 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "collection.h" + +#include +#include + +#include "dynamic/DynamicPlaylist.h" +#include "playlist.h" + +using namespace Tomahawk; + + +Collection::Collection( const source_ptr& source, const QString& name, QObject* parent ) + : QObject( parent ) + , m_name( name ) + , m_lastmodified( 0 ) + , m_isLoaded( false ) + , m_source( source ) +{ + qDebug() << Q_FUNC_INFO << name << source->friendlyName(); +} + + +Collection::~Collection() +{ + qDebug() << Q_FUNC_INFO; +} + + +QString +Collection::name() const +{ + return m_name; +} + + +const +source_ptr& Collection::source() const +{ + return m_source; +} + + +void +Collection::addPlaylist( const Tomahawk::playlist_ptr& p ) +{ + qDebug() << Q_FUNC_INFO; + QList toadd; + toadd << p; + m_playlists.append( toadd ); + + qDebug() << Q_FUNC_INFO << "Collection name" << name() + << "from source id" << source()->id() + << "numplaylists:" << m_playlists.length(); + emit playlistsAdded( toadd ); +} + + +void +Collection::addDynamicPlaylist( const Tomahawk::dynplaylist_ptr& p ) +{ + qDebug() << Q_FUNC_INFO; + QList toadd; + toadd << p; + m_dynplaylists.append( toadd ); + + qDebug() << Q_FUNC_INFO << "Collection name" << name() + << "from source id" << source()->id() + << "numplaylists:" << m_playlists.length(); + emit dynamicPlaylistsAdded( toadd ); +} + + +void +Collection::deletePlaylist( const Tomahawk::playlist_ptr& p ) +{ + qDebug() << Q_FUNC_INFO; + QList todelete; + todelete << p; + m_playlists.removeAll( p ); + + qDebug() << Q_FUNC_INFO << "Collection name" << name() + << "from source id" << source()->id() + << "numplaylists:" << m_playlists.length(); + emit playlistsDeleted( todelete ); +} + + +void +Collection::deleteDynamicPlaylist( const Tomahawk::dynplaylist_ptr& p ) +{ + qDebug() << Q_FUNC_INFO; + QList todelete; + todelete << p; + m_dynplaylists.removeAll( p ); + + qDebug() << Q_FUNC_INFO << "Collection name" << name() + << "from source id" << source()->id() + << "numplaylists:" << m_playlists.length(); + emit dynamicPlaylistsDeleted( todelete ); +} + + +playlist_ptr +Collection::playlist( const QString& guid ) +{ + foreach( const playlist_ptr& pp, m_playlists ) + { + if( pp->guid() == guid ) + return pp; + } + + // TODO do we really want to do this? + foreach( const dynplaylist_ptr& pp, m_dynplaylists ) + { + if( pp->guid() == guid ) + return pp.staticCast(); + } + + return playlist_ptr(); +} + + +dynplaylist_ptr +Collection::dynamicPlaylist( const QString& guid ) +{ + foreach( const dynplaylist_ptr& pp, m_dynplaylists ) + { + if( pp->guid() == guid ) + return pp; + } + + return dynplaylist_ptr(); +} + + +void +Collection::setPlaylists( const QList& plists ) +{ + qDebug() << Q_FUNC_INFO << plists.count(); + + m_playlists.append( plists ); + emit playlistsAdded( plists ); +} + + +void +Collection::setDynamicPlaylists( const QList< Tomahawk::dynplaylist_ptr >& plists ) +{ + qDebug() << Q_FUNC_INFO << plists.count(); + + m_dynplaylists.append( plists ); + emit dynamicPlaylistsAdded( plists ); +} + + +void +Collection::setTracks( const QList& tracks ) +{ + qDebug() << Q_FUNC_INFO << tracks.count() << name(); + + m_tracks << tracks; + emit tracksAdded( tracks ); +} + + +void +Collection::delTracks( const QStringList& files ) +{ + qDebug() << Q_FUNC_INFO << files.count() << name(); + + QList tracks; + + int i = 0; + foreach ( const query_ptr& query, m_tracks ) + { + foreach ( QString file, files ) + { + foreach ( const result_ptr& result, query->results() ) + { + if ( file == result->url() ) + { + qDebug() << Q_FUNC_INFO << "Found deleted result:" << file; + tracks << query; + m_tracks.removeAt( i ); + } + } + } + + i++; + } + + emit tracksRemoved( tracks ); +} diff --git a/src/libtomahawk/collection.h b/src/libtomahawk/collection.h new file mode 100644 index 000000000..ca2aa66fd --- /dev/null +++ b/src/libtomahawk/collection.h @@ -0,0 +1,119 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +/* + The collection - acts as container for someones music library + load() -> async populate by calling addArtists etc, + then finishedLoading() is emitted. + then use artists() etc to get the data. +*/ + +#ifndef TOMAHAWK_COLLECTION_H +#define TOMAHAWK_COLLECTION_H + +#include +#include +#include +#include +#include + +#include "functimeout.h" +#include "playlist.h" +#include "source.h" +#include "typedefs.h" +#include "playlist/dynamic/DynamicPlaylist.h" + +#include "dllmacro.h" + +namespace Tomahawk +{ + +class DLLEXPORT Collection : public QObject +{ +Q_OBJECT + +public: + Collection( const source_ptr& source, const QString& name, QObject* parent = 0 ); + virtual ~Collection(); + + virtual void setLoaded() { m_isLoaded = true; } + virtual bool isLoaded() const { return m_isLoaded; } + virtual QString name() const; + + virtual void loadPlaylists() { qDebug() << Q_FUNC_INFO; } + virtual void loadTracks() { qDebug() << Q_FUNC_INFO; } + virtual void loadDynamicPlaylists() { qDebug() << Q_FUNC_INFO ; } + + virtual Tomahawk::playlist_ptr playlist( const QString& guid ); + virtual Tomahawk::dynplaylist_ptr dynamicPlaylist( const QString& guid ); + + virtual void addPlaylist( const Tomahawk::playlist_ptr& p ); + virtual void deletePlaylist( const Tomahawk::playlist_ptr& p ); + + virtual void addDynamicPlaylist( const Tomahawk::dynplaylist_ptr& p ); + virtual void deleteDynamicPlaylist( const Tomahawk::dynplaylist_ptr& p ); + + virtual QList< Tomahawk::playlist_ptr > playlists() { return m_playlists; } + virtual QList< Tomahawk::dynplaylist_ptr > dynamicPlaylists() { return m_dynplaylists; } + virtual QList< Tomahawk::query_ptr > tracks() { return m_tracks; } + + const source_ptr& source() const; + unsigned int lastmodified() const { return m_lastmodified; } + +signals: + void tracksAdded( const QList& tracks ); + void tracksRemoved( const QList& tracks ); + + void playlistsAdded( const QList& ); + void playlistsDeleted( const QList& ); + + void dynamicPlaylistsAdded( const QList& ); + void dynamicPlaylistsDeleted( const QList& ); + +public slots: + virtual void addTracks( const QList& newitems ) = 0; + virtual void removeTracks( const QDir& dir ) = 0; + + void setPlaylists( const QList& plists ); + void setDynamicPlaylists( const QList< Tomahawk::dynplaylist_ptr >& dynplists ); + + void setTracks( const QList& tracks ); + void delTracks( const QStringList& files ); + void resetTrackCache() { m_tracks.clear(); m_isLoaded = false; } + +protected: + QString m_name; + unsigned int m_lastmodified; // unix time of last change to collection + +private: + bool m_isLoaded; + + source_ptr m_source; + QList< Tomahawk::query_ptr > m_tracks; + QList< Tomahawk::playlist_ptr > m_playlists; + QList< Tomahawk::dynplaylist_ptr > m_dynplaylists; +}; + +}; // ns + +inline uint qHash( const QSharedPointer& key ) +{ + return qHash( (void *)key.data() ); +} + +#endif // TOMAHAWK_COLLECTION_H diff --git a/src/database/README.txt b/src/libtomahawk/database/README.txt similarity index 100% rename from src/database/README.txt rename to src/libtomahawk/database/README.txt diff --git a/src/libtomahawk/database/database.cpp b/src/libtomahawk/database/database.cpp new file mode 100644 index 000000000..46e61badb --- /dev/null +++ b/src/libtomahawk/database/database.cpp @@ -0,0 +1,111 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "database.h" + +#define WORKER_THREADS 5 + +Database* Database::s_instance = 0; + + +Database* +Database::instance() +{ + return s_instance; +} + + +Database::Database( const QString& dbname, QObject* parent ) + : QObject( parent ) + , m_impl( new DatabaseImpl( dbname, this ) ) + , m_workerRW( new DatabaseWorker( m_impl, this, true ) ) +{ + s_instance = this; + + m_workerRW->start(); +} + + +Database::~Database() +{ + qDebug() << Q_FUNC_INFO; + + qDeleteAll( m_workers ); + delete m_workerRW; + delete m_impl; +} + + +void +Database::loadIndex() +{ + m_impl->loadIndex(); +} + + +void +Database::enqueue( QSharedPointer lc ) +{ + if( lc->doesMutates() ) + { + //qDebug() << Q_FUNC_INFO << "RW" << lc->commandname(); + qDebug() << "Enqueueing command to rw thread:" << lc->commandname(); + m_workerRW->enqueue( lc ); + } + else + { + // find existing amount of worker threads for commandname + // create new thread if < WORKER_THREADS + if ( m_workers.count( lc->commandname() ) < WORKER_THREADS ) + { + DatabaseWorker* worker = new DatabaseWorker( m_impl, this, false ); + worker->start(); + + m_workers.insertMulti( lc->commandname(), worker ); + } + + // find thread for commandname with lowest amount of outstanding jobs and enqueue job + int busyThreads = 0; + DatabaseWorker* happyThread = 0; + QList< DatabaseWorker* > workers = m_workers.values( lc->commandname() ); + for ( int i = 0; i < workers.count(); i++ ) + { + DatabaseWorker* worker = workers.at( i ); + + if ( !worker->busy() ) + { + happyThread = worker; + break; + } + busyThreads++; + + if ( !happyThread || worker->outstandingJobs() < happyThread->outstandingJobs() ) + happyThread = worker; + } + + qDebug() << "Enqueueing command to thread:" << happyThread << busyThreads << lc->commandname(); + happyThread->enqueue( lc ); + } +} + + +QString +Database::dbid() const +{ + return m_impl->dbid(); +} diff --git a/src/libtomahawk/database/database.h b/src/libtomahawk/database/database.h new file mode 100644 index 000000000..498f5239b --- /dev/null +++ b/src/libtomahawk/database/database.h @@ -0,0 +1,72 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASE_H +#define DATABASE_H + +#include +#include + +#include "database/databaseimpl.h" +#include "database/databasecommand.h" +#include "database/databaseworker.h" + +#include "dllmacro.h" + +/* + This class is really a firewall/pimpl - the public functions of LibraryImpl + are the ones that operate on the database, without any locks. + + HOWEVER, we're using the command pattern to serialize access to the database + and provide an async api. You create a DatabaseCommand object, and add it to + the queue of work. There is a threadpool responsible for exec'ing all + the non-mutating (readonly) commands and one separate thread for mutating ones, + so sqlite doesn't write to the Database from multiple threads. +*/ +class DLLEXPORT Database : public QObject +{ +Q_OBJECT +public: + static Database* instance(); + + explicit Database( const QString& dbname, QObject* parent = 0 ); + ~Database(); + + QString dbid() const; + const bool indexReady() const { return m_indexReady; } + + void loadIndex(); + +signals: + void indexReady(); // search index + void newJobRO( QSharedPointer ); + void newJobRW( QSharedPointer ); + +public slots: + void enqueue( QSharedPointer lc ); + +private: + DatabaseImpl* m_impl; + DatabaseWorker* m_workerRW; + QHash< QString, DatabaseWorker* > m_workers; + bool m_indexReady; + + static Database* s_instance; +}; + +#endif // DATABASE_H diff --git a/src/libtomahawk/database/databasecollection.cpp b/src/libtomahawk/database/databasecollection.cpp new file mode 100644 index 000000000..564180398 --- /dev/null +++ b/src/libtomahawk/database/databasecollection.cpp @@ -0,0 +1,153 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecollection.h" + +#include "database/database.h" +#include "databasecommand_alltracks.h" +#include "databasecommand_addfiles.h" +#include "databasecommand_deletefiles.h" +#include "databasecommand_loadallplaylists.h" +#include "databasecommand_loadalldynamicplaylists.h" + +using namespace Tomahawk; + + +DatabaseCollection::DatabaseCollection( const source_ptr& src, QObject* parent ) + : Collection( src, QString( "dbcollection:%1" ).arg( src->userName() ), parent ) +{ +} + + +void +DatabaseCollection::loadPlaylists() +{ + qDebug() << Q_FUNC_INFO; + DatabaseCommand_LoadAllPlaylists* cmd = new DatabaseCommand_LoadAllPlaylists( source() ); + + connect( cmd, SIGNAL( done( const QList& ) ), + SLOT( setPlaylists( const QList& ) ) ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + + +void +DatabaseCollection::loadDynamicPlaylists() +{ + qDebug() << Q_FUNC_INFO; + DatabaseCommand_LoadAllDynamicPlaylists* cmd = new DatabaseCommand_LoadAllDynamicPlaylists( source() ); + + connect( cmd, SIGNAL( playlistLoaded( Tomahawk::source_ptr, QVariantList ) ), + SLOT( dynamicPlaylistCreated( const Tomahawk::source_ptr&, const QVariantList& ) ) ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + + +void +DatabaseCollection::loadTracks() +{ + qDebug() << Q_FUNC_INFO << source()->userName(); + + setLoaded(); + DatabaseCommand_AllTracks* cmd = new DatabaseCommand_AllTracks( source()->collection() ); + + connect( cmd, SIGNAL( tracks( QList ) ), + SLOT( setTracks( QList ) ) ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + + +void +DatabaseCollection::addTracks( const QList& newitems ) +{ + qDebug() << Q_FUNC_INFO << newitems.length(); + DatabaseCommand_AddFiles* cmd = new DatabaseCommand_AddFiles( newitems, source() ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + + +void +DatabaseCollection::removeTracks( const QDir& dir ) +{ + qDebug() << Q_FUNC_INFO << dir; + DatabaseCommand_DeleteFiles* cmd = new DatabaseCommand_DeleteFiles( dir, source() ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + + +QList< Tomahawk::playlist_ptr > +DatabaseCollection::playlists() +{ + qDebug() << Q_FUNC_INFO; + + if ( Collection::playlists().isEmpty() ) + { + loadPlaylists(); + } + + return Collection::playlists(); +} + + +QList< dynplaylist_ptr > DatabaseCollection::dynamicPlaylists() +{ + qDebug() << Q_FUNC_INFO; + + if ( Collection::dynamicPlaylists().isEmpty() ) + { + loadDynamicPlaylists(); + } + + return Collection::dynamicPlaylists(); +} + + +QList< Tomahawk::query_ptr > +DatabaseCollection::tracks() +{ + qDebug() << Q_FUNC_INFO; + + if ( !isLoaded() ) + { + loadTracks(); + } + + return Collection::tracks(); +} + + +void DatabaseCollection::dynamicPlaylistCreated( const source_ptr& source, const QVariantList& data ) +{ + dynplaylist_ptr p( new DynamicPlaylist( source, //src + data[0].toString(), //current rev + data[1].toString(), //title + data[2].toString(), //info + data[3].toString(), //creator + data[4].toString(), // dynamic type + static_cast(data[5].toInt()), // dynamic mode + data[6].toBool(), //shared + data[7].toInt(), //lastmod + data[8].toString() ) ); //GUID + addDynamicPlaylist( p ); +} + diff --git a/src/libtomahawk/database/databasecollection.h b/src/libtomahawk/database/databasecollection.h new file mode 100644 index 000000000..5b15913c0 --- /dev/null +++ b/src/libtomahawk/database/databasecollection.h @@ -0,0 +1,57 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOLLECTION_H +#define DATABASECOLLECTION_H + +#include + +#include "collection.h" +#include "source.h" +#include "typedefs.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCollection : public Tomahawk::Collection +{ +Q_OBJECT + +public: + explicit DatabaseCollection( const Tomahawk::source_ptr& source, QObject* parent = 0 ); + ~DatabaseCollection() + { + qDebug() << Q_FUNC_INFO; + } + + virtual void loadTracks(); + virtual void loadPlaylists(); + virtual void loadDynamicPlaylists(); + + virtual QList< Tomahawk::playlist_ptr > playlists(); + virtual QList< Tomahawk::query_ptr > tracks(); + virtual QList< Tomahawk::dynplaylist_ptr > dynamicPlaylists(); + +public slots: + virtual void addTracks( const QList& newitems ); + virtual void removeTracks( const QDir& dir ); + +private slots: + void dynamicPlaylistCreated( const Tomahawk::source_ptr& source, const QVariantList& data ); +}; + +#endif // DATABASECOLLECTION_H diff --git a/src/libtomahawk/database/databasecommand.cpp b/src/libtomahawk/database/databasecommand.cpp new file mode 100644 index 000000000..41824c87c --- /dev/null +++ b/src/libtomahawk/database/databasecommand.cpp @@ -0,0 +1,149 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand.h" + +#include + +#include "databasecommand_addfiles.h" +#include "databasecommand_createplaylist.h" +#include "databasecommand_deletefiles.h" +#include "databasecommand_deleteplaylist.h" +#include "databasecommand_logplayback.h" +#include "databasecommand_renameplaylist.h" +#include "databasecommand_setplaylistrevision.h" +#include "databasecommand_createdynamicplaylist.h" +#include "databasecommand_deletedynamicplaylist.h" +#include "databasecommand_setdynamicplaylistrevision.h" + + +DatabaseCommand::DatabaseCommand( QObject* parent ) + : QObject( parent ) + , m_state( PENDING ) +{ + //qDebug() << Q_FUNC_INFO; +} + + +DatabaseCommand::DatabaseCommand( const source_ptr& src, QObject* parent ) + : QObject( parent ) + , m_state( PENDING ) + , m_source( src ) +{ + //qDebug() << Q_FUNC_INFO; +} + + +DatabaseCommand::~DatabaseCommand() +{ + //qDebug() << Q_FUNC_INFO; +} + + +void +DatabaseCommand::_exec( DatabaseImpl* lib ) +{ + //qDebug() << "RUNNING" << thread(); + m_state = RUNNING; + emit running(); + exec( lib ); + m_state = FINISHED; + //qDebug() << "FINISHED" << thread(); +} + + +DatabaseCommand* +DatabaseCommand::factory( const QVariant& op, const source_ptr& source ) +{ + const QString name = op.toMap().value( "command" ).toString(); + + if( name == "addfiles" ) + { + DatabaseCommand_AddFiles * cmd = new DatabaseCommand_AddFiles; + cmd->setSource( source ); + QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd ); + return cmd; + } + else if( name == "deletefiles" ) + { + DatabaseCommand_DeleteFiles * cmd = new DatabaseCommand_DeleteFiles; + cmd->setSource( source ); + QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd ); + return cmd; + } + else if( name == "createplaylist" ) + { + DatabaseCommand_CreatePlaylist * cmd = new DatabaseCommand_CreatePlaylist; + cmd->setSource( source ); + QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd ); + return cmd; + } + else if( name == "deleteplaylist" ) + { + DatabaseCommand_DeletePlaylist * cmd = new DatabaseCommand_DeletePlaylist; + cmd->setSource( source ); + QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd ); + return cmd; + } + else if( name == "logplayback" ) + { + DatabaseCommand_LogPlayback * cmd = new DatabaseCommand_LogPlayback; + cmd->setSource( source ); + QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd ); + return cmd; + } + else if( name == "renameplaylist" ) + { + DatabaseCommand_RenamePlaylist * cmd = new DatabaseCommand_RenamePlaylist; + cmd->setSource( source ); + QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd ); + return cmd; + } + else if( name == "setplaylistrevision" ) + { + DatabaseCommand_SetPlaylistRevision * cmd = new DatabaseCommand_SetPlaylistRevision; + cmd->setSource( source ); + QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd ); + return cmd; + } else if( name == "createdynamicplaylist" ) + { + DatabaseCommand_CreateDynamicPlaylist * cmd = new DatabaseCommand_CreateDynamicPlaylist; + cmd->setSource( source ); + QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd ); + return cmd; + } + else if( name == "deletedynamicplaylist" ) + { + DatabaseCommand_DeleteDynamicPlaylist * cmd = new DatabaseCommand_DeleteDynamicPlaylist; + cmd->setSource( source ); + QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd ); + return cmd; + } + else if( name == "setdynamicplaylistrevision" ) + { + qDebug() << "SETDYN CONTENT:" << op; + DatabaseCommand_SetDynamicPlaylistRevision * cmd = new DatabaseCommand_SetDynamicPlaylistRevision; + cmd->setSource( source ); + QJson::QObjectHelper::qvariant2qobject( op.toMap(), cmd ); + return cmd; + } + + qDebug() << "ERROR in" << Q_FUNC_INFO << name; +// Q_ASSERT( false ); + return NULL; +} diff --git a/src/database/databasecommand.h b/src/libtomahawk/database/databasecommand.h similarity index 65% rename from src/database/databasecommand.h rename to src/libtomahawk/database/databasecommand.h index 9aa0fd020..f18440ca7 100644 --- a/src/database/databasecommand.h +++ b/src/libtomahawk/database/databasecommand.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_H #define DATABASECOMMAND_H @@ -6,13 +24,15 @@ #include #include -#include "tomahawk/source.h" -#include "tomahawk/typedefs.h" +#include "source.h" +#include "typedefs.h" #include "database/op.h" +#include "dllmacro.h" + class DatabaseImpl; -class DatabaseCommand : public QObject +class DLLEXPORT DatabaseCommand : public QObject { Q_OBJECT Q_PROPERTY( QString guid READ guid WRITE setGuid ) @@ -52,6 +72,8 @@ public: const Tomahawk::source_ptr& source() const { return m_source; } virtual bool loggable() const { return false; } + virtual bool singletonCmd() const { return false; } + virtual bool localOnly() const { return false; } QString guid() const { diff --git a/src/libtomahawk/database/databasecommand_addclientauth.cpp b/src/libtomahawk/database/databasecommand_addclientauth.cpp new file mode 100644 index 000000000..a1aacc48f --- /dev/null +++ b/src/libtomahawk/database/databasecommand_addclientauth.cpp @@ -0,0 +1,48 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_addclientauth.h" + +DatabaseCommand_AddClientAuth::DatabaseCommand_AddClientAuth( const QString& clientToken, + const QString& website, + const QString& name, + const QString& userAgent, + QObject* parent ) + : DatabaseCommand( parent ) + , m_clientToken( clientToken ) + , m_website( website ) + , m_name( name ) + , m_userAgent( userAgent ) +{ +} + +void DatabaseCommand_AddClientAuth::exec(DatabaseImpl* lib) +{ + TomahawkSqlQuery q = lib->newquery(); + q.prepare( "INSERT INTO http_client_auth (token, website, name, ua, mtime, permissions) VALUES (?, ?, ?, ?, ?, ?)" ); + q.addBindValue( m_clientToken ); + q.addBindValue( m_website ); + q.addBindValue( m_name ); + q.addBindValue( m_userAgent ); + q.addBindValue( 0 ); + q.addBindValue( "*" ); + + if( !q.exec() ) { + qWarning() << "Failed to insert http client into auth table!"; + } +} diff --git a/src/libtomahawk/database/databasecommand_addclientauth.h b/src/libtomahawk/database/databasecommand_addclientauth.h new file mode 100644 index 000000000..b562ef65a --- /dev/null +++ b/src/libtomahawk/database/databasecommand_addclientauth.h @@ -0,0 +1,47 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_ADDCLIENTAUTH_H +#define DATABASECOMMAND_ADDCLIENTAUTH_H + +#include "databaseimpl.h" +#include "databasecommand.h" +#include "dllmacro.h" + +#include + +class DLLEXPORT DatabaseCommand_AddClientAuth : public DatabaseCommand +{ + Q_OBJECT +public: + explicit DatabaseCommand_AddClientAuth( QObject* parent = 0 ) + : DatabaseCommand( parent ) + {} + + explicit DatabaseCommand_AddClientAuth( const QString& clientToken, const QString& website, const QString& name, const QString& userAgent, QObject* parent = 0 ); + + QString commandname() const { return "addclientauth"; } + + virtual void exec( DatabaseImpl* lib ); + virtual bool doesMutates() const { return true; } + +private: + QString m_clientToken, m_website, m_name, m_userAgent; +}; + +#endif // DATABASECOMMAND_ADDCLIENTAUTH_H diff --git a/src/libtomahawk/database/databasecommand_addfiles.cpp b/src/libtomahawk/database/databasecommand_addfiles.cpp new file mode 100644 index 000000000..e28164290 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_addfiles.cpp @@ -0,0 +1,221 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_addfiles.h" + +#include + +#include "artist.h" +#include "album.h" +#include "collection.h" +#include "database/database.h" +#include "databasecommand_collectionstats.h" +#include "databaseimpl.h" +#include "network/controlconnection.h" + +using namespace Tomahawk; + + +// remove file paths when making oplog/for network transmission +QVariantList +DatabaseCommand_AddFiles::files() const +{ + QVariantList list; + foreach ( const QVariant& v, m_files ) + { + // replace url with the id, we don't leak file paths over the network. + QVariantMap m = v.toMap(); + m.remove( "url" ); + m.insert( "url", QString::number( m.value( "id" ).toInt() ) ); + list.append( m ); + } + return list; +} + + +// After changing a collection, we need to tell other bits of the system: +void +DatabaseCommand_AddFiles::postCommitHook() +{ + qDebug() << Q_FUNC_INFO; + if ( source().isNull() || source()->collection().isNull() ) + { + qDebug() << "Source has gone offline, not emitting to GUI."; + return; + } + + // make the collection object emit its tracksAdded signal, so the + // collection browser will update/fade in etc. + Collection* coll = source()->collection().data(); + + connect( this, SIGNAL( notify( QList ) ), + coll, SLOT( setTracks( QList ) ), + Qt::QueuedConnection ); + + emit notify( m_queries ); + + // also re-calc the collection stats, to updates the "X tracks" in the sidebar etc: + DatabaseCommand_CollectionStats* cmd = new DatabaseCommand_CollectionStats( source() ); + connect( cmd, SIGNAL( done( QVariantMap ) ), + source().data(), SLOT( setStats( QVariantMap ) ), Qt::QueuedConnection ); + Database::instance()->enqueue( QSharedPointer( cmd ) ); + + if( source()->isLocal() ) + Servent::instance()->triggerDBSync(); +} + + +void +DatabaseCommand_AddFiles::exec( DatabaseImpl* dbi ) +{ + qDebug() << Q_FUNC_INFO; + Q_ASSERT( !source().isNull() ); + + TomahawkSqlQuery query_file = dbi->newquery(); + TomahawkSqlQuery query_filejoin = dbi->newquery(); + TomahawkSqlQuery query_trackattr = dbi->newquery(); + TomahawkSqlQuery query_file_del = dbi->newquery(); + + query_file.prepare( "INSERT INTO file(source, url, size, mtime, md5, mimetype, duration, bitrate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" ); + query_filejoin.prepare( "INSERT INTO file_join(file, artist, album, track, albumpos) VALUES (?, ?, ?, ?, ?)" ); + query_trackattr.prepare( "INSERT INTO track_attributes(id, k, v) VALUES (?, ?, ?)" ); + query_file_del.prepare( QString( "DELETE FROM file WHERE source %1 AND url = ?" ) + .arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) ) ); + + int added = 0; + QVariant srcid = source()->isLocal() ? QVariant( QVariant::Int ) : source()->id(); + qDebug() << "Adding" << m_files.length() << "files to db for source" << srcid; + + QList::iterator it; + for( it = m_files.begin(); it != m_files.end(); ++it ) + { + QVariant& v = *it; + QVariantMap m = v.toMap(); + + QString url = m.value( "url" ).toString(); + int mtime = m.value( "mtime" ).toInt(); + uint size = m.value( "size" ).toUInt(); + QString hash = m.value( "hash" ).toString(); + QString mimetype = m.value( "mimetype" ).toString(); + uint duration = m.value( "duration" ).toUInt(); + uint bitrate = m.value( "bitrate" ).toUInt(); + QString artist = m.value( "artist" ).toString(); + QString album = m.value( "album" ).toString(); + QString track = m.value( "track" ).toString(); + uint albumpos = m.value( "albumpos" ).toUInt(); + int year = m.value( "year" ).toInt(); + + int fileid = 0, artistid = 0, albumid = 0, trackid = 0; + query_file_del.bindValue( 0, url ); + query_file_del.exec(); + + query_file.bindValue( 0, srcid ); + query_file.bindValue( 1, url ); + query_file.bindValue( 2, size ); + query_file.bindValue( 3, mtime ); + query_file.bindValue( 4, hash ); + query_file.bindValue( 5, mimetype ); + query_file.bindValue( 6, duration ); + query_file.bindValue( 7, bitrate ); + if( !query_file.exec() ) + { + qDebug() << "Failed to insert to file:" + << query_file.lastError().databaseText() + << query_file.lastError().driverText() + << query_file.boundValues(); + continue; + } + else + { + if( added % 1000 == 0 ) + qDebug() << "Inserted" << added; + } + // get internal IDs for art/alb/trk + fileid = query_file.lastInsertId().toInt(); + m.insert( "id", fileid ); + // this is the qvariant(map) the remote will get + v = m; + + if( !source()->isLocal() ) + url = QString( "servent://%1\t%2" ).arg( source()->userName() ).arg( url ); + + bool isnew; + artistid = dbi->artistId( artist, isnew ); + if ( artistid < 1 ) + continue; + trackid = dbi->trackId( artistid, track, isnew ); + if ( trackid < 1 ) + continue; + albumid = dbi->albumId( artistid, album, isnew ); + + // Now add the association + query_filejoin.bindValue( 0, fileid ); + query_filejoin.bindValue( 1, artistid ); + query_filejoin.bindValue( 2, albumid > 0 ? albumid : QVariant( QVariant::Int ) ); + query_filejoin.bindValue( 3, trackid ); + query_filejoin.bindValue( 4, albumpos ); + if ( !query_filejoin.exec() ) + { + qDebug() << "Error inserting into file_join table"; + continue; + } + + query_trackattr.bindValue( 0, trackid ); + query_trackattr.bindValue( 1, "releaseyear" ); + query_trackattr.bindValue( 2, year ); + query_trackattr.exec(); + + QVariantMap attr; + Tomahawk::query_ptr query = Tomahawk::Query::get( artist, track, album ); + attr["releaseyear"] = m.value( "year" ); + + Tomahawk::artist_ptr artistptr = Tomahawk::Artist::get( artistid, artist ); + Tomahawk::album_ptr albumptr = Tomahawk::Album::get( albumid, album, artistptr ); + Tomahawk::result_ptr result = Tomahawk::result_ptr( new Tomahawk::Result() ); + result->setModificationTime( mtime ); + result->setSize( size ); + result->setMimetype( mimetype ); + result->setDuration( duration ); + result->setBitrate( bitrate ); + result->setArtist( artistptr ); + result->setAlbum( albumptr ); + result->setTrack( track ); + result->setAlbumPos( albumpos ); + result->setAttributes( attr ); + result->setCollection( source()->collection() ); + result->setScore( 1.0 ); + result->setUrl( url ); + result->setId( trackid ); + + QList results; + results << result; + query->addResults( results ); + + m_queries << query; + + added++; + } + qDebug() << "Inserted" << added; + + // TODO building the index could be a separate job, outside this transaction + if ( added ) + dbi->updateSearchIndex(); + + qDebug() << "Committing" << added << "tracks..."; + emit done( m_files, source()->collection() ); +} diff --git a/src/libtomahawk/database/databasecommand_addfiles.h b/src/libtomahawk/database/databasecommand_addfiles.h new file mode 100644 index 000000000..5d82ec9ac --- /dev/null +++ b/src/libtomahawk/database/databasecommand_addfiles.h @@ -0,0 +1,65 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_ADDFILES_H +#define DATABASECOMMAND_ADDFILES_H + +#include +#include + +#include "database/databasecommandloggable.h" +#include "typedefs.h" +#include "query.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_AddFiles : public DatabaseCommandLoggable +{ +Q_OBJECT +Q_PROPERTY( QVariantList files READ files WRITE setFiles ) + +public: + explicit DatabaseCommand_AddFiles( QObject* parent = 0 ) + : DatabaseCommandLoggable( parent ) + {} + + explicit DatabaseCommand_AddFiles( const QList& files, const Tomahawk::source_ptr& source, QObject* parent = 0 ) + : DatabaseCommandLoggable( parent ), m_files( files ) + { + setSource( source ); + } + + virtual QString commandname() const { return "addfiles"; } + + virtual void exec( DatabaseImpl* ); + virtual bool doesMutates() const { return true; } + virtual void postCommitHook(); + + QVariantList files() const; + void setFiles( const QVariantList& f ) { m_files = f; } + +signals: + void done( const QList&, const Tomahawk::collection_ptr& ); + void notify( const QList& ); + +private: + QVariantList m_files; + QList m_queries; +}; + +#endif // DATABASECOMMAND_ADDFILES_H diff --git a/src/database/databasecommand_addsource.cpp b/src/libtomahawk/database/databasecommand_addsource.cpp similarity index 61% rename from src/database/databasecommand_addsource.cpp rename to src/libtomahawk/database/databasecommand_addsource.cpp index 765e715d3..5af922f5e 100644 --- a/src/database/databasecommand_addsource.cpp +++ b/src/libtomahawk/database/databasecommand_addsource.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include #include "databasecommand_addsource.h" #include "databaseimpl.h" diff --git a/src/libtomahawk/database/databasecommand_addsource.h b/src/libtomahawk/database/databasecommand_addsource.h new file mode 100644 index 000000000..375467ea1 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_addsource.h @@ -0,0 +1,45 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_ADDSOURCE_H +#define DATABASECOMMAND_ADDSOURCE_H + +#include +#include + +#include "databasecommand.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_addSource : public DatabaseCommand +{ +Q_OBJECT + +public: + explicit DatabaseCommand_addSource( const QString& username, const QString& fname, QObject* parent = 0 ); + virtual void exec( DatabaseImpl* lib ); + virtual bool doesMutates() const { return true; } + virtual QString commandname() const { return "addsource"; } +signals: + void done( unsigned int, const QString& ); + +private: + QString m_username, m_fname; +}; + +#endif // DATABASECOMMAND_ADDSOURCE_H diff --git a/src/database/databasecommand_allalbums.cpp b/src/libtomahawk/database/databasecommand_allalbums.cpp similarity index 59% rename from src/database/databasecommand_allalbums.cpp rename to src/libtomahawk/database/databasecommand_allalbums.cpp index 0dfaf7345..99af75aff 100644 --- a/src/database/databasecommand_allalbums.cpp +++ b/src/libtomahawk/database/databasecommand_allalbums.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "databasecommand_allalbums.h" #include @@ -41,8 +59,8 @@ DatabaseCommand_AllAlbums::exec( DatabaseImpl* dbi ) while( query.next() ) { - Tomahawk::artist_ptr artist = Tomahawk::artist_ptr( new Tomahawk::Artist( query.value( 3 ).toString() ) ); - Tomahawk::album_ptr album = Tomahawk::Album::get( query.value( 0 ).toUInt(), query.value( 1 ).toString(), artist, m_collection ); + Tomahawk::artist_ptr artist = Tomahawk::Artist::get( query.value( 2 ).toUInt(), query.value( 3 ).toString() ); + Tomahawk::album_ptr album = Tomahawk::Album::get( query.value( 0 ).toUInt(), query.value( 1 ).toString(), artist ); al << album; } diff --git a/src/database/databasecommand_allalbums.h b/src/libtomahawk/database/databasecommand_allalbums.h similarity index 57% rename from src/database/databasecommand_allalbums.h rename to src/libtomahawk/database/databasecommand_allalbums.h index 69ec71ced..501e9ae5e 100644 --- a/src/database/databasecommand_allalbums.h +++ b/src/libtomahawk/database/databasecommand_allalbums.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_ALLALBUMS_H #define DATABASECOMMAND_ALLALBUMS_H @@ -5,11 +23,13 @@ #include #include "databasecommand.h" -#include "tomahawk/album.h" -#include "tomahawk/collection.h" -#include "tomahawk/typedefs.h" +#include "album.h" +#include "collection.h" +#include "typedefs.h" -class DatabaseCommand_AllAlbums : public DatabaseCommand +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_AllAlbums : public DatabaseCommand { Q_OBJECT public: diff --git a/src/libtomahawk/database/databasecommand_alltracks.cpp b/src/libtomahawk/database/databasecommand_alltracks.cpp new file mode 100644 index 000000000..5259ca64c --- /dev/null +++ b/src/libtomahawk/database/databasecommand_alltracks.cpp @@ -0,0 +1,149 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_alltracks.h" + +#include + +#include "databaseimpl.h" +#include "artist.h" +#include "album.h" +#include "sourcelist.h" + + +void +DatabaseCommand_AllTracks::exec( DatabaseImpl* dbi ) +{ + TomahawkSqlQuery query = dbi->newquery(); + QList ql; + + QString m_orderToken, sourceToken; + switch ( m_sortOrder ) + { + case 0: + break; + + case Album: + m_orderToken = "album.name, file_join.albumpos"; + break; + + case ModificationTime: + m_orderToken = "file.mtime"; + break; + + case AlbumPosition: + m_orderToken = "file_join.albumpos"; + break; + } + + + if ( !m_collection.isNull() ) + sourceToken = QString( "AND file.source %1" ).arg( m_collection->source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( m_collection->source()->id() ) ); + + QString sql = QString( + "SELECT file.id, artist.name, album.name, track.name, file.size, " + "file.duration, file.bitrate, file.url, file.source, file.mtime, file.mimetype, file_join.albumpos, artist.id, album.id, track.id " + "FROM file, artist, track, file_join " + "LEFT OUTER JOIN album " + "ON file_join.album = album.id " + "WHERE file.id = file_join.file " + "AND file_join.artist = artist.id " + "AND file_join.track = track.id " + "%1 " + "%2 %3 " + "%4 %5 %6" + ).arg( sourceToken ) + .arg( !m_artist ? QString() : QString( "AND artist.id = %1" ).arg( m_artist->id() ) ) + .arg( !m_album ? QString() : QString( "AND album.id = %1" ).arg( m_album->id() ) ) + .arg( m_sortOrder > 0 ? QString( "ORDER BY %1" ).arg( m_orderToken ) : QString() ) + .arg( m_sortDescending ? "DESC" : QString() ) + .arg( m_amount > 0 ? QString( "LIMIT 0, %1" ).arg( m_amount ) : QString() ); + + query.prepare( sql ); + query.exec(); + + int i = 0; + while( query.next() ) + { + Tomahawk::result_ptr result = Tomahawk::result_ptr( new Tomahawk::Result() ); + + QVariantMap attr; + TomahawkSqlQuery attrQuery = dbi->newquery(); + Tomahawk::source_ptr s; + + if( query.value( 8 ).toUInt() == 0 ) + { + s = SourceList::instance()->getLocal(); + result->setUrl( query.value( 7 ).toString() ); + } + else + { + s = SourceList::instance()->get( query.value( 8 ).toUInt() ); + if( s.isNull() ) + { + Q_ASSERT( false ); + continue; + } + + result->setUrl( QString( "servent://%1\t%2" ).arg( s->userName() ).arg( query.value( 7 ).toString() ) ); + } + + QString artist, track, album; + artist = query.value( 1 ).toString(); + album = query.value( 2 ).toString(); + track = query.value( 3 ).toString(); + + Tomahawk::query_ptr qry = Tomahawk::Query::get( artist, track, album ); + Tomahawk::artist_ptr artistptr = Tomahawk::Artist::get( query.value( 12 ).toUInt(), artist ); + Tomahawk::album_ptr albumptr = Tomahawk::Album::get( query.value( 13 ).toUInt(), album, artistptr ); + + result->setId( query.value( 14 ).toUInt() ); + result->setArtist( artistptr ); + result->setAlbum( albumptr ); + result->setTrack( query.value( 3 ).toString() ); + result->setSize( query.value( 4 ).toUInt() ); + result->setDuration( query.value( 5 ).toUInt() ); + result->setBitrate( query.value( 6 ).toUInt() ); + result->setModificationTime( query.value( 9 ).toUInt() ); + result->setMimetype( query.value( 10 ).toString() ); + result->setAlbumPos( query.value( 11 ).toUInt() ); + result->setScore( 1.0 ); + result->setCollection( s->collection() ); + + attrQuery.prepare( "SELECT k, v FROM track_attributes WHERE id = ?" ); + attrQuery.bindValue( 0, result->dbid() ); + attrQuery.exec(); + while ( attrQuery.next() ) + { + attr[ attrQuery.value( 0 ).toString() ] = attrQuery.value( 1 ).toString(); + } + + result->setAttributes( attr ); + + QList results; + results << result; + qry->addResults( results ); + + ql << qry; + } + + qDebug() << Q_FUNC_INFO << ql.length(); + + emit tracks( ql ); + emit done( m_collection ); +} diff --git a/src/libtomahawk/database/databasecommand_alltracks.h b/src/libtomahawk/database/databasecommand_alltracks.h new file mode 100644 index 000000000..5b8981aed --- /dev/null +++ b/src/libtomahawk/database/databasecommand_alltracks.h @@ -0,0 +1,80 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_ALLTRACKS_H +#define DATABASECOMMAND_ALLTRACKS_H + +#include +#include + +#include "databasecommand.h" +#include "album.h" +#include "collection.h" +#include "typedefs.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_AllTracks : public DatabaseCommand +{ +Q_OBJECT +public: + enum SortOrder { + None = 0, + Album = 1, + ModificationTime = 2, + AlbumPosition = 3 + }; + + explicit DatabaseCommand_AllTracks( const Tomahawk::collection_ptr& collection = Tomahawk::collection_ptr(), QObject* parent = 0 ) + : DatabaseCommand( parent ) + , m_collection( collection ) + , m_artist( 0 ) + , m_album( 0 ) + , m_amount( 0 ) + , m_sortOrder( DatabaseCommand_AllTracks::None ) + , m_sortDescending( false ) + {} + + virtual void exec( DatabaseImpl* ); + + virtual bool doesMutates() const { return false; } + virtual QString commandname() const { return "alltracks"; } + + void setArtist( Tomahawk::Artist* artist ) { m_artist = artist; } + void setAlbum( Tomahawk::Album* album ) { m_album = album; } + + void setLimit( unsigned int amount ) { m_amount = amount; } + void setSortOrder( DatabaseCommand_AllTracks::SortOrder order ) { m_sortOrder = order; } + void setSortDescending( bool descending ) { m_sortDescending = descending; } + +signals: + void tracks( const QList& ); + void done( const Tomahawk::collection_ptr& ); + +private: + Tomahawk::collection_ptr m_collection; + + Tomahawk::Artist* m_artist; + Tomahawk::Album* m_album; + + unsigned int m_amount; + DatabaseCommand_AllTracks::SortOrder m_sortOrder; + bool m_sortDescending; +}; + +#endif // DATABASECOMMAND_ALLTRACKS_H diff --git a/src/libtomahawk/database/databasecommand_clientauthvalid.cpp b/src/libtomahawk/database/databasecommand_clientauthvalid.cpp new file mode 100644 index 000000000..be0e4d495 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_clientauthvalid.cpp @@ -0,0 +1,44 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_clientauthvalid.h" + +DatabaseCommand_ClientAuthValid::DatabaseCommand_ClientAuthValid( const QString& clientToken, QObject* parent ) + : DatabaseCommand( parent ) + , m_clientToken( clientToken ) +{ + +} + +void DatabaseCommand_ClientAuthValid::exec(DatabaseImpl* lib) +{ + TomahawkSqlQuery q = lib->newquery(); + q.prepare( "SELECT name FROM http_client_auth WHERE token = ?" ); + q.addBindValue( m_clientToken ); + + if( q.exec() ) { + if( q.next() ) { + QString name = q.value( 0 ).toString(); + emit authValid( m_clientToken, name, true ); + } else { + emit authValid( m_clientToken, QString(), false ); + } + } else { + qWarning() << "Failed to query http auth table for client:" << m_clientToken; + } +} diff --git a/src/libtomahawk/database/databasecommand_clientauthvalid.h b/src/libtomahawk/database/databasecommand_clientauthvalid.h new file mode 100644 index 000000000..94191200e --- /dev/null +++ b/src/libtomahawk/database/databasecommand_clientauthvalid.h @@ -0,0 +1,51 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_CLIENTAUTHVALID_H +#define DATABASECOMMAND_CLIENTAUTHVALID_H + +#include "databaseimpl.h" +#include "databasecommand.h" +#include "dllmacro.h" + +#include + +class DLLEXPORT DatabaseCommand_ClientAuthValid : public DatabaseCommand +{ + Q_OBJECT +public: + explicit DatabaseCommand_ClientAuthValid( QObject* parent = 0 ) + : DatabaseCommand( parent ) + {} + + explicit DatabaseCommand_ClientAuthValid( const QString& clientToken, QObject* parent = 0 ); + + QString commandname() const { return "clientauthvalid"; } + + virtual void exec( DatabaseImpl* lib ); + virtual bool doesMutates() const { return false; } + +signals: + // if auth is invalid name is empty + void authValid( const QString& clientToken, const QString& name, bool valid ); + +private: + QString m_clientToken; +}; + +#endif // DATABASECOMMAND_CLIENTAUTHVALID_H diff --git a/src/libtomahawk/database/databasecommand_collectionstats.cpp b/src/libtomahawk/database/databasecommand_collectionstats.cpp new file mode 100644 index 000000000..081b24ade --- /dev/null +++ b/src/libtomahawk/database/databasecommand_collectionstats.cpp @@ -0,0 +1,67 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_collectionstats.h" + +#include "databaseimpl.h" + +using namespace Tomahawk; + + +DatabaseCommand_CollectionStats::DatabaseCommand_CollectionStats( const source_ptr& source, QObject* parent ) + : DatabaseCommand( source, parent ) +{ +} + + +void +DatabaseCommand_CollectionStats::exec( DatabaseImpl* dbi ) +{ + Q_ASSERT( source()->isLocal() || source()->id() >= 1 ); + TomahawkSqlQuery query = dbi->newquery(); + + QVariantMap m; + if ( source()->isLocal() ) + { + query.exec( "SELECT count(*), max(mtime), (SELECT guid FROM oplog WHERE source IS NULL ORDER BY id DESC LIMIT 1) " + "FROM file " + "WHERE source IS NULL" ); + } + else + { + query.prepare( "SELECT count(*), max(mtime), (SELECT lastop FROM source WHERE id = ?) " + "FROM file " + "WHERE source = ?" ); + query.addBindValue( source()->id() ); + query.addBindValue( source()->id() ); + query.exec(); + } + + if ( query.next() ) + { + m.insert( "numfiles", query.value( 0 ).toInt() ); + m.insert( "lastmodified", query.value( 1 ).toInt() ); + + if ( !source()->isLocal() && !source()->lastOpGuid().isEmpty() ) + m.insert( "lastop", source()->lastOpGuid() ); + else + m.insert( "lastop", query.value( 2 ).toString() ); + } + + emit done( m ); +} diff --git a/src/libtomahawk/database/databasecommand_collectionstats.h b/src/libtomahawk/database/databasecommand_collectionstats.h new file mode 100644 index 000000000..58b55aefc --- /dev/null +++ b/src/libtomahawk/database/databasecommand_collectionstats.h @@ -0,0 +1,44 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_COLLECTIONSTATS_H +#define DATABASECOMMAND_COLLECTIONSTATS_H + +#include + +#include "databasecommand.h" +#include "source.h" +#include "typedefs.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_CollectionStats : public DatabaseCommand +{ +Q_OBJECT + +public: + explicit DatabaseCommand_CollectionStats( const Tomahawk::source_ptr& source, QObject* parent = 0 ); + virtual void exec( DatabaseImpl* lib ); + virtual bool doesMutates() const { return false; } + virtual QString commandname() const { return "collectionstats"; } + +signals: + void done( const QVariantMap& ); +}; + +#endif // DATABASECOMMAND_COLLECTIONSTATS_H diff --git a/src/libtomahawk/database/databasecommand_createdynamicplaylist.cpp b/src/libtomahawk/database/databasecommand_createdynamicplaylist.cpp new file mode 100644 index 000000000..f432f3a25 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_createdynamicplaylist.cpp @@ -0,0 +1,121 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_createdynamicplaylist.h" + +#include +#include + +#include "dynamic/DynamicPlaylist.h" +#include "dynamic/DynamicControl.h" +#include "dynamic/GeneratorInterface.h" + +#include "network/servent.h" +#include "playlist/playlistmanager.h" + +using namespace Tomahawk; + + +DatabaseCommand_CreateDynamicPlaylist::DatabaseCommand_CreateDynamicPlaylist( QObject* parent ) +: DatabaseCommand_CreatePlaylist( parent ) +{ + qDebug() << Q_FUNC_INFO << "creating dynamiccreatecommand 1"; +} + + +DatabaseCommand_CreateDynamicPlaylist::DatabaseCommand_CreateDynamicPlaylist( const source_ptr& author, + const dynplaylist_ptr& playlist ) + : DatabaseCommand_CreatePlaylist( author, playlist.staticCast() ) + , m_playlist( playlist ) +{ + qDebug() << Q_FUNC_INFO << "creating dynamiccreatecommand 2"; +} + + +void +DatabaseCommand_CreateDynamicPlaylist::exec( DatabaseImpl* lib ) +{ + qDebug() << Q_FUNC_INFO; + Q_ASSERT( !( m_playlist.isNull() && m_v.isNull() ) ); + Q_ASSERT( !source().isNull() ); + + DatabaseCommand_CreatePlaylist::createPlaylist( lib, true ); + qDebug() << "Created normal playlist, now creating additional dynamic info!"; + + TomahawkSqlQuery cre = lib->newquery(); + + cre.prepare( "INSERT INTO dynamic_playlist( guid, pltype, plmode ) " + "VALUES( ?, ?, ? )" ); + + if( m_playlist.isNull() ) { + QVariantMap m = m_v.toMap(); + cre.addBindValue( m.value( "guid" ) ); + cre.addBindValue( m.value( "type" ) ); + cre.addBindValue( m.value( "mode" ) ); + } else { + cre.addBindValue( m_playlist->guid() ); + cre.addBindValue( m_playlist->type() ); + cre.addBindValue( m_playlist->mode() ); + } + cre.exec(); + + // save the controls -- wait, no controls in a new playlist :P +// cre = lib->newquery(); +// cre.prepare( "INSERT INTO dynamic_playlist_controls( id, selectedType, match, input) " +// "VALUES( :id, :selectedType, :match, :input )" ); +// foreach( const dyncontrol_ptr& control, m_playlist->generator()->controls() ) { +// +// cre.bindValue( ":id", control->id() ); +// cre.bindValue( ":selectedType", control->selectedType() ); +// cre.bindValue( ":match", control->match() ); +// cre.bindValue( ":input", control->input() ); +// +// qDebug() << "CREATE DYNPLAYLIST CONTROL:" << cre.boundValues(); +// +// cre.exec(); +// } +} + + +void +DatabaseCommand_CreateDynamicPlaylist::postCommitHook() +{ + qDebug() << Q_FUNC_INFO; + if ( source().isNull() || source()->collection().isNull() ) + { + qDebug() << "Source has gone offline, not emitting to GUI."; + return; + } + + if( report() == false ) + return; + + qDebug() << Q_FUNC_INFO << "..reporting.."; + if( m_playlist.isNull() ) { + source_ptr src = source(); + QMetaObject::invokeMethod( PlaylistManager::instance(), + "createDynamicPlaylist", + Qt::BlockingQueuedConnection, + QGenericArgument( "Tomahawk::source_ptr", (const void*)&src ), + Q_ARG( QVariant, m_v ) ); + } else { + m_playlist->reportCreated( m_playlist ); + } + if( source()->isLocal() ) + Servent::instance()->triggerDBSync(); +} diff --git a/src/libtomahawk/database/databasecommand_createdynamicplaylist.h b/src/libtomahawk/database/databasecommand_createdynamicplaylist.h new file mode 100644 index 000000000..2c2183b61 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_createdynamicplaylist.h @@ -0,0 +1,59 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_CREATEDYNAMICPLAYLIST_H +#define DATABASECOMMAND_CREATEDYNAMICPLAYLIST_H + +#include "databaseimpl.h" +#include "databasecommand_createplaylist.h" +#include "dynamic/DynamicPlaylist.h" +#include "typedefs.h" + +class DatabaseCommand_CreateDynamicPlaylist : public DatabaseCommand_CreatePlaylist +{ + Q_OBJECT + Q_PROPERTY( QVariant playlist READ playlistV WRITE setPlaylistV ) + +public: + explicit DatabaseCommand_CreateDynamicPlaylist( QObject* parent = 0 ); + explicit DatabaseCommand_CreateDynamicPlaylist( const Tomahawk::source_ptr& author, const Tomahawk::dynplaylist_ptr& playlist ); + + QString commandname() const { return "createdynamicplaylist"; } + + virtual void exec( DatabaseImpl* lib ); + virtual void postCommitHook(); + virtual bool doesMutates() const { return true; } + + QVariant playlistV() const + { + if( m_v.isNull() ) + return QJson::QObjectHelper::qobject2qvariant( (QObject*)m_playlist.data() ); + else + return m_v; + } + + void setPlaylistV( const QVariant& v ) + { + m_v = v; + } + +private: + Tomahawk::dynplaylist_ptr m_playlist; +}; + +#endif // DATABASECOMMAND_CREATEDYNAMICPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_createplaylist.cpp b/src/libtomahawk/database/databasecommand_createplaylist.cpp new file mode 100644 index 000000000..b5bd2ccc2 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_createplaylist.cpp @@ -0,0 +1,116 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_createplaylist.h" + +#include + +#include "network/servent.h" +#include "playlist/playlistmanager.h" + +using namespace Tomahawk; + + +DatabaseCommand_CreatePlaylist::DatabaseCommand_CreatePlaylist( QObject* parent ) + : DatabaseCommandLoggable( parent ) + , m_report( true ) +{ + qDebug() << Q_FUNC_INFO << "def"; +} + + +DatabaseCommand_CreatePlaylist::DatabaseCommand_CreatePlaylist( const source_ptr& author, + const playlist_ptr& playlist ) + : DatabaseCommandLoggable( author ) + , m_playlist( playlist ) + , m_report( false ) //this ctor used when creating locally, reporting done elsewhere +{ + qDebug() << Q_FUNC_INFO; +} + + +void +DatabaseCommand_CreatePlaylist::exec( DatabaseImpl* lib ) +{ + createPlaylist(lib, false); +} + + +void +DatabaseCommand_CreatePlaylist::postCommitHook() +{ + qDebug() << Q_FUNC_INFO; + if ( source().isNull() || source()->collection().isNull() ) + { + qDebug() << "Source has gone offline, not emitting to GUI."; + return; + } + if( m_report == false ) + return; + + qDebug() << Q_FUNC_INFO << "..reporting.."; + if( m_playlist.isNull() ) { + source_ptr src = source(); + QMetaObject::invokeMethod( PlaylistManager::instance(), + "createPlaylist", + Qt::BlockingQueuedConnection, + QGenericArgument( "Tomahawk::source_ptr", (const void*)&src ), + Q_ARG( QVariant, m_v ) + ); + } else { + m_playlist->reportCreated( m_playlist ); + } + + + if( source()->isLocal() ) + Servent::instance()->triggerDBSync(); +} + +void +DatabaseCommand_CreatePlaylist::createPlaylist( DatabaseImpl* lib, bool dynamic) +{ + qDebug() << Q_FUNC_INFO; + Q_ASSERT( !( m_playlist.isNull() && m_v.isNull() ) ); + Q_ASSERT( !source().isNull() ); + + TomahawkSqlQuery cre = lib->newquery(); + cre.prepare( "INSERT INTO playlist( guid, source, shared, title, info, creator, lastmodified, dynplaylist) " + "VALUES( :guid, :source, :shared, :title, :info, :creator, :lastmodified, :dynplaylist )" ); + + cre.bindValue( ":source", source()->isLocal() ? QVariant(QVariant::Int) : source()->id() ); + cre.bindValue( ":dynplaylist", dynamic ); + if( !m_playlist.isNull() ) { + cre.bindValue( ":guid", m_playlist->guid() ); + cre.bindValue( ":shared", m_playlist->shared() ); + cre.bindValue( ":title", m_playlist->title() ); + cre.bindValue( ":info", m_playlist->info() ); + cre.bindValue( ":creator", m_playlist->creator() ); + cre.bindValue( ":lastmodified", m_playlist->lastmodified() ); + } else { + QVariantMap m = m_v.toMap(); + cre.bindValue( ":guid", m.value( "guid" ) ); + cre.bindValue( ":shared", m.value( "shared" ) ); + cre.bindValue( ":title", m.value( "title" ) ); + cre.bindValue( ":info", m.value( "info" ) ); + cre.bindValue( ":creator", m.value( "creator" ) ); + cre.bindValue( ":lastmodified", m.value( "lastmodified", 0 ) ); + } + qDebug() << "CREATE PLAYLIST:" << cre.boundValues(); + + cre.exec(); +} diff --git a/src/libtomahawk/database/databasecommand_createplaylist.h b/src/libtomahawk/database/databasecommand_createplaylist.h new file mode 100644 index 000000000..ec3aa14a3 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_createplaylist.h @@ -0,0 +1,69 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_CREATEPLAYLIST_H +#define DATABASECOMMAND_CREATEPLAYLIST_H + +#include "databaseimpl.h" +#include "databasecommandloggable.h" +#include "playlist.h" +#include "typedefs.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_CreatePlaylist : public DatabaseCommandLoggable +{ +Q_OBJECT +Q_PROPERTY( QVariant playlist READ playlistV WRITE setPlaylistV ) + +public: + explicit DatabaseCommand_CreatePlaylist( QObject* parent = 0 ); + explicit DatabaseCommand_CreatePlaylist( const Tomahawk::source_ptr& author, const Tomahawk::playlist_ptr& playlist ); + + QString commandname() const { return "createplaylist"; } + + virtual void exec( DatabaseImpl* lib ); + virtual void postCommitHook(); + virtual bool doesMutates() const { return true; } + + QVariant playlistV() const + { + if( m_v.isNull() ) + return QJson::QObjectHelper::qobject2qvariant( (QObject*)m_playlist.data() ); + else + return m_v; + } + + void setPlaylistV( const QVariant& v ) + { + m_v = v; + } + +protected: + void createPlaylist( DatabaseImpl* lib, bool dynamic = false ); + + bool report() { return m_report; } + void setPlaylist( const Tomahawk::playlist_ptr& playlist ) { m_playlist = playlist; } + + QVariant m_v; +private: + Tomahawk::playlist_ptr m_playlist; + bool m_report; // call Playlist::reportCreated? +}; + +#endif // DATABASECOMMAND_CREATEPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_deletedynamicplaylist.cpp b/src/libtomahawk/database/databasecommand_deletedynamicplaylist.cpp new file mode 100644 index 000000000..34c5defbd --- /dev/null +++ b/src/libtomahawk/database/databasecommand_deletedynamicplaylist.cpp @@ -0,0 +1,65 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_deletedynamicplaylist.h" + +#include +#include "network/servent.h" + +using namespace Tomahawk; + + +DatabaseCommand_DeleteDynamicPlaylist::DatabaseCommand_DeleteDynamicPlaylist( const source_ptr& source, const QString& playlistguid ) + : DatabaseCommand_DeletePlaylist( source, playlistguid ) +{ +} + + +void +DatabaseCommand_DeleteDynamicPlaylist::exec( DatabaseImpl* lib ) +{ + qDebug() << Q_FUNC_INFO; + qDebug() << "deleting dynamic playlist:" << m_playlistguid; + DatabaseCommand_DeletePlaylist::exec( lib ); + TomahawkSqlQuery cre = lib->newquery(); + + cre.prepare( "DELETE FROM dynamic_playlist WHERE guid = :id" ); + cre.bindValue( ":id", m_playlistguid ); + + cre.exec(); +} + + +void +DatabaseCommand_DeleteDynamicPlaylist::postCommitHook() +{ + qDebug() << Q_FUNC_INFO << "..reporting..:" << m_playlistguid; + if ( source().isNull() || source()->collection().isNull() ) + { + qDebug() << "Source has gone offline, not emitting to GUI."; + return; + } + + dynplaylist_ptr playlist = source()->collection()->dynamicPlaylist( m_playlistguid ); + Q_ASSERT( !playlist.isNull() ); + + playlist->reportDeleted( playlist ); + + if( source()->isLocal() ) + Servent::instance()->triggerDBSync(); +} diff --git a/src/libtomahawk/database/databasecommand_deletedynamicplaylist.h b/src/libtomahawk/database/databasecommand_deletedynamicplaylist.h new file mode 100644 index 000000000..be0ba5df1 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_deletedynamicplaylist.h @@ -0,0 +1,45 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_DELETEDYNAMICPLAYLIST_H +#define DATABASECOMMAND_DELETEDYNAMICPLAYLIST_H + +#include "databaseimpl.h" +#include "databasecommand_deleteplaylist.h" +#include "source.h" +#include "typedefs.h" + +class DatabaseCommand_DeleteDynamicPlaylist : public DatabaseCommand_DeletePlaylist +{ + Q_OBJECT +public: + explicit DatabaseCommand_DeleteDynamicPlaylist( QObject* parent = 0 ) + : DatabaseCommand_DeletePlaylist( parent ) + {} + + explicit DatabaseCommand_DeleteDynamicPlaylist( const Tomahawk::source_ptr& source, const QString& playlistguid ); + + QString commandname() const { return "deletedynamicplaylist"; } + + virtual void exec( DatabaseImpl* lib ); + virtual void postCommitHook(); + virtual bool doesMutates() const { return true; } + +}; + +#endif // DATABASECOMMAND_DELETEDYNAMICPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_deletefiles.cpp b/src/libtomahawk/database/databasecommand_deletefiles.cpp new file mode 100644 index 000000000..503d3887b --- /dev/null +++ b/src/libtomahawk/database/databasecommand_deletefiles.cpp @@ -0,0 +1,144 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_deletefiles.h" + +#include + +#include "artist.h" +#include "album.h" +#include "collection.h" +#include "database/database.h" +#include "databasecommand_collectionstats.h" +#include "databaseimpl.h" +#include "network/controlconnection.h" + +using namespace Tomahawk; + + +// After changing a collection, we need to tell other bits of the system: +void +DatabaseCommand_DeleteFiles::postCommitHook() +{ + qDebug() << Q_FUNC_INFO; + + // make the collection object emit its tracksAdded signal, so the + // collection browser will update/fade in etc. + Collection* coll = source()->collection().data(); + + connect( this, SIGNAL( notify( QStringList ) ), + coll, SLOT( delTracks( QStringList ) ), Qt::QueuedConnection ); + + emit notify( m_files ); + + // also re-calc the collection stats, to updates the "X tracks" in the sidebar etc: + DatabaseCommand_CollectionStats* cmd = new DatabaseCommand_CollectionStats( source() ); + connect( cmd, SIGNAL( done( QVariantMap ) ), + source().data(), SLOT( setStats( QVariantMap ) ), Qt::QueuedConnection ); + Database::instance()->enqueue( QSharedPointer( cmd ) ); + + if( source()->isLocal() ) + Servent::instance()->triggerDBSync(); +} + + +void +DatabaseCommand_DeleteFiles::exec( DatabaseImpl* dbi ) +{ + qDebug() << Q_FUNC_INFO; + Q_ASSERT( !source().isNull() ); + + int deleted = 0; + QVariant srcid = source()->isLocal() ? QVariant( QVariant::Int ) : source()->id(); + TomahawkSqlQuery delquery = dbi->newquery(); + QString lastPath; + + if ( !m_dir.path().isEmpty() && source()->isLocal() ) + { + qDebug() << "Deleting" << m_dir.path() << "from db for localsource" << srcid; + TomahawkSqlQuery dirquery = dbi->newquery(); + + dirquery.prepare( QString( "SELECT id, url FROM file WHERE source %1 AND url LIKE ?" ) + .arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) ) ); + delquery.prepare( QString( "DELETE FROM file WHERE source %1 AND id = ?" ) + .arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) ) ); + + dirquery.bindValue( 0, "file://" + m_dir.absolutePath() + "/%" ); + dirquery.exec(); + + while ( dirquery.next() ) + { + QFileInfo fi( dirquery.value( 1 ).toString().mid( 7 ) ); // remove file:// + if ( fi.absolutePath() != m_dir.absolutePath() ) + { + if ( lastPath != fi.absolutePath() ) + qDebug() << "Skipping subdir:" << fi.absolutePath(); + + lastPath = fi.absolutePath(); + continue; + } + + m_ids << dirquery.value( 0 ).toUInt(); + m_files << dirquery.value( 1 ).toString(); + } + + foreach ( const QVariant& id, m_ids ) + { + delquery.bindValue( 0, id.toUInt() ); + if( !delquery.exec() ) + { + qDebug() << "Failed to delete file:" + << delquery.lastError().databaseText() + << delquery.lastError().driverText() + << delquery.boundValues(); + continue; + } + + deleted++; + } + } + else + { + delquery.prepare( QString( "DELETE FROM file WHERE source %1 AND url = ?" ) + .arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) ) ); + + foreach( const QVariant& id, m_ids ) + { + qDebug() << "Deleting" << id.toUInt() << "from db for source" << srcid; + + const QString url = QString( "servent://%1\t%2" ).arg( source()->userName() ).arg( id.toString() ); + m_files << url; + + delquery.bindValue( 0, id.toUInt() ); + if( !delquery.exec() ) + { + qDebug() << "Failed to delete file:" + << delquery.lastError().databaseText() + << delquery.lastError().driverText() + << delquery.boundValues(); + continue; + } + + deleted++; + } + } + + qDebug() << "Deleted" << deleted << m_ids << m_files; + + emit done( m_files, source()->collection() ); +} diff --git a/src/libtomahawk/database/databasecommand_deletefiles.h b/src/libtomahawk/database/databasecommand_deletefiles.h new file mode 100644 index 000000000..4668d0249 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_deletefiles.h @@ -0,0 +1,70 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_DELETEFILES_H +#define DATABASECOMMAND_DELETEFILES_H + +#include +#include +#include + +#include "database/databasecommandloggable.h" +#include "typedefs.h" +#include "query.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_DeleteFiles : public DatabaseCommandLoggable +{ +Q_OBJECT +Q_PROPERTY( QVariantList ids READ ids WRITE setIds ) + +public: + explicit DatabaseCommand_DeleteFiles( QObject* parent = 0 ) + : DatabaseCommandLoggable( parent ) + {} + + explicit DatabaseCommand_DeleteFiles( const QDir& dir, const Tomahawk::source_ptr& source, QObject* parent = 0 ) + : DatabaseCommandLoggable( parent ), m_dir( dir ) + { + setSource( source ); + } + + virtual QString commandname() const { return "deletefiles"; } + + virtual void exec( DatabaseImpl* ); + virtual bool doesMutates() const { return true; } + virtual void postCommitHook(); + + QStringList files() const { return m_files; } + void setFiles( const QStringList& f ) { m_files = f; } + + QVariantList ids() const { return m_ids; } + void setIds( const QVariantList& i ) { m_ids = i; } + +signals: + void done( const QStringList&, const Tomahawk::collection_ptr& ); + void notify( const QStringList& ); + +private: + QDir m_dir; + QStringList m_files; + QVariantList m_ids; +}; + +#endif // DATABASECOMMAND_DELETEFILES_H diff --git a/src/libtomahawk/database/databasecommand_deleteplaylist.cpp b/src/libtomahawk/database/databasecommand_deleteplaylist.cpp new file mode 100644 index 000000000..d87699594 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_deleteplaylist.cpp @@ -0,0 +1,68 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_deleteplaylist.h" + +#include + +#include "network/servent.h" + +using namespace Tomahawk; + + +DatabaseCommand_DeletePlaylist::DatabaseCommand_DeletePlaylist( const source_ptr& source, const QString& playlistguid ) + : DatabaseCommandLoggable( source ) +{ + setPlaylistguid( playlistguid ); +} + + +void +DatabaseCommand_DeletePlaylist::exec( DatabaseImpl* lib ) +{ + qDebug() << Q_FUNC_INFO; + + TomahawkSqlQuery cre = lib->newquery(); + + QString sql = QString( "DELETE FROM playlist WHERE guid = :id AND source %1" ) + .arg( source()->isLocal() ? "IS NULL" : QString("= %1").arg( source()->id() ) ); + cre.prepare( sql ); + cre.bindValue( ":id", m_playlistguid ); + + cre.exec(); +} + + +void +DatabaseCommand_DeletePlaylist::postCommitHook() +{ + qDebug() << Q_FUNC_INFO << "..reporting.."; + if ( source().isNull() || source()->collection().isNull() ) + { + qDebug() << "Source has gone offline, not emitting to GUI."; + return; + } + + playlist_ptr playlist = source()->collection()->playlist( m_playlistguid ); + Q_ASSERT( !playlist.isNull() ); + + playlist->reportDeleted( playlist ); + + if( source()->isLocal() ) + Servent::instance()->triggerDBSync(); +} diff --git a/src/libtomahawk/database/databasecommand_deleteplaylist.h b/src/libtomahawk/database/databasecommand_deleteplaylist.h new file mode 100644 index 000000000..995e0efcf --- /dev/null +++ b/src/libtomahawk/database/databasecommand_deleteplaylist.h @@ -0,0 +1,54 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_DELETEPLAYLIST_H +#define DATABASECOMMAND_DELETEPLAYLIST_H + +#include "databaseimpl.h" +#include "databasecommandloggable.h" +#include "source.h" +#include "typedefs.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_DeletePlaylist : public DatabaseCommandLoggable +{ +Q_OBJECT +Q_PROPERTY( QString playlistguid READ playlistguid WRITE setPlaylistguid ) + +public: + explicit DatabaseCommand_DeletePlaylist( QObject* parent = 0 ) + : DatabaseCommandLoggable( parent ) + {} + + explicit DatabaseCommand_DeletePlaylist( const Tomahawk::source_ptr& source, const QString& playlistguid ); + + QString commandname() const { return "deleteplaylist"; } + + virtual void exec( DatabaseImpl* lib ); + virtual void postCommitHook(); + virtual bool doesMutates() const { return true; } + + QString playlistguid() const { return m_playlistguid; } + void setPlaylistguid( const QString& s ) { m_playlistguid = s; } + +protected: + QString m_playlistguid; +}; + +#endif // DATABASECOMMAND_DELETEPLAYLIST_H diff --git a/src/database/databasecommand_dirmtimes.cpp b/src/libtomahawk/database/databasecommand_dirmtimes.cpp similarity index 57% rename from src/database/databasecommand_dirmtimes.cpp rename to src/libtomahawk/database/databasecommand_dirmtimes.cpp index d8c11c799..829a609c8 100644 --- a/src/database/databasecommand_dirmtimes.cpp +++ b/src/libtomahawk/database/databasecommand_dirmtimes.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "databasecommand_dirmtimes.h" #include @@ -26,7 +44,7 @@ DatabaseCommand_DirMtimes::execSelect( DatabaseImpl* dbi ) { query.prepare( QString( "SELECT name, mtime " "FROM dirs_scanned " - "WHERE name LIKE '%1%'" ).arg(m_prefix.replace( '\'',"''" ) ) ); + "WHERE name LIKE '%1%'" ).arg( m_prefix.replace( '\'',"''" ) ) ); query.exec(); } while( query.next() ) @@ -44,7 +62,7 @@ DatabaseCommand_DirMtimes::execUpdate( DatabaseImpl* dbi ) qDebug() << "Saving mtimes..."; TomahawkSqlQuery query = dbi->newquery(); query.exec( "DELETE FROM dirs_scanned" ); - query.prepare( "INSERT INTO dirs_scanned(name, mtime) VALUES(?,?)" ); + query.prepare( "INSERT INTO dirs_scanned(name, mtime) VALUES(?, ?)" ); foreach( const QString& k, m_tosave.keys() ) { diff --git a/src/database/databasecommand_dirmtimes.h b/src/libtomahawk/database/databasecommand_dirmtimes.h similarity index 53% rename from src/database/databasecommand_dirmtimes.h rename to src/libtomahawk/database/databasecommand_dirmtimes.h index bc9ba4bef..f35a78437 100644 --- a/src/database/databasecommand_dirmtimes.h +++ b/src/libtomahawk/database/databasecommand_dirmtimes.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_DIRMTIMES_H #define DATABASECOMMAND_DIRMTIMES_H @@ -7,9 +25,11 @@ #include "databasecommand.h" +#include "dllmacro.h" + // Not loggable, mtimes only used to speed up our local scanner. -class DatabaseCommand_DirMtimes : public DatabaseCommand +class DLLEXPORT DatabaseCommand_DirMtimes : public DatabaseCommand { Q_OBJECT diff --git a/src/database/databasecommand_importplaylist.cpp b/src/libtomahawk/database/databasecommand_importplaylist.cpp similarity index 60% rename from src/database/databasecommand_importplaylist.cpp rename to src/libtomahawk/database/databasecommand_importplaylist.cpp index b0e84c07d..2d5fdddc2 100644 --- a/src/database/databasecommand_importplaylist.cpp +++ b/src/libtomahawk/database/databasecommand_importplaylist.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "databasecommand_importplaylist.h" #include diff --git a/src/libtomahawk/database/databasecommand_importplaylist.h b/src/libtomahawk/database/databasecommand_importplaylist.h new file mode 100644 index 000000000..90629f1ba --- /dev/null +++ b/src/libtomahawk/database/databasecommand_importplaylist.h @@ -0,0 +1,51 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_IMPORTPLAYLIST_H +#define DATABASECOMMAND_IMPORTPLAYLIST_H + +#include +#include + +#include "databasecommand.h" +#include "tomahawk/source.h" + +#include "dllmacro.h" + +class Playlist; + +class DLLEXPORT DatabaseCommand_ImportPlaylist : public DatabaseCommand +{ + Q_OBJECT +public: + explicit DatabaseCommand_ImportPlaylist(Playlist * p, QObject *parent = 0) + : DatabaseCommand(parent), m_playlist(p) + {} + + virtual void exec(DatabaseImpl *); + virtual bool doesMutates() const { return true; } + virtual QString commandname() const { return "importplaylist"; } + +signals: + void done(int id); + +private: + Playlist * m_playlist; +}; + +#endif // DATABASECOMMAND_ADDFILES_H diff --git a/src/libtomahawk/database/databasecommand_loadalldynamicplaylists.cpp b/src/libtomahawk/database/databasecommand_loadalldynamicplaylists.cpp new file mode 100644 index 000000000..333b596fd --- /dev/null +++ b/src/libtomahawk/database/databasecommand_loadalldynamicplaylists.cpp @@ -0,0 +1,56 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_loadalldynamicplaylists.h" + +#include + +#include "dynamic/DynamicPlaylist.h" +#include "databaseimpl.h" + +using namespace Tomahawk; + + +void DatabaseCommand_LoadAllDynamicPlaylists::exec( DatabaseImpl* dbi ) +{ + TomahawkSqlQuery query = dbi->newquery(); + + query.exec( QString( "SELECT playlist.guid as guid, title, info, creator, lastmodified, shared, currentrevision, dynamic_playlist.pltype, dynamic_playlist.plmode " + "FROM playlist, dynamic_playlist WHERE source %1 AND dynplaylist = 'true' AND playlist.guid = dynamic_playlist.guid" ) + .arg( source()->isLocal() ? "IS NULL" : + QString( "=%1" ).arg( source()->id() ) + ) ); + + QList plists; + while ( query.next() ) + { + QVariantList data = QVariantList() << query.value(6).toString() //current rev + << query.value(1).toString() //title + << query.value(2).toString() //info + << query.value(3).toString() //creator + << query.value(7).toString() // dynamic type + << static_cast(query.value(8).toInt()) // dynamic mode + << query.value(5).toBool() //shared + << query.value(4).toInt() //lastmod + << query.value(0).toString(); //GUID + emit playlistLoaded( source(), data ); + } + + emit done(); +} + diff --git a/src/libtomahawk/database/databasecommand_loadalldynamicplaylists.h b/src/libtomahawk/database/databasecommand_loadalldynamicplaylists.h new file mode 100644 index 000000000..9b2834c3f --- /dev/null +++ b/src/libtomahawk/database/databasecommand_loadalldynamicplaylists.h @@ -0,0 +1,46 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_LOADALLDYNAMICPLAYLISTS_H +#define DATABASECOMMAND_LOADALLDYNAMICPLAYLISTS_H + +#include +#include + +#include "databasecommand.h" +#include "typedefs.h" + +class DatabaseCommand_LoadAllDynamicPlaylists : public DatabaseCommand +{ + Q_OBJECT + +public: + explicit DatabaseCommand_LoadAllDynamicPlaylists( const Tomahawk::source_ptr& s, QObject* parent = 0 ) + : DatabaseCommand( s, parent ) + {} + + virtual void exec( DatabaseImpl* ); + virtual bool doesMutates() const { return false; } + virtual QString commandname() const { return "loadalldynamicplaylists"; } + +signals: + void playlistLoaded( const Tomahawk::source_ptr& source, const QVariantList& data ); + void done(); +}; + +#endif // DATABASECOMMAND_ADDFILES_H diff --git a/src/database/databasecommand_loadallplaylists.cpp b/src/libtomahawk/database/databasecommand_loadallplaylists.cpp similarity index 54% rename from src/database/databasecommand_loadallplaylists.cpp rename to src/libtomahawk/database/databasecommand_loadallplaylists.cpp index 1822532d1..db7865767 100644 --- a/src/database/databasecommand_loadallplaylists.cpp +++ b/src/libtomahawk/database/databasecommand_loadallplaylists.cpp @@ -1,8 +1,26 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "databasecommand_loadallplaylists.h" #include -#include "tomahawk/playlist.h" +#include "playlist.h" #include "databaseimpl.h" using namespace Tomahawk; @@ -13,9 +31,9 @@ void DatabaseCommand_LoadAllPlaylists::exec( DatabaseImpl* dbi ) TomahawkSqlQuery query = dbi->newquery(); query.exec( QString( "SELECT guid, title, info, creator, lastmodified, shared, currentrevision " - "FROM playlist WHERE source %1" ) + "FROM playlist WHERE source %1 AND dynplaylist = 'false'" ) .arg( source()->isLocal() ? "IS NULL" : - QString( "=%1" ).arg( source()->id() ) + QString( "= %1" ).arg( source()->id() ) ) ); QList plists; diff --git a/src/libtomahawk/database/databasecommand_loadallplaylists.h b/src/libtomahawk/database/databasecommand_loadallplaylists.h new file mode 100644 index 000000000..4a2c6f66d --- /dev/null +++ b/src/libtomahawk/database/databasecommand_loadallplaylists.h @@ -0,0 +1,47 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_LOADALLPLAYLIST_H +#define DATABASECOMMAND_LOADALLPLAYLIST_H + +#include +#include + +#include "databasecommand.h" +#include "typedefs.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_LoadAllPlaylists : public DatabaseCommand +{ +Q_OBJECT + +public: + explicit DatabaseCommand_LoadAllPlaylists( const Tomahawk::source_ptr& s, QObject* parent = 0 ) + : DatabaseCommand( s, parent ) + {} + + virtual void exec( DatabaseImpl* ); + virtual bool doesMutates() const { return false; } + virtual QString commandname() const { return "loadallplaylists"; } + +signals: + void done( const QList& playlists ); +}; + +#endif // DATABASECOMMAND_LOADALLPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_loadallsources.cpp b/src/libtomahawk/database/databasecommand_loadallsources.cpp new file mode 100644 index 000000000..79e09387c --- /dev/null +++ b/src/libtomahawk/database/databasecommand_loadallsources.cpp @@ -0,0 +1,47 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_loadallsources.h" + +#include + +#include "network/servent.h" +#include "source.h" +#include "databaseimpl.h" + +using namespace Tomahawk; + + +void DatabaseCommand_LoadAllSources::exec( DatabaseImpl* dbi ) +{ + TomahawkSqlQuery query = dbi->newquery(); + + query.exec( QString( "SELECT id, name, friendlyname " + "FROM source" ) ); + + QList sources; + while ( query.next() ) + { + source_ptr src( new Source( query.value( 0 ).toUInt(), query.value( 1 ).toString() ) ); + src->setFriendlyName( query.value( 2 ).toString() ); + sources << src; + } + + emit done( sources ); +} + diff --git a/src/libtomahawk/database/databasecommand_loadallsources.h b/src/libtomahawk/database/databasecommand_loadallsources.h new file mode 100644 index 000000000..46ef0ec2c --- /dev/null +++ b/src/libtomahawk/database/databasecommand_loadallsources.h @@ -0,0 +1,47 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_LOADALLSOURCES_H +#define DATABASECOMMAND_LOADALLSOURCES_H + +#include +#include + +#include "databasecommand.h" +#include "typedefs.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_LoadAllSources : public DatabaseCommand +{ +Q_OBJECT + +public: + explicit DatabaseCommand_LoadAllSources( QObject* parent = 0 ) + : DatabaseCommand( parent ) + {} + + virtual void exec( DatabaseImpl* ); + virtual bool doesMutates() const { return false; } + virtual QString commandname() const { return "loadallsources"; } + +signals: + void done( const QList& sources ); +}; + +#endif // DATABASECOMMAND_LOADALLSOURCES_H diff --git a/src/libtomahawk/database/databasecommand_loaddynamicplaylist.cpp b/src/libtomahawk/database/databasecommand_loaddynamicplaylist.cpp new file mode 100644 index 000000000..eb80312b0 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_loaddynamicplaylist.cpp @@ -0,0 +1,113 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_loaddynamicplaylist.h" + +#include +#include + +#include "databaseimpl.h" +#include "tomahawksqlquery.h" +#include "dynamic/DynamicControl.h" +#include "dynamic/GeneratorInterface.h" +#include + +using namespace Tomahawk; + + +void +DatabaseCommand_LoadDynamicPlaylist::exec( DatabaseImpl* dbi ) +{ + qDebug() << "Loading dynamic playlist guid" << guid(); + // load the entries first + generateEntries( dbi ); + + // now load the controls etc + + TomahawkSqlQuery controlsQuery = dbi->newquery(); + controlsQuery.prepare("SELECT playlist_revision.playlist, controls, plmode, pltype " + "FROM dynamic_playlist_revision, playlist_revision " + "WHERE dynamic_playlist_revision.guid = ? AND playlist_revision.guid = dynamic_playlist_revision.guid"); + controlsQuery.addBindValue( revisionGuid() ); + controlsQuery.exec(); + + QString type; + GeneratorMode mode; + + QList< QVariantMap > controls; + QString playlist_guid; + qDebug() << "Loading controls..." << revisionGuid(); + qDebug() << "SELECT playlist_revision.playlist, controls, plmode, pltype " + "FROM dynamic_playlist_revision, playlist_revision " + "WHERE dynamic_playlist_revision.guid = "<< revisionGuid() << " AND playlist_revision.guid = dynamic_playlist_revision.guid"; + if( controlsQuery.first() ) + { + playlist_guid = controlsQuery.value( 0 ).toString(); + QJson::Parser parser; + bool ok; + QVariant v = parser.parse( controlsQuery.value(1).toByteArray(), &ok ); + Q_ASSERT( ok && v.type() == QVariant::List ); //TODO + + + type = controlsQuery.value( 3 ).toString(); + mode = static_cast( controlsQuery.value( 2 ).toInt() ); + + QStringList controlIds = v.toStringList(); + qDebug() << "Got controls in dynamic playlist, loading:" << controlIds << controlsQuery.value(1); + foreach( const QString& controlId, controlIds ) + { + TomahawkSqlQuery controlQuery = dbi->newquery(); + controlQuery.prepare( "SELECT selectedType, match, input " + "FROM dynamic_playlist_controls " + "WHERE id = :id" ); + controlQuery.bindValue( ":id", controlId ); + controlQuery.exec(); + if( controlQuery.next() ) + { + QVariantMap c; + c[ "type" ] = type; + c[ "id" ] = controlId; + c[ "selectedType" ] = controlQuery.value( 0 ).toString(); + c[ "match" ] = controlQuery.value( 1 ).toString(); + c[ "input" ] = controlQuery.value( 2 ).toString(); + controls << c; + } + } + } else { + // No controls, lets load the info we need directly from the playlist table + TomahawkSqlQuery info = dbi->newquery(); + info.prepare( QString( "SELECT dynamic_playlist.pltype, dynamic_playlist.plmode FROM playlist, dynamic_playlist WHERE playlist.guid = \"%1\" AND playlist.guid = dynamic_playlist.guid" ).arg( playlist_guid ) ); + if( !info.exec() ) { + qWarning() << "Failed to load dynplaylist info.."; + return; + } else if( !info.first() ) { + qWarning() << "Noo results for queryL:" << info.lastQuery(); + return; + } + type = info.value( 0 ).toString(); + mode = static_cast( info.value( 1 ).toInt() ); + } + + if( mode == OnDemand ) { + Q_ASSERT( m_entrymap.isEmpty() ); // ondemand should have no entry + + emit done( revisionGuid(), m_islatest, type, controls, true ); + } else { + emit done( revisionGuid(), m_guids, m_oldentries, type, controls, m_islatest, m_entrymap, true ); + } +} diff --git a/src/libtomahawk/database/databasecommand_loaddynamicplaylist.h b/src/libtomahawk/database/databasecommand_loaddynamicplaylist.h new file mode 100644 index 000000000..e797e1cec --- /dev/null +++ b/src/libtomahawk/database/databasecommand_loaddynamicplaylist.h @@ -0,0 +1,65 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_LOADDYNAMICPLAYLIST_H +#define DATABASECOMMAND_LOADDYNAMICPLAYLIST_H + +#include +#include + +#include "typedefs.h" +#include "databasecommand.h" +#include "databasecommand_loadplaylistentries.h" +#include "playlist.h" +#include "dynamic/DynamicControl.h" + +class DatabaseCommand_LoadDynamicPlaylist : public DatabaseCommand_LoadPlaylistEntries +{ + Q_OBJECT + +public: + explicit DatabaseCommand_LoadDynamicPlaylist( QString revision_guid, QObject* parent = 0 ) + : DatabaseCommand_LoadPlaylistEntries( revision_guid, parent ) + { qDebug() << "loaded with:" << revision_guid << guid(); } + + + virtual void exec( DatabaseImpl* ); + virtual bool doesMutates() const { return false; } + virtual QString commandname() const { return "loaddynamicplaylist"; } + +signals: + // used if loading an ondemand playlist + void done( QString, + bool, + QString, + QList< QVariantMap>, + bool ); + // used when loading a static playlist + void done( QString, + QList< QString >, + QList< QString >, + QString, + QList< QVariantMap>, + bool, + QMap< QString, Tomahawk::plentry_ptr >, + bool ); + +private: +}; + +#endif // DATABASECOMMAND_LOADDYNAMICPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_loadfile.cpp b/src/libtomahawk/database/databasecommand_loadfile.cpp new file mode 100644 index 000000000..8890b3ee5 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_loadfile.cpp @@ -0,0 +1,48 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_loadfile.h" + +#include "databaseimpl.h" +#include "collection.h" + + +DatabaseCommand_LoadFile::DatabaseCommand_LoadFile( const QString& id, QObject* parent ) + : DatabaseCommand( parent ) + , m_id( id ) +{ +} + + +void +DatabaseCommand_LoadFile::exec( DatabaseImpl* dbi ) +{ + Tomahawk::result_ptr r; + // file ids internally are really ints, at least for now: + bool ok; + do + { + unsigned int fid = m_id.toInt( &ok ); + if( !ok ) + break; + + r = dbi->file( fid ); + } while( false ); + + emit result( r ); +} diff --git a/src/libtomahawk/database/databasecommand_loadfile.h b/src/libtomahawk/database/databasecommand_loadfile.h new file mode 100644 index 000000000..7d40c281c --- /dev/null +++ b/src/libtomahawk/database/databasecommand_loadfile.h @@ -0,0 +1,47 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_LOADFILE_H +#define DATABASECOMMAND_LOADFILE_H + +#include +#include +#include + +#include "databasecommand.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_LoadFile : public DatabaseCommand +{ +Q_OBJECT + +public: + explicit DatabaseCommand_LoadFile( const QString& id, QObject* parent = 0 ); + virtual void exec( DatabaseImpl* ); + virtual bool doesMutates() const { return false; } + virtual QString commandname() const { return "loadfile"; } + +signals: + void result( const Tomahawk::result_ptr& result ); + +private: + QString m_id; +}; + +#endif // DATABASECOMMAND_LOADFILE_H diff --git a/src/libtomahawk/database/databasecommand_loadops.cpp b/src/libtomahawk/database/databasecommand_loadops.cpp new file mode 100644 index 000000000..afc131da4 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_loadops.cpp @@ -0,0 +1,55 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_loadops.h" + + +void +DatabaseCommand_loadOps::exec( DatabaseImpl* dbi ) +{ + QList< dbop_ptr > ops; + + TomahawkSqlQuery query = dbi->newquery(); + query.prepare( QString( + "SELECT guid, command, json, compressed, singleton " + "FROM oplog " + "WHERE source %1 " + "AND id > coalesce((SELECT id FROM oplog WHERE guid = ?),0) " + "ORDER BY id ASC" + ).arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) ) + ); + query.addBindValue( m_since ); + query.exec(); + + QString lastguid = m_since; + while( query.next() ) + { + dbop_ptr op( new DBOp ); + op->guid = query.value( 0 ).toString(); + op->command = query.value( 1 ).toString(); + op->payload = query.value( 2 ).toByteArray(); + op->compressed = query.value( 3 ).toBool(); + op->singleton = query.value( 4 ).toBool(); + + lastguid = op->guid; + ops << op; + } + +// qDebug() << "Loaded" << ops.length() << "ops from db"; + emit done( m_since, lastguid, ops ); +} diff --git a/src/libtomahawk/database/databasecommand_loadops.h b/src/libtomahawk/database/databasecommand_loadops.h new file mode 100644 index 000000000..6a3580f42 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_loadops.h @@ -0,0 +1,48 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_LOADOPS_H +#define DATABASECOMMAND_LOADOPS_H + +#include "typedefs.h" +#include "databasecommand.h" +#include "databaseimpl.h" +#include "op.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_loadOps : public DatabaseCommand +{ +Q_OBJECT +public: + explicit DatabaseCommand_loadOps( const Tomahawk::source_ptr& src, QString since, QObject* parent = 0 ) + : DatabaseCommand( src ), m_since( since ) + {} + + virtual void exec( DatabaseImpl* db ); + virtual bool doesMutates() const { return false; } + virtual QString commandname() const { return "loadops"; } + +signals: + void done( QString sinceguid, QString lastguid, QList< dbop_ptr > ops ); + +private: + QString m_since; // guid to load from +}; + +#endif // DATABASECOMMAND_LOADOPS_H diff --git a/src/database/databasecommand_loadplaylistentries.cpp b/src/libtomahawk/database/databasecommand_loadplaylistentries.cpp similarity index 54% rename from src/database/databasecommand_loadplaylistentries.cpp rename to src/libtomahawk/database/databasecommand_loadplaylistentries.cpp index f29b77813..20143dd53 100644 --- a/src/database/databasecommand_loadplaylistentries.cpp +++ b/src/libtomahawk/database/databasecommand_loadplaylistentries.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "databasecommand_loadplaylistentries.h" #include @@ -10,41 +28,44 @@ using namespace Tomahawk; void DatabaseCommand_LoadPlaylistEntries::exec( DatabaseImpl* dbi ) { - qDebug() << "Loading playlist entries for revision" << m_guid; + qDebug() << "Loading playlist entries for revision" << m_revguid; + generateEntries( dbi ); + + emit done( m_revguid, m_guids, m_oldentries, m_islatest, m_entrymap, true ); +} + +void +DatabaseCommand_LoadPlaylistEntries::generateEntries( DatabaseImpl* dbi ) +{ TomahawkSqlQuery query_entries = dbi->newquery(); query_entries.prepare("SELECT entries, playlist, author, timestamp, previous_revision " "FROM playlist_revision " "WHERE guid = :guid"); - query_entries.bindValue( ":guid", m_guid ); - - bool aok = query_entries.exec(); - Q_ASSERT( aok ); - - QStringList guids; - QMap< QString, plentry_ptr > entrymap; - bool islatest = true; - QStringList oldentries; + query_entries.bindValue( ":guid", m_revguid ); + query_entries.exec(); + + qDebug() << "trying to load entries:" << m_revguid; QString prevrev; QJson::Parser parser; bool ok; - + if( query_entries.next() ) { // entries should be a list of strings: QVariant v = parser.parse( query_entries.value(0).toByteArray(), &ok ); Q_ASSERT( ok && v.type() == QVariant::List ); //TODO - guids = v.toStringList(); -// qDebug() << "Entries:" << guids; - - QString inclause = QString("('%1')").arg(guids.join("', '")); - + m_guids = v.toStringList(); + // qDebug() << "Entries:" << guids; + + QString inclause = QString("('%1')").arg(m_guids.join("', '")); + TomahawkSqlQuery query = dbi->newquery(); QString sql = QString("SELECT guid, trackname, artistname, albumname, annotation, " "duration, addedon, addedby, result_hint " "FROM playlist_item " "WHERE guid IN %1").arg( inclause ); //qDebug() << sql; - + query.exec( sql ); while( query.next() ) { @@ -53,27 +74,23 @@ DatabaseCommand_LoadPlaylistEntries::exec( DatabaseImpl* dbi ) e->setAnnotation( query.value( 4 ).toString() ); e->setDuration( query.value( 5 ).toUInt() ); e->setLastmodified( 0 ); // TODO e->lastmodified = query.value(6).toInt(); - e->setResulthint( query.value( 8 ).toString() ); - - QVariantMap m; - m.insert( "artist", query.value( 2 ).toString() ); - m.insert( "album", query.value( 3 ).toString() ); - m.insert( "track", query.value( 1 ).toString() ); - m.insert( "qid", uuid() ); - - Tomahawk::query_ptr q( new Tomahawk::Query( m ) ); + e->setResultHint( query.value( 8 ).toString() ); + + Tomahawk::query_ptr q = Tomahawk::Query::get( query.value( 2 ).toString(), query.value( 1 ).toString(), query.value( 3 ).toString() ); + q->setResultHint( query.value( 8 ).toString() ); e->setQuery( q ); - - entrymap.insert( e->guid(), e ); + + m_entrymap.insert( e->guid(), e ); } - + prevrev = query_entries.value( 4 ).toString(); + } else { qDebug() << "Playlist has no current revision data"; } - + if( prevrev.length() ) { TomahawkSqlQuery query_entries_old = dbi->newquery(); @@ -81,24 +98,22 @@ DatabaseCommand_LoadPlaylistEntries::exec( DatabaseImpl* dbi ) "(SELECT currentrevision = ? FROM playlist WHERE guid = ?) " "FROM playlist_revision " "WHERE guid = ?" ); - query_entries_old.addBindValue( m_guid ); + query_entries_old.addBindValue( m_revguid ); query_entries_old.addBindValue( query_entries.value( 1 ).toString() ); query_entries_old.addBindValue( prevrev ); - + query_entries_old.exec(); if( !query_entries_old.next() ) { return; Q_ASSERT( false ); } - + QVariant v = parser.parse( query_entries_old.value( 0 ).toByteArray(), &ok ); Q_ASSERT( ok && v.type() == QVariant::List ); //TODO - oldentries = v.toStringList(); - islatest = query_entries_old.value( 1 ).toBool(); + m_oldentries = v.toStringList(); + m_islatest = query_entries_old.value( 1 ).toBool(); } - - qDebug() << Q_FUNC_INFO << "entrymap:" << entrymap; - - emit done( m_guid, guids, oldentries, islatest, entrymap, true ); + + qDebug() << Q_FUNC_INFO << "entrymap:" << m_entrymap; } diff --git a/src/libtomahawk/database/databasecommand_loadplaylistentries.h b/src/libtomahawk/database/databasecommand_loadplaylistentries.h new file mode 100644 index 000000000..ec597ef67 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_loadplaylistentries.h @@ -0,0 +1,64 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_LOADPLAYLIST_H +#define DATABASECOMMAND_LOADPLAYLIST_H + +#include +#include + +#include "databasecommand.h" +#include "playlist.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_LoadPlaylistEntries : public DatabaseCommand +{ +Q_OBJECT + +public: + explicit DatabaseCommand_LoadPlaylistEntries( QString revision_guid, QObject* parent = 0 ) + : DatabaseCommand( parent ), m_revguid( revision_guid ), m_islatest( true ) + {} + + virtual void exec( DatabaseImpl* ); + virtual bool doesMutates() const { return false; } + virtual QString commandname() const { return "loadplaylistentries"; } + + QString revisionGuid() const { return m_revguid; } +signals: + void done( const QString& rev, + const QList& orderedguid, + const QList& oldorderedguid, + bool islatest, + const QMap< QString, Tomahawk::plentry_ptr >& added, + bool applied ); + +protected: + void generateEntries( DatabaseImpl* dbi ); + + QStringList m_guids; + QMap< QString, Tomahawk::plentry_ptr > m_entrymap; + bool m_islatest; + QStringList m_oldentries; + +private: + QString m_revguid; +}; + +#endif diff --git a/src/libtomahawk/database/databasecommand_logplayback.cpp b/src/libtomahawk/database/databasecommand_logplayback.cpp new file mode 100644 index 000000000..9254ea979 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_logplayback.cpp @@ -0,0 +1,96 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_logplayback.h" + +#include + +#include "collection.h" +#include "database/database.h" +#include "databaseimpl.h" +#include "network/servent.h" + +using namespace Tomahawk; + + +// After changing a collection, we need to tell other bits of the system: +void +DatabaseCommand_LogPlayback::postCommitHook() +{ + qDebug() << Q_FUNC_INFO; + if ( source().isNull() || source()->collection().isNull() ) + { + qDebug() << "Source has gone offline, not emitting to GUI."; + return; + } + + connect( this, SIGNAL( trackPlaying( Tomahawk::query_ptr ) ), + source().data(), SLOT( onPlaybackStarted( Tomahawk::query_ptr ) ), Qt::QueuedConnection ); + connect( this, SIGNAL( trackPlayed( Tomahawk::query_ptr ) ), + source().data(), SLOT( onPlaybackFinished( Tomahawk::query_ptr ) ), Qt::QueuedConnection ); + + Tomahawk::query_ptr q = Tomahawk::Query::get( m_artist, m_track, QString(), uuid() ); + + if ( m_action == Finished ) + { + emit trackPlayed( q ); + } + else if ( m_action == Started ) + { + emit trackPlaying( q ); + } + + if( source()->isLocal() ) + Servent::instance()->triggerDBSync(); +} + + +void +DatabaseCommand_LogPlayback::exec( DatabaseImpl* dbi ) +{ + qDebug() << Q_FUNC_INFO; + Q_ASSERT( !source().isNull() ); + + if ( m_action != Finished ) + return; + + TomahawkSqlQuery query = dbi->newquery(); + query.prepare( "INSERT INTO playback_log(source, track, playtime, secs_played) " + "VALUES (?, ?, ?, ?)" ); + + QVariant srcid = source()->isLocal() ? QVariant( QVariant::Int ) : source()->id(); + + qDebug() << "Logging playback of" << m_artist << "-" << m_track << "for source" << srcid; + + query.bindValue( 0, srcid ); + + bool isnew; + int artid = dbi->artistId( m_artist, isnew ); + if( artid < 1 ) + return; + + int trkid = dbi->trackId( artid, m_track, isnew ); + if( trkid < 1 ) + return; + + query.bindValue( 1, trkid ); + query.bindValue( 2, m_playtime ); + query.bindValue( 3, m_secsPlayed ); + + query.exec(); +} diff --git a/src/libtomahawk/database/databasecommand_logplayback.h b/src/libtomahawk/database/databasecommand_logplayback.h new file mode 100644 index 000000000..ab1f7b8ed --- /dev/null +++ b/src/libtomahawk/database/databasecommand_logplayback.h @@ -0,0 +1,101 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_LOGPLAYBACK_H +#define DATABASECOMMAND_LOGPLAYBACK_H + +#include +#include + +#include "database/databasecommandloggable.h" +#include "sourcelist.h" +#include "typedefs.h" +#include "query.h" +#include "result.h" +#include "artist.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_LogPlayback : public DatabaseCommandLoggable +{ +Q_OBJECT +Q_PROPERTY( QString artist READ artist WRITE setArtist ) +Q_PROPERTY( QString track READ track WRITE setTrack ) +Q_PROPERTY( unsigned int playtime READ playtime WRITE setPlaytime ) +Q_PROPERTY( unsigned int secsPlayed READ secsPlayed WRITE setSecsPlayed ) +Q_PROPERTY( int action READ action WRITE setAction ) + +public: + enum Action + { + Started = 1, + Finished = 2 + }; + + explicit DatabaseCommand_LogPlayback( QObject* parent = 0 ) + : DatabaseCommandLoggable( parent ) + {} + + explicit DatabaseCommand_LogPlayback( const Tomahawk::result_ptr& result, Action action, unsigned int secsPlayed = 0, QObject* parent = 0 ) + : DatabaseCommandLoggable( parent ), m_result( result ), m_secsPlayed( secsPlayed ), m_action( action ) + { + m_playtime = QDateTime::currentDateTimeUtc().toTime_t(); + setSource( SourceList::instance()->getLocal() ); + + setArtist( result->artist()->name() ); + setTrack( result->track() ); + } + + virtual QString commandname() const { return "logplayback"; } + + virtual void exec( DatabaseImpl* ); + virtual void postCommitHook(); + + virtual bool doesMutates() const { return true; } + virtual bool singletonCmd() const { return ( m_action == Started ); } + + QString artist() const { return m_artist; } + void setArtist( const QString& s ) { m_artist = s; } + + QString track() const { return m_track; } + void setTrack( const QString& s ) { m_track = s; } + + unsigned int playtime() const { return m_playtime; } + void setPlaytime( unsigned int i ) { m_playtime = i; } + + unsigned int secsPlayed() const { return m_secsPlayed; } + void setSecsPlayed( unsigned int i ) { m_secsPlayed = i; } + + int action() const { return m_action; } + void setAction( int a ) { m_action = (Action)a; } + +signals: + void trackPlaying( const Tomahawk::query_ptr& query ); + void trackPlayed( const Tomahawk::query_ptr& query ); + +private: + Tomahawk::result_ptr m_result; + + QString m_artist; + QString m_track; + unsigned int m_playtime; + unsigned int m_secsPlayed; + Action m_action; +}; + +#endif // DATABASECOMMAND_LOGPLAYBACK_H diff --git a/src/libtomahawk/database/databasecommand_modifyplaylist.cpp b/src/libtomahawk/database/databasecommand_modifyplaylist.cpp new file mode 100644 index 000000000..640fe8a3c --- /dev/null +++ b/src/libtomahawk/database/databasecommand_modifyplaylist.cpp @@ -0,0 +1,35 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_modifyplaylist.h" + +using namespace Tomahawk; + + +DatabaseCommand_ModifyPlaylist::DatabaseCommand_ModifyPlaylist( Playlist* playlist, const QList< plentry_ptr >& entries, Mode mode ) + : DatabaseCommand() + , m_playlist( playlist ) + , m_entries( entries ) + , m_mode( mode ) +{ +} + + +void DatabaseCommand_ModifyPlaylist::exec( DatabaseImpl* lib ) +{ +} diff --git a/src/libtomahawk/database/databasecommand_modifyplaylist.h b/src/libtomahawk/database/databasecommand_modifyplaylist.h new file mode 100644 index 000000000..fd2f6e7d9 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_modifyplaylist.h @@ -0,0 +1,59 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_MODIFYPLAYLIST_H +#define DATABASECOMMAND_MODIFYPLAYLIST_H + +#include +#include + +#include "databasecommand.h" +#include "source.h" +#include "playlist.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_ModifyPlaylist : public DatabaseCommand +{ +Q_OBJECT +Q_PROPERTY( int mode READ mode WRITE setMode ) + +public: + enum Mode + { + ADD = 1, + REMOVE = 2, + UPDATE = 3 + }; + + explicit DatabaseCommand_ModifyPlaylist( Tomahawk::Playlist* playlist, const QList< Tomahawk::plentry_ptr >& entries, Mode mode ); + + virtual bool doesMutates() const { return true; } + + virtual void exec( DatabaseImpl* lib ); + + int mode() const { return m_mode; } + void setMode( int m ) { m_mode = (Mode)m; } + +private: + Tomahawk::Playlist* m_playlist; + QList< Tomahawk::plentry_ptr > m_entries; + Mode m_mode; +}; + +#endif // DATABASECOMMAND_MODIFYPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_playbackhistory.cpp b/src/libtomahawk/database/databasecommand_playbackhistory.cpp new file mode 100644 index 000000000..853bc2a03 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_playbackhistory.cpp @@ -0,0 +1,74 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_playbackhistory.h" + +#include + +#include "databaseimpl.h" + + +void +DatabaseCommand_PlaybackHistory::exec( DatabaseImpl* dbi ) +{ + TomahawkSqlQuery query = dbi->newquery(); + QList ql; + + QString whereToken; + if ( !source().isNull() ) + { + whereToken = QString( "WHERE source %1" ).arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) ); + } + + QString sql = QString( + "SELECT track, playtime, secs_played " + "FROM playback_log " + "%1 " + "ORDER BY playtime DESC " + "%2" ).arg( whereToken ) + .arg( m_amount > 0 ? QString( "LIMIT 0, %1" ).arg( m_amount ) : QString() ); + + query.prepare( sql ); + query.exec(); + + while( query.next() ) + { + TomahawkSqlQuery query_track = dbi->newquery(); + + QString sql = QString( + "SELECT track.name, artist.name " + "FROM track, artist " + "WHERE artist.id = track.artist " + "AND track.id = %1" + ).arg( query.value( 0 ).toUInt() ); + + query_track.prepare( sql ); + query_track.exec(); + + if ( query_track.next() ) + { + Tomahawk::query_ptr q = Tomahawk::Query::get( query_track.value( 1 ).toString(), query_track.value( 0 ).toString(), QString(), uuid() ); + ql << q; + } + } + + qDebug() << Q_FUNC_INFO << ql.length(); + + if ( ql.count() ) + emit tracks( ql ); +} diff --git a/src/libtomahawk/database/databasecommand_playbackhistory.h b/src/libtomahawk/database/databasecommand_playbackhistory.h new file mode 100644 index 000000000..662e66ff2 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_playbackhistory.h @@ -0,0 +1,56 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_PLAYBACKHISTORY_H +#define DATABASECOMMAND_PLAYBACKHISTORY_H + +#include +#include + +#include "databasecommand.h" +#include "source.h" +#include "typedefs.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_PlaybackHistory : public DatabaseCommand +{ +Q_OBJECT +public: + explicit DatabaseCommand_PlaybackHistory( const Tomahawk::source_ptr& source, QObject* parent = 0 ) + : DatabaseCommand( parent ) + , m_amount( 0 ) + { + setSource( source ); + } + + virtual void exec( DatabaseImpl* ); + + virtual bool doesMutates() const { return false; } + virtual QString commandname() const { return "playbackhistory"; } + + void setLimit( unsigned int amount ) { m_amount = amount; } + +signals: + void tracks( const QList& queries ); + +private: + unsigned int m_amount; +}; + +#endif // DATABASECOMMAND_PLAYBACKHISTORY_H diff --git a/src/libtomahawk/database/databasecommand_renameplaylist.cpp b/src/libtomahawk/database/databasecommand_renameplaylist.cpp new file mode 100644 index 000000000..4187ae27e --- /dev/null +++ b/src/libtomahawk/database/databasecommand_renameplaylist.cpp @@ -0,0 +1,74 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_renameplaylist.h" + +#include + +#include "network/servent.h" + +using namespace Tomahawk; + + +DatabaseCommand_RenamePlaylist::DatabaseCommand_RenamePlaylist( const source_ptr& source, const QString& playlistguid, const QString& playlistTitle ) + : DatabaseCommandLoggable( source ) +{ + setPlaylistguid( playlistguid ); + setPlaylistTitle( playlistTitle ); +} + + +void +DatabaseCommand_RenamePlaylist::exec( DatabaseImpl* lib ) +{ + qDebug() << Q_FUNC_INFO; + + TomahawkSqlQuery cre = lib->newquery(); + + QString sql = QString( "UPDATE playlist SET title = :title WHERE guid = :id AND source %1" ) + .arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) ); + + cre.prepare( sql ); + cre.bindValue( ":id", m_playlistguid ); + cre.bindValue( ":title", m_playlistTitle ); + + qDebug() << Q_FUNC_INFO << m_playlistTitle << m_playlistguid; + + cre.exec(); +} + + +void +DatabaseCommand_RenamePlaylist::postCommitHook() +{ + qDebug() << Q_FUNC_INFO << "..reporting.."; + if ( source().isNull() || source()->collection().isNull() ) + { + qDebug() << "Source has gone offline, not emitting to GUI."; + return; + } + + playlist_ptr playlist = source()->collection()->playlist( m_playlistguid ); + Q_ASSERT( !playlist.isNull() ); + + qDebug() << "Renaming old playlist" << playlist->title() << "to" << m_playlistTitle << m_playlistguid; + playlist->setTitle( m_playlistTitle ); + + if( source()->isLocal() ) + Servent::instance()->triggerDBSync(); +} diff --git a/src/database/databasecommand_renameplaylist.h b/src/libtomahawk/database/databasecommand_renameplaylist.h similarity index 54% rename from src/database/databasecommand_renameplaylist.h rename to src/libtomahawk/database/databasecommand_renameplaylist.h index a5e3c186e..9b0015b70 100644 --- a/src/database/databasecommand_renameplaylist.h +++ b/src/libtomahawk/database/databasecommand_renameplaylist.h @@ -1,12 +1,32 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_RENAMEPLAYLIST_H #define DATABASECOMMAND_RENAMEPLAYLIST_H #include "databaseimpl.h" #include "databasecommandloggable.h" -#include "tomahawk/source.h" -#include "tomahawk/typedefs.h" +#include "source.h" +#include "typedefs.h" -class DatabaseCommand_RenamePlaylist : public DatabaseCommandLoggable +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_RenamePlaylist : public DatabaseCommandLoggable { Q_OBJECT Q_PROPERTY( QString playlistguid READ playlistguid WRITE setPlaylistguid ) diff --git a/src/libtomahawk/database/databasecommand_resolve.cpp b/src/libtomahawk/database/databasecommand_resolve.cpp new file mode 100644 index 000000000..f6f2b1c42 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_resolve.cpp @@ -0,0 +1,273 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_resolve.h" + +#include "album.h" +#include "sourcelist.h" + +#define MINSCORE 0.5 + +using namespace Tomahawk; + + +DatabaseCommand_Resolve::DatabaseCommand_Resolve( const query_ptr& query ) + : DatabaseCommand() + , m_query( query ) +{ +} + + +void +DatabaseCommand_Resolve::exec( DatabaseImpl* lib ) +{ + QList res; + if ( !m_query->resultHint().isEmpty() ) + { + qDebug() << "Using result-hint to speed up resolving:" << m_query->resultHint(); + + Tomahawk::result_ptr result = lib->result( m_query->resultHint() ); + if ( !result.isNull() && result->collection()->source()->isOnline() ) + { + res << result; + emit results( m_query->id(), res ); + return; + } + } + + /* + Resolving is a 2 stage process. + 1) find list of trk/art/alb IDs that are reasonable matches to the metadata given + 2) find files in database by permitted sources and calculate score, ignoring + results that are less than MINSCORE + */ + + typedef QPair scorepair_t; + + // STEP 1 + QList< int > artists = lib->searchTable( "artist", m_query->artist(), 10 ); + QList< int > tracks = lib->searchTable( "track", m_query->track(), 10 ); + QList< int > albums = lib->searchTable( "album", m_query->album(), 10 ); + + if( artists.length() == 0 || tracks.length() == 0 ) + { + qDebug() << "No candidates found in first pass, aborting resolve" << m_query->artist() << m_query->track(); + emit results( m_query->id(), res ); + return; + } + + // STEP 2 + TomahawkSqlQuery files_query = lib->newquery(); + + QStringList artsl, trksl; + foreach( int i, artists ) + artsl.append( QString::number( i ) ); + foreach( int i, tracks ) + trksl.append( QString::number( i ) ); + + QString sql = QString( "SELECT " + "url, mtime, size, md5, mimetype, duration, bitrate, file_join.artist, file_join.album, file_join.track, " + "artist.name as artname, " + "album.name as albname, " + "track.name as trkname, " + "file.source, " + "file_join.albumpos, " + "artist.id as artid, " + "album.id as albid " + "FROM file, file_join, artist, track " + "LEFT JOIN album ON album.id = file_join.album " + "WHERE " + "artist.id = file_join.artist AND " + "track.id = file_join.track AND " + "file.id = file_join.file AND " + "file_join.artist IN (%1) AND " + "file_join.track IN (%2)" ) + .arg( artsl.join( "," ) ) + .arg( trksl.join( "," ) ); + + files_query.prepare( sql ); + files_query.exec(); + + while( files_query.next() ) + { + Tomahawk::result_ptr result( new Tomahawk::Result() ); + source_ptr s; + + const QString url_str = files_query.value( 0 ).toString(); + if( files_query.value( 13 ).toUInt() == 0 ) + { + s = SourceList::instance()->getLocal(); + result->setUrl( url_str ); + } + else + { + s = SourceList::instance()->get( files_query.value( 13 ).toUInt() ); + if( s.isNull() ) + { + Q_ASSERT( false ); + continue; + } + + result->setUrl( QString( "servent://%1\t%2" ).arg( s->userName() ).arg( url_str ) ); + } + + Tomahawk::artist_ptr artist = Tomahawk::Artist::get( files_query.value( 15 ).toUInt(), files_query.value( 10 ).toString() ); + Tomahawk::album_ptr album = Tomahawk::Album::get( files_query.value( 16 ).toUInt(), files_query.value( 11 ).toString(), artist ); + + result->setModificationTime( files_query.value( 1 ).toUInt() ); + result->setSize( files_query.value( 2 ).toUInt() ); + result->setMimetype( files_query.value( 4 ).toString() ); + result->setDuration( files_query.value( 5 ).toUInt() ); + result->setBitrate( files_query.value( 6 ).toUInt() ); + result->setArtist( artist ); + result->setAlbum( album ); + result->setTrack( files_query.value( 12 ).toString() ); + result->setRID( uuid() ); + result->setAlbumPos( files_query.value( 14 ).toUInt() ); + result->setId( files_query.value( 9 ).toUInt() ); + + float score = how_similar( m_query, result ); + result->setScore( score ); + if( score < MINSCORE ) + continue; + + result->setCollection( s->collection() ); + res << result; + } + + emit results( m_query->id(), res ); +} + + +// TODO make clever (ft. featuring live (stuff) etc) +float +DatabaseCommand_Resolve::how_similar( const Tomahawk::query_ptr& q, const Tomahawk::result_ptr& r ) +{ + // query values + const QString qArtistname = DatabaseImpl::sortname( q->artist() ); + const QString qAlbumname = DatabaseImpl::sortname( q->album() ); + const QString qTrackname = DatabaseImpl::sortname( q->track() ); + + // result values + const QString rArtistname = DatabaseImpl::sortname( r->artist()->name() ); + const QString rAlbumname = DatabaseImpl::sortname( r->album()->name() ); + const QString rTrackname = DatabaseImpl::sortname( r->track() ); + + // normal edit distance + int artdist = levenshtein( qArtistname, rArtistname ); + int albdist = levenshtein( qAlbumname, rAlbumname ); + int trkdist = levenshtein( qTrackname, rTrackname ); + + // max length of name + int mlart = qMax( qArtistname.length(), rArtistname.length() ); + int mlalb = qMax( qAlbumname.length(), rAlbumname.length() ); + int mltrk = qMax( qTrackname.length(), rTrackname.length() ); + + // distance scores + float dcart = (float)( mlart - artdist ) / mlart; + float dcalb = (float)( mlalb - albdist ) / mlalb; + float dctrk = (float)( mltrk - trkdist ) / mltrk; + + // don't penalize for missing album name + if( qAlbumname.length() == 0 ) + dcalb = 1.0; + + // weighted, so album match is worth less than track title + float combined = ( dcart*4 + dcalb + dctrk*5 ) / 10; + return combined; +} + + +int +DatabaseCommand_Resolve::levenshtein( const QString& source, const QString& target ) +{ + // Step 1 + const int n = source.length(); + const int m = target.length(); + + if ( n == 0 ) + return m; + if ( m == 0 ) + return n; + + // Good form to declare a TYPEDEF + typedef QVector< QVector > Tmatrix; + Tmatrix matrix; + matrix.resize( n + 1 ); + + // Size the vectors in the 2.nd dimension. Unfortunately C++ doesn't + // allow for allocation on declaration of 2.nd dimension of vec of vec + for ( int i = 0; i <= n; i++ ) + { + QVector tmp; + tmp.resize( m + 1 ); + matrix.insert( i, tmp ); + } + + // Step 2 + for ( int i = 0; i <= n; i++ ) + matrix[i][0] = i; + for ( int j = 0; j <= m; j++ ) + matrix[0][j] = j; + + // Step 3 + for ( int i = 1; i <= n; i++ ) + { + const QChar s_i = source[i - 1]; + + // Step 4 + for ( int j = 1; j <= m; j++ ) + { + const QChar t_j = target[j - 1]; + + // Step 5 + int cost; + if ( s_i == t_j ) + cost = 0; + else + cost = 1; + + // Step 6 + const int above = matrix[i - 1][j]; + const int left = matrix[i][j - 1]; + const int diag = matrix[i - 1][j - 1]; + + int cell = ( ((left + 1) > (diag + cost)) ? diag + cost : left + 1 ); + if( above + 1 < cell ) + cell = above + 1; + + // Step 6A: Cover transposition, in addition to deletion, + // insertion and substitution. This step is taken from: + // Berghel, Hal ; Roach, David : "An Extension of Ukkonen's + // Enhanced Dynamic Programming ASM Algorithm" + // (http://www.acm.org/~hlb/publications/asm/asm.html) + if ( i > 2 && j > 2 ) + { + int trans = matrix[i - 2][j - 2] + 1; + + if ( source[ i - 2 ] != t_j ) trans++; + if ( s_i != target[ j - 2 ] ) trans++; + if ( cell > trans) cell = trans; + } + matrix[i][j] = cell; + } + } + + // Step 7 + return matrix[n][m]; +} diff --git a/src/libtomahawk/database/databasecommand_resolve.h b/src/libtomahawk/database/databasecommand_resolve.h new file mode 100644 index 000000000..8c194451e --- /dev/null +++ b/src/libtomahawk/database/databasecommand_resolve.h @@ -0,0 +1,53 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_RESOLVE_H +#define DATABASECOMMAND_RESOLVE_H + +#include "databasecommand.h" +#include "databaseimpl.h" +#include "result.h" + +#include + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_Resolve : public DatabaseCommand +{ +Q_OBJECT +public: + explicit DatabaseCommand_Resolve( const Tomahawk::query_ptr& query ); + + virtual QString commandname() const { return "dbresolve"; } + virtual bool doesMutates() const { return false; } + + virtual void exec( DatabaseImpl *lib ); + +signals: + void results( Tomahawk::QID qid, QList results ); + +public slots: + +private: + Tomahawk::query_ptr m_query; + + float how_similar( const Tomahawk::query_ptr& q, const Tomahawk::result_ptr& r ); + static int levenshtein( const QString& source, const QString& target ); +}; + +#endif // DATABASECOMMAND_RESOLVE_H diff --git a/src/libtomahawk/database/databasecommand_setdynamicplaylistrevision.cpp b/src/libtomahawk/database/databasecommand_setdynamicplaylistrevision.cpp new file mode 100644 index 000000000..39494f3fd --- /dev/null +++ b/src/libtomahawk/database/databasecommand_setdynamicplaylistrevision.cpp @@ -0,0 +1,225 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_setdynamicplaylistrevision.h" + +#include + +#include "tomahawksqlquery.h" +#include "dynamic/DynamicPlaylist.h" +#include "dynamic/DynamicControl.h" + +DatabaseCommand_SetDynamicPlaylistRevision::DatabaseCommand_SetDynamicPlaylistRevision(const Tomahawk::source_ptr& s, + const QString& playlistguid, + const QString& newrev, + const QString& oldrev, + const QStringList& orderedguids, + const QList< plentry_ptr >& addedentries, + const QList& entries, + const QString& type, + GeneratorMode mode, + const QList< dyncontrol_ptr >& controls ) + : DatabaseCommand_SetPlaylistRevision( s, playlistguid, newrev, oldrev, orderedguids, addedentries, entries ) + , m_type( type ) + , m_mode( mode ) + , m_controls( controls ) +{ + +} + +DatabaseCommand_SetDynamicPlaylistRevision::DatabaseCommand_SetDynamicPlaylistRevision(const Tomahawk::source_ptr& s, + const QString& playlistguid, + const QString& newrev, + const QString& oldrev, + const QString& type, + GeneratorMode mode, + const QList< dyncontrol_ptr >& controls ) + : DatabaseCommand_SetPlaylistRevision( s, playlistguid, newrev, oldrev, QStringList(), QList< plentry_ptr >(), QList< plentry_ptr >() ) + , m_type( type ) + , m_mode( mode ) + , m_controls( controls ) +{ + +} + +QVariantList DatabaseCommand_SetDynamicPlaylistRevision::controlsV() +{ + if( m_controls.isEmpty() ) + return m_controlsV; + + if( !m_controls.isEmpty() && m_controlsV.isEmpty() ) + { + foreach( const dyncontrol_ptr& control, m_controls ) + { + m_controlsV << QJson::QObjectHelper::qobject2qvariant( control.data() ); + } + } + return m_controlsV; + +} + +void +DatabaseCommand_SetDynamicPlaylistRevision::postCommitHook() +{ + qDebug() << Q_FUNC_INFO; + if ( source().isNull() || source()->collection().isNull() ) + { + qDebug() << "Source has gone offline, not emitting to GUI."; + return; + } + + QStringList orderedentriesguids; + foreach( const QVariant& v, orderedguids() ) + orderedentriesguids << v.toString(); + + Q_ASSERT( !source().isNull() ); + Q_ASSERT( !source()->collection().isNull() ); + qDebug() << "Postcommitting this playlist:" << playlistguid() << source().isNull() << source().data(); + // private, but we are a friend. will recall itself in its own thread: + dynplaylist_ptr playlist = source()->collection()->dynamicPlaylist( playlistguid() ); + + if ( playlist.isNull() ) + { + qDebug() <<"Got null playlist with guid:" << playlistguid() << "from source and collection:" << source()->friendlyName() << source()->collection()->name(); + Q_ASSERT( !playlist.isNull() ); + return; + } + if( !m_controlsV.isEmpty() && m_controls.isEmpty() ) { + QList controlMap; + foreach( const QVariant& v, m_controlsV ) + controlMap << v.toMap(); + + if( m_mode == OnDemand ) + playlist->setRevision( newrev(), + true, // this *is* the newest revision so far + m_type, + controlMap, + m_applied ); + else + playlist->setRevision( newrev(), + orderedentriesguids, + m_previous_rev_orderedguids, + m_type, + controlMap, + true, // this *is* the newest revision so far + m_addedmap, + m_applied ); + } else { + if( m_mode == OnDemand ) + playlist->setRevision( newrev(), + true, // this *is* the newest revision so far + m_type, + m_controls, + m_applied ); + else + playlist->setRevision( newrev(), + orderedentriesguids, + m_previous_rev_orderedguids, + m_type, + m_controls, + true, // this *is* the newest revision so far + m_addedmap, + m_applied ); + } + if( source()->isLocal() ) + Servent::instance()->triggerDBSync(); +} + + +void +DatabaseCommand_SetDynamicPlaylistRevision::exec( DatabaseImpl* lib ) +{ + DatabaseCommand_SetPlaylistRevision::exec( lib ); + + QVariantList newcontrols; + if( m_controlsV.isEmpty() && !m_controls.isEmpty() ) { + foreach( const dyncontrol_ptr& control, m_controls ) { + newcontrols << control->id(); + } + } else if( !m_controlsV.isEmpty() ) { + foreach( const QVariant& v, m_controlsV ) { + newcontrols << v.toMap().value( "id" ); + } + } + + QJson::Serializer ser; + const QByteArray newcontrols_data = ser.serialize( newcontrols ); + + TomahawkSqlQuery query = lib->newquery(); + QString sql = "INSERT INTO dynamic_playlist_revision (guid, controls, plmode, pltype) " + "VALUES(?, ?, ?, ?)"; + + query.prepare( sql ); + query.addBindValue( m_newrev ); + query.addBindValue( newcontrols_data ); + query.addBindValue( QString::number( (int) m_mode ) ); + query.addBindValue( m_type ); + query.exec(); + + // delete all the old controls, replace with new onws + qDebug() << "Deleting controls with playlist id" << m_playlistguid; + TomahawkSqlQuery delQuery = lib->newquery(); + delQuery.prepare( "DELETE FROM dynamic_playlist_controls WHERE playlist = ?" ); + delQuery.addBindValue( m_playlistguid ); + if( !delQuery.exec() ) + qWarning() << "Failed to delete controls from dynamic playlist controls table"; + + TomahawkSqlQuery controlsQuery = lib->newquery(); + controlsQuery.prepare( "INSERT INTO dynamic_playlist_controls( id, playlist, selectedType, match, input ) " + "VALUES( ?, ?, ?, ?, ? )" ); + if( m_controlsV.isEmpty() && !m_controls.isEmpty() ) { + foreach( const dyncontrol_ptr& control, m_controls ) + { + qDebug() << "inserting dynamic control:" << control->id() << m_playlistguid << control->selectedType() << control->match() << control->input(); + controlsQuery.addBindValue( control->id() ); + controlsQuery.addBindValue( m_playlistguid ); + controlsQuery.addBindValue( control->selectedType() ); + controlsQuery.addBindValue( control->match() ); + controlsQuery.addBindValue( control->input() ); + + controlsQuery.exec(); + } + } else { + foreach( const QVariant& v, m_controlsV ) { + QVariantMap control = v.toMap(); + qDebug() << "inserting dynamic control from JSON:" << control.value( "id" ) << m_playlistguid << control.value( "selectedType" ) << control.value( "match" ) << control.value( "input" ); + controlsQuery.addBindValue( control.value( "id" ) ); + controlsQuery.addBindValue( m_playlistguid ); + controlsQuery.addBindValue( control.value( "selectedType" ) ); + controlsQuery.addBindValue( control.value( "match" ) ); + controlsQuery.addBindValue( control.value( "input" ) ); + + controlsQuery.exec(); + } + + } + if( m_applied ) + { + qDebug() << "updating dynamic playlist, optimistic locking okay"; + + TomahawkSqlQuery query2 = lib->newquery(); + query2.prepare( "UPDATE dynamic_playlist SET pltype = ?, plmode = ? WHERE guid = ?" ); + query2.bindValue( 0, m_type ); + query2.bindValue( 1, m_mode ); + query2.bindValue( 2, m_playlistguid ); + query2.exec(); + + } + + +} diff --git a/src/libtomahawk/database/databasecommand_setdynamicplaylistrevision.h b/src/libtomahawk/database/databasecommand_setdynamicplaylistrevision.h new file mode 100644 index 000000000..238aa163f --- /dev/null +++ b/src/libtomahawk/database/databasecommand_setdynamicplaylistrevision.h @@ -0,0 +1,88 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_SETDYNAMICPLAYLISTREVISION_H +#define DATABASECOMMAND_SETDYNAMICPLAYLISTREVISION_H + +#include "databasecommand_setplaylistrevision.h" +#include "databaseimpl.h" +#include "collection.h" +#include "playlist.h" +#include "dynamic/GeneratorInterface.h" +#include "network/servent.h" + +using namespace Tomahawk; + +class DatabaseCommand_SetDynamicPlaylistRevision : public DatabaseCommand_SetPlaylistRevision +{ + Q_OBJECT + Q_PROPERTY( QString type READ type WRITE setType ) + Q_PROPERTY( int mode READ mode WRITE setMode ) + Q_PROPERTY( QVariantList controls READ controlsV WRITE setControlsV ) + +public: + explicit DatabaseCommand_SetDynamicPlaylistRevision( QObject* parent = 0 ) + : DatabaseCommand_SetPlaylistRevision( parent ) + {} + + explicit DatabaseCommand_SetDynamicPlaylistRevision( const source_ptr& s, + const QString& playlistguid, + const QString& newrev, + const QString& oldrev, + const QStringList& orderedguids, + const QList& addedentries, + const QList& entries, + const QString& type, + GeneratorMode mode, + const QList< dyncontrol_ptr >& controls ); + + explicit DatabaseCommand_SetDynamicPlaylistRevision( const source_ptr& s, + const QString& playlistguid, + const QString& newrev, + const QString& oldrev, + const QString& type, + GeneratorMode mode, + const QList< dyncontrol_ptr >& controls ); + + QString commandname() const { return "setdynamicplaylistrevision"; } + + virtual void exec( DatabaseImpl* lib ); + virtual void postCommitHook(); + virtual bool doesMutates() const { return true; } + + void setControlsV( const QVariantList& vlist ) + { + m_controlsV = vlist; + } + + QVariantList controlsV(); + + QString type() const { return m_type; } + int mode() const { return (int)m_mode; } + + void setType( const QString& type ) { m_type = type; } + void setMode( int mode ) { m_mode = (GeneratorMode)mode; } + +private: + QString m_type; + GeneratorMode m_mode; + QList< dyncontrol_ptr > m_controls; + QList< QVariant > m_controlsV; +}; + +#endif // DATABASECOMMAND_SETDYNAMICPLAYLISTREVISION_H diff --git a/src/database/databasecommand_setplaylistrevision.cpp b/src/libtomahawk/database/databasecommand_setplaylistrevision.cpp similarity index 51% rename from src/database/databasecommand_setplaylistrevision.cpp rename to src/libtomahawk/database/databasecommand_setplaylistrevision.cpp index be2dbdfda..62eec9d6e 100644 --- a/src/database/databasecommand_setplaylistrevision.cpp +++ b/src/libtomahawk/database/databasecommand_setplaylistrevision.cpp @@ -1,9 +1,27 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "databasecommand_setplaylistrevision.h" #include #include "tomahawksqlquery.h" -#include "tomahawk/tomahawkapp.h" +#include "network/servent.h" DatabaseCommand_SetPlaylistRevision::DatabaseCommand_SetPlaylistRevision( @@ -12,13 +30,17 @@ DatabaseCommand_SetPlaylistRevision::DatabaseCommand_SetPlaylistRevision( const QString& newrev, const QString& oldrev, const QStringList& orderedguids, - const QList& addedentries ) - : DatabaseCommandLoggable( s ) + const QList& addedentries, + const QList& entries ) +: DatabaseCommandLoggable( s ) + , m_applied( false ) , m_newrev( newrev ) , m_oldrev( oldrev ) , m_addedentries( addedentries ) - , m_applied( false ) + , m_entries( entries ) { + m_localOnly = ( newrev == oldrev ); + setPlaylistguid( playlistguid ); QVariantList tmp; @@ -33,6 +55,14 @@ void DatabaseCommand_SetPlaylistRevision::postCommitHook() { qDebug() << Q_FUNC_INFO; + if ( source().isNull() || source()->collection().isNull() ) + { + qDebug() << "Source has gone offline, not emitting to GUI."; + return; + } + + if ( m_localOnly ) + return; QStringList orderedentriesguids; foreach( const QVariant& v, m_orderedguids ) @@ -56,7 +86,7 @@ DatabaseCommand_SetPlaylistRevision::postCommitHook() m_applied ); if( source()->isLocal() ) - APP->servent().triggerDBSync(); + Servent::instance()->triggerDBSync(); } @@ -65,17 +95,15 @@ DatabaseCommand_SetPlaylistRevision::exec( DatabaseImpl* lib ) { using namespace Tomahawk; - QString currentrevision; - // get the current revision for this playlist // this also serves to check the playlist exists. TomahawkSqlQuery chkq = lib->newquery(); - chkq.prepare("SELECT currentrevision FROM playlist WHERE guid = ?"); + chkq.prepare( "SELECT currentrevision FROM playlist WHERE guid = ?" ); chkq.addBindValue( m_playlistguid ); if( chkq.exec() && chkq.next() ) { - currentrevision = chkq.value( 0 ).toString(); - //qDebug() << Q_FUNC_INFO << "pl guid" << m_playlistguid << " curr rev" << currentrevision; + m_currentRevision = chkq.value( 0 ).toString(); + qDebug() << Q_FUNC_INFO << "pl guid" << m_playlistguid << " curr rev" << m_currentRevision; } else { @@ -89,38 +117,59 @@ DatabaseCommand_SetPlaylistRevision::exec( DatabaseImpl* lib ) // add any new items: TomahawkSqlQuery adde = lib->newquery(); - - QString sql = "INSERT INTO playlist_item( guid, playlist, trackname, artistname, albumname, " - "annotation, duration, addedon, addedby, result_hint ) " - "VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )"; - adde.prepare( sql ); - - qDebug() << "Num new playlist_items to add:" << m_addedentries.length(); - foreach( const plentry_ptr& e, m_addedentries ) + if ( m_localOnly ) { - m_addedmap.insert( e->guid(), e ); // needed in postcommithook + QString sql = "UPDATE playlist_item SET result_hint = ? WHERE guid = ?"; + adde.prepare( sql ); - adde.bindValue( 0, e->guid() ); - adde.bindValue( 1, m_playlistguid ); - adde.bindValue( 2, e->query()->track() ); - adde.bindValue( 3, e->query()->artist() ); - adde.bindValue( 4, e->query()->album() ); - adde.bindValue( 5, e->annotation() ); - adde.bindValue( 6, (int) e->duration() ); - adde.bindValue( 7, e->lastmodified() ); - adde.bindValue( 8, source()->isLocal() ? QVariant(QVariant::Int) : source()->id() ); - adde.bindValue( 9, "" ); - adde.exec(); + foreach( const plentry_ptr& e, m_entries ) + { + if ( e->query()->results().isEmpty() ) + continue; + + adde.bindValue( 0, e->query()->results().first()->url() ); + adde.bindValue( 1, e->guid() ); + adde.exec(); + } + + return; + } + else + { + QString sql = "INSERT INTO playlist_item( guid, playlist, trackname, artistname, albumname, " + "annotation, duration, addedon, addedby, result_hint ) " + "VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )"; + adde.prepare( sql ); + + qDebug() << "Num new playlist_items to add:" << m_addedentries.length(); + foreach( const plentry_ptr& e, m_addedentries ) + { + m_addedmap.insert( e->guid(), e ); // needed in postcommithook + + QString resultHint; + if ( !e->query()->results().isEmpty() ) + resultHint = e->query()->results().first()->url(); + + adde.bindValue( 0, e->guid() ); + adde.bindValue( 1, m_playlistguid ); + adde.bindValue( 2, e->query()->track() ); + adde.bindValue( 3, e->query()->artist() ); + adde.bindValue( 4, e->query()->album() ); + adde.bindValue( 5, e->annotation() ); + adde.bindValue( 6, (int) e->duration() ); + adde.bindValue( 7, e->lastmodified() ); + adde.bindValue( 8, source()->isLocal() ? QVariant(QVariant::Int) : source()->id() ); + adde.bindValue( 9, resultHint ); + adde.exec(); + } } - // add the new revision: - //qDebug() << "Adding new playlist revision, guid:" << m_newrev - // << entries; + // add / update the revision: TomahawkSqlQuery query = lib->newquery(); - sql = "INSERT INTO playlist_revision(guid, playlist, entries, author, timestamp, previous_revision) " - "VALUES(?, ?, ?, ?, ?, ?)"; - + QString sql = "INSERT INTO playlist_revision(guid, playlist, entries, author, timestamp, previous_revision) " + "VALUES(?, ?, ?, ?, ?, ?)"; query.prepare( sql ); + query.addBindValue( m_newrev ); query.addBindValue( m_playlistguid ); query.addBindValue( entries ); @@ -129,13 +178,14 @@ DatabaseCommand_SetPlaylistRevision::exec( DatabaseImpl* lib ) query.addBindValue( m_oldrev.isEmpty() ? QVariant(QVariant::String) : m_oldrev ); query.exec(); - qDebug() << "Currentrevision:" << currentrevision << "oldrev:" << m_oldrev; + qDebug() << "Currentrevision:" << m_currentRevision << "oldrev:" << m_oldrev; // if optimistic locking is ok, update current revision to this new one - if( currentrevision == m_oldrev ) + if( m_currentRevision == m_oldrev ) { - TomahawkSqlQuery query2 = lib->newquery(); qDebug() << "updating current revision, optimistic locking ok"; - query2.prepare("UPDATE playlist SET currentrevision = ? WHERE guid = ?"); + + TomahawkSqlQuery query2 = lib->newquery(); + query2.prepare( "UPDATE playlist SET currentrevision = ? WHERE guid = ?" ); query2.bindValue( 0, m_newrev ); query2.bindValue( 1, m_playlistguid ); query2.exec(); @@ -157,6 +207,7 @@ DatabaseCommand_SetPlaylistRevision::exec( DatabaseImpl* lib ) QJson::Parser parser; QVariant v = parser.parse( query_entries.value(0).toByteArray(), &ok ); Q_ASSERT( ok && v.type() == QVariant::List ); //TODO + m_previous_rev_orderedguids = v.toStringList(); } } diff --git a/src/database/databasecommand_setplaylistrevision.h b/src/libtomahawk/database/databasecommand_setplaylistrevision.h similarity index 67% rename from src/database/databasecommand_setplaylistrevision.h rename to src/libtomahawk/database/databasecommand_setplaylistrevision.h index f9d420df0..e71c1f7e1 100644 --- a/src/database/databasecommand_setplaylistrevision.h +++ b/src/libtomahawk/database/databasecommand_setplaylistrevision.h @@ -1,14 +1,34 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef DATABASECOMMAND_SETPLAYLISTREVISION_H #define DATABASECOMMAND_SETPLAYLISTREVISION_H #include "databasecommandloggable.h" #include "databaseimpl.h" -#include "tomahawk/collection.h" -#include "tomahawk/playlist.h" +#include "collection.h" +#include "playlist.h" + +#include "dllmacro.h" using namespace Tomahawk; -class DatabaseCommand_SetPlaylistRevision : public DatabaseCommandLoggable +class DLLEXPORT DatabaseCommand_SetPlaylistRevision : public DatabaseCommandLoggable { Q_OBJECT Q_PROPERTY( QString playlistguid READ playlistguid WRITE setPlaylistguid ) @@ -21,6 +41,7 @@ public: explicit DatabaseCommand_SetPlaylistRevision( QObject* parent = 0 ) : DatabaseCommandLoggable( parent ) , m_applied( false ) + , m_localOnly( false ) {} explicit DatabaseCommand_SetPlaylistRevision( const source_ptr& s, @@ -28,13 +49,16 @@ public: const QString& newrev, const QString& oldrev, const QStringList& orderedguids, - const QList& addedentries ); + const QList& addedentries, + const QList& entries ); QString commandname() const { return "setplaylistrevision"; } virtual void exec( DatabaseImpl* lib ); virtual void postCommitHook(); + virtual bool doesMutates() const { return true; } + virtual bool localOnly() const { return m_localOnly; } void setAddedentriesV( const QVariantList& vlist ) { @@ -69,14 +93,19 @@ public: void setOrderedguids( const QVariantList& l ) { m_orderedguids = l; } QVariantList orderedguids() const { return m_orderedguids; } -private: +protected: + bool m_applied; + QStringList m_previous_rev_orderedguids; QString m_playlistguid; QString m_newrev, m_oldrev; - QVariantList m_orderedguids; - QStringList m_previous_rev_orderedguids; - QList m_addedentries; - bool m_applied; QMap m_addedmap; + + QString m_currentRevision; +private: + QVariantList m_orderedguids; + QList m_addedentries, m_entries; + + bool m_localOnly; }; #endif // DATABASECOMMAND_SETPLAYLISTREVISION_H diff --git a/src/libtomahawk/database/databasecommand_sourceoffline.cpp b/src/libtomahawk/database/databasecommand_sourceoffline.cpp new file mode 100644 index 000000000..e09186ddc --- /dev/null +++ b/src/libtomahawk/database/databasecommand_sourceoffline.cpp @@ -0,0 +1,34 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_sourceoffline.h" + + +DatabaseCommand_SourceOffline::DatabaseCommand_SourceOffline( int id ) + : DatabaseCommand() + , m_id( id ) +{ +} + + +void DatabaseCommand_SourceOffline::exec( DatabaseImpl* lib ) +{ + TomahawkSqlQuery q = lib->newquery(); + q.exec( QString( "UPDATE source SET isonline = 'false' WHERE id = %1" ) + .arg( m_id ) ); +} diff --git a/src/libtomahawk/database/databasecommand_sourceoffline.h b/src/libtomahawk/database/databasecommand_sourceoffline.h new file mode 100644 index 000000000..7f50578e1 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_sourceoffline.h @@ -0,0 +1,40 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_SOURCEOFFLINE_H +#define DATABASECOMMAND_SOURCEOFFLINE_H + +#include "databasecommand.h" +#include "databaseimpl.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_SourceOffline : public DatabaseCommand +{ +Q_OBJECT + +public: + explicit DatabaseCommand_SourceOffline( int id ); + bool doesMutates() const { return true; } + void exec( DatabaseImpl* lib ); + +private: + int m_id; +}; + +#endif // DATABASECOMMAND_SOURCEOFFLINE_H diff --git a/src/libtomahawk/database/databasecommand_updatesearchindex.cpp b/src/libtomahawk/database/databasecommand_updatesearchindex.cpp new file mode 100644 index 000000000..8362c9749 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_updatesearchindex.cpp @@ -0,0 +1,58 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommand_updatesearchindex.h" + + +DatabaseCommand_UpdateSearchIndex::DatabaseCommand_UpdateSearchIndex() + : DatabaseCommand() +{ +} + + +void +DatabaseCommand_UpdateSearchIndex::indexTable( DatabaseImpl* db, const QString& table ) +{ + qDebug() << Q_FUNC_INFO; + + TomahawkSqlQuery query = db->newquery(); + qDebug() << "Building index for" << table; + query.exec( QString( "SELECT id, name FROM %1" ).arg( table ) ); + + QMap< unsigned int, QString > fields; + while ( query.next() ) + { + fields.insert( query.value( 0 ).toUInt(), query.value( 1 ).toString() ); + } + + db->m_fuzzyIndex->appendFields( table, fields ); + qDebug() << "Building index for" << table << "finished."; +} + + +void +DatabaseCommand_UpdateSearchIndex::exec( DatabaseImpl* db ) +{ + db->m_fuzzyIndex->beginIndexing(); + + indexTable( db, "artist" ); + indexTable( db, "album" ); + indexTable( db, "track" ); + + db->m_fuzzyIndex->endIndexing(); +} diff --git a/src/libtomahawk/database/databasecommand_updatesearchindex.h b/src/libtomahawk/database/databasecommand_updatesearchindex.h new file mode 100644 index 000000000..8a0fc95a8 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_updatesearchindex.h @@ -0,0 +1,46 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_UPDATESEARCHINDEX_H +#define DATABASECOMMAND_UPDATESEARCHINDEX_H + +#include "databasecommand.h" +#include "databaseimpl.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseCommand_UpdateSearchIndex : public DatabaseCommand +{ +Q_OBJECT +public: + explicit DatabaseCommand_UpdateSearchIndex(); + + virtual QString commandname() const { return "updatesearchindex"; } + virtual bool doesMutates() const { return true; } + virtual void exec( DatabaseImpl* db ); + +signals: + void indexUpdated(); + +private: + void indexTable( DatabaseImpl* db, const QString& table ); + + QString table; +}; + +#endif // DATABASECOMMAND_UPDATESEARCHINDEX_H diff --git a/src/libtomahawk/database/databasecommandloggable.cpp b/src/libtomahawk/database/databasecommandloggable.cpp new file mode 100644 index 000000000..9c193b454 --- /dev/null +++ b/src/libtomahawk/database/databasecommandloggable.cpp @@ -0,0 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databasecommandloggable.h" + + diff --git a/src/libtomahawk/database/databasecommandloggable.h b/src/libtomahawk/database/databasecommandloggable.h new file mode 100644 index 000000000..be400621b --- /dev/null +++ b/src/libtomahawk/database/databasecommandloggable.h @@ -0,0 +1,46 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMANDLOGGABLE_H +#define DATABASECOMMANDLOGGABLE_H + +#include "database/databasecommand.h" + +#include "dllmacro.h" + +/// A Database Command that will be added to the oplog and sent over the network +/// so peers can sync up and changes to our collection in their cached copy. +class DLLEXPORT DatabaseCommandLoggable : public DatabaseCommand +{ +Q_OBJECT +Q_PROPERTY(QString command READ commandname) + +public: + + explicit DatabaseCommandLoggable( QObject* parent = 0 ) + : DatabaseCommand( parent ) + {} + + explicit DatabaseCommandLoggable( const Tomahawk::source_ptr& s, QObject* parent = 0 ) + : DatabaseCommand( s, parent ) + {} + + virtual bool loggable() const { return true; } +}; + +#endif // DATABASECOMMANDLOGGABLE_H diff --git a/src/database/databaseimpl.cpp b/src/libtomahawk/database/databaseimpl.cpp similarity index 53% rename from src/database/databaseimpl.cpp rename to src/libtomahawk/database/databaseimpl.cpp index 6e3103e19..485c7a5ef 100644 --- a/src/database/databaseimpl.cpp +++ b/src/libtomahawk/database/databaseimpl.cpp @@ -1,20 +1,43 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "databaseimpl.h" +#include #include #include #include #include -#include "database.h" -#include "tomahawk/tomahawkapp.h" + +#include "database/database.h" #include "databasecommand_updatesearchindex.h" +#include "sourcelist.h" +#include "result.h" +#include "artist.h" +#include "album.h" /* !!!! You need to manually generate schema.sql.h when the schema changes: - cd src/database + cd src/libtomahawk/database ./gen_schema.h.sh ./schema.sql tomahawk > schema.sql.h */ #include "schema.sql.h" -#define CURRENT_SCHEMA_VERSION 14 +#define CURRENT_SCHEMA_VERSION 22 DatabaseImpl::DatabaseImpl( const QString& dbname, Database* parent ) @@ -22,9 +45,8 @@ DatabaseImpl::DatabaseImpl( const QString& dbname, Database* parent ) , m_lastartid( 0 ) , m_lastalbid( 0 ) , m_lasttrkid( 0 ) - , m_fuzzyIndex( *this ) { - connect( this, SIGNAL(indexReady()), parent, SIGNAL(indexReady()) ); + connect( this, SIGNAL( indexReady() ), parent, SIGNAL( indexReady() ) ); db = QSqlDatabase::addDatabase( "QSQLITE", "tomahawk" ); db.setDatabaseName( dbname ); @@ -35,8 +57,8 @@ DatabaseImpl::DatabaseImpl( const QString& dbname, Database* parent ) } QSqlQuery qry = QSqlQuery( db ); - query = newquery(); + bool schemaUpdated = false; qry.exec( "SELECT v FROM settings WHERE k='schema_version'" ); if ( qry.next() ) { @@ -52,9 +74,7 @@ DatabaseImpl::DatabaseImpl( const QString& dbname, Database* parent ) qDebug() << endl << "****************************" << endl; qry.clear(); - query.clear(); qry.finish(); - query.finish(); db.close(); db.removeDatabase( "tomahawk" ); @@ -63,20 +83,27 @@ DatabaseImpl::DatabaseImpl( const QString& dbname, Database* parent ) { db = QSqlDatabase::addDatabase( "QSQLITE", "tomahawk" ); db.setDatabaseName( dbname ); - if( !db.open() ) throw "db moving failed"; - updateSchema( v ); + if( !db.open() ) + throw "db moving failed"; + + TomahawkSqlQuery query = newquery(); + query.exec( "PRAGMA auto_vacuum = FULL" ); + schemaUpdated = updateSchema( v ); } else { - Q_ASSERT(0); - QTimer::singleShot( 0, APP, SLOT( quit() ) ); + Q_ASSERT( false ); + QTimer::singleShot( 0, qApp, SLOT( quit() ) ); return; } } - } else { - updateSchema( 0 ); + } + else + { + schemaUpdated = updateSchema( 0 ); } + TomahawkSqlQuery query = newquery(); query.exec( "SELECT v FROM settings WHERE k='dbid'" ); if( query.next() ) { @@ -96,32 +123,30 @@ DatabaseImpl::DatabaseImpl( const QString& dbname, Database* parent ) // in case of unclean shutdown last time: query.exec( "UPDATE source SET isonline = 'false'" ); + + m_fuzzyIndex = new FuzzyIndex( *this, schemaUpdated ); } DatabaseImpl::~DatabaseImpl() { - m_indexThread.quit(); - m_indexThread.wait(5000); + delete m_fuzzyIndex; } void DatabaseImpl::loadIndex() { - // load ngram index in the background - m_fuzzyIndex.moveToThread( &m_indexThread ); - connect( &m_indexThread, SIGNAL(started()), &m_fuzzyIndex, SLOT(loadNgramIndex()) ); - connect( &m_fuzzyIndex, SIGNAL(indexReady()), this, SIGNAL(indexReady()) ); - m_indexThread.start(); + connect( m_fuzzyIndex, SIGNAL( indexReady() ), SIGNAL( indexReady() ) ); + m_fuzzyIndex->loadLuceneIndex(); } void -DatabaseImpl::updateSearchIndex( const QString& table, int pkey ) +DatabaseImpl::updateSearchIndex() { - DatabaseCommand* cmd = new DatabaseCommand_UpdateSearchIndex(table, pkey); - APP->database()->enqueue( QSharedPointer( cmd ) ); + DatabaseCommand* cmd = new DatabaseCommand_UpdateSearchIndex(); + Database::instance()->enqueue( QSharedPointer( cmd ) ); } @@ -140,6 +165,7 @@ DatabaseImpl::updateSchema( int currentver ) continue; qDebug() << "Executing:" << s; + TomahawkSqlQuery query = newquery(); query.exec( s ); } @@ -148,35 +174,59 @@ DatabaseImpl::updateSchema( int currentver ) } -QVariantMap +Tomahawk::result_ptr DatabaseImpl::file( int fid ) { - QVariantMap m; + Tomahawk::result_ptr r = Tomahawk::result_ptr( new Tomahawk::Result() ); + TomahawkSqlQuery query = newquery(); query.exec( QString( "SELECT url, mtime, size, md5, mimetype, duration, bitrate, " "file_join.artist, file_join.album, file_join.track, " "(select name from artist where id = file_join.artist) as artname, " "(select name from album where id = file_join.album) as albname, " - "(select name from track where id = file_join.track) as trkname " + "(select name from track where id = file_join.track) as trkname, " + "source " "FROM file, file_join " "WHERE file.id = file_join.file AND file.id = %1" ) .arg( fid ) ); if( query.next() ) { - m["url"] = query.value( 0 ).toString(); - m["mtime"] = query.value( 1 ).toString(); - m["size"] = query.value( 2 ).toInt(); - m["hash"] = query.value( 3 ).toString(); - m["mimetype"] = query.value( 4 ).toString(); - m["duration"] = query.value( 5 ).toInt(); - m["bitrate"] = query.value( 6 ).toInt(); - m["artist"] = query.value( 10 ).toString(); - m["album"] = query.value( 11 ).toString(); - m["track"] = query.value( 12 ).toString(); + Tomahawk::source_ptr s; + + const QString url_str = query.value( 0 ).toString(); + if ( query.value( 13 ).toUInt() == 0 ) + { + s = SourceList::instance()->getLocal(); + } + else + { + s = SourceList::instance()->get( query.value( 13 ).toUInt() ); + if ( s.isNull() ) + { + return r; + } + + r->setUrl( QString( "servent://%1\t%2" ).arg( s->userName() ).arg( url_str ) ); + } + + Tomahawk::artist_ptr artist = Tomahawk::Artist::get( query.value( 7 ).toUInt(), query.value( 10 ).toString() ); + Tomahawk::album_ptr album = Tomahawk::Album::get( query.value( 8 ).toUInt(), query.value( 11 ).toString(), artist ); + + r->setUrl( query.value( 0 ).toString() ); + r->setModificationTime( query.value( 1 ).toUInt() ); + r->setSize( query.value( 2 ).toUInt() ); + r->setMimetype( query.value( 4 ).toString() ); + r->setDuration( query.value( 5 ).toUInt() ); + r->setBitrate( query.value( 6 ).toUInt() ); + r->setArtist( artist ); + r->setAlbum( album ); + r->setTrack( query.value( 12 ).toString() ); + r->setId( query.value( 9 ).toUInt() ); + r->setCollection( s->collection() ); + r->setScore( 1.0 ); } - //qDebug() << m; - return m; + return r; } @@ -190,6 +240,7 @@ DatabaseImpl::artistId( const QString& name_orig, bool& isnew ) int id = 0; QString sortname = DatabaseImpl::sortname( name_orig ); + TomahawkSqlQuery query = newquery(); query.prepare( "SELECT id FROM artist WHERE sortname = ?" ); query.addBindValue( sortname ); query.exec(); @@ -230,6 +281,7 @@ DatabaseImpl::trackId( int artistid, const QString& name_orig, bool& isnew ) QString sortname = DatabaseImpl::sortname( name_orig ); //if( ( id = m_artistcache[sortname] ) ) return id; + TomahawkSqlQuery query = newquery(); query.prepare( "SELECT id FROM track WHERE artist = ? AND sortname = ?" ); query.addBindValue( artistid ); query.addBindValue( sortname ); @@ -280,6 +332,7 @@ DatabaseImpl::albumId( int artistid, const QString& name_orig, bool& isnew ) QString sortname = DatabaseImpl::sortname( name_orig ); //if( ( id = m_albumcache[sortname] ) ) return id; + TomahawkSqlQuery query = newquery(); query.prepare( "SELECT id FROM album WHERE artist = ? AND sortname = ?" ); query.addBindValue( artistid ); query.addBindValue( sortname ); @@ -316,43 +369,27 @@ DatabaseImpl::albumId( int artistid, const QString& name_orig, bool& isnew ) QList< int > -DatabaseImpl::searchTable( const QString& table, const QString& name_orig, uint limit ) +DatabaseImpl::searchTable( const QString& table, const QString& name, uint limit ) { QList< int > results; if( table != "artist" && table != "track" && table != "album" ) return results; - QString name = sortname( name_orig ); + QMap< int, float > resultsmap = m_fuzzyIndex->search( table, name ); - // first check for exact matches: - query.prepare( QString( "SELECT id FROM %1 WHERE sortname = ?" ).arg( table ) ); - query.addBindValue( name ); - bool exactok = query.exec(); - Q_ASSERT( exactok ); - - while( query.next() ) - results.append( query.value( 0 ).toInt() ); - - // ngram stuff only works on tracks over a certain length atm: - if( name_orig.length() > 3 ) + QList< QPair > resultslist; + foreach( int i, resultsmap.keys() ) { - // consult ngram index to find candidates: - QMap< int, float > resultsmap = m_fuzzyIndex.search( table, name ); + resultslist << QPair( i, (float)resultsmap.value( i ) ); + } + qSort( resultslist.begin(), resultslist.end(), DatabaseImpl::scorepairSorter ); - //qDebug() << "results map for" << table << resultsmap.size(); - QList< QPair > resultslist; - foreach( int i, resultsmap.keys() ) - { - resultslist << QPair( i, (float)resultsmap.value( i ) ); - } - qSort( resultslist.begin(), resultslist.end(), DatabaseImpl::scorepairSorter ); - - for( int k = 0; k < resultslist.size() && k < (int)limit; ++k ) - { - results << resultslist.at( k ).first; - } + for( int k = 0; k < resultslist.count(); k++ ) + { + results << resultslist.at( k ).first; } +// qDebug() << "Returning" << results.count() << "results"; return results; } @@ -361,6 +398,8 @@ QList< int > DatabaseImpl::getTrackFids( int tid ) { QList< int > ret; + + TomahawkSqlQuery query = newquery(); query.exec( QString( "SELECT file.id FROM file, file_join " "WHERE file_join.file=file.id " "AND file_join.track = %1 ").arg( tid ) ); @@ -373,35 +412,6 @@ DatabaseImpl::getTrackFids( int tid ) } -QMap< QString, int > -DatabaseImpl::ngrams( const QString& str_orig ) -{ - static QMap< QString, QMap > memo; - if( memo.contains( str_orig ) ) - return memo.value( str_orig ); - - int n = 3; - QMap ret; - QString str( " " + DatabaseImpl::sortname( str_orig ) + " " ); - int num = str.length(); - QString ngram; - - for( int j = 0; j < num - ( n - 1 ); j++ ) - { - ngram = str.mid( j, n ); - Q_ASSERT( ngram.length() == n ); - - if( ret.contains( ngram ) ) - ret[ngram]++; - else - ret[ngram] = 1; - } - - memo.insert( str_orig, ret ); - return ret; -} - - QString DatabaseImpl::sortname( const QString& str ) { @@ -412,7 +422,9 @@ DatabaseImpl::sortname( const QString& str ) QVariantMap DatabaseImpl::artist( int id ) { + TomahawkSqlQuery query = newquery(); query.exec( QString( "SELECT id, name, sortname FROM artist WHERE id = %1" ).arg( id ) ); + QVariantMap m; if( !query.next() ) return m; @@ -427,7 +439,9 @@ DatabaseImpl::artist( int id ) QVariantMap DatabaseImpl::track( int id ) { + TomahawkSqlQuery query = newquery(); query.exec( QString( "SELECT id, artist, name, sortname FROM track WHERE id = %1" ).arg( id ) ); + QVariantMap m; if( !query.next() ) return m; @@ -443,7 +457,9 @@ DatabaseImpl::track( int id ) QVariantMap DatabaseImpl::album( int id ) { + TomahawkSqlQuery query = newquery(); query.exec( QString( "SELECT id, artist, name, sortname FROM album WHERE id = %1" ).arg( id ) ); + QVariantMap m; if( !query.next() ) return m; @@ -455,3 +471,100 @@ DatabaseImpl::album( int id ) return m; } + +Tomahawk::result_ptr +DatabaseImpl::result( const QString& url ) +{ + TomahawkSqlQuery query = newquery(); + Tomahawk::source_ptr s; + Tomahawk::result_ptr res; + QString fileUrl; + + if ( url.contains( "servent://" ) ) + { + QStringList parts = url.mid( QString( "servent://" ).length() ).split( "\t" ); + s = SourceList::instance()->get( parts.at( 0 ) ); + fileUrl = parts.at( 1 ); + + if ( s.isNull() ) + return res; + } + else if ( url.contains( "file://" ) ) + { + s = SourceList::instance()->getLocal(); + fileUrl = url; + } + else + { +// Q_ASSERT( false ); + qDebug() << "We don't support non-servent / non-file result-hints yet."; + return res; + } + + res = Tomahawk::result_ptr( new Tomahawk::Result() ); + bool searchlocal = s->isLocal(); + + QString sql = QString( "SELECT " + "url, mtime, size, md5, mimetype, duration, bitrate, file_join.artist, file_join.album, file_join.track, " + "artist.name as artname, " + "album.name as albname, " + "track.name as trkname, " + "file.source, " + "file_join.albumpos, " + "artist.id as artid, " + "album.id as albid " + "FROM file, file_join, artist, track " + "LEFT JOIN album ON album.id = file_join.album " + "WHERE " + "artist.id = file_join.artist AND " + "track.id = file_join.track AND " + "file.source %1 AND " + "file_join.file = file.id AND " + "file.url = ?" + ).arg( searchlocal ? "IS NULL" : QString( "= %1" ).arg( s->id() ) ); + + query.prepare( sql ); + query.bindValue( 0, fileUrl ); + query.exec(); + + if( query.next() ) + { + Tomahawk::source_ptr s; + + const QString url_str = query.value( 0 ).toString(); + if ( query.value( 13 ).toUInt() == 0 ) + { + s = SourceList::instance()->getLocal(); + res->setUrl( url_str ); + } + else + { + s = SourceList::instance()->get( query.value( 13 ).toUInt() ); + if ( s.isNull() ) + { + return res; + } + + res->setUrl( QString( "servent://%1\t%2" ).arg( s->userName() ).arg( url_str ) ); + } + + Tomahawk::artist_ptr artist = Tomahawk::Artist::get( query.value( 15 ).toUInt(), query.value( 10 ).toString() ); + Tomahawk::album_ptr album = Tomahawk::Album::get( query.value( 16 ).toUInt(), query.value( 11 ).toString(), artist ); + + res->setModificationTime( query.value( 1 ).toUInt() ); + res->setSize( query.value( 2 ).toUInt() ); + res->setMimetype( query.value( 4 ).toString() ); + res->setDuration( query.value( 5 ).toInt() ); + res->setBitrate( query.value( 6 ).toInt() ); + res->setArtist( artist ); + res->setAlbum( album ); + res->setScore( 1.0 ); + res->setTrack( query.value( 12 ).toString() ); + res->setAlbumPos( query.value( 14 ).toUInt() ); + res->setRID( uuid() ); + res->setId( query.value( 9 ).toUInt() ); + res->setCollection( s->collection() ); + } + + return res; +} diff --git a/src/database/databaseimpl.h b/src/libtomahawk/database/databaseimpl.h similarity index 59% rename from src/database/databaseimpl.h rename to src/libtomahawk/database/databaseimpl.h index 899e57d74..579850b7e 100644 --- a/src/database/databaseimpl.h +++ b/src/libtomahawk/database/databaseimpl.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef DATABASEIMPL_H #define DATABASEIMPL_H @@ -15,6 +33,7 @@ #include "tomahawksqlquery.h" #include "fuzzyindex.h" +#include "typedefs.h" class Database; @@ -36,16 +55,16 @@ public: int trackId( int artistid, const QString& name_orig, bool& isnew ); int albumId( int artistid, const QString& name_orig, bool& isnew ); - QList< int > searchTable( const QString& table, const QString& name_orig, uint limit = 10 ); + QList< int > searchTable( const QString& table, const QString& name, uint limit = 10 ); QList< int > getTrackFids( int tid ); - static QMap ngrams( const QString& str_orig ); static QString sortname( const QString& str ); QVariantMap artist( int id ); QVariantMap album( int id ); QVariantMap track( int id ); - QVariantMap file( int fid ); + Tomahawk::result_ptr file( int fid ); + Tomahawk::result_ptr result( const QString& url ); static bool scorepairSorter( const QPair& left, const QPair& right ) { @@ -53,30 +72,28 @@ public: } // indexes entries from "table" where id >= pkey - void updateSearchIndex( const QString& table, int pkey ); + void updateSearchIndex(); - const QString& dbid() const { return m_dbid; } + QString dbid() const { return m_dbid; } void loadIndex(); signals: void indexReady(); -public slots: +public slots: private: - bool updateSchema( int currentver ); + QSqlDatabase db; - TomahawkSqlQuery query; QString m_lastart, m_lastalb, m_lasttrk; int m_lastartid, m_lastalbid, m_lasttrkid; QString m_dbid; - QThread m_indexThread; - FuzzyIndex m_fuzzyIndex; + FuzzyIndex* m_fuzzyIndex; }; #endif // DATABASEIMPL_H diff --git a/src/libtomahawk/database/databaseresolver.cpp b/src/libtomahawk/database/databaseresolver.cpp new file mode 100644 index 000000000..e5da896af --- /dev/null +++ b/src/libtomahawk/database/databaseresolver.cpp @@ -0,0 +1,59 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databaseresolver.h" + +#include "network/servent.h" +#include "database/database.h" +#include "database/databasecommand_resolve.h" + + +DatabaseResolver::DatabaseResolver( int weight ) + : Resolver() + , m_weight( weight ) +{ +} + + +void +DatabaseResolver::resolve( const Tomahawk::query_ptr& query ) +{ + DatabaseCommand_Resolve* cmd = new DatabaseCommand_Resolve( query ); + + connect( cmd, SIGNAL( results( Tomahawk::QID, QList< Tomahawk::result_ptr > ) ), + SLOT( gotResults( Tomahawk::QID, QList< Tomahawk::result_ptr > ) ), Qt::QueuedConnection ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); + +} + + +void +DatabaseResolver::gotResults( const Tomahawk::QID qid, QList< Tomahawk::result_ptr> results ) +{ + qDebug() << Q_FUNC_INFO << qid << results.length(); + + Tomahawk::Pipeline::instance()->reportResults( qid, results ); +} + + +QString +DatabaseResolver::name() const +{ + return QString( "DatabaseResolver" ); +} diff --git a/src/libtomahawk/database/databaseresolver.h b/src/libtomahawk/database/databaseresolver.h new file mode 100644 index 000000000..11abb80a0 --- /dev/null +++ b/src/libtomahawk/database/databaseresolver.h @@ -0,0 +1,51 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASERESOLVER_H +#define DATABASERESOLVER_H + +#include "pipeline.h" + +#include "resolver.h" +#include "result.h" + +#include "dllmacro.h" + +class DLLEXPORT DatabaseResolver : public Tomahawk::Resolver +{ +Q_OBJECT + +public: + explicit DatabaseResolver( int weight ); + + virtual QString name() const; + virtual unsigned int weight() const { return m_weight; } + virtual unsigned int preference() const { return 100; } + virtual unsigned int timeout() const { return 2500; } + +public slots: + virtual void resolve( const Tomahawk::query_ptr& query ); + +private slots: + void gotResults( const Tomahawk::QID qid, QList< Tomahawk::result_ptr> results ); + +private: + int m_weight; +}; + +#endif // DATABASERESOLVER_H diff --git a/src/libtomahawk/database/databaseworker.cpp b/src/libtomahawk/database/databaseworker.cpp new file mode 100644 index 000000000..791888e80 --- /dev/null +++ b/src/libtomahawk/database/databaseworker.cpp @@ -0,0 +1,256 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "databaseworker.h" + +#include +#include +#include + +#include "database/database.h" +#include "database/databasecommandloggable.h" + + +DatabaseWorker::DatabaseWorker( DatabaseImpl* lib, Database* db, bool mutates ) + : QThread() + , m_dbimpl( lib ) + , m_abort( false ) + , m_outstanding( 0 ) +{ + moveToThread( this ); + + qDebug() << "CTOR DatabaseWorker" << this->thread(); +} + + +DatabaseWorker::~DatabaseWorker() +{ + qDebug() << Q_FUNC_INFO << m_outstanding; + + if ( m_commands.count() ) + qDebug() << m_commands; + + quit(); + wait( 60000 ); +} + + +void +DatabaseWorker::run() +{ + exec(); + qDebug() << Q_FUNC_INFO << "DatabaseWorker finishing..."; +} + + +void +DatabaseWorker::enqueue( const QSharedPointer& cmd ) +{ + if ( QThread::currentThread() != thread() ) + { +// qDebug() << Q_FUNC_INFO << "Reinvoking in correct thread."; + QMetaObject::invokeMethod( this, "enqueue", Qt::QueuedConnection, Q_ARG( QSharedPointer, cmd ) ); + return; + } + + QMutexLocker lock( &m_mut ); + m_outstanding++; + m_commands << cmd; + + if ( m_outstanding == 1 ) + QTimer::singleShot( 0, this, SLOT( doWork() ) ); +} + + +void +DatabaseWorker::doWork() +{ + /* + Run the dbcmd. Only inside a transaction if the cmd does mutates. + + If the cmd is modifying local content (ie source->isLocal()) then + log to the database oplog for replication to peers. + + */ + + QTime timer; + timer.start(); + + QSharedPointer cmd; + { + QMutexLocker lock( &m_mut ); + cmd = m_commands.takeFirst(); + } + + if( cmd->doesMutates() ) + { + bool transok = m_dbimpl->database().transaction(); +// Q_ASSERT( transok ); + Q_UNUSED( transok ); + } + try + { + { + cmd->_exec( m_dbimpl ); // runs actual SQL stuff + + if( cmd->loggable() && !cmd->localOnly() ) + { + // We only save our own ops to the oplog, since incoming ops from peers + // are applied immediately. + // + // Crazy idea: if peers had keypairs and could sign ops/msgs, in theory it + // would be safe to sync ops for friend A from friend B's cache, if he saved them, + // which would mean you could get updates even if a peer was offline. + if( cmd->source()->isLocal() ) + { + // save to op-log + DatabaseCommandLoggable* command = (DatabaseCommandLoggable*)cmd.data(); + logOp( command ); + } + else + { + // Make a note of the last guid we applied for this source + // so we can always request just the newer ops in future. + // + if ( !cmd->singletonCmd() ) + { + qDebug() << "Setting lastop for source" << cmd->source()->id() << "to" << cmd->guid(); + + TomahawkSqlQuery query = m_dbimpl->newquery(); + query.prepare( "UPDATE source SET lastop = ? WHERE id = ?" ); + query.addBindValue( cmd->guid() ); + query.addBindValue( cmd->source()->id() ); + + if( !query.exec() ) + { + qDebug() << "Failed to set lastop"; + throw "Failed to set lastop"; + } + } + } + } + + if( cmd->doesMutates() ) + { + qDebug() << "Committing" << cmd->commandname();; + if( !m_dbimpl->database().commit() ) + { + + qDebug() << "*FAILED TO COMMIT TRANSACTION*"; + throw "commit failed"; + } + else + { + qDebug() << "Committed" << cmd->commandname(); + } + } + + //uint duration = timer.elapsed(); + //qDebug() << "DBCmd Duration:" << duration << "ms, now running postcommit for" << cmd->commandname(); + cmd->postCommit(); + //qDebug() << "Post commit finished for"<< cmd->commandname(); + } + } + catch( const char * msg ) + { + qDebug() << endl + << "*ERROR* processing databasecommand:" + << cmd->commandname() + << msg + << m_dbimpl->database().lastError().databaseText() + << m_dbimpl->database().lastError().driverText() + << endl; + + if( cmd->doesMutates() ) + m_dbimpl->database().rollback(); + +// Q_ASSERT( false ); + } + catch(...) + { + qDebug() << "Uncaught exception processing dbcmd"; + if( cmd->doesMutates() ) + m_dbimpl->database().rollback(); + + Q_ASSERT( false ); + throw; + } + + cmd->emitFinished(); + + QMutexLocker lock( &m_mut ); + m_outstanding--; + if ( m_outstanding > 0 ) + QTimer::singleShot( 0, this, SLOT( doWork() ) ); +} + + +// this should take a const command, need to check/make json stuff mutable for some objs tho maybe. +void +DatabaseWorker::logOp( DatabaseCommandLoggable* command ) +{ + TomahawkSqlQuery oplogquery = m_dbimpl->newquery(); + oplogquery.prepare( "INSERT INTO oplog(source, guid, command, singleton, compressed, json) " + "VALUES(?, ?, ?, ?, ?, ?)" ); + + QVariantMap variant = QJson::QObjectHelper::qobject2qvariant( command ); + QByteArray ba = m_serializer.serialize( variant ); + +// qDebug() << "OP JSON:" << ba.isNull() << ba << "from:" << variant; // debug + + bool compressed = false; + if( ba.length() >= 512 ) + { + // We need to compress this in this thread, since inserting into the log + // has to happen as part of the same transaction as the dbcmd. + // (we are in a worker thread for RW dbcmds anyway, so it's ok) + //qDebug() << "Compressing DB OP JSON, uncompressed size:" << ba.length(); + ba = qCompress( ba, 9 ); + compressed = true; + //qDebug() << "Compressed DB OP JSON size:" << ba.length(); + } + + if ( command->singletonCmd() ) + { + qDebug() << "Singleton command, deleting previous oplog commands"; + + TomahawkSqlQuery oplogdelquery = m_dbimpl->newquery(); + oplogdelquery.prepare( QString( "DELETE FROM oplog WHERE source %1 AND singleton = 'true' AND command = ?" ) + .arg( command->source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( command->source()->id() ) ) ); + + oplogdelquery.bindValue( 0, command->commandname() ); + oplogdelquery.exec(); + } + + qDebug() << "Saving to oplog:" << command->commandname() + << "bytes:" << ba.length() + << "guid:" << command->guid(); + + oplogquery.bindValue( 0, command->source()->isLocal() ? + QVariant(QVariant::Int) : command->source()->id() ); + oplogquery.bindValue( 1, command->guid() ); + oplogquery.bindValue( 2, command->commandname() ); + oplogquery.bindValue( 3, command->singletonCmd() ); + oplogquery.bindValue( 4, compressed ); + oplogquery.bindValue( 5, ba ); + if( !oplogquery.exec() ) + { + qDebug() << "Error saving to oplog"; + throw "Failed to save to oplog"; + } +} diff --git a/src/libtomahawk/database/databaseworker.h b/src/libtomahawk/database/databaseworker.h new file mode 100644 index 000000000..ea45cec0f --- /dev/null +++ b/src/libtomahawk/database/databaseworker.h @@ -0,0 +1,71 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DATABASEWORKER_H +#define DATABASEWORKER_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "databasecommand.h" +#include "databaseimpl.h" + +class Database; +class DatabaseCommandLoggable; + +class DatabaseWorker : public QThread +{ +Q_OBJECT + +public: + DatabaseWorker( DatabaseImpl*, Database*, bool mutates ); + ~DatabaseWorker(); + + bool busy() const { return m_outstanding > 0; } + unsigned int outstandingJobs() const { return m_outstanding; } + +public slots: + void enqueue( const QSharedPointer& ); + +protected: + void run(); + +private slots: + void doWork(); + +private: + void logOp( DatabaseCommandLoggable* command ); + + QMutex m_mut; + DatabaseImpl* m_dbimpl; + QList< QSharedPointer > m_commands; + + bool m_abort; + int m_outstanding; + + QJson::Serializer m_serializer; +}; + +#endif // DATABASEWORKER_H diff --git a/src/libtomahawk/database/fuzzyindex.cpp b/src/libtomahawk/database/fuzzyindex.cpp new file mode 100644 index 000000000..25719317c --- /dev/null +++ b/src/libtomahawk/database/fuzzyindex.cpp @@ -0,0 +1,212 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "fuzzyindex.h" + +#include "databaseimpl.h" +#include "utils/tomahawkutils.h" + +#include +#include + +#include + +using namespace lucene::analysis; +using namespace lucene::document; +using namespace lucene::store; +using namespace lucene::index; +using namespace lucene::queryParser; +using namespace lucene::search; + + +FuzzyIndex::FuzzyIndex( DatabaseImpl& db, bool wipeIndex ) + : QObject() + , m_db( db ) + , m_luceneReader( 0 ) + , m_luceneSearcher( 0 ) +{ + QString m_lucenePath = TomahawkUtils::appDataDir().absoluteFilePath( "tomahawk.lucene" ); + m_luceneDir = FSDirectory::getDirectory( m_lucenePath.toStdString().c_str() ); + m_analyzer = _CLNEW SimpleAnalyzer(); + + if ( wipeIndex ) + { + beginIndexing(); + endIndexing(); + } +} + + +FuzzyIndex::~FuzzyIndex() +{ + delete m_luceneSearcher; + delete m_luceneReader; + delete m_analyzer; + delete m_luceneDir; +} + + +void +FuzzyIndex::beginIndexing() +{ + m_mutex.lock(); + + try + { + qDebug() << Q_FUNC_INFO << "Starting indexing."; + if ( m_luceneReader != 0 ) + { + qDebug() << "Deleting old lucene stuff."; + m_luceneSearcher->close(); + m_luceneReader->close(); + delete m_luceneSearcher; + delete m_luceneReader; + m_luceneSearcher = 0; + m_luceneReader = 0; + } + + qDebug() << "Creating new index writer."; + IndexWriter luceneWriter = IndexWriter( m_luceneDir, m_analyzer, true ); + } + catch( CLuceneError& error ) + { + qDebug() << "Caught CLucene error:" << error.what(); + Q_ASSERT( false ); + } +} + + +void +FuzzyIndex::endIndexing() +{ + m_mutex.unlock(); + emit indexReady(); +} + + +void +FuzzyIndex::appendFields( const QString& table, const QMap< unsigned int, QString >& fields ) +{ + try + { + qDebug() << "Appending to index:" << fields.count(); + bool create = !IndexReader::indexExists( TomahawkUtils::appDataDir().absoluteFilePath( "tomahawk.lucene" ).toStdString().c_str() ); + IndexWriter luceneWriter = IndexWriter( m_luceneDir, m_analyzer, create ); + Document doc; + + QMapIterator< unsigned int, QString > it( fields ); + while ( it.hasNext() ) + { + it.next(); + unsigned int id = it.key(); + QString name = it.value(); + + { + Field* field = _CLNEW Field( table.toStdWString().c_str(), DatabaseImpl::sortname( name ).toStdWString().c_str(), + Field::STORE_YES | Field::INDEX_UNTOKENIZED ); + doc.add( *field ); + } + + { + Field* field = _CLNEW Field( _T( "id" ), QString::number( id ).toStdWString().c_str(), + Field::STORE_YES | Field::INDEX_NO ); + doc.add( *field ); + } + + luceneWriter.addDocument( &doc ); + doc.clear(); + } + + luceneWriter.close(); + } + catch( CLuceneError& error ) + { + qDebug() << "Caught CLucene error:" << error.what(); + Q_ASSERT( false ); + } +} + + +void +FuzzyIndex::loadLuceneIndex() +{ + emit indexReady(); +} + + +QMap< int, float > +FuzzyIndex::search( const QString& table, const QString& name ) +{ + QMutexLocker lock( &m_mutex ); + + QMap< int, float > resultsmap; + try + { + if ( !m_luceneReader ) + { + if ( !IndexReader::indexExists( TomahawkUtils::appDataDir().absoluteFilePath( "tomahawk.lucene" ).toStdString().c_str() ) ) + { + qDebug() << Q_FUNC_INFO << "index didn't exist."; + return resultsmap; + } + + m_luceneReader = IndexReader::open( m_luceneDir ); + m_luceneSearcher = _CLNEW IndexSearcher( m_luceneReader ); + } + + if ( name.isEmpty() ) + return resultsmap; + + SimpleAnalyzer analyzer; + QueryParser parser( table.toStdWString().c_str(), m_analyzer ); + Hits* hits = 0; + + FuzzyQuery* qry = _CLNEW FuzzyQuery( _CLNEW Term( table.toStdWString().c_str(), DatabaseImpl::sortname( name ).toStdWString().c_str() ) ); + hits = m_luceneSearcher->search( qry ); + + for ( int i = 0; i < hits->length(); i++ ) + { + Document* d = &hits->doc( i ); + + float score = hits->score( i ); + int id = QString::fromWCharArray( d->get( _T( "id" ) ) ).toInt(); + QString result = QString::fromWCharArray( d->get( table.toStdWString().c_str() ) ); + + if ( DatabaseImpl::sortname( result ) == DatabaseImpl::sortname( name ) ) + score = 1.0; + else + score = qMin( score, (float)0.99 ); + + if ( score > 0.05 ) + { + resultsmap.insert( id, score ); +// qDebug() << "Hitres:" << result << id << score << table << name; + } + } + + delete hits; + delete qry; + } + catch( CLuceneError& error ) + { + qDebug() << "Caught CLucene error:" << error.what(); + Q_ASSERT( false ); + } + + return resultsmap; +} diff --git a/src/libtomahawk/database/fuzzyindex.h b/src/libtomahawk/database/fuzzyindex.h new file mode 100644 index 000000000..3e93fbb23 --- /dev/null +++ b/src/libtomahawk/database/fuzzyindex.h @@ -0,0 +1,82 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef FUZZYINDEX_H +#define FUZZYINDEX_H + +#include +#include +#include +#include +#include + +namespace lucene +{ + namespace analysis + { + class SimpleAnalyzer; + } + namespace store + { + class Directory; + } + namespace index + { + class IndexReader; + class IndexWriter; + } + namespace search + { + class IndexSearcher; + } +} + +class DatabaseImpl; + +class FuzzyIndex : public QObject +{ +Q_OBJECT + +public: + explicit FuzzyIndex( DatabaseImpl& db, bool wipeIndex = false ); + ~FuzzyIndex(); + + void beginIndexing(); + void endIndexing(); + void appendFields( const QString& table, const QMap< unsigned int, QString >& fields ); + +signals: + void indexReady(); + +public slots: + void loadLuceneIndex(); + + QMap< int, float > search( const QString& table, const QString& name ); + +private: + DatabaseImpl& m_db; + QMutex m_mutex; + QString m_lucenePath; + + lucene::analysis::SimpleAnalyzer* m_analyzer; + lucene::store::Directory* m_luceneDir; + lucene::index::IndexReader* m_luceneReader; + lucene::search::IndexSearcher* m_luceneSearcher; +}; + +#endif // FUZZYINDEX_H diff --git a/src/database/gen_schema.h.sh b/src/libtomahawk/database/gen_schema.h.sh similarity index 78% rename from src/database/gen_schema.h.sh rename to src/libtomahawk/database/gen_schema.h.sh index b8a6a6b79..424ee18c0 100755 --- a/src/database/gen_schema.h.sh +++ b/src/libtomahawk/database/gen_schema.h.sh @@ -1,4 +1,8 @@ #!/bin/bash + +# !!!! You need to manually generate schema.sql.h when the schema changes: +# ./gen_schema.h.sh ./schema.sql tomahawk > schema.sql.h + schema=$1 name=$2 diff --git a/src/libtomahawk/database/op.h b/src/libtomahawk/database/op.h new file mode 100644 index 000000000..9c58e9c56 --- /dev/null +++ b/src/libtomahawk/database/op.h @@ -0,0 +1,36 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef OP_H +#define OP_H +#include +#include +#include + +struct DBOp +{ + QString guid; + QString command; + QByteArray payload; + bool compressed; + bool singleton; +}; + +typedef QSharedPointer dbop_ptr; + +#endif // OP_H diff --git a/src/database/schema.sql b/src/libtomahawk/database/schema.sql similarity index 68% rename from src/database/schema.sql rename to src/libtomahawk/database/schema.sql index efaaf45bc..e35973508 100644 --- a/src/database/schema.sql +++ b/src/libtomahawk/database/schema.sql @@ -7,12 +7,15 @@ CREATE TABLE IF NOT EXISTS oplog ( source INTEGER REFERENCES source(id) ON DELETE CASCADE ON UPDATE CASCADE, -- DEFERRABLE INITIALLY DEFERRED, guid TEXT NOT NULL, command TEXT NOT NULL, + singleton BOOLEAN NOT NULL, compressed BOOLEAN NOT NULL, json TEXT NOT NULL ); CREATE UNIQUE INDEX oplog_guid ON oplog(guid); CREATE INDEX oplog_source ON oplog(source); + + -- the basic 3 catalogue tables: CREATE TABLE IF NOT EXISTS artist ( @@ -38,6 +41,8 @@ CREATE TABLE IF NOT EXISTS album ( ); CREATE UNIQUE INDEX album_artist_sortname ON album(artist,sortname); + + -- Source, typically a remote peer. CREATE TABLE IF NOT EXISTS source ( @@ -50,6 +55,7 @@ CREATE TABLE IF NOT EXISTS source ( CREATE UNIQUE INDEX source_name ON source(name); + -- playlists CREATE TABLE IF NOT EXISTS playlist ( @@ -60,11 +66,15 @@ CREATE TABLE IF NOT EXISTS playlist ( info TEXT, creator TEXT, lastmodified INTEGER NOT NULL DEFAULT 0, - currentrevision TEXT REFERENCES playlist_revision(guid) DEFERRABLE INITIALLY DEFERRED + currentrevision TEXT REFERENCES playlist_revision(guid) DEFERRABLE INITIALLY DEFERRED, + dynplaylist BOOLEAN DEFAULT false ); ---INSERT INTO playlist(guid, title, info, currentrevision) --- VALUES('playlistguid-1','Test Playlist','this playlist automatically created and used for testing','revisionguid-1'); +--INSERT INTO playlist(guid, title, info, currentrevision, dynplaylist) +--VALUES('dynamic_playlist-guid-1','Test Dynamic Playlist Dynamic','this playlist automatically created and used for testing','revisionguid-1', 1); + +--INSERT INTO playlist(guid, title, info, currentrevision, dynplaylist) +--VALUES('dynamic_playlist-guid-2','Test Dynamic Playlist Static','this playlist automatically created and used for testing','revisionguid-11', 1); CREATE TABLE IF NOT EXISTS playlist_item ( guid TEXT PRIMARY KEY, @@ -78,16 +88,8 @@ CREATE TABLE IF NOT EXISTS playlist_item ( addedby INTEGER REFERENCES source(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, -- who added this to the playlist result_hint TEXT -- hint as to a result, to avoid using the resolver ); - CREATE INDEX playlist_item_playlist ON playlist_item(playlist); ---INSERT INTO playlist_item(guid, playlist, trackname, artistname) --- VALUES('itemguid-1','playlistguid-1','track name 01','artist name 01'); ---INSERT INTO playlist_item(guid, playlist, trackname, artistname) --- VALUES('itemguid-2','playlistguid-1','track name 02','artist name 02'); ---INSERT INTO playlist_item(guid, playlist, trackname, artistname) --- VALUES('itemguid-3','playlistguid-1','track name 03','artist name 03'); --- CREATE TABLE IF NOT EXISTS playlist_revision ( guid TEXT PRIMARY KEY, playlist TEXT NOT NULL REFERENCES playlist(guid) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, @@ -98,33 +100,49 @@ CREATE TABLE IF NOT EXISTS playlist_revision ( ); --INSERT INTO playlist_revision(guid, playlist, entries) --- VALUES('revisionguid-1', 'playlistguid-1', '["itemguid-2","itemguid-1","itemguid-3"]'); +-- VALUES('revisionguid-1', 'dynamic_playlist-guid-1', '[]'); +--INSERT INTO playlist_revision(guid, playlist, entries) +-- VALUES('revisionguid-11', 'dynamic_playlist-guid-2', '[]'); --- the trigram search indexes - -CREATE TABLE IF NOT EXISTS artist_search_index ( - ngram TEXT NOT NULL, - id INTEGER NOT NULL REFERENCES artist(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, - num INTEGER NOT NULL DEFAULT 1, - PRIMARY KEY(ngram, id) +CREATE TABLE IF NOT EXISTS dynamic_playlist ( + guid TEXT PRIMARY KEY, + pltype TEXT, -- the generator type + plmode INTEGER -- the mode of this playlist ); --- CREATE INDEX artist_search_index_ngram ON artist_search_index(ngram); -CREATE TABLE IF NOT EXISTS album_search_index ( - ngram TEXT NOT NULL, - id INTEGER NOT NULL REFERENCES album(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, - num INTEGER NOT NULL DEFAULT 1, - PRIMARY KEY(ngram, id) +--INSERT INTO dynamic_playlist(guid, pltype, plmode) +-- VALUES('dynamic_playlist-guid-1', 'echonest', 0); +--INSERT INTO dynamic_playlist(guid, pltype, plmode) +-- VALUES('dynamic_playlist-guid-2', 'echonest', 1); + +-- list of controls in each playlist. each control saves a selectedType, a match, and an input +CREATE TABLE IF NOT EXISTS dynamic_playlist_controls ( + id TEXT PRIMARY KEY, + playlist TEXT NOT NULL REFERENCES playlist(guid) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + selectedType TEXT, + match TEXT, + input TEXT ); --- CREATE INDEX album_search_index_ngram ON album_search_index(ngram); -CREATE TABLE IF NOT EXISTS track_search_index ( - ngram TEXT NOT NULL, - id INTEGER NOT NULL REFERENCES track(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, - num INTEGER NOT NULL DEFAULT 1, - PRIMARY KEY(ngram, id) -); --- CREATE INDEX track_search_index_ngram ON track_search_index(ngram); +--INSERT INTO dynamic_playlist_controls(id, playlist, selectedType, match, input) +-- VALUES('controlid-1', 'dynamic_playlist-guid-1', "artist", 0, "FooArtist" ); +--INSERT INTO dynamic_playlist_controls(id, playlist, selectedType, match, input) +-- VALUES('controlid-2', 'dynamic_playlist-guid-11', "artist", 0, "FooArtist" ); + + + +CREATE TABLE IF NOT EXISTS dynamic_playlist_revision ( + guid TEXT PRIMARY KEY NOT NULL REFERENCES playlist_revision(guid) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + controls TEXT, -- qlist( id, id, id ) + plmode INTEGER, + pltype TEXT +); + +--INSERT INTO dynamic_playlist_revision(guid, controls, plmode, pltype) +-- VALUES('revisionguid-1', '["controlid-1"]', 0, "echonest"); +--INSERT INTO dynamic_playlist_revision(guid, controls, plmode, pltype) +-- VALUES('revisionguid-11', '["controlid-2"]', 1, "echonest"); + -- files on disk and joinage with catalogue. physical properties of files only: @@ -150,7 +168,6 @@ CREATE TABLE IF NOT EXISTS dirs_scanned ( mtime INTEGER NOT NULL ); - CREATE TABLE IF NOT EXISTS file_join ( file INTEGER PRIMARY KEY REFERENCES file(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, artist INTEGER NOT NULL REFERENCES artist(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, @@ -165,9 +182,9 @@ CREATE INDEX file_join_album ON file_join(album); -- tags, weighted and by source (rock, jazz etc) + -- weight is always 1.0 if tag provided by our user. -- may be less from aggregate sources like lastfm global tags - CREATE TABLE IF NOT EXISTS track_tags ( id INTEGER PRIMARY KEY, -- track id source INTEGER REFERENCES source(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, @@ -202,17 +219,48 @@ CREATE INDEX artist_tags_tag ON artist_tags(tag); -- so that we can always do range queries. CREATE TABLE IF NOT EXISTS track_attributes ( - id INTEGER NOT NULL, -- track id + id INTEGER REFERENCES track(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, -- track id k TEXT NOT NULL, v TEXT NOT NULL ); CREATE INDEX track_attrib_id ON track_attributes(id); CREATE INDEX track_attrib_k ON track_attributes(k); + + +-- playback history + +-- if source=null, file is local to this machine +CREATE TABLE IF NOT EXISTS playback_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + source INTEGER REFERENCES source(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + track INTEGER REFERENCES track(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + playtime INTEGER NOT NULL, -- when playback finished (timestamp) + secs_played INTEGER NOT NULL +); +CREATE INDEX playback_log_source ON playback_log(source); +CREATE INDEX playback_log_track ON playback_log(track); + + + +-- auth information for http clients + +CREATE TABLE IF NOT EXISTS http_client_auth ( + token TEXT NOT NULL PRIMARY KEY, + website TEXT NOT NULL, + name TEXT NOT NULL, + ua TEXT, + mtime INTEGER, + permissions TEXT NOT NULL +); + + + -- Schema version, and misc tomahawk settings relating to the collection db CREATE TABLE IF NOT EXISTS settings ( k TEXT NOT NULL PRIMARY KEY, v TEXT NOT NULL DEFAULT '' ); -INSERT INTO settings(k,v) VALUES('schema_version', '14'); + +INSERT INTO settings(k,v) VALUES('schema_version', '22'); diff --git a/src/database/schema.sql.h b/src/libtomahawk/database/schema.sql.h similarity index 77% rename from src/database/schema.sql.h rename to src/libtomahawk/database/schema.sql.h index fd3127304..dfd468cf8 100644 --- a/src/database/schema.sql.h +++ b/src/libtomahawk/database/schema.sql.h @@ -1,5 +1,5 @@ /* - This file was automatically generated from schema.sql on Tue Jul 13 12:23:44 CEST 2010. + This file was automatically generated from ./schema.sql on Wed Mar 2 01:40:39 CET 2011. */ static const char * tomahawk_schema_sql = @@ -8,6 +8,7 @@ static const char * tomahawk_schema_sql = " source INTEGER REFERENCES source(id) ON DELETE CASCADE ON UPDATE CASCADE, " " guid TEXT NOT NULL," " command TEXT NOT NULL," +" singleton BOOLEAN NOT NULL," " compressed BOOLEAN NOT NULL," " json TEXT NOT NULL" ");" @@ -49,7 +50,8 @@ static const char * tomahawk_schema_sql = " info TEXT," " creator TEXT," " lastmodified INTEGER NOT NULL DEFAULT 0," -" currentrevision TEXT REFERENCES playlist_revision(guid) DEFERRABLE INITIALLY DEFERRED" +" currentrevision TEXT REFERENCES playlist_revision(guid) DEFERRABLE INITIALLY DEFERRED," +" dynplaylist BOOLEAN DEFAULT false" ");" "CREATE TABLE IF NOT EXISTS playlist_item (" " guid TEXT PRIMARY KEY," @@ -72,23 +74,25 @@ static const char * tomahawk_schema_sql = " timestamp INTEGER NOT NULL DEFAULT 0," " previous_revision TEXT REFERENCES playlist_revision(guid) DEFERRABLE INITIALLY DEFERRED" ");" -"CREATE TABLE IF NOT EXISTS artist_search_index (" -" ngram TEXT NOT NULL," -" id INTEGER NOT NULL REFERENCES artist(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED," -" num INTEGER NOT NULL DEFAULT 1," -" PRIMARY KEY(ngram, id)" +"CREATE TABLE IF NOT EXISTS dynamic_playlist (" +" guid TEXT PRIMARY KEY," +" pltype TEXT, " +" plmode INTEGER " ");" -"CREATE TABLE IF NOT EXISTS album_search_index (" -" ngram TEXT NOT NULL," -" id INTEGER NOT NULL REFERENCES album(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED," -" num INTEGER NOT NULL DEFAULT 1," -" PRIMARY KEY(ngram, id)" +"CREATE TABLE IF NOT EXISTS dynamic_playlist_controls (" +" id TEXT PRIMARY KEY," +" playlist TEXT NOT NULL REFERENCES playlist(guid) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED," +" selectedType TEXT," +" match TEXT," +" input TEXT" ");" -"CREATE TABLE IF NOT EXISTS track_search_index (" -" ngram TEXT NOT NULL," -" id INTEGER NOT NULL REFERENCES track(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED," -" num INTEGER NOT NULL DEFAULT 1," -" PRIMARY KEY(ngram, id)" +"" +"" +"CREATE TABLE IF NOT EXISTS dynamic_playlist_revision (" +" guid TEXT PRIMARY KEY NOT NULL REFERENCES playlist_revision(guid) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED," +" controls TEXT, " +" plmode INTEGER," +" pltype TEXT" ");" "CREATE TABLE IF NOT EXISTS file (" " id INTEGER PRIMARY KEY AUTOINCREMENT," @@ -142,17 +146,34 @@ static const char * tomahawk_schema_sql = ");" "CREATE INDEX artist_tags_tag ON artist_tags(tag);" "CREATE TABLE IF NOT EXISTS track_attributes (" -" id INTEGER NOT NULL, " +" id INTEGER REFERENCES track(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, " " k TEXT NOT NULL," " v TEXT NOT NULL" ");" "CREATE INDEX track_attrib_id ON track_attributes(id);" "CREATE INDEX track_attrib_k ON track_attributes(k);" +"CREATE TABLE IF NOT EXISTS playback_log (" +" id INTEGER PRIMARY KEY AUTOINCREMENT," +" source INTEGER REFERENCES source(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED," +" track INTEGER REFERENCES track(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED," +" playtime INTEGER NOT NULL, " +" secs_played INTEGER NOT NULL" +");" +"CREATE INDEX playback_log_source ON playback_log(source);" +"CREATE INDEX playback_log_track ON playback_log(track);" +"CREATE TABLE IF NOT EXISTS http_client_auth (" +" token TEXT NOT NULL PRIMARY KEY," +" website TEXT NOT NULL," +" name TEXT NOT NULL," +" ua TEXT," +" mtime INTEGER," +" permissions TEXT NOT NULL" +");" "CREATE TABLE IF NOT EXISTS settings (" " k TEXT NOT NULL PRIMARY KEY," " v TEXT NOT NULL DEFAULT ''" ");" -"INSERT INTO settings(k,v) VALUES('schema_version', '14');" +"INSERT INTO settings(k,v) VALUES('schema_version', '22');" ; const char * get_tomahawk_sql() diff --git a/src/database/tomahawksqlquery.h b/src/libtomahawk/database/tomahawksqlquery.h similarity index 54% rename from src/database/tomahawksqlquery.h rename to src/libtomahawk/database/tomahawksqlquery.h index 543072786..76f65605c 100644 --- a/src/database/tomahawksqlquery.h +++ b/src/libtomahawk/database/tomahawksqlquery.h @@ -1,11 +1,30 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef TOMAHAWKSQLQUERY_H #define TOMAHAWKSQLQUERY_H // subclass QSqlQuery so that it prints the error msg if a query fails #include +#include #include -#define TOMAHAWK_QUERY_THRESHOLD 20 +#define TOMAHAWK_QUERY_THRESHOLD 60 class TomahawkSqlQuery : public QSqlQuery { @@ -16,7 +35,7 @@ public: : QSqlQuery() {} - TomahawkSqlQuery( QSqlDatabase db ) + TomahawkSqlQuery( const QSqlDatabase& db ) : QSqlQuery( db ) {} diff --git a/src/libtomahawk/dllmacro.h b/src/libtomahawk/dllmacro.h new file mode 100644 index 000000000..55a2b6ab9 --- /dev/null +++ b/src/libtomahawk/dllmacro.h @@ -0,0 +1,32 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DLLMACRO_H +#define DLLMACRO_H + +#include + +#ifndef DLLEXPORT +# if defined (DLLEXPORT_PRO) +# define DLLEXPORT Q_DECL_EXPORT +# else +# define DLLEXPORT Q_DECL_IMPORT +# endif +#endif + +#endif diff --git a/src/libtomahawk/functimeout.h b/src/libtomahawk/functimeout.h new file mode 100644 index 000000000..c66134311 --- /dev/null +++ b/src/libtomahawk/functimeout.h @@ -0,0 +1,71 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef FUNCTIMEOUT_H +#define FUNCTIMEOUT_H + +#include +#include +#include + +#include "boost/function.hpp" +#include "boost/bind.hpp" + +#include "dllmacro.h" + +/* + I want to do: + QTimer::singleShot(1000, this, SLOT(doSomething(x))); + instead, I'm doing: + new FuncTimeout(1000, boost::bind(&MyClass::doSomething, this, x)); + + */ +namespace Tomahawk +{ + +class DLLEXPORT FuncTimeout : public QObject +{ +Q_OBJECT + +public: + FuncTimeout( int ms, boost::function func ) + : m_func( func ) + { + //qDebug() << Q_FUNC_INFO; + QTimer::singleShot( ms, this, SLOT( exec() ) ); + }; + + ~FuncTimeout() + { + //qDebug() << Q_FUNC_INFO; + }; + +public slots: + void exec() + { + m_func(); + this->deleteLater(); + }; + +private: + boost::function m_func; +}; + +}; // ns + +#endif // FUNCTIMEOUT_H diff --git a/src/libtomahawk/network/bufferiodevice.cpp b/src/libtomahawk/network/bufferiodevice.cpp new file mode 100644 index 000000000..435a2b2cd --- /dev/null +++ b/src/libtomahawk/network/bufferiodevice.cpp @@ -0,0 +1,275 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "bufferiodevice.h" + +#include +#include +#include + +// Msgs are framed, this is the size each msg we send containing audio data: +#define BLOCKSIZE 4096 + + +BufferIODevice::BufferIODevice( unsigned int size, QObject* parent ) + : QIODevice( parent ) + , m_size( size ) + , m_received( 0 ) + , m_pos( 0 ) +{ +} + + +bool +BufferIODevice::open( OpenMode mode ) +{ + QMutexLocker lock( &m_mut ); + + qDebug() << Q_FUNC_INFO; + QIODevice::open( QIODevice::ReadOnly | QIODevice::Unbuffered ); // FIXME? + return true; +} + + +void +BufferIODevice::close() +{ + QMutexLocker lock( &m_mut ); + + qDebug() << Q_FUNC_INFO; + QIODevice::close(); +} + + +bool +BufferIODevice::seek( qint64 pos ) +{ + qDebug() << Q_FUNC_INFO << pos << m_size; + + if ( pos >= m_size ) + return false; + + int block = blockForPos( pos ); + if ( isBlockEmpty( block ) ) + emit blockRequest( block ); + + m_pos = pos; + qDebug() << "Finished seeking"; + + return true; +} + + +void +BufferIODevice::seeked( int block ) +{ + qDebug() << Q_FUNC_INFO << block << m_size; +} + + +void +BufferIODevice::inputComplete( const QString& errmsg ) +{ + qDebug() << Q_FUNC_INFO; + setErrorString( errmsg ); + m_size = m_received; + emit readChannelFinished(); +} + + +void +BufferIODevice::addData( int block, const QByteArray& ba ) +{ + { + QMutexLocker lock( &m_mut ); + + while ( m_buffer.count() <= block ) + m_buffer << QByteArray(); + + m_buffer.replace( block, ba ); + } + + // If this was the last block of the transfer, check if we need to fill up gaps + if ( block + 1 == maxBlocks() ) + { + if ( nextEmptyBlock() >= 0 ) + { + emit blockRequest( nextEmptyBlock() ); + } + } + + m_received += ba.count(); + emit bytesWritten( ba.count() ); + emit readyRead(); +} + + +qint64 +BufferIODevice::bytesAvailable() const +{ + return m_size - m_pos; +} + + +qint64 +BufferIODevice::readData( char* data, qint64 maxSize ) +{ +// qDebug() << Q_FUNC_INFO << m_pos << maxSize << 1; + + if ( atEnd() ) + return 0; + + QByteArray ba; + ba.append( getData( m_pos, maxSize ) ); + m_pos += ba.count(); + +// qDebug() << Q_FUNC_INFO << maxSize << ba.count() << 2; + memcpy( data, ba.data(), ba.count() ); + + return ba.count(); +} + + +qint64 +BufferIODevice::writeData( const char* data, qint64 maxSize ) +{ + // call addData instead + Q_ASSERT( false ); + return 0; +} + + +qint64 +BufferIODevice::size() const +{ + qDebug() << Q_FUNC_INFO << m_size; + return m_size; +} + + +bool +BufferIODevice::atEnd() const +{ +// qDebug() << Q_FUNC_INFO << ( m_size <= m_pos ); + return ( m_size <= m_pos ); +} + + +void +BufferIODevice::clear() +{ + QMutexLocker lock( &m_mut ); + + m_pos = 0; + m_buffer.clear(); +} + + +unsigned int +BufferIODevice::blockSize() +{ + return BLOCKSIZE; +} + + +int +BufferIODevice::blockForPos( qint64 pos ) const +{ + // 0 / 4096 -> block 0 + // 4095 / 4096 -> block 0 + // 4096 / 4096 -> block 1 + + return pos / BLOCKSIZE; +} + + +int +BufferIODevice::offsetForPos( qint64 pos ) const +{ + // 0 % 4096 -> offset 0 + // 4095 % 4096 -> offset 4095 + // 4096 % 4096 -> offset 0 + + return pos % BLOCKSIZE; +} + + +int +BufferIODevice::nextEmptyBlock() const +{ + int i = 0; + foreach( const QByteArray& ba, m_buffer ) + { + if ( ba.isEmpty() ) + return i; + + i++; + } + + if ( i == maxBlocks() ) + return -1; + + return i; +} + + +int +BufferIODevice::maxBlocks() const +{ + int i = m_size / BLOCKSIZE; + + if ( ( m_size % BLOCKSIZE ) > 0 ) + i++; + + return i; +} + + +bool +BufferIODevice::isBlockEmpty( int block ) const +{ + if ( block >= m_buffer.count() ) + return true; + + return m_buffer.at( block ).isEmpty(); +} + + +QByteArray +BufferIODevice::getData( qint64 pos, qint64 size ) +{ +// qDebug() << Q_FUNC_INFO << pos << size << 1; + QByteArray ba; + int block = blockForPos( pos ); + int offset = offsetForPos( pos ); + + QMutexLocker lock( &m_mut ); + while( ba.count() < size ) + { + if ( block > maxBlocks() ) + break; + + if ( isBlockEmpty( block ) ) + break; + + ba.append( m_buffer.at( block++ ).mid( offset ) ); + } + +// qDebug() << Q_FUNC_INFO << pos << size << 2; + return ba.left( size ); +} diff --git a/src/libtomahawk/network/bufferiodevice.h b/src/libtomahawk/network/bufferiodevice.h new file mode 100644 index 000000000..4e0244991 --- /dev/null +++ b/src/libtomahawk/network/bufferiodevice.h @@ -0,0 +1,79 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef BUFFERIODEVICE_H +#define BUFFERIODEVICE_H + +#include +#include +#include +#include + +class BufferIODevice : public QIODevice +{ +Q_OBJECT + +public: + explicit BufferIODevice( unsigned int size = 0, QObject* parent = 0 ); + + virtual bool open( OpenMode mode ); + virtual void close(); + + virtual bool seek( qint64 pos ); + void seeked( int block ); + + virtual qint64 bytesAvailable() const; + virtual qint64 size() const; + virtual bool atEnd() const; + virtual qint64 pos() const { qDebug() << Q_FUNC_INFO << m_pos; return m_pos; } + + void addData( int block, const QByteArray& ba ); + void clear(); + + OpenMode openMode() const { qDebug() << "openMode"; return QIODevice::ReadOnly | QIODevice::Unbuffered; } + + void inputComplete( const QString& errmsg = "" ); + + virtual bool isSequential() const { return false; } + + static unsigned int blockSize(); + + int maxBlocks() const; + int nextEmptyBlock() const; + bool isBlockEmpty( int block ) const; + +signals: + void blockRequest( int block ); + +protected: + virtual qint64 readData( char* data, qint64 maxSize ); + virtual qint64 writeData( const char* data, qint64 maxSize ); + +private: + int blockForPos( qint64 pos ) const; + int offsetForPos( qint64 pos ) const; + QByteArray getData( qint64 pos, qint64 size ); + + QList m_buffer; + mutable QMutex m_mut; //const methods need to lock + unsigned int m_size, m_received; + + unsigned int m_pos; +}; + +#endif // BUFFERIODEVICE_H diff --git a/src/network/connection.cpp b/src/libtomahawk/network/connection.cpp similarity index 88% rename from src/network/connection.cpp rename to src/libtomahawk/network/connection.cpp index 21fb2bd44..afa009713 100644 --- a/src/network/connection.cpp +++ b/src/libtomahawk/network/connection.cpp @@ -1,11 +1,29 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "connection.h" #include #include -#include "servent.h" +#include "network/servent.h" -#define PROTOVER "2" // must match remote peer, or we can't talk. +#define PROTOVER "4" // must match remote peer, or we can't talk. Connection::Connection( Servent* parent ) @@ -54,6 +72,7 @@ Connection::~Connection() { qDebug() << "no valid sock to delete"; } + delete m_statstimer; } @@ -69,8 +88,8 @@ Connection::handleIncomingQueueEmpty() if( m_sock->bytesAvailable() == 0 && m_peer_disconnected ) { qDebug() << "No more data to read, peer disconnected. shutting down connection." - << "bytesavail" << m_sock->bytesAvailable() - << "bytesrx" << m_rx_bytes; + << "bytesavail" << m_sock->bytesAvailable() + << "bytesrx" << m_rx_bytes; shutdown(); } } @@ -100,14 +119,14 @@ void Connection::shutdown( bool waitUntilSentAll ) { qDebug() << Q_FUNC_INFO << waitUntilSentAll; - if(m_do_shutdown) + if ( m_do_shutdown ) { //qDebug() << id() << " already shutting down"; return; } m_do_shutdown = true; - if( !waitUntilSentAll ) + if ( !waitUntilSentAll ) { qDebug() << "Shutting down immediately " << id(); actualShutdown(); @@ -205,7 +224,7 @@ Connection::doSetup() //stats timer calculates BW used by this connection m_statstimer = new QTimer; m_statstimer->moveToThread( this->thread() ); - m_statstimer->setInterval(1000); + m_statstimer->setInterval( 1000 ); connect( m_statstimer, SIGNAL( timeout() ), SLOT( calcStats() ) ); m_statstimer->start(); m_statstimer_mark.start(); @@ -248,18 +267,18 @@ Connection::doSetup() void Connection::socketDisconnected() { - qDebug() << "SOCKET DISCONNECTED" << this->name() + qDebug() << "SOCKET DISCONNECTED" << this->name() << id() << "shutdown will happen after incoming queue empties." << "bytesavail:" << m_sock->bytesAvailable() << "bytesRecvd" << bytesReceived(); m_peer_disconnected = true; - emit socketClosed(); if( m_msgprocessor_in.length() == 0 && m_sock->bytesAvailable() == 0 ) { handleIncomingQueueEmpty(); + actualShutdown(); } } @@ -297,7 +316,8 @@ Connection::setId( const QString& id ) void Connection::readyRead() { - //qDebug() << "readyRead, m_bs:" << m_bs << "bytesavail:" << m_sock->bytesAvailable(); +// qDebug() << "readyRead, bytesavail:" << m_sock->bytesAvailable(); + if( m_msg.isNull() ) { if( m_sock->bytesAvailable() < Msg::headerSize() ) @@ -395,7 +415,7 @@ Connection::sendMsg( msg_ptr msg ) if( m_do_shutdown ) { qDebug() << Q_FUNC_INFO << "SHUTTING DOWN, NOT SENDING msg flags:" - << (int)msg->flags() << "length:" << msg->length(); + << (int)msg->flags() << "length:" << msg->length() << id(); return; } @@ -414,7 +434,7 @@ Connection::sendMsg_now( msg_ptr msg ) if( m_sock.isNull() || !m_sock->isOpen() || !m_sock->isWritable() ) { qDebug() << "***** Socket problem, whilst in sendMsg(). Cleaning up. *****"; - shutdown( true ); + shutdown( false ); return; } @@ -432,7 +452,7 @@ Connection::bytesWritten( qint64 i ) { m_tx_bytes += i; // if we are waiting to shutdown, and have sent all queued data, do actual shutdown: - if( m_do_shutdown && m_tx_bytes == m_tx_bytes_requested ) + if ( m_do_shutdown && m_tx_bytes == m_tx_bytes_requested ) actualShutdown(); } diff --git a/src/network/connection.h b/src/libtomahawk/network/connection.h similarity index 77% rename from src/network/connection.h rename to src/libtomahawk/network/connection.h index 79a3259e9..95c94980f 100644 --- a/src/network/connection.h +++ b/src/libtomahawk/network/connection.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef CONNECTION_H #define CONNECTION_H @@ -21,9 +39,11 @@ #include "msg.h" #include "msgprocessor.h" +#include "dllmacro.h" + class Servent; -class Connection : public QObject +class DLLEXPORT Connection : public QObject { Q_OBJECT @@ -119,7 +139,7 @@ private: QString m_id; QTimer* m_statstimer; - QTime m_statstimer_mark; + QTime m_statstimer_mark; qint64 m_stats_tx_bytes_per_sec, m_stats_rx_bytes_per_sec; qint64 m_rx_bytes_last, m_tx_bytes_last; diff --git a/src/network/controlconnection.cpp b/src/libtomahawk/network/controlconnection.cpp similarity index 62% rename from src/network/controlconnection.cpp rename to src/libtomahawk/network/controlconnection.cpp index e851bfa9d..2e122ca96 100644 --- a/src/network/controlconnection.cpp +++ b/src/libtomahawk/network/controlconnection.cpp @@ -1,11 +1,30 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "controlconnection.h" -#include "tomahawk/tomahawkapp.h" -#include "remotecollection.h" #include "filetransferconnection.h" -#include "database.h" -#include "databasecommand_collectionstats.h" +#include "database/database.h" +#include "database/databasecommand_collectionstats.h" #include "dbsyncconnection.h" +#include "sourcelist.h" + +#define TCP_TIMEOUT 600 using namespace Tomahawk; @@ -14,6 +33,7 @@ ControlConnection::ControlConnection( Servent* parent ) : Connection( parent ) , m_dbsyncconn( 0 ) , m_registered( false ) + , m_pingtimer( 0 ) { qDebug() << "CTOR controlconnection"; setId("ControlConnection()"); @@ -29,17 +49,23 @@ ControlConnection::ControlConnection( Servent* parent ) ControlConnection::~ControlConnection() { qDebug() << "DTOR controlconnection"; + + if ( !m_source.isNull() ) + m_source->setOffline(); + + delete m_pingtimer; m_servent->unregisterControlConnection(this); - if( m_dbsyncconn ) m_dbsyncconn->deleteLater(); + if( m_dbsyncconn ) + m_dbsyncconn->deleteLater(); } Connection* ControlConnection::clone() { - ControlConnection * clone = new ControlConnection(servent()); - clone->setOnceOnly(onceOnly()); - clone->setName(name()); + ControlConnection* clone = new ControlConnection( servent() ); + clone->setOnceOnly( onceOnly() ); + clone->setName( name() ); return clone; } @@ -48,29 +74,34 @@ void ControlConnection::setup() { qDebug() << Q_FUNC_INFO << id() << name(); - // setup source and remote collection for this peer - m_source = source_ptr( new Source( id(), this ) ); - if( Servent::isIPWhitelisted( m_sock->peerAddress() ) ) + QString friendlyName; + if ( Servent::isIPWhitelisted( m_sock->peerAddress() ) ) { // FIXME TODO blocking DNS lookup if LAN, slow/fails on windows? QHostInfo i = QHostInfo::fromName( m_sock->peerAddress().toString() ); if( i.hostName().length() ) - { - m_source->setFriendlyName( i.hostName() ); - } + friendlyName = i.hostName(); } else - { - m_source->setFriendlyName( QString( "%1" ).arg( name() ) ); - } + friendlyName = name(); + + // setup source and remote collection for this peer + m_source = SourceList::instance()->get( id(), friendlyName ); + m_source->setControlConnection( this ); // delay setting up collection/etc until source is synced. // we need it DB synced so it has an ID + exists in DB. connect( m_source.data(), SIGNAL( syncedWithDatabase() ), SLOT( registerSource() ), Qt::QueuedConnection ); - m_source->doDBSync(); + m_source->setOnline(); + + m_pingtimer = new QTimer; + m_pingtimer->setInterval( 5000 ); + connect( m_pingtimer, SIGNAL( timeout() ), SLOT( onPingTimer() ) ); + m_pingtimer->start(); + m_pingtimer_mark.start(); } @@ -78,41 +109,32 @@ ControlConnection::setup() void ControlConnection::registerSource() { - qDebug() << Q_FUNC_INFO; - Source * source = (Source*) sender(); + qDebug() << Q_FUNC_INFO << m_source->id(); + Source* source = (Source*) sender(); Q_ASSERT( source == m_source.data() ); // .. but we'll use the shared pointer we've already made: - collection_ptr coll( new RemoteCollection( m_source ) ); - m_source->addCollection( coll ); - TomahawkApp::instance()->sourcelist().add( m_source ); - m_registered = true; + m_servent->registerControlConnection( this ); setupDbSyncConnection(); - m_servent->registerControlConnection(this); } void ControlConnection::setupDbSyncConnection( bool ondemand ) { - if( m_dbsyncconn != NULL || ! m_registered ) + qDebug() << Q_FUNC_INFO << ondemand << m_source->id() << m_dbconnkey << m_dbsyncconn << m_registered; + + if ( m_dbsyncconn || !m_registered ) return; - qDebug() << Q_FUNC_INFO << ondemand << m_source->id(); Q_ASSERT( m_source->id() > 0 ); - if( ! m_dbconnkey.isEmpty() ) + if( !m_dbconnkey.isEmpty() ) { qDebug() << "Connecting to DBSync offer from peer..."; m_dbsyncconn = new DBSyncConnection( m_servent, m_source ); - connect( m_dbsyncconn, SIGNAL( finished() ), - m_dbsyncconn, SLOT( deleteLater() ) ); - - connect( m_dbsyncconn, SIGNAL( destroyed( QObject* ) ), - SLOT( dbSyncConnFinished( QObject* ) ), Qt::DirectConnection ); - m_servent->createParallelConnection( this, m_dbsyncconn, m_dbconnkey ); m_dbconnkey.clear(); } @@ -121,12 +143,6 @@ ControlConnection::setupDbSyncConnection( bool ondemand ) qDebug() << "Offering a DBSync key to peer..."; m_dbsyncconn = new DBSyncConnection( m_servent, m_source ); - connect( m_dbsyncconn, SIGNAL( finished() ), - m_dbsyncconn, SLOT( deleteLater()) ); - - connect( m_dbsyncconn, SIGNAL( destroyed(QObject* ) ), - SLOT( dbSyncConnFinished( QObject* ) ), Qt::DirectConnection ); - QString key = uuid(); m_servent->registerOffer( key, m_dbsyncconn ); QVariantMap m; @@ -134,6 +150,15 @@ ControlConnection::setupDbSyncConnection( bool ondemand ) m.insert( "key", key ); sendMsg( m ); } + + if ( m_dbsyncconn ) + { + connect( m_dbsyncconn, SIGNAL( finished() ), + m_dbsyncconn, SLOT( deleteLater() ) ); + + connect( m_dbsyncconn, SIGNAL( destroyed( QObject* ) ), + SLOT( dbSyncConnFinished( QObject* ) ), Qt::DirectConnection ); + } } @@ -146,15 +171,20 @@ ControlConnection::dbSyncConnFinished( QObject* c ) //qDebug() << "Setting m_dbsyncconn to NULL"; m_dbsyncconn = NULL; } + else + qDebug() << "Old DbSyncConn destroyed?!"; } DBSyncConnection* ControlConnection::dbSyncConnection() { - qDebug() << Q_FUNC_INFO; - if( m_dbsyncconn == NULL ) + qDebug() << Q_FUNC_INFO << m_source->id(); + if ( !m_dbsyncconn ) + { setupDbSyncConnection( true ); +// Q_ASSERT( m_dbsyncconn ); + } return m_dbsyncconn; } @@ -163,6 +193,13 @@ ControlConnection::dbSyncConnection() void ControlConnection::handleMsg( msg_ptr msg ) { + if ( msg->is( Msg::PING ) ) + { + // qDebug() << "Received Connection PING, nice." << m_pingtimer_mark.elapsed(); + m_pingtimer_mark.restart(); + return; + } + // if small and not compresed, print it out for debug if( msg->length() < 1024 && !msg->is( Msg::COMPRESSED ) ) { @@ -207,3 +244,17 @@ ControlConnection::handleMsg( msg_ptr msg ) qDebug() << id() << "Invalid msg:" << QString::fromAscii(msg->payload()); } + + + +void +ControlConnection::onPingTimer() +{ + if ( m_pingtimer_mark.elapsed() >= TCP_TIMEOUT * 1000 ) + { + qDebug() << "Timeout reached! Shutting down connection to" << m_source->friendlyName(); + shutdown( true ); + } + + sendMsg( Msg::factory( QByteArray(), Msg::PING ) ); +} diff --git a/src/libtomahawk/network/controlconnection.h b/src/libtomahawk/network/controlconnection.h new file mode 100644 index 000000000..17e060579 --- /dev/null +++ b/src/libtomahawk/network/controlconnection.h @@ -0,0 +1,75 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +/* + One ControlConnection always remains open to each peer. + + They arrange connections/reverse connections, inform us + when the peer goes offline, and own+setup DBSyncConnections. + +*/ +#ifndef CONTROLCONNECTION_H +#define CONTROLCONNECTION_H + +#include "connection.h" +#include "network/servent.h" +#include "source.h" +#include "typedefs.h" + +#include "dllmacro.h" + +class FileTransferSession; + +class DLLEXPORT ControlConnection : public Connection +{ +Q_OBJECT + +public: + explicit ControlConnection( Servent* parent = 0 ); + ~ControlConnection(); + Connection* clone(); + + DBSyncConnection* dbSyncConnection(); + +protected: + virtual void setup(); + +protected slots: + virtual void handleMsg( msg_ptr msg ); + +signals: + +private slots: + void dbSyncConnFinished( QObject* c ); + void registerSource(); + void onPingTimer(); + +private: + void setupDbSyncConnection( bool ondemand = false ); + + Tomahawk::source_ptr m_source; + DBSyncConnection* m_dbsyncconn; + + QString m_dbconnkey; + bool m_registered; + + QTimer* m_pingtimer; + QTime m_pingtimer_mark; +}; + +#endif // CONTROLCONNECTION_H diff --git a/src/libtomahawk/network/dbsyncconnection.cpp b/src/libtomahawk/network/dbsyncconnection.cpp new file mode 100644 index 000000000..a25d8ab8f --- /dev/null +++ b/src/libtomahawk/network/dbsyncconnection.cpp @@ -0,0 +1,330 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +/* + Database syncing using the oplog table. + ======================================= + Load the last GUID we applied for the peer, tell them it. + In return, they send us all new ops since that guid. + + We then apply those new ops to our cache of their data + + Synced. + +*/ + +#include "dbsyncconnection.h" + +#include + +#include "database/database.h" +#include "database/databasecommand.h" +#include "database/databasecommand_collectionstats.h" +#include "database/databasecommand_loadops.h" +#include "remotecollection.h" +#include "source.h" +#include "sourcelist.h" + +// close the dbsync connection after this much inactivity. +// it's automatically reestablished as needed. +#define IDLE_TIMEOUT 300000 + +using namespace Tomahawk; + + +DBSyncConnection::DBSyncConnection( Servent* s, source_ptr src ) + : Connection( s ) + , m_source( src ) + , m_state( UNKNOWN ) +{ + qDebug() << Q_FUNC_INFO << src->id() << thread(); + connect( this, SIGNAL( stateChanged( DBSyncConnection::State, DBSyncConnection::State, QString ) ), + m_source.data(), SLOT( onStateChanged( DBSyncConnection::State, DBSyncConnection::State, QString ) ) ); + + m_timer.setInterval( IDLE_TIMEOUT ); + connect( &m_timer, SIGNAL( timeout() ), SLOT( idleTimeout() ) ); + + this->setMsgProcessorModeIn( MsgProcessor::PARSE_JSON | MsgProcessor::UNCOMPRESS_ALL ); + + // msgs are stored compressed in the db, so not typically needed here, but doesnt hurt: + this->setMsgProcessorModeOut( MsgProcessor::COMPRESS_IF_LARGE ); +} + + +DBSyncConnection::~DBSyncConnection() +{ + qDebug() << "DTOR" << Q_FUNC_INFO; +} + + +void +DBSyncConnection::idleTimeout() +{ + qDebug() << Q_FUNC_INFO << "*************"; + shutdown( true ); +} + + +void +DBSyncConnection::changeState( State newstate ) +{ + State s = m_state; + m_state = newstate; + qDebug() << "DBSYNC State changed from" << s << "to" << newstate; + emit stateChanged( newstate, s, "" ); + + if ( newstate == SYNCED ) + { + qDebug() << "Synced :)"; + } +} + + +void +DBSyncConnection::setup() +{ + qDebug() << Q_FUNC_INFO; + setId( QString( "DBSyncConnection/%1" ).arg( socket()->peerAddress().toString() ) ); + check(); +} + + +void +DBSyncConnection::trigger() +{ + qDebug() << Q_FUNC_INFO; + + // if we're still setting up the connection, do nothing - we sync on first connect anyway: + if ( !isRunning() ) + return; + + QMetaObject::invokeMethod( this, "sendMsg", Qt::QueuedConnection, + Q_ARG( msg_ptr, + Msg::factory( "{\"method\":\"trigger\"}", Msg::JSON ) ) + ); +} + + +void +DBSyncConnection::check() +{ + qDebug() << Q_FUNC_INFO << m_source->id(); + if ( m_state != UNKNOWN && m_state != SYNCED ) + { + qDebug() << "Syncing in progress already."; + return; + } + m_uscache.clear(); + m_themcache.clear(); + m_us.clear(); + + changeState( CHECKING ); + + // load last-modified etc data for our collection and theirs from our DB: + DatabaseCommand_CollectionStats* cmd_us = + new DatabaseCommand_CollectionStats( SourceList::instance()->getLocal() ); + + DatabaseCommand_CollectionStats* cmd_them = + new DatabaseCommand_CollectionStats( m_source ); + + connect( cmd_us, SIGNAL( done( QVariantMap ) ), + SLOT( gotUs( QVariantMap ) ) ); + + connect( cmd_them, SIGNAL( done( QVariantMap ) ), + SLOT( gotThemCache( QVariantMap ) ) ); + + + Database::instance()->enqueue( QSharedPointer(cmd_us) ); + Database::instance()->enqueue( QSharedPointer(cmd_them) ); + + // restarts idle countdown + m_timer.start(); +} + + +/// Called once we've loaded our mtimes etc from the DB for our local +/// collection - send them to the remote peer to compare. +void +DBSyncConnection::gotUs( const QVariantMap& m ) +{ + m_us = m; + if ( !m_uscache.empty() ) + sendOps(); +} + + +/// Called once we've loaded our cached data about their collection +void +DBSyncConnection::gotThemCache( const QVariantMap& m ) +{ + m_themcache = m; + changeState( FETCHING ); + + qDebug() << "Sending a FETCHOPS cmd since:" << m_themcache.value( "lastop" ).toString(); + + QVariantMap msg; + msg.insert( "method", "fetchops" ); + msg.insert( "lastop", m_themcache.value( "lastop" ).toString() ); + sendMsg( msg ); +} + + +void +DBSyncConnection::handleMsg( msg_ptr msg ) +{ + Q_ASSERT( !msg->is( Msg::COMPRESSED ) ); + + if ( m_state == FETCHING ) + changeState( PARSING ); + + // "everything is synced" indicated by non-json msg containing "ok": + if ( !msg->is( Msg::JSON ) && + msg->is( Msg::DBOP ) && + msg->payload() == "ok" ) + { + qDebug() << "No ops to apply, we are synced."; + changeState( SYNCED ); + // calc the collection stats, to updates the "X tracks" in the sidebar etc + // this is done automatically if you run a dbcmd to add files. + DatabaseCommand_CollectionStats* cmd = new DatabaseCommand_CollectionStats( m_source ); + connect( cmd, SIGNAL( done( const QVariantMap & ) ), + m_source.data(), SLOT( setStats( const QVariantMap& ) ), Qt::QueuedConnection ); + Database::instance()->enqueue( QSharedPointer(cmd) ); + return; + } + + Q_ASSERT( msg->is( Msg::JSON ) ); + + QVariantMap m = msg->json().toMap(); + if ( m.empty() ) + { + qDebug() << "Failed to parse msg in dbsync" << m_source->id() << m_source->friendlyName(); + Q_ASSERT( false ); + return; + } + + // a db sync op msg + if ( msg->is( Msg::DBOP ) ) + { + DatabaseCommand* cmd = DatabaseCommand::factory( m, m_source ); + if ( !cmd ) + { + qDebug() << "UNKNOWN DBOP CMD"; + + if ( !msg->is( Msg::FRAGMENT ) ) // last msg in this batch + lastOpApplied(); + return; + } + + qDebug() << "APPLYING CMD" << cmd->commandname() << cmd->guid(); + + if ( !msg->is( Msg::FRAGMENT ) ) // last msg in this batch + { + changeState( SAVING ); // just DB work left to complete + connect( cmd, SIGNAL( finished() ), SLOT( lastOpApplied() ) ); + } + + if ( !cmd->singletonCmd() ) + m_source->setLastOpGuid( cmd->guid() ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); + return; + } + + if ( m.value( "method" ).toString() == "fetchops" ) + { + m_uscache = m; + if ( !m_us.empty() ) + sendOps(); + return; + } + + if ( m.value( "method" ).toString() == "trigger" ) + { + qDebug() << "Got trigger msg on dbsyncconnection, checking for new stuff."; + check(); + return; + } + + qDebug() << Q_FUNC_INFO << "Unhandled msg:" << msg->payload(); + Q_ASSERT( false ); +} + + +void +DBSyncConnection::lastOpApplied() +{ + qDebug() << Q_FUNC_INFO; + changeState( SYNCED ); + // check again, until peer responds we have no new ops to process + check(); +} + + +/// request new copies of anything we've cached that is stale +void +DBSyncConnection::sendOps() +{ + qDebug() << Q_FUNC_INFO; + qDebug() << "Will send peer all ops since" << m_uscache.value( "lastop" ).toString(); + + source_ptr src = SourceList::instance()->getLocal(); + + DatabaseCommand_loadOps* cmd = new DatabaseCommand_loadOps( src, m_uscache.value( "lastop" ).toString() ); + connect( cmd, SIGNAL( done( QString, QString, QList< dbop_ptr > ) ), + SLOT( sendOpsData( QString, QString, QList< dbop_ptr > ) ) ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + + +void +DBSyncConnection::sendOpsData( QString sinceguid, QString lastguid, QList< dbop_ptr > ops ) +{ + if ( m_lastSentOp == lastguid ) + ops.clear(); + + qDebug() << Q_FUNC_INFO << sinceguid << lastguid << "Num ops to send:" << ops.length(); + m_lastSentOp = lastguid; + if ( ops.length() == 0 ) + { + sendMsg( Msg::factory( "ok", Msg::DBOP ) ); + return; + } + + int i; + for( i = 0; i < ops.length(); ++i ) + { + quint8 flags = Msg::JSON | Msg::DBOP; + + if ( ops.at( i )->compressed ) + flags |= Msg::COMPRESSED; + if ( i != ops.length() - 1 ) + flags |= Msg::FRAGMENT; + + sendMsg( Msg::factory( ops.at( i )->payload, flags ) ); + } +} + + +Connection* +DBSyncConnection::clone() +{ + Q_ASSERT( false ); + return 0; +} diff --git a/src/network/dbsyncconnection.h b/src/libtomahawk/network/dbsyncconnection.h similarity index 55% rename from src/network/dbsyncconnection.h rename to src/libtomahawk/network/dbsyncconnection.h index 762a01111..f97c633d5 100644 --- a/src/network/dbsyncconnection.h +++ b/src/libtomahawk/network/dbsyncconnection.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef DBSYNCCONNECTION_H #define DBSYNCCONNECTION_H @@ -6,9 +24,9 @@ #include #include -#include "connection.h" +#include "network/connection.h" #include "database/op.h" -#include "tomahawk/typedefs.h" +#include "typedefs.h" class DBSyncConnection : public Connection { @@ -47,7 +65,7 @@ private slots: void gotUs( const QVariantMap& m ); void gotThemCache( const QVariantMap& m ); void lastOpApplied(); - void sendOpsData( QString sinceguid, QList< dbop_ptr > ops ); + void sendOpsData( QString sinceguid, QString lastguid, QList< dbop_ptr > ops ); void check(); void idleTimeout(); @@ -60,6 +78,8 @@ private: QVariantMap m_us, m_uscache, m_themcache; State m_state; + QString m_lastSentOp; + QTimer m_timer; }; diff --git a/src/libtomahawk/network/filetransferconnection.cpp b/src/libtomahawk/network/filetransferconnection.cpp new file mode 100644 index 000000000..d357a2f33 --- /dev/null +++ b/src/libtomahawk/network/filetransferconnection.cpp @@ -0,0 +1,285 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "filetransferconnection.h" + +#include + +#include "result.h" + +#include "bufferiodevice.h" +#include "network/controlconnection.h" +#include "database/databasecommand_loadfile.h" +#include "database/database.h" +#include "sourcelist.h" + +using namespace Tomahawk; + + +FileTransferConnection::FileTransferConnection( Servent* s, ControlConnection* cc, QString fid, const Tomahawk::result_ptr& result ) + : Connection( s ) + , m_cc( cc ) + , m_fid( fid ) + , m_type( RECEIVING ) + , m_curBlock( 0 ) + , m_badded( 0 ) + , m_bsent( 0 ) + , m_allok( false ) + , m_result( result ) + , m_transferRate( 0 ) +{ + qDebug() << Q_FUNC_INFO; + + BufferIODevice* bio = new BufferIODevice( result->size() ); + m_iodev = QSharedPointer( bio ); // device audio data gets written to + m_iodev->open( QIODevice::ReadWrite ); + + Servent::instance()->registerFileTransferConnection( this ); + + // if the audioengine closes the iodev (skip/stop/etc) then kill the connection + // immediately to avoid unnecessary network transfer + connect( m_iodev.data(), SIGNAL( aboutToClose() ), SLOT( shutdown() ), Qt::QueuedConnection ); + connect( m_iodev.data(), SIGNAL( blockRequest( int ) ), SLOT( onBlockRequest( int ) ) ); + + // auto delete when connection closes: + connect( this, SIGNAL( finished() ), SLOT( deleteLater() ), Qt::QueuedConnection ); + + // don't fuck with our messages at all. No compression, no parsing, nothing: + this->setMsgProcessorModeIn ( MsgProcessor::NOTHING ); + this->setMsgProcessorModeOut( MsgProcessor::NOTHING ); +} + + +FileTransferConnection::FileTransferConnection( Servent* s, ControlConnection* cc, QString fid ) + : Connection( s ) + , m_cc( cc ) + , m_fid( fid ) + , m_type( SENDING ) + , m_badded( 0 ) + , m_bsent( 0 ) + , m_allok( false ) + , m_transferRate( 0 ) +{ + Servent::instance()->registerFileTransferConnection( this ); + // auto delete when connection closes: + connect( this, SIGNAL( finished() ), SLOT( deleteLater() ), Qt::QueuedConnection ); +} + + +FileTransferConnection::~FileTransferConnection() +{ + qDebug() << Q_FUNC_INFO << "TX/RX:" << bytesSent() << bytesReceived(); + if( m_type == RECEIVING && !m_allok ) + { + qDebug() << "FTConnection closing before last data msg received, shame."; + //TODO log the fact that our peer was bad-mannered enough to not finish the upload + + // protected, we could expose it: + //m_iodev->setErrorString("FTConnection providing data went away mid-transfer"); + + ((BufferIODevice*)m_iodev.data())->inputComplete(); + } + + Servent::instance()->onFileTransferFinished( this ); +} + + +QString +FileTransferConnection::id() const +{ + return QString( "FTC[%1 %2]" ) + .arg( m_type == SENDING ? "TX" : "RX" ) + .arg( m_fid ); +} + + +Tomahawk::source_ptr +FileTransferConnection::source() const +{ + return m_source; +} + +void +FileTransferConnection::showStats( qint64 tx, qint64 rx ) +{ + if( tx > 0 || rx > 0 ) + { + qDebug() << id() + << QString( "Down: %L1 bytes/sec," ).arg( rx ) + << QString( "Up: %L1 bytes/sec" ).arg( tx ); + } + + m_transferRate = tx + rx; + emit updated(); +} + + +void +FileTransferConnection::setup() +{ + QList sources = SourceList::instance()->sources(); + foreach( const source_ptr& src, sources ) + { + // local src doesnt have a control connection, skip it: + if( src.isNull() || src->isLocal() ) + continue; + + if ( src->controlConnection() == m_cc ) + { + m_source = src; + break; + } + } + + connect( this, SIGNAL( statsTick( qint64, qint64 ) ), SLOT( showStats( qint64, qint64 ) ) ); + if( m_type == RECEIVING ) + { + qDebug() << "in RX mode"; + emit updated(); + return; + } + + qDebug() << "in TX mode, fid:" << m_fid; + + DatabaseCommand_LoadFile* cmd = new DatabaseCommand_LoadFile( m_fid ); + connect( cmd, SIGNAL( result( Tomahawk::result_ptr ) ), SLOT( startSending( Tomahawk::result_ptr ) ) ); + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + + +void +FileTransferConnection::startSending( const Tomahawk::result_ptr& result ) +{ + if ( result.isNull() ) + { + qDebug() << "Can't handle invalid result!"; + shutdown(); + return; + } + + m_result = result; + qDebug() << "Starting to transmit" << m_result->url(); + + QSharedPointer io = Servent::instance()->getIODeviceForUrl( m_result ); + if( !io ) + { + qDebug() << "Couldn't read from source:" << m_result->url(); + shutdown(); + return; + } + + m_readdev = QSharedPointer( io ); + sendSome(); + + emit updated(); +} + + +void +FileTransferConnection::handleMsg( msg_ptr msg ) +{ + Q_ASSERT( msg->is( Msg::RAW ) ); + + if ( msg->payload().startsWith( "block" ) ) + { + int block = QString( msg->payload() ).mid( 5 ).toInt(); + m_readdev->seek( block * BufferIODevice::blockSize() ); + + qDebug() << "Seeked to block:" << block; + + QByteArray sm; + sm.append( QString( "doneblock%1" ).arg( block ) ); + + sendMsg( Msg::factory( sm, Msg::RAW | Msg::FRAGMENT ) ); + QTimer::singleShot( 0, this, SLOT( sendSome() ) ); + } + else if ( msg->payload().startsWith( "doneblock" ) ) + { + int block = QString( msg->payload() ).mid( 9 ).toInt(); + ((BufferIODevice*)m_iodev.data())->seeked( block ); + + m_curBlock = block; + qDebug() << "Next block is now:" << block; + } + else if ( msg->payload().startsWith( "data" ) ) + { + m_badded += msg->payload().length() - 4; + ((BufferIODevice*)m_iodev.data())->addData( m_curBlock++, msg->payload().mid( 4 ) ); + } + + //qDebug() << Q_FUNC_INFO << "flags" << (int) msg->flags() + // << "payload len" << msg->payload().length() + // << "written to device so far: " << m_badded; + + if ( ((BufferIODevice*)m_iodev.data())->nextEmptyBlock() < 0 ) + { + m_allok = true; + // tell our iodev there is no more data to read, no args meaning a success: + ((BufferIODevice*)m_iodev.data())->inputComplete(); + shutdown(); + } +} + + +Connection* +FileTransferConnection::clone() +{ + Q_ASSERT( false ); + return 0; +} + + +void +FileTransferConnection::sendSome() +{ + Q_ASSERT( m_type == FileTransferConnection::SENDING ); + + QByteArray ba = "data"; + ba.append( m_readdev->read( BufferIODevice::blockSize() ) ); + m_bsent += ba.length() - 4; + + if( m_readdev->atEnd() ) + { + sendMsg( Msg::factory( ba, Msg::RAW ) ); + return; + } + else + { + // more to come -> FRAGMENT + sendMsg( Msg::factory( ba, Msg::RAW | Msg::FRAGMENT ) ); + } + + // HINT: change the 0 to 50 to transmit at 640Kbps, for example + // (this is where upload throttling could be implemented) + QTimer::singleShot( 0, this, SLOT( sendSome() ) ); +} + + +void +FileTransferConnection::onBlockRequest( int block ) +{ + qDebug() << Q_FUNC_INFO << block; + + if ( m_curBlock == block ) + return; + + QByteArray sm; + sm.append( QString( "block%1" ).arg( block ) ); + + sendMsg( Msg::factory( sm, Msg::RAW | Msg::FRAGMENT ) ); +} diff --git a/src/libtomahawk/network/filetransferconnection.h b/src/libtomahawk/network/filetransferconnection.h new file mode 100644 index 000000000..330a6863a --- /dev/null +++ b/src/libtomahawk/network/filetransferconnection.h @@ -0,0 +1,96 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef FILETRANSFERCONNECTION_H +#define FILETRANSFERCONNECTION_H + +#include +#include +#include + +#include "network/connection.h" +#include "result.h" + +#include "dllmacro.h" + +class ControlConnection; +class BufferIODevice; + +class DLLEXPORT FileTransferConnection : public Connection +{ +Q_OBJECT + +public: + enum Type + { + SENDING = 0, + RECEIVING = 1 + }; + + // RX: + explicit FileTransferConnection( Servent* s, ControlConnection* cc, QString fid, const Tomahawk::result_ptr& result ); + // TX: + explicit FileTransferConnection( Servent* s, ControlConnection* cc, QString fid ); + + virtual ~FileTransferConnection(); + + QString id() const; + void setup(); + Connection* clone(); + + const QSharedPointer& iodevice() { return m_iodev; } + ControlConnection* controlConnection() const { return m_cc; } + + Tomahawk::source_ptr source() const; + Tomahawk::result_ptr track() const { return m_result; } + qint64 transferRate() const { return m_transferRate; } + + Type type() const { return m_type; } + QString fid() const { return m_fid; } + +signals: + void updated(); + +protected slots: + virtual void handleMsg( msg_ptr msg ); + +private slots: + void startSending( const Tomahawk::result_ptr& ); + void sendSome(); + void showStats( qint64 tx, qint64 rx ); + + void onBlockRequest( int pos ); + +private: + QSharedPointer m_iodev; + ControlConnection* m_cc; + QString m_fid; + Type m_type; + QSharedPointer m_readdev; + + int m_curBlock; + + int m_badded, m_bsent; + bool m_allok; // got last msg ok, transfer complete? + + Tomahawk::source_ptr m_source; + Tomahawk::result_ptr m_result; + qint64 m_transferRate; +}; + +#endif // FILETRANSFERCONNECTION_H diff --git a/src/msg.h b/src/libtomahawk/network/msg.h similarity index 80% rename from src/msg.h rename to src/libtomahawk/network/msg.h index f0fd5fa33..d79fd0982 100644 --- a/src/msg.h +++ b/src/libtomahawk/network/msg.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + /* Msg is a wire msg used by p2p connections. Msgs have a 5-byte header: @@ -35,8 +53,8 @@ public: FRAGMENT = 4, COMPRESSED = 8, DBOP = 16, - UNUSED_FLAG_6 = 32, - UNUSED_FLAG_7 = 64, + PING = 32, + RESERVED_1 = 64, SETUP = 128 // used to handshake/auth the connection prior to handing over to Connection subclass }; diff --git a/src/msgprocessor.cpp b/src/libtomahawk/network/msgprocessor.cpp similarity index 71% rename from src/msgprocessor.cpp rename to src/libtomahawk/network/msgprocessor.cpp index f910b62a6..85bdcd26c 100644 --- a/src/msgprocessor.cpp +++ b/src/libtomahawk/network/msgprocessor.cpp @@ -1,14 +1,35 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "msgprocessor.h" -#include "tomahawk/tomahawkapp.h" + +#include "network/servent.h" + MsgProcessor::MsgProcessor( quint32 mode, quint32 t ) : QObject(), m_mode( mode ), m_threshold( t ), m_totmsgsize( 0 ) { - moveToThread( APP->servent().thread() ); + moveToThread( Servent::instance()->thread() ); } -void MsgProcessor::append( msg_ptr msg ) +void +MsgProcessor::append( msg_ptr msg ) { if( QThread::currentThread() != thread() ) { @@ -39,7 +60,8 @@ void MsgProcessor::append( msg_ptr msg ) } -void MsgProcessor::processed() +void +MsgProcessor::processed() { QFutureWatcher * watcher = (QFutureWatcher *) sender(); msg_ptr msg = watcher->result(); @@ -47,7 +69,9 @@ void MsgProcessor::processed() handleProcessedMsg( msg ); } -void MsgProcessor::handleProcessedMsg( msg_ptr msg ) + +void +MsgProcessor::handleProcessedMsg( msg_ptr msg ) { Q_ASSERT( QThread::currentThread() == thread() ); @@ -74,7 +98,8 @@ void MsgProcessor::handleProcessedMsg( msg_ptr msg ) /// This method is run by QtConcurrent: -msg_ptr MsgProcessor::process( msg_ptr msg, quint32 mode, quint32 threshold ) +msg_ptr +MsgProcessor::process( msg_ptr msg, quint32 mode, quint32 threshold ) { // uncompress if needed if( (mode & UNCOMPRESS_ALL) && msg->is( Msg::COMPRESSED ) ) @@ -109,5 +134,3 @@ msg_ptr MsgProcessor::process( msg_ptr msg, quint32 mode, quint32 threshold ) } return msg; } - - diff --git a/src/msgprocessor.h b/src/libtomahawk/network/msgprocessor.h similarity index 61% rename from src/msgprocessor.h rename to src/libtomahawk/network/msgprocessor.h index 9ecf1e58d..b8dc7ef7d 100644 --- a/src/msgprocessor.h +++ b/src/libtomahawk/network/msgprocessor.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + /* MsgProcessor is a FIFO queue of msg_ptr, you .add() a msg_ptr, and it emits done(msg_ptr) for each msg, preserving the order. diff --git a/src/libtomahawk/network/portfwdthread.cpp b/src/libtomahawk/network/portfwdthread.cpp new file mode 100644 index 000000000..36cdd8088 --- /dev/null +++ b/src/libtomahawk/network/portfwdthread.cpp @@ -0,0 +1,107 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "portfwdthread.h" + +#include +#include +#include +#include +#include + +#include "portfwd/portfwd.h" + + +PortFwdThread::PortFwdThread( unsigned int port ) + : QThread() + , m_externalPort( 0 ) + , m_port( port ) +{ + moveToThread( this ); + start(); +} + + +PortFwdThread::~PortFwdThread() +{ + qDebug() << Q_FUNC_INFO << "waiting for event loop to finish..."; + quit(); + wait( 10000 ); + + delete m_portfwd; +} + + +void +PortFwdThread::work() +{ + qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); + m_portfwd = new Portfwd(); + + // try and pick an available port: + if( m_portfwd->init( 2000 ) ) + { + int tryport = m_port; + + // last.fm office firewall policy hack + // (corp. firewall allows outgoing connections to this port, + // so listen on this if you want lastfmers to connect to you) + if( qApp->arguments().contains( "--porthack" ) ) + { + tryport = 3389; + m_portfwd->remove( tryport ); + } + + for( int r = 0; r < 3; ++r ) + { + qDebug() << "Trying to setup portfwd on" << tryport; + if( m_portfwd->add( tryport, m_port ) ) + { + QString pubip = QString( m_portfwd->external_ip().c_str() ); + m_externalAddress = QHostAddress( pubip ); + m_externalPort = tryport; + qDebug() << "External servent address detected as" << pubip << ":" << m_externalPort; + qDebug() << "Max upstream " << m_portfwd->max_upstream_bps() << "bps"; + qDebug() << "Max downstream" << m_portfwd->max_downstream_bps() << "bps"; + break; + } + tryport = qAbs( 10000 + 50000 * (float)qrand() / RAND_MAX ); + } + } + else + qDebug() << "No UPNP Gateway device found?"; + + if( !m_externalPort ) + qDebug() << "Could not setup fwd for port:" << m_port; + + emit externalAddressDetected( m_externalAddress, m_externalPort ); +} + + +void +PortFwdThread::run() +{ + QTimer::singleShot( 0, this, SLOT( work() ) ); + exec(); + + if ( m_externalPort ) + { + qDebug() << "Unregistering port fwd"; + m_portfwd->remove( m_externalPort ); + } +} diff --git a/src/libtomahawk/network/portfwdthread.h b/src/libtomahawk/network/portfwdthread.h new file mode 100644 index 000000000..85eb98023 --- /dev/null +++ b/src/libtomahawk/network/portfwdthread.h @@ -0,0 +1,50 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef PORTFWDTHREAD_H +#define PORTFWDTHREAD_H + +#include +#include +#include + +class Portfwd; + +class PortFwdThread : public QThread +{ +Q_OBJECT + +public: + explicit PortFwdThread( unsigned int port ); + ~PortFwdThread(); + +signals: + void externalAddressDetected( QHostAddress ha, unsigned int port ); + +private slots: + void work(); + +private: + void run(); + + Portfwd* m_portfwd; + QHostAddress m_externalAddress; + unsigned int m_externalPort, m_port; +}; + +#endif // PORTFWDTHREAD_H diff --git a/src/libtomahawk/network/remotecollection.cpp b/src/libtomahawk/network/remotecollection.cpp new file mode 100644 index 000000000..8bfdfd3a2 --- /dev/null +++ b/src/libtomahawk/network/remotecollection.cpp @@ -0,0 +1,43 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "remotecollection.h" + +using namespace Tomahawk; + +RemoteCollection::RemoteCollection( source_ptr source, QObject* parent ) + : DatabaseCollection( source, parent ) +{ + qDebug() << Q_FUNC_INFO; +} + + +// adding/removing is done by dbsyncconnection, and the dbcmd objects that modify +// the database will make us emit the appropriate signals (tracksAdded etc.) +void RemoteCollection::addTracks( const QList& newitems ) +{ + qDebug() << Q_FUNC_INFO; + Q_ASSERT( false ); +} + + +void RemoteCollection::removeTracks( const QDir& dir ) +{ + qDebug() << Q_FUNC_INFO; + Q_ASSERT( false ); +} diff --git a/src/libtomahawk/network/remotecollection.h b/src/libtomahawk/network/remotecollection.h new file mode 100644 index 000000000..ef28be853 --- /dev/null +++ b/src/libtomahawk/network/remotecollection.h @@ -0,0 +1,45 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef REMOTECOLLECTION_H +#define REMOTECOLLECTION_H + +#include "typedefs.h" + +#include "network/controlconnection.h" +#include "database/databasecollection.h" + +class RemoteCollection : public DatabaseCollection +{ +Q_OBJECT + +friend class ControlConnection; // for receiveTracks() + +public: + explicit RemoteCollection( Tomahawk::source_ptr source, QObject* parent = 0 ); + ~RemoteCollection() + { + qDebug() << Q_FUNC_INFO; + } + +public slots: + virtual void addTracks( const QList& newitems ); + virtual void removeTracks( const QDir& dir ); +}; + +#endif // REMOTECOLLECTION_H diff --git a/src/network/servent.cpp b/src/libtomahawk/network/servent.cpp similarity index 58% rename from src/network/servent.cpp rename to src/libtomahawk/network/servent.cpp index 85c0dae8f..495e903de 100644 --- a/src/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -1,37 +1,90 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "servent.h" +#include #include #include -#include +#include #include +#include +#include +#include -#include "tomahawk/tomahawkapp.h" -#include "tomahawk/result.h" -#include "tomahawk/source.h" +#include "result.h" +#include "source.h" #include "bufferiodevice.h" #include "connection.h" #include "controlconnection.h" +#include "database/database.h" #include "filetransferconnection.h" +#include "sourcelist.h" + +#include "portfwdthread.h" +#include "tomahawksettings.h" +#include "utils/tomahawkutils.h" using namespace Tomahawk; +Servent* Servent::s_instance = 0; + + +Servent* +Servent::instance() +{ + return s_instance; +} + Servent::Servent( QObject* parent ) : QTcpServer( parent ) , m_port( 0 ) , m_externalPort( 0 ) + , m_portfwd( 0 ) { - qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); + s_instance = this; + + setProxy( QNetworkProxy::NoProxy ); + + { + boost::function(result_ptr)> fac = + boost::bind( &Servent::localFileIODeviceFactory, this, _1 ); + this->registerIODeviceFactory( "file", fac ); + } + + { + boost::function(result_ptr)> fac = + boost::bind( &Servent::remoteIODeviceFactory, this, _1 ); + this->registerIODeviceFactory( "servent", fac ); + } + + { + boost::function(result_ptr)> fac = + boost::bind( &Servent::httpIODeviceFactory, this, _1 ); + this->registerIODeviceFactory( "http", fac ); + } } Servent::~Servent() { - if( m_externalPort ) - { - qDebug() << "Unregistering port fwd"; - pf.remove( m_externalPort ); - } + delete m_portfwd; } @@ -44,6 +97,7 @@ Servent::startListening( QHostAddress ha, bool upnp, int port ) if( !listen( ha, m_port ) && !listen( ha, ++m_port ) ) { qDebug() << "Failed to listen on port" << m_port; + qDebug() << "Error string is " << errorString(); return false; } else @@ -51,66 +105,46 @@ Servent::startListening( QHostAddress ha, bool upnp, int port ) qDebug() << "Servent listening on port" << m_port << " servent thread:" << thread(); } - // TODO check if we have a public/internet IP on this machine directly - // FIXME the portfwd stuff is blocking, so we hang here for 2 secs atm - if( upnp ) - { - // try and pick an available port: - if( pf.init( 2000 ) ) - { - int tryport = m_port; - - // last.fm office firewall policy hack - // (corp. firewall allows outgoing connections to this port, - // so listen on this if you want lastfmers to connect to you) - if( APP->arguments().contains("--porthack") ) - { - tryport = 3389; - pf.remove( tryport ); - } - - for( int r=0; r<5; ++r ) - { - qDebug() << "Trying to setup portfwd on" << tryport; - if( pf.add( tryport, m_port ) ) - { - QString pubip = QString( pf.external_ip().c_str() ); - m_externalAddress = QHostAddress( pubip ); - m_externalPort = tryport; - qDebug() << "External servent address detected as" << pubip << ":" << m_externalPort; - qDebug() << "Max upstream " << pf.max_upstream_bps() << "bps"; - qDebug() << "Max downstream" << pf.max_downstream_bps() << "bps"; - break; - } - tryport = 10000 + 50000 * (float)qrand()/RAND_MAX; - } - if( !m_externalPort ) - { - qDebug() << "Could not setup fwd for port:" << m_port; - } - } - else qDebug() << "No UPNP Gateway device found?"; - } - - if( m_externalPort == 0 ) - { - qDebug() << "No external access, LAN and outbound connections only!"; - } - // --lanhack means to advertise your LAN IP over jabber as if it were externallyVisible - if( TomahawkApp::instance()->arguments().contains( "--lanhack" ) ) + qDebug() << "Address mode = " << (int)(TomahawkSettings::instance()->externalAddressMode()); + qDebug() << "Static host/port preferred ? = " << ( TomahawkSettings::instance()->preferStaticHostPort() ? "true" : "false" ); + + if( TomahawkSettings::instance()->preferStaticHostPort() ) { - QList ifs = QNetworkInterface::allAddresses(); - foreach( QHostAddress ha, ifs ) - { - if( ha.toString() == "127.0.0.1" ) continue; - if( ha.toString().contains( ":" ) ) continue; //ipv6 + qDebug() << "Forcing static preferred host and port"; + m_externalHostname = TomahawkSettings::instance()->externalHostname(); + m_externalPort = TomahawkSettings::instance()->externalPort(); + qDebug() << m_externalHostname << m_externalPort; + emit ready(); + return true; + } + + switch( TomahawkSettings::instance()->externalAddressMode() ) + { + case TomahawkSettings::Lan: + foreach( QHostAddress ha, QNetworkInterface::allAddresses() ) + { + if( ha.toString() == "127.0.0.1" ) continue; + if( ha.toString().contains( ":" ) ) continue; //ipv6 - m_externalAddress = ha; - m_externalPort = m_port; - qDebug() << "LANHACK: set external address to lan address" << ha.toString(); + if ( qApp->arguments().contains( "--lanhack" ) ) + { + qDebug() << "LANHACK: set external address to lan address" << ha.toString(); + QMetaObject::invokeMethod( this, "setExternalAddress", Qt::QueuedConnection, Q_ARG( QHostAddress, ha ), Q_ARG( unsigned int, m_port ) ); + } + else + emit ready(); + break; + } + break; + + case TomahawkSettings::Upnp: + // TODO check if we have a public/internet IP on this machine directly + qDebug() << "External address mode set to upnp...."; + m_portfwd = new PortFwdThread( m_port ); + connect( m_portfwd, SIGNAL( externalAddressDetected( QHostAddress, unsigned int ) ), + SLOT( setExternalAddress( QHostAddress, unsigned int ) ) ); break; - } } return true; @@ -118,22 +152,44 @@ Servent::startListening( QHostAddress ha, bool upnp, int port ) QString -Servent::createConnectionKey( const QString& name ) +Servent::createConnectionKey( const QString& name, const QString &nodeid, const QString &key, bool onceOnly ) { + qDebug() << Q_FUNC_INFO; Q_ASSERT( this->thread() == QThread::currentThread() ); - QString key = uuid(); - ControlConnection * cc = new ControlConnection( this ); - cc->setName( name=="" ? QString( "KEY(%1)" ).arg(key) : name ); - registerOffer( key, cc ); - return key; + + QString _key = ( key.isEmpty() ? uuid() : key ); + ControlConnection* cc = new ControlConnection( this ); + cc->setName( name.isEmpty() ? QString( "KEY(%1)" ).arg( key ) : name ); + if( !nodeid.isEmpty() ) + cc->setId( nodeid ); + cc->setOnceOnly( onceOnly ); + qDebug() << "Creating connection key with name of " << cc->name() << " and id of " << cc->id() << " and key of " << _key << "; key is once only? : " << (onceOnly ? "true" : "false"); + registerOffer( _key, cc ); + return _key; } void -Servent::setExternalAddress( QHostAddress ha, int port ) +Servent::setExternalAddress( QHostAddress ha, unsigned int port ) { m_externalAddress = ha; m_externalPort = port; + + if( m_externalPort == 0 || m_externalAddress.toString().isEmpty() ) + { + if( !TomahawkSettings::instance()->externalHostname().isEmpty() && + !TomahawkSettings::instance()->externalPort() == 0 ) + { + qDebug() << "UPnP failed, have external address/port -- falling back"; + m_externalHostname = TomahawkSettings::instance()->externalHostname(); + m_externalPort = TomahawkSettings::instance()->externalPort(); + qDebug() << m_externalHostname << m_externalPort; + } + else + qDebug() << "No external access, LAN and outbound connections only!"; + } + + emit ready(); } @@ -179,8 +235,10 @@ void Servent::incomingConnection( int sd ) { Q_ASSERT( this->thread() == QThread::currentThread() ); + QTcpSocketExtra* sock = new QTcpSocketExtra; qDebug() << Q_FUNC_INFO << "Accepting connection, sock" << sock; + sock->moveToThread( thread() ); sock->_disowned = false; sock->_outbound = false; @@ -227,29 +285,45 @@ Servent::readyRead() sock->_msg->fill( ba ); Q_ASSERT( sock->_msg->is( Msg::JSON ) ); + ControlConnection* cc = 0; bool ok; int pport = 0; - QString key, conntype, nodeid; + QString key, conntype, nodeid, controlid; QVariantMap m = parser.parse( sock->_msg->payload(), &ok ).toMap(); if( !ok ) { qDebug() << "Invalid JSON on new conection, aborting"; goto closeconnection; } - conntype = m.value( "conntype" ).toString(); - key = m.value( "key" ).toString(); - pport = m.value( "port" ).toInt(); - nodeid = m.value( "nodeid" ).toString(); + conntype = m.value( "conntype" ).toString(); + key = m.value( "key" ).toString(); + pport = m.value( "port" ).toInt(); + nodeid = m.value( "nodeid" ).toString(); + controlid = m.value( "controlid" ).toString(); qDebug() << m; if( !nodeid.isEmpty() ) // only control connections send nodeid - foreach( ControlConnection* cc, m_controlconnections ) { - if( cc->id() == nodeid ) + foreach( ControlConnection* con, m_controlconnections ) { - qDebug() << "Duplicate control connection detected, dropping:" << nodeid << conntype; - goto closeconnection; + qDebug() << con->socket() << sock; + if( con->id() == nodeid ) + { + qDebug() << "Duplicate control connection detected, dropping:" << nodeid << conntype; + goto closeconnection; + } + } + } + + foreach( ControlConnection* con, m_controlconnections ) + { + qDebug() << "conid:" << con->id(); + + if ( con->id() == controlid ) + { + cc = con; + break; } } @@ -257,7 +331,7 @@ Servent::readyRead() if( conntype == "accept-offer" || "push-offer" ) { sock->_msg.clear(); - Connection * conn = claimOffer( key, sock->peerAddress() ); + Connection* conn = claimOffer( cc, key, sock->peerAddress() ); if( !conn ) { qDebug() << "claimOffer FAILED, key:" << key; @@ -265,10 +339,10 @@ Servent::readyRead() } qDebug() << "claimOffer OK:" << key; - if( !nodeid.isEmpty() ) conn->setId( nodeid ); + if( !nodeid.isEmpty() ) + conn->setId( nodeid ); handoverSocket( conn, sock ); - return; } else @@ -289,9 +363,9 @@ closeconnection: void Servent::createParallelConnection( Connection* orig_conn, Connection* new_conn, const QString& key ) { - qDebug() << "Servent::createParallelConnection, key:" << key << thread(); + qDebug() << "Servent::createParallelConnection, key:" << key << thread() << orig_conn; // if we can connect to them directly: - if( orig_conn->outbound() ) + if( orig_conn && orig_conn->outbound() ) { qDebug() << "Connecting directly"; connectToPeer( orig_conn->socket()->peerAddress().toString(), @@ -310,6 +384,7 @@ Servent::createParallelConnection( Connection* orig_conn, Connection* new_conn, m.insert( "key", tmpkey ); m.insert( "offer", key ); m.insert( "port", externalPort() ); + m.insert( "controlid", Database::instance()->dbid() ); QJson::Serializer ser; orig_conn->sendMsg( Msg::factory( ser.serialize(m), Msg::JSON ) ); @@ -317,7 +392,6 @@ Servent::createParallelConnection( Connection* orig_conn, Connection* new_conn, } -/// for outbound connections. DRY out the socket handover code from readyread too? void Servent::socketConnected() { @@ -326,7 +400,6 @@ Servent::socketConnected() qDebug() << "Servent::SocketConnected" << thread() << "socket:" << sock; Connection* conn = sock->_conn; - handoverSocket( conn, sock ); } @@ -366,7 +439,7 @@ Servent::socketError( QAbstractSocket::SocketError e ) Connection* conn = sock->_conn; qDebug() << "Servent::SocketError:" << e << conn->id() << conn->name(); - if(!sock->_disowned) + if( !sock->_disowned ) { // connection will delete if we already transferred ownership, otherwise: sock->deleteLater(); @@ -383,10 +456,10 @@ Servent::connectToPeer( const QString& ha, int port, const QString &key, const Q ControlConnection* conn = new ControlConnection( this ); QVariantMap m; - m["conntype"] = "accept-offer"; - m["key"] = key; - m["port"] = externalPort(); - m["nodeid"] = APP->nodeID(); + m["conntype"] = "accept-offer"; + m["key"] = key; + m["port"] = externalPort(); + m["nodeid"] = Database::instance()->dbid(); conn->setFirstMessage( m ); if( name.length() ) @@ -408,12 +481,20 @@ Servent::connectToPeer( const QString& ha, int port, const QString &key, Connect qDebug() << "Servent::connectToPeer:" << ha << ":" << port << thread() << QThread::currentThread(); + if ( ( ha == m_externalAddress.toString() || ha == m_externalHostname ) && + ( port == m_externalPort ) ) + { + qDebug() << "ERROR: Tomahawk won't try to connect to" << ha << ":" << port << ": identified as ourselves."; + return; + } + if( key.length() && conn->firstMessage().isNull() ) { QVariantMap m; - m["conntype"] = "accept-offer"; - m["key"] = key; - m["port"] = externalPort(); + m["conntype"] = "accept-offer"; + m["key"] = key; + m["port"] = externalPort(); + m["controlid"] = Database::instance()->dbid(); conn->setFirstMessage( m ); } @@ -435,13 +516,13 @@ Servent::connectToPeer( const QString& ha, int port, const QString &key, Connect void -Servent::reverseOfferRequest( Connection* orig_conn, const QString& key, const QString& theirkey ) +Servent::reverseOfferRequest( ControlConnection* orig_conn, const QString& key, const QString& theirkey ) { Q_ASSERT( this->thread() == QThread::currentThread() ); qDebug() << "Servent::reverseOfferRequest received for" << key; - Connection * new_conn = claimOffer( key ); - if( !new_conn ) + Connection* new_conn = claimOffer( orig_conn, key ); + if ( !new_conn ) { qDebug() << "claimOffer failed, killing requesting connection out of spite"; orig_conn->shutdown(); @@ -449,9 +530,10 @@ Servent::reverseOfferRequest( Connection* orig_conn, const QString& key, const Q } QVariantMap m; - m["conntype"] = "push-offer"; - m["key"] = theirkey; - m["port"] = externalPort(); + m["conntype"] = "push-offer"; + m["key"] = theirkey; + m["port"] = externalPort(); + m["controlid"] = Database::instance()->dbid(); new_conn->setFirstMessage( m ); createParallelConnection( orig_conn, new_conn, QString() ); } @@ -459,12 +541,12 @@ Servent::reverseOfferRequest( Connection* orig_conn, const QString& key, const Q // return the appropriate connection for a given offer key, or NULL if invalid Connection* -Servent::claimOffer( const QString &key, const QHostAddress peer ) +Servent::claimOffer( ControlConnection* cc, const QString &key, const QHostAddress peer ) { bool noauth = qApp->arguments().contains( "--noauth" ); // magic key for file transfers: - if( key.startsWith("FILE_REQUEST_KEY:") ) + if( key.startsWith( "FILE_REQUEST_KEY:" ) ) { // check if the source IP matches an existing, authenticated connection if ( !noauth && peer != QHostAddress::Any && !isIPWhitelisted( peer ) ) @@ -486,13 +568,13 @@ Servent::claimOffer( const QString &key, const QHostAddress peer ) } QString fid = key.right( key.length() - 17 ); - FileTransferConnection* ftc = new FileTransferConnection( this, fid ); + FileTransferConnection* ftc = new FileTransferConnection( this, cc, fid ); return ftc; } if( key == "whitelist" ) // LAN IP address, check source IP { - if( isIPWhitelisted(peer) ) + if( isIPWhitelisted( peer ) ) { qDebug() << "Connection is from whitelisted IP range (LAN)"; Connection* conn = new ControlConnection( this ); @@ -546,18 +628,18 @@ Servent::claimOffer( const QString &key, const QHostAddress peer ) QSharedPointer Servent::remoteIODeviceFactory( const result_ptr& result ) { - qDebug() << Q_FUNC_INFO << thread() ; + qDebug() << Q_FUNC_INFO << thread(); QSharedPointer sp; - QStringList parts = result->url().mid( QString( "servent://" ).length()).split( "\t" ); - const QString& sourceName = parts.at( 0 ); - const QString& fileId = parts.at( 1 ); - const source_ptr& s = TomahawkApp::instance()->sourcelist().lookup( sourceName ); - if ( s.isNull() ) + QStringList parts = result->url().mid( QString( "servent://" ).length() ).split( "\t" ); + const QString sourceName = parts.at( 0 ); + const QString fileId = parts.at( 1 ); + source_ptr s = SourceList::instance()->get( sourceName ); + if ( s.isNull() || !s->controlConnection() ) return sp; ControlConnection* cc = s->controlConnection(); - FileTransferConnection* ftc = new FileTransferConnection( this, cc, fileId, result->size() ); + FileTransferConnection* ftc = new FileTransferConnection( this, cc, fileId, result ); createParallelConnection( cc, ftc, QString( "FILE_REQUEST_KEY:%1" ).arg( fileId ) ); return ftc->iodevice(); } @@ -567,23 +649,27 @@ void Servent::registerFileTransferConnection( FileTransferConnection* ftc ) { Q_ASSERT( !m_ftsessions.contains( ftc ) ); - QMutexLocker lock( &m_ftsession_mut ); qDebug() << "Registering FileTransfer" << m_ftsessions.length() + 1; + + QMutexLocker lock( &m_ftsession_mut ); m_ftsessions.append( ftc ); + printCurrentTransfers(); + emit fileTransferStarted( ftc ); } void -Servent::fileTransferFinished( FileTransferConnection* ftc ) +Servent::onFileTransferFinished( FileTransferConnection* ftc ) { Q_ASSERT( ftc ); - qDebug() << "FileTransfer Finished, unregistering" << ftc->id(); + QMutexLocker lock( &m_ftsession_mut ); - int rem = m_ftsessions.removeAll( ftc ); - Q_ASSERT( rem == 1 ); + m_ftsessions.removeAll( ftc ); + printCurrentTransfers(); + emit fileTransferFinished( ftc ); } @@ -608,11 +694,11 @@ Servent::isIPWhitelisted( QHostAddress ip ) static QList whitelist; if( whitelist.isEmpty() ) { - whitelist << range( QHostAddress("10.0.0.0"), 8 ) - << range( QHostAddress("172.16.0.0"), 12 ) - << range( QHostAddress("192.168.0.0"), 16 ) - << range( QHostAddress("169.254.0.0"), 16 ) - << range( QHostAddress("127.0.0.0"), 24 ); + whitelist << range( QHostAddress( "10.0.0.0" ), 8 ) + << range( QHostAddress( "172.16.0.0" ), 12 ) + << range( QHostAddress( "192.168.0.0" ), 16 ) + << range( QHostAddress( "169.254.0.0" ), 16 ) + << range( QHostAddress( "127.0.0.0" ), 24 ); // qDebug() << "Loaded whitelist IP range:" << whitelist; } @@ -644,13 +730,64 @@ Servent::triggerDBSync() qDebug() << Q_FUNC_INFO; // tell peers we have new stuff they should sync - QList sources = APP->sourcelist().sources(); + QList sources = SourceList::instance()->sources(); foreach( const source_ptr& src, sources ) { - // local src doesnt have a control connection, skip it: - if( src.isNull() || src->isLocal() ) + // skip local source + if ( src.isNull() || src->isLocal() ) continue; - src->controlConnection()->dbSyncConnection()->trigger(); + if ( src->controlConnection() && src->controlConnection()->dbSyncConnection() ) // source online? + src->controlConnection()->dbSyncConnection()->trigger(); } } + + +void +Servent::registerIODeviceFactory( const QString &proto, boost::function(Tomahawk::result_ptr)> fac ) +{ + m_iofactories.insert( proto, fac ); + qDebug() << "Registered IODevice Factory for" << proto; +} + + + +QSharedPointer +Servent::getIODeviceForUrl( const Tomahawk::result_ptr& result ) +{ + qDebug() << Q_FUNC_INFO << thread(); + QSharedPointer sp; + + QRegExp rx( "^([a-zA-Z0-9]+)://(.+)$" ); + if ( rx.indexIn( result->url() ) == -1 ) + return sp; + + const QString proto = rx.cap( 1 ); + //const QString urlpart = rx.cap( 2 ); + if ( !m_iofactories.contains( proto ) ) + return sp; + + return m_iofactories.value( proto )( result ); +} + + +QSharedPointer +Servent::localFileIODeviceFactory( const Tomahawk::result_ptr& result ) +{ + // ignore "file://" at front of url + QFile* io = new QFile( result->url().mid( QString( "file://" ).length() ) ); + if ( io ) + io->open( QIODevice::ReadOnly ); + + return QSharedPointer( io ); +} + + +QSharedPointer +Servent::httpIODeviceFactory( const Tomahawk::result_ptr& result ) +{ + qDebug() << Q_FUNC_INFO << result->url(); + QNetworkRequest req( result->url() ); + QNetworkReply* reply = TomahawkUtils::nam()->get( req ); + return QSharedPointer( reply ); +} diff --git a/src/network/servent.h b/src/libtomahawk/network/servent.h similarity index 52% rename from src/network/servent.h rename to src/libtomahawk/network/servent.h index 64d830cd4..21ef28d27 100644 --- a/src/network/servent.h +++ b/src/libtomahawk/network/servent.h @@ -1,10 +1,26 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef SERVENT_H #define SERVENT_H -// port for servent to listen on -#define DEFAULT_LISTEN_PORT 50210 // time before new connection terminates if no auth received -#define AUTH_TIMEOUT 15000 +#define AUTH_TIMEOUT 180000 #include #include @@ -20,20 +36,24 @@ #include #include -#include "portfwd/portfwd.h" -#include "tomahawk/typedefs.h" +#include "typedefs.h" #include "msg.h" +#include + +#include "dllmacro.h" + class Connection; class Connector; class ControlConnection; class FileTransferConnection; class ProxyConnection; class RemoteCollectionConnection; +class PortFwdThread; // this is used to hold a bit of state, so when a connected signal is emitted // from a socket, we can associate it with a Connection object etc. -class QTcpSocketExtra : public QTcpSocket +class DLLEXPORT QTcpSocketExtra : public QTcpSocket { Q_OBJECT @@ -59,20 +79,22 @@ private slots: } }; -class Servent : public QTcpServer +class DLLEXPORT Servent : public QTcpServer { Q_OBJECT public: + static Servent* instance(); + explicit Servent( QObject* parent = 0 ); virtual ~Servent(); - bool startListening( QHostAddress ha, bool upnp = false, int port = DEFAULT_LISTEN_PORT ); + bool startListening( QHostAddress ha, bool upnp, int port ); int port() const { return m_port; } // creates new token that allows a controlconnection to be set up - QString createConnectionKey( const QString& name = "" ); + QString createConnectionKey( const QString& name = "", const QString &nodeid = "", const QString &key = "", bool onceOnly = true ); void registerOffer( const QString& key, Connection* conn ); @@ -82,29 +104,41 @@ public: void connectToPeer( const QString& ha, int port, const QString &key, const QString& name = "", const QString& id = "" ); void connectToPeer( const QString& ha, int port, const QString &key, Connection* conn ); - void reverseOfferRequest( Connection* orig_conn, const QString& key, const QString& theirkey ); + void reverseOfferRequest( ControlConnection* orig_conn, const QString& key, const QString& theirkey ); - void setExternalAddress( QHostAddress ha, int port ); - bool visibleExternally() const { return m_externalPort > 0; } - QHostAddress externalAddress() const { return m_externalAddress; } + bool visibleExternally() const { return !m_externalHostname.isNull() || (m_externalPort > 0 && !m_externalAddress.isNull()); } + QString externalAddress() const { return !m_externalHostname.isNull() ? m_externalHostname : m_externalAddress.toString(); } int externalPort() const { return m_externalPort; } QSharedPointer remoteIODeviceFactory( const Tomahawk::result_ptr& ); static bool isIPWhitelisted( QHostAddress ip ); bool connectedToSession( const QString& session ); - unsigned int numConnectedPeers() const { return m_controlconnections.length(); } + QList< FileTransferConnection* > fileTransfers() const { return m_ftsessions; } + + QSharedPointer getIODeviceForUrl( const Tomahawk::result_ptr& result ); + void registerIODeviceFactory( const QString &proto, boost::function(Tomahawk::result_ptr)> fac ); + QSharedPointer localFileIODeviceFactory( const Tomahawk::result_ptr& result ); + QSharedPointer httpIODeviceFactory( const Tomahawk::result_ptr& result ); + +signals: + void fileTransferStarted( FileTransferConnection* ); + void fileTransferFinished( FileTransferConnection* ); + void ready(); + protected: void incomingConnection( int sd ); public slots: + void setExternalAddress( QHostAddress ha, unsigned int port ); + void socketError( QAbstractSocket::SocketError ); void createParallelConnection( Connection* orig_conn, Connection* new_conn, const QString& key ); void registerFileTransferConnection( FileTransferConnection* ); - void fileTransferFinished( FileTransferConnection* ftc ); + void onFileTransferFinished( FileTransferConnection* ftc ); void socketConnected(); void triggerDBSync(); @@ -112,7 +146,7 @@ public slots: private slots: void readyRead(); - Connection* claimOffer( const QString &key, const QHostAddress peer = QHostAddress::Any ); + Connection* claimOffer( ControlConnection* cc, const QString &key, const QHostAddress peer = QHostAddress::Any ); private: void handoverSocket( Connection* conn, QTcpSocketExtra* sock ); @@ -124,12 +158,16 @@ private: QMap< QString, QPointer > m_offers; int m_port, m_externalPort; QHostAddress m_externalAddress; + QString m_externalHostname; // currently active file transfers: QList< FileTransferConnection* > m_ftsessions; QMutex m_ftsession_mut; - Portfwd pf; + QMap< QString,boost::function(Tomahawk::result_ptr)> > m_iofactories; + + PortFwdThread* m_portfwd; + static Servent* s_instance; }; #endif // SERVENT_H diff --git a/src/libtomahawk/pipeline.cpp b/src/libtomahawk/pipeline.cpp new file mode 100644 index 000000000..3ffc4a154 --- /dev/null +++ b/src/libtomahawk/pipeline.cpp @@ -0,0 +1,357 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "pipeline.h" + +#include +#include +#include + +#include "functimeout.h" +#include "database/database.h" + +#define CONCURRENT_QUERIES 8 + +using namespace Tomahawk; + +Pipeline* Pipeline::s_instance = 0; + + +Pipeline* +Pipeline::instance() +{ + return s_instance; +} + + +Pipeline::Pipeline( QObject* parent ) + : QObject( parent ) + , m_index_ready( false ) +{ + s_instance = this; +} + + +void +Pipeline::databaseReady() +{ + connect( Database::instance(), SIGNAL( indexReady() ), this, SLOT( indexReady() ), Qt::QueuedConnection ); + Database::instance()->loadIndex(); +} + + +void Pipeline::indexReady() +{ + qDebug() << Q_FUNC_INFO << "shunting this many pending queries:" << m_queries_pending.size(); + m_index_ready = true; + + shuntNext(); +} + + +void +Pipeline::removeResolver( Resolver* r ) +{ + m_resolvers.removeAll( r ); +} + + +void +Pipeline::addResolver( Resolver* r, bool sort ) +{ + m_resolvers.append( r ); + if( sort ) + { + qSort( m_resolvers.begin(), + m_resolvers.end(), + Pipeline::resolverSorter ); + } + qDebug() << "Adding resolver" << r->name(); + +/* qDebug() << "Current pipeline:"; + foreach( Resolver * r, m_resolvers ) + { + qDebug() << "* score:" << r->weight() + << "pref:" << r->preference() + << "name:" << r->name(); + }*/ +} + + +void +Pipeline::resolve( const QList& qlist, bool prioritized ) +{ + { + QMutexLocker lock( &m_mut ); + + int i = 0; + foreach( const query_ptr& q, qlist ) + { + qDebug() << Q_FUNC_INFO << (qlonglong)q.data() << q->toString(); + if ( !m_qids.contains( q->id() ) ) + { + m_qids.insert( q->id(), q ); + } + + if ( m_queries_pending.contains( q ) ) + { + qDebug() << "Already queued for resolving:" << q->toString(); + continue; + } + + if ( prioritized ) + { + m_queries_pending.insert( i++, q ); + } + else + { + m_queries_pending.append( q ); + } + } + } + + shuntNext(); +} + + +void +Pipeline::resolve( const query_ptr& q, bool prioritized ) +{ + if ( q.isNull() ) + return; + + QList< query_ptr > qlist; + qlist << q; + resolve( qlist, prioritized ); +} + + +void +Pipeline::resolve( QID qid, bool prioritized ) +{ + resolve( query( qid ), prioritized ); +} + + +void +Pipeline::reportResults( QID qid, const QList< result_ptr >& results ) +{ + { + QMutexLocker lock( &m_mut ); + + if ( !m_qids.contains( qid ) ) + { + qDebug() << "reportResults called for unknown QID" << qid; + Q_ASSERT( false ); + return; + } + + if ( !m_qidsState.contains( qid ) ) + { + qDebug() << "reportResults called for unknown QID-state" << qid; + Q_ASSERT( false ); + return; + } + } + + const query_ptr& q = m_qids.value( qid ); + if ( !results.isEmpty() ) + { + //qDebug() << Q_FUNC_INFO << qid; + //qDebug() << "solved query:" << (qlonglong)q.data() << q->toString(); + + q->addResults( results ); + + foreach( const result_ptr& r, q->results() ) + { + m_rids.insert( r->id(), r ); + } + + if ( q->solved() ) + q->onResolvingFinished(); + } + + if ( decQIDState( q ) == 0 ) + { + // All resolvers have reported back their results for this query now + qDebug() << "Finished resolving:" << q->toString(); + + if ( !q->solved() ) + q->onResolvingFinished(); + + shuntNext(); + } +} + + +void +Pipeline::shuntNext() +{ + if ( !m_index_ready ) + return; + + query_ptr q; + { + QMutexLocker lock( &m_mut ); + + if ( m_queries_pending.isEmpty() ) + { + if ( m_qidsState.isEmpty() ) + emit idle(); + return; + } + + qDebug() << Q_FUNC_INFO << m_qidsState.count(); + // Check if we are ready to dispatch more queries + if ( m_qidsState.count() >= CONCURRENT_QUERIES ) + return; + + /* + Since resolvers are async, we now dispatch to the highest weighted ones + and after timeout, dispatch to next highest etc, aborting when solved + */ + q = m_queries_pending.takeFirst(); + q->setLastPipelineWeight( 101 ); + } + + if ( !q.isNull() ) + { + incQIDState( q ); + shunt( q ); // bump into next stage of pipeline (highest weights are 100) + } +} + + +void +Pipeline::shunt( const query_ptr& q ) +{ + qDebug() << Q_FUNC_INFO << q->solved() << q->toString() << q->id(); + unsigned int lastweight = 0; + unsigned int lasttimeout = 0; + + if ( q->solved() ) + { + qDebug() << "Query solved, pipeline aborted:" << q->toString() + << "numresults:" << q->results().length(); + + QList< result_ptr > rl; + reportResults( q->id(), rl ); + return; + } + + int thisResolver = 0; + int i = 0; + foreach( Resolver* r, m_resolvers ) + { + i++; + if ( r->weight() >= q->lastPipelineWeight() ) + continue; + + if ( lastweight == 0 ) + { + lastweight = r->weight(); + lasttimeout = r->timeout(); + //qDebug() << "Shunting into weight" << lastweight << "q:" << q->toString(); + } + if ( lastweight == r->weight() ) + { + // snag the lowest timeout at this weight + if ( r->timeout() < lasttimeout ) + lasttimeout = r->timeout(); + + // resolvers aren't allowed to block in this call: + qDebug() << "Dispatching to resolver" << r->name(); + + thisResolver = i; + r->resolve( q ); + } + else + break; + } + + if ( lastweight > 0 ) + { + q->setLastPipelineWeight( lastweight ); + + if ( thisResolver < m_resolvers.count() ) + { + incQIDState( q ); + qDebug() << "Shunting in" << lasttimeout << "ms, q:" << q->toString(); + new FuncTimeout( lasttimeout, boost::bind( &Pipeline::shunt, this, q ) ); + } + } + else + { + //qDebug() << "Reached end of pipeline for:" << q->toString(); + // reached end of pipeline + QList< result_ptr > rl; + reportResults( q->id(), rl ); + return; + } + + shuntNext(); +} + + +bool +Pipeline::resolverSorter( const Resolver* left, const Resolver* right ) +{ + if( left->weight() == right->weight() ) + return left->preference() > right->preference(); + else + return left->weight() > right->weight(); +} + + +int +Pipeline::incQIDState( const Tomahawk::query_ptr& query ) +{ + QMutexLocker lock( &m_mut ); + + int state = 1; + if ( m_qidsState.contains( query->id() ) ) + { + state = m_qidsState.value( query->id() ) + 1; + } + + qDebug() << Q_FUNC_INFO << "inserting to qidsstate:" << query->id() << state; + m_qidsState.insert( query->id(), state ); + + return state; +} + + +int +Pipeline::decQIDState( const Tomahawk::query_ptr& query ) +{ + QMutexLocker lock( &m_mut ); + + int state = m_qidsState.value( query->id() ) - 1; + if ( state ) + { + qDebug() << Q_FUNC_INFO << "replacing" << query->id() << state; + m_qidsState.insert( query->id(), state ); + } + else + { + qDebug() << Q_FUNC_INFO << "removing" << query->id() << state; + m_qidsState.remove( query->id() ); + } + + return state; +} diff --git a/src/libtomahawk/pipeline.h b/src/libtomahawk/pipeline.h new file mode 100644 index 000000000..2c31bc28b --- /dev/null +++ b/src/libtomahawk/pipeline.h @@ -0,0 +1,102 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef PIPELINE_H +#define PIPELINE_H + +#include +#include +#include +#include + +#include "typedefs.h" +#include "query.h" +#include "result.h" +#include "resolver.h" + +#include "dllmacro.h" + +namespace Tomahawk +{ + +class Resolver; + +class DLLEXPORT Pipeline : public QObject +{ +Q_OBJECT + +public: + static Pipeline* instance(); + + explicit Pipeline( QObject* parent = 0 ); + + void reportResults( QID qid, const QList< result_ptr >& results ); + + /// sorter to rank resolver priority + static bool resolverSorter( const Resolver* left, const Resolver* right ); + + void addResolver( Resolver* r, bool sort = true ); + void removeResolver( Resolver* r ); + + query_ptr query( const QID& qid ) const + { + return m_qids.value( qid ); + } + + result_ptr result( const RID& rid ) const + { + return m_rids.value( rid ); + } + +public slots: + void resolve( const query_ptr& q, bool prioritized = false ); + void resolve( const QList& qlist, bool prioritized = false ); + void resolve( QID qid, bool prioritized = false ); + void databaseReady(); + +signals: + void idle(); + +private slots: + void shunt( const query_ptr& q ); + void shuntNext(); + + void indexReady(); + +private: + int incQIDState( const Tomahawk::query_ptr& query ); + int decQIDState( const Tomahawk::query_ptr& query ); + + QList< Resolver* > m_resolvers; + + QMap< QID, unsigned int > m_qidsState; + QMap< QID, query_ptr > m_qids; + QMap< RID, result_ptr > m_rids; + + QMutex m_mut; // for m_qids, m_rids + + // store queries here until DB index is loaded, then shunt them all + QList< query_ptr > m_queries_pending; + bool m_index_ready; + + static Pipeline* s_instance; +}; + +}; //ns + +#endif // PIPELINE_H diff --git a/src/playlist.cpp b/src/libtomahawk/playlist.cpp similarity index 50% rename from src/playlist.cpp rename to src/libtomahawk/playlist.cpp index 824e34c48..f27e592e0 100644 --- a/src/playlist.cpp +++ b/src/libtomahawk/playlist.cpp @@ -1,33 +1,98 @@ -#include "tomahawk/playlist.h" +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "playlist.h" #include #include -#include "tomahawk/tomahawkapp.h" -#include "database.h" -#include "databasecommand_loadplaylistentries.h" -#include "databasecommand_setplaylistrevision.h" -#include "databasecommand_createplaylist.h" -#include "databasecommand_deleteplaylist.h" -#include "databasecommand_renameplaylist.h" +#include "database/database.h" +#include "database/databasecommand_loadplaylistentries.h" +#include "database/databasecommand_setplaylistrevision.h" +#include "database/databasecommand_createplaylist.h" +#include "database/databasecommand_deleteplaylist.h" +#include "database/databasecommand_renameplaylist.h" + +#include "pipeline.h" +#include "source.h" +#include "sourcelist.h" using namespace Tomahawk; +PlaylistEntry::PlaylistEntry() {} +PlaylistEntry::~PlaylistEntry() {} + void -PlaylistEntry::setQueryvariant( const QVariant& v ) +PlaylistEntry::setQueryVariant( const QVariant& v ) { - m_query = query_ptr( new Query( v ) ); + QVariantMap m = v.toMap(); + + QString artist = m.value( "artist" ).toString(); + QString album = m.value( "album" ).toString(); + QString track = m.value( "track" ).toString(); + m_query = Tomahawk::Query::get( artist, track, album ); } QVariant -PlaylistEntry::queryvariant() const +PlaylistEntry::queryVariant() const { return m_query->toVariant(); } +void +PlaylistEntry::setQuery( const Tomahawk::query_ptr& q ) +{ + m_query = q; +} + + +const Tomahawk::query_ptr& +PlaylistEntry::query() const +{ + return m_query; +} + + +source_ptr +PlaylistEntry::lastSource() const +{ + return m_lastsource; +} + + +void +PlaylistEntry::setLastSource( source_ptr s ) +{ + m_lastsource = s; +} + + +Playlist::Playlist( const source_ptr& author ) + : m_source( author ) + , m_lastmodified( 0 ) +{ + qDebug() << Q_FUNC_INFO << "JSON"; +} + + // used when loading from DB: Playlist::Playlist( const source_ptr& src, const QString& currentrevision, @@ -48,6 +113,7 @@ Playlist::Playlist( const source_ptr& src, , m_shared( shared ) { qDebug() << Q_FUNC_INFO << "1"; + init(); } @@ -67,9 +133,21 @@ Playlist::Playlist( const source_ptr& author, , m_shared( shared ) { qDebug() << Q_FUNC_INFO << "2"; + init(); } +void +Playlist::init() +{ + m_locallyChanged = false; + connect( Pipeline::instance(), SIGNAL( idle() ), SLOT( onResolvingFinished() ) ); +} + + +Playlist::~Playlist() {} + + playlist_ptr Playlist::create( const source_ptr& author, const QString& guid, @@ -93,17 +171,33 @@ Playlist::create( const source_ptr& author, DatabaseCommand_CreatePlaylist* cmd = new DatabaseCommand_CreatePlaylist( author, playlist ); connect( cmd, SIGNAL(finished()), playlist.data(), SIGNAL(created()) ); - APP->database()->enqueue( QSharedPointer(cmd) ); + Database::instance()->enqueue( QSharedPointer(cmd) ); playlist->reportCreated( playlist ); return playlist; } +playlist_ptr +Playlist::load( const QString& guid ) +{ + playlist_ptr p; + + foreach( const Tomahawk::source_ptr& source, SourceList::instance()->sources() ) + { + p = source->collection()->playlist( guid ); + if ( !p.isNull() ) + return p; + } + + return p; +} + + bool Playlist::remove( const playlist_ptr& playlist ) { DatabaseCommand_DeletePlaylist* cmd = new DatabaseCommand_DeletePlaylist( playlist->author(), playlist->guid() ); - APP->database()->enqueue( QSharedPointer(cmd) ); + Database::instance()->enqueue( QSharedPointer(cmd) ); return true; // FIXME } @@ -113,7 +207,7 @@ bool Playlist::rename( const QString& title ) { DatabaseCommand_RenamePlaylist* cmd = new DatabaseCommand_RenamePlaylist( author(), guid(), title ); - APP->database()->enqueue( QSharedPointer(cmd) ); + Database::instance()->enqueue( QSharedPointer(cmd) ); return true; // FIXME } @@ -141,7 +235,7 @@ Playlist::reportDeleted( const Tomahawk::playlist_ptr& self ) void Playlist::loadRevision( const QString& rev ) { - qDebug() << Q_FUNC_INFO; + qDebug() << Q_FUNC_INFO << currentrevision() << rev << m_title; DatabaseCommand_LoadPlaylistEntries* cmd = new DatabaseCommand_LoadPlaylistEntries( rev.isEmpty() ? currentrevision() : rev ); @@ -159,7 +253,7 @@ Playlist::loadRevision( const QString& rev ) const QMap< QString, Tomahawk::plentry_ptr >&, bool ) ) ); - APP->database()->enqueue( QSharedPointer( cmd ) ); + Database::instance()->enqueue( QSharedPointer( cmd ) ); } @@ -168,24 +262,14 @@ void Playlist::createNewRevision( const QString& newrev, const QString& oldrev, const QList< plentry_ptr >& entries ) { // qDebug() << "m_entries guids:"; - // foreach( plentry_ptr pp, m_entries ) qDebug() << pp->guid(); - - QSet currentguids; - foreach( plentry_ptr p, m_entries ) - currentguids.insert( p->guid() ); // could be cached as member? - // calc list of newly added entries: - QList added; + QList added = newEntries( entries ); QStringList orderedguids; foreach( plentry_ptr p, entries ) - { orderedguids << p->guid(); - if( !currentguids.contains(p->guid()) ) - added << p; - } - // source making the change (localy user in this case) - source_ptr author = APP->sourcelist().getLocal(); + // source making the change (local user in this case) + source_ptr author = SourceList::instance()->getLocal(); // command writes new rev to DB and calls setRevision, which emits our signal DatabaseCommand_SetPlaylistRevision* cmd = new DatabaseCommand_SetPlaylistRevision( author, @@ -193,8 +277,10 @@ Playlist::createNewRevision( const QString& newrev, const QString& oldrev, const newrev, oldrev, orderedguids, - added ); - APP->database()->enqueue( QSharedPointer( cmd ) ); + added, + entries + ); + Database::instance()->enqueue( QSharedPointer( cmd ) ); } @@ -210,9 +296,6 @@ Playlist::setRevision( const QString& rev, { if( QThread::currentThread() != thread() ) { - //qDebug() << "Calling setRevision in correct thread, instead of" - // << QThread::currentThread(); - QMetaObject::invokeMethod( this, "setRevision", Qt::BlockingQueuedConnection, @@ -220,108 +303,145 @@ Playlist::setRevision( const QString& rev, Q_ARG( QList, neworderedguids ), Q_ARG( QList, oldorderedguids ), Q_ARG( bool, is_newest_rev ), - QGenericArgument( "QMap< QString,Tomahawk::plentry_ptr >" , (const void*)&addedmap ), + QGenericArgument( "QMap< QString,Tomahawk::plentry_ptr >", (const void*)&addedmap ), Q_ARG( bool, applied ) ); return; } - //qDebug() << Q_FUNC_INFO << (qlonglong)this - // << rev << neworderedguids << oldorderedguids - // << "isnewest:" << is_newest_rev << addedmap << applied << m_entries - // ; + + PlaylistRevision pr = setNewRevision( rev, neworderedguids, oldorderedguids, is_newest_rev, addedmap ); + + if( applied ) + m_currentrevision = rev; + pr.applied = applied; + + foreach( const plentry_ptr& entry, m_entries ) + { + connect( entry->query().data(), SIGNAL( resultsAdded( QList ) ), + SLOT( onResultsFound( QList ) ), Qt::UniqueConnection ); + + } + emit revisionLoaded( pr ); +} + +PlaylistRevision +Playlist::setNewRevision( const QString& rev, + const QList& neworderedguids, + const QList& oldorderedguids, + bool is_newest_rev, + const QMap< QString, Tomahawk::plentry_ptr >& addedmap ) +{ + qDebug() << Q_FUNC_INFO << rev << is_newest_rev << m_title << addedmap.count() << neworderedguids.count() << oldorderedguids.count(); // build up correctly ordered new list of plentry_ptrs from // existing ones, and the ones that have been added QMap entriesmap; foreach( const plentry_ptr& p, m_entries ) entriesmap.insert( p->guid(), p ); - - //qDebug() << "Entries map:" << entriesmap; - - QList entries; - //qDebug() << "m_entries:" << m_entries.count() << m_entries; - - //qDebug() << "counters:" << neworderedguids.count() << entriesmap.count() << addedmap.count(); - foreach( const QString& id, neworderedguids ) - { - //qDebug() << "id:" << id; - //qDebug() << "newordered:" << neworderedguids.count() << neworderedguids; - //qDebug() << "entriesmap:" << entriesmap.count() << entriesmap; - //qDebug() << "addedmap:" << addedmap.count() << addedmap; - //qDebug() << "m_entries" << m_entries; - - if( entriesmap.contains( id ) ) + + + QList entries; + + foreach( const QString& id, neworderedguids ) { - entries.append( entriesmap.value( id ) ); - } - else if( addedmap.contains( id ) ) - { - entries.append( addedmap.value( id ) ); - if( is_newest_rev ) m_entries.append( addedmap.value( id ) ); - } - else - { - Q_ASSERT( false ); // XXX - } - } - - //qDebug() << Q_FUNC_INFO << rev << entries.length() << applied; - - PlaylistRevision pr; - pr.oldrevisionguid = m_currentrevision; - pr.revisionguid = rev; - - // entries that have been removed: - QSet removedguids = oldorderedguids.toSet().subtract( neworderedguids.toSet() ); - //qDebug() << "Removedguids:" << removedguids << "oldorederedguids" << oldorderedguids << "newog" << neworderedguids; - foreach( QString remid, removedguids ) - { - // NB: entriesmap will contain old/removed entries only if the removal was done - // in the same session - after a restart, history is not in memory. - if( entriesmap.contains( remid ) ) - { - pr.removed << entriesmap.value( remid ); - if( is_newest_rev ) + //qDebug() << "id:" << id; + //qDebug() << "newordered:" << neworderedguids.count() << neworderedguids; + //qDebug() << "entriesmap:" << entriesmap.count() << entriesmap; + //qDebug() << "addedmap:" << addedmap.count() << addedmap; + //qDebug() << "m_entries" << m_entries; + + if( entriesmap.contains( id ) ) { - //qDebug() << "Removing from m_entries" << remid; - for( int k = 0 ; k removedguids = oldorderedguids.toSet().subtract( neworderedguids.toSet() ); + //qDebug() << "Removedguids:" << removedguids << "oldorederedguids" << oldorderedguids << "newog" << neworderedguids; + foreach( QString remid, removedguids ) + { + // NB: entriesmap will contain old/removed entries only if the removal was done + // in the same session - after a restart, history is not in memory. + if( entriesmap.contains( remid ) ) + { + pr.removed << entriesmap.value( remid ); + if( is_newest_rev ) { - if( m_entries.at(k)->guid() == remid ) + //qDebug() << "Removing from m_entries" << remid; + for( int k = 0 ; k < m_entries.length(); ++k ) { - //qDebug() << "removed at " << k; - m_entries.removeAt(k); - break; + if( m_entries.at( k )->guid() == remid ) + { + //qDebug() << "removed at" << k; + m_entries.removeAt( k ); + break; + } } } } } - } - - pr.added = addedmap.values(); - - //qDebug() << "Revision set:" << rev - // << "added" << pr.added.size() - // << "removed" << pr.removed.size() - // << "total entries" << m_entries.size(); - - pr.newlist = entries; - - if( applied ) - m_currentrevision = rev; - pr.applied = applied; - - emit revisionLoaded( pr ); + + pr.added = addedmap.values(); + + + pr.newlist = entries; + return pr; } -void Playlist::resolve() +source_ptr +Playlist::author() const +{ + return m_source; +} + + +void +Playlist::resolve() { QList< query_ptr > qlist; foreach( const plentry_ptr& p, m_entries ) { qlist << p->query(); } - APP->pipeline()->add( qlist ); + + Pipeline::instance()->resolve( qlist ); +} + + +void +Playlist::onResultsFound( const QList& results ) +{ + m_locallyChanged = true; +} + + +void +Playlist::onResolvingFinished() +{ + if ( m_locallyChanged ) + { + qDebug() << Q_FUNC_INFO; + m_locallyChanged = false; + createNewRevision( currentrevision(), currentrevision(), m_entries ); + } } @@ -339,25 +459,63 @@ void Playlist::addEntries( const QList& queries, const QString& oldrev ) { //qDebug() << Q_FUNC_INFO; + + QList el = addEntriesInternal( queries ); + QString newrev = uuid(); + createNewRevision( newrev, oldrev, el ); +} + + +QList +Playlist::addEntriesInternal( const QList& queries ) +{ QList el = entries(); foreach( const query_ptr& query, queries ) { plentry_ptr e( new PlaylistEntry() ); e->setGuid( uuid() ); - + if ( query->results().count() ) e->setDuration( query->results().at( 0 )->duration() ); else e->setDuration( 0 ); - + e->setLastmodified( 0 ); e->setAnnotation( "" ); // FIXME e->setQuery( query ); - + el << e; } - - QString newrev = uuid(); - createNewRevision( newrev, oldrev, el ); + return el; } + + +QList< plentry_ptr > +Playlist::newEntries( const QList< plentry_ptr >& entries ) +{ + QSet currentguids; + foreach( const plentry_ptr& p, m_entries ) + currentguids.insert( p->guid() ); // could be cached as member? + + // calc list of newly added entries: + QList added; + foreach( const plentry_ptr& p, entries ) + { + if( !currentguids.contains( p->guid() ) ) + added << p; + } + return added; +} + + +QList +Playlist::tracks() +{ + QList queries; + foreach( const plentry_ptr& p, m_entries ) + queries << p->query(); + + return queries; +} + diff --git a/include/tomahawk/playlist.h b/src/libtomahawk/playlist.h similarity index 57% rename from include/tomahawk/playlist.h rename to src/libtomahawk/playlist.h index 92ae6d280..f973927cc 100644 --- a/include/tomahawk/playlist.h +++ b/src/libtomahawk/playlist.h @@ -1,12 +1,36 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef PLAYLIST_H #define PLAYLIST_H #include #include +#include +#include #include -#include "tomahawk/query.h" -#include "tomahawk/typedefs.h" +#include "query.h" +#include "typedefs.h" + +#include "playlistinterface.h" + +#include "dllmacro.h" class DatabaseCommand_LoadAllPlaylists; class DatabaseCommand_SetPlaylistRevision; @@ -15,23 +39,25 @@ class DatabaseCommand_CreatePlaylist; namespace Tomahawk { -class PlaylistEntry : public QObject +class DLLEXPORT PlaylistEntry : public QObject { Q_OBJECT Q_PROPERTY( QString guid READ guid WRITE setGuid ) Q_PROPERTY( QString annotation READ annotation WRITE setAnnotation ) -Q_PROPERTY( QString resulthint READ resulthint WRITE setResulthint ) Q_PROPERTY( unsigned int duration READ duration WRITE setDuration ) Q_PROPERTY( unsigned int lastmodified READ lastmodified WRITE setLastmodified ) -Q_PROPERTY( QVariant query READ queryvariant WRITE setQueryvariant ) +Q_PROPERTY( QVariant query READ queryVariant WRITE setQueryVariant ) public: - void setQuery( const Tomahawk::query_ptr& q ) { m_query = q; } - const Tomahawk::query_ptr& query() const { return m_query; } + PlaylistEntry(); + virtual ~PlaylistEntry(); + + void setQuery( const Tomahawk::query_ptr& q ); + const Tomahawk::query_ptr& query() const; // I wish Qt did this for me once i specified the Q_PROPERTIES: - void setQueryvariant( const QVariant& v ); - QVariant queryvariant() const; + void setQueryVariant( const QVariant& v ); + QVariant queryVariant() const; QString guid() const { return m_guid; } void setGuid( const QString& s ) { m_guid = s; } @@ -39,8 +65,8 @@ public: QString annotation() const { return m_annotation; } void setAnnotation( const QString& s ) { m_annotation = s; } - QString resulthint() const { return m_resulthint; } - void setResulthint( const QString& s ) { m_resulthint= s; } + QString resultHint() const { return m_resulthint; } + void setResultHint( const QString& s ) { m_resulthint= s; } unsigned int duration() const { return m_duration; } void setDuration( unsigned int i ) { m_duration = i; } @@ -48,8 +74,8 @@ public: unsigned int lastmodified() const { return m_lastmodified; } void setLastmodified( unsigned int i ) { m_lastmodified = i; } - source_ptr lastsource() const { return m_lastsource; } - void setLastsource( source_ptr s ) { m_lastsource = s; } + source_ptr lastSource() const; + void setLastSource( source_ptr s ); private: QString m_guid; @@ -73,7 +99,7 @@ struct PlaylistRevision }; -class Playlist : public QObject +class DLLEXPORT Playlist : public QObject, public PlaylistInterface { Q_OBJECT Q_PROPERTY( QString guid READ guid WRITE setGuid ) @@ -88,6 +114,10 @@ friend class ::DatabaseCommand_SetPlaylistRevision; friend class ::DatabaseCommand_CreatePlaylist; public: + ~Playlist(); + + static Tomahawk::playlist_ptr load( const QString& guid ); + // one CTOR is private, only called by DatabaseCommand_LoadAllPlaylists static Tomahawk::playlist_ptr create( const source_ptr& author, const QString& guid, @@ -101,37 +131,47 @@ public: virtual void loadRevision( const QString& rev = "" ); - const source_ptr& author() { return m_source; } - const QString& currentrevision() { return m_currentrevision; } - const QString& title() { return m_title; } - const QString& info() { return m_info; } - const QString& creator() { return m_creator; } - unsigned int lastmodified() { return m_lastmodified; } - const QString& guid() { return m_guid; } - bool shared() const { return m_shared; } + source_ptr author() const; + QString currentrevision() const { return m_currentrevision; } + QString title() const { return m_title; } + QString info() const { return m_info; } + QString creator() const { return m_creator; } + QString guid() const { return m_guid; } + bool shared() const { return m_shared; } + unsigned int lastmodified() const { return m_lastmodified; } const QList< plentry_ptr >& entries() { return m_entries; } - void addEntry( const Tomahawk::query_ptr& query, const QString& oldrev ); - void addEntries( const QList& queries, const QString& oldrev ); + virtual void addEntry( const Tomahawk::query_ptr& query, const QString& oldrev ); + virtual void addEntries( const QList& queries, const QString& oldrev ); // // these need to exist and be public for the json serialization stuff // you SHOULD NOT call them. They are used for an alternate CTOR method from json. // maybe friend QObjectHelper and make them private? - Playlist( const source_ptr& author ) - : m_source( author ) - , m_lastmodified( 0 ) - { - qDebug() << Q_FUNC_INFO << "JSON"; - } + explicit Playlist( const source_ptr& author ); void setCurrentrevision( const QString& s ) { m_currentrevision = s; } - void setTitle( const QString& s ) { m_title = s; } + void setTitle( const QString& s ) { m_title = s; emit changed(); } void setInfo( const QString& s ) { m_info = s; } void setCreator( const QString& s ) { m_creator = s; } void setGuid( const QString& s ) { m_guid = s; } void setShared( bool b ) { m_shared = b; } // + virtual QList tracks(); + + virtual int unfilteredTrackCount() const { return m_entries.count(); } + virtual int trackCount() const { return m_entries.count(); } + + virtual Tomahawk::result_ptr siblingItem( int itemsAway ) { return result_ptr(); } + + virtual PlaylistInterface::RepeatMode repeatMode() const { return PlaylistInterface::NoRepeat; } + virtual bool shuffled() const { return false; } + + virtual void setRepeatMode( PlaylistInterface::RepeatMode ) {} + virtual void setShuffled( bool ) {} + + virtual void setFilter( const QString& pattern ) {} + signals: /// emitted when the playlist revision changes (whenever the playlist changes) void revisionLoaded( Tomahawk::PlaylistRevision ); @@ -139,6 +179,15 @@ signals: /// watch for this to see when newly created playlist is synced to DB (if you care) void created(); + /// renamed etc. + void changed(); + + void repeatModeChanged( PlaylistInterface::RepeatMode mode ); + void shuffleModeChanged( bool enabled ); + + void trackCountChanged( unsigned int tracks ); + void sourceTrackCountChanged( unsigned int tracks ); + public slots: // want to update the playlist from the model? // generate a newrev using uuid() and call this: @@ -155,7 +204,7 @@ public slots: void resolve(); -private: +protected: // called from loadAllPlaylists DB cmd: explicit Playlist( const source_ptr& src, const QString& currentrevision, @@ -174,7 +223,22 @@ private: const QString& creator, bool shared ); - void rundb(); + QList< plentry_ptr > newEntries( const QList< plentry_ptr >& entries ); + PlaylistRevision setNewRevision( const QString& rev, + const QList& neworderedguids, + const QList& oldorderedguids, + bool is_newest_rev, + const QMap< QString, Tomahawk::plentry_ptr >& addedmap ); + + QList addEntriesInternal( const QList& queries ); + +private slots: + void onResultsFound( const QList& results ); + void onResolvingFinished(); + +private: + Playlist(); + void init(); source_ptr m_source; QString m_currentrevision; @@ -183,6 +247,7 @@ private: bool m_shared; QList< plentry_ptr > m_entries; + bool m_locallyChanged; }; diff --git a/src/playlist/albumitem.cpp b/src/libtomahawk/playlist/albumitem.cpp similarity index 59% rename from src/playlist/albumitem.cpp rename to src/libtomahawk/playlist/albumitem.cpp index c2bc9de5a..398703e68 100644 --- a/src/playlist/albumitem.cpp +++ b/src/libtomahawk/playlist/albumitem.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "albumitem.h" #include "utils/tomahawkutils.h" diff --git a/src/libtomahawk/playlist/albumitem.h b/src/libtomahawk/playlist/albumitem.h new file mode 100644 index 000000000..ece235c3e --- /dev/null +++ b/src/libtomahawk/playlist/albumitem.h @@ -0,0 +1,60 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef ALBUMITEM_H +#define ALBUMITEM_H + +#include +#include +#include +#include + +#include "album.h" + +#include "dllmacro.h" + +class DLLEXPORT AlbumItem : public QObject +{ +Q_OBJECT + +public: + ~AlbumItem(); + + explicit AlbumItem( AlbumItem* parent = 0, QAbstractItemModel* model = 0 ); + explicit AlbumItem( const Tomahawk::album_ptr& album, AlbumItem* parent = 0, int row = -1 ); + + const Tomahawk::album_ptr& album() const { return m_album; }; + void setCover( const QPixmap& cover ) { this->cover = cover; emit dataChanged(); } + + AlbumItem* parent; + QList children; + QHash hash; + int childCount; + QPersistentModelIndex index; + QAbstractItemModel* model; + QPixmap cover; + bool toberemoved; + +signals: + void dataChanged(); + +private: + Tomahawk::album_ptr m_album; +}; + +#endif // ALBUMITEM_H diff --git a/src/libtomahawk/playlist/albumitemdelegate.cpp b/src/libtomahawk/playlist/albumitemdelegate.cpp new file mode 100644 index 000000000..a0b4ec2ae --- /dev/null +++ b/src/libtomahawk/playlist/albumitemdelegate.cpp @@ -0,0 +1,86 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "albumitemdelegate.h" + +#include +#include +#include +#include + +#include "query.h" +#include "result.h" + +#include "utils/tomahawkutils.h" + +#include "playlist/albumitem.h" +#include "playlist/albumproxymodel.h" + + +AlbumItemDelegate::AlbumItemDelegate( QAbstractItemView* parent, AlbumProxyModel* proxy ) + : QStyledItemDelegate( (QObject*)parent ) + , m_view( parent ) + , m_model( proxy ) +{ +} + + +QSize +AlbumItemDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + QSize size = QStyledItemDelegate::sizeHint( option, index ); + return size; +} + + +void +AlbumItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + AlbumItem* item = m_model->sourceModel()->itemFromIndex( m_model->mapToSource( index ) ); + if ( !item ) + return; + + QStyleOptionViewItemV4 opt = option; + initStyleOption( &opt, QModelIndex() ); + qApp->style()->drawControl( QStyle::CE_ItemViewItem, &opt, painter ); + + if ( option.state & QStyle::State_Selected ) + { + opt.palette.setColor( QPalette::Text, opt.palette.color( QPalette::HighlightedText ) ); + } + + painter->save(); + painter->setRenderHint( QPainter::Antialiasing ); + painter->setPen( opt.palette.color( QPalette::Text ) ); + + painter->drawPixmap( option.rect.adjusted( 4, 4, -4, -38 ), QPixmap( RESPATH "images/cover-shadow.png" ) ); + painter->drawPixmap( option.rect.adjusted( 6, 4, -6, -41 ), item->cover ); + + QTextOption to; + to.setAlignment( Qt::AlignHCenter ); + QFont font = opt.font; + QFont boldFont = opt.font; + boldFont.setBold( true ); + + painter->drawText( option.rect.adjusted( 0, option.rect.height() - 16, 0, -2 ), item->album()->artist()->name(), to ); + + painter->setFont( boldFont ); + painter->drawText( option.rect.adjusted( 0, option.rect.height() - 32, 0, -18 ), item->album()->name(), to ); + + painter->restore(); +} diff --git a/src/libtomahawk/playlist/albumitemdelegate.h b/src/libtomahawk/playlist/albumitemdelegate.h new file mode 100644 index 000000000..279f4a569 --- /dev/null +++ b/src/libtomahawk/playlist/albumitemdelegate.h @@ -0,0 +1,46 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef ALBUMITEMDELEGATE_H +#define ALBUMITEMDELEGATE_H + +#include + +#include "dllmacro.h" + +class AlbumProxyModel; + +class DLLEXPORT AlbumItemDelegate : public QStyledItemDelegate +{ +Q_OBJECT + +public: + AlbumItemDelegate( QAbstractItemView* parent = 0, AlbumProxyModel* proxy = 0 ); + +protected: + void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; + QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const; + +// QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const; + +private: + QAbstractItemView* m_view; + AlbumProxyModel* m_model; +}; + +#endif // ALBUMITEMDELEGATE_H diff --git a/src/playlist/albummodel.cpp b/src/libtomahawk/playlist/albummodel.cpp similarity index 73% rename from src/playlist/albummodel.cpp rename to src/libtomahawk/playlist/albummodel.cpp index 867c34ab8..a60610e36 100644 --- a/src/playlist/albummodel.cpp +++ b/src/libtomahawk/playlist/albummodel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "albummodel.h" #include @@ -5,8 +23,9 @@ #include #include -#include "tomahawk/tomahawkapp.h" -#include "database.h" +#include "database/database.h" + +#include "utils/tomahawkutils.h" #define LASTFM_DEFAULT_COVER "http://cdn.last.fm/flatness/catalogue/noimage" @@ -20,7 +39,7 @@ AlbumModel::AlbumModel( QObject* parent ) qDebug() << Q_FUNC_INFO; m_defaultCover = QPixmap( RESPATH "images/no-album-art-placeholder.png" ) - .scaled( QSize( 64, 64 ), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); + .scaled( QSize( 120, 120 ), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); } @@ -100,7 +119,7 @@ AlbumModel::data( const QModelIndex& index, int role ) const if ( role == Qt::SizeHintRole ) { - return QSize( 90, 90 ); + return QSize( 116, 150 ); } if ( role != Qt::DisplayRole ) // && role != Qt::ToolTipRole ) @@ -211,6 +230,24 @@ AlbumModel::removeIndexes( const QList& indexes ) } +void +AlbumModel::addCollection( const collection_ptr& collection ) +{ + qDebug() << Q_FUNC_INFO << collection->name() + << collection->source()->id() + << collection->source()->userName(); + + DatabaseCommand_AllAlbums* cmd = new DatabaseCommand_AllAlbums( collection ); + + connect( cmd, SIGNAL( albums( QList, Tomahawk::collection_ptr ) ), + SLOT( onAlbumsAdded( QList, Tomahawk::collection_ptr ) ) ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); + + m_title = tr( "All albums from %1" ).arg( collection->source()->friendlyName() ); +} + + void AlbumModel::addFilteredCollection( const collection_ptr& collection, unsigned int amount, DatabaseCommand_AllAlbums::SortOrder order ) { @@ -227,7 +264,9 @@ AlbumModel::addFilteredCollection( const collection_ptr& collection, unsigned in connect( cmd, SIGNAL( albums( QList, Tomahawk::collection_ptr ) ), SLOT( onAlbumsAdded( QList, Tomahawk::collection_ptr ) ) ); - TomahawkApp::instance()->database()->enqueue( QSharedPointer( cmd ) ); + Database::instance()->enqueue( QSharedPointer( cmd ) ); + + m_title = tr( "All albums from %1" ).arg( collection->source()->friendlyName() ); } @@ -251,13 +290,13 @@ AlbumModel::onAlbumsAdded( const QList& albums, const Tomah albumitem->cover = m_defaultCover; albumitem->index = createIndex( m_rootItem->children.count() - 1, 0, albumitem ); - QString imgurl = "http://ws.audioscrobbler.com/2.0/?method=album.imageredirect&artist=%1&album=%2&size=medium&api_key=7a90f6672a04b809ee309af169f34b8b"; +/* QString imgurl = "http://ws.audioscrobbler.com/2.0/?method=album.imageredirect&artist=%1&album=%2&size=large&api_key=7a90f6672a04b809ee309af169f34b8b"; QNetworkRequest req( imgurl.arg( album->artist()->name() ).arg( album->name() ) ); req.setAttribute( QNetworkRequest::User, (qlonglong)albumitem ); - QNetworkReply* reply = APP->nam()->get( req ); - connect( reply, SIGNAL( finished() ), SLOT( onCoverArtDownloaded() ) ); + QNetworkReply* reply = TomahawkUtils::nam()->get( req ); + connect( reply, SIGNAL( finished() ), SLOT( onCoverArtDownloaded() ) );*/ -// connect( albumitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); + connect( albumitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); } emit endInsertRows(); @@ -269,7 +308,6 @@ void AlbumModel::onCoverArtDownloaded() { QNetworkReply* reply = qobject_cast( sender() ); - qDebug() << "attr:" << reply->request().attribute( QNetworkRequest::User ); QUrl redir = reply->attribute( QNetworkRequest::RedirectionTargetAttribute ).toUrl(); if ( redir.isEmpty() ) @@ -289,7 +327,7 @@ AlbumModel::onCoverArtDownloaded() } else { - ai->cover = pm.scaled( QSize( 64, 64 ), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); + ai->setCover( pm.scaled( QSize( 150, 150 ), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ); } } } @@ -298,9 +336,17 @@ AlbumModel::onCoverArtDownloaded() // Follow HTTP redirect QNetworkRequest req( redir ); req.setAttribute( QNetworkRequest::User, reply->request().attribute( QNetworkRequest::User ) ); - QNetworkReply* reply = APP->nam()->get( req ); + QNetworkReply* reply = TomahawkUtils::nam()->get( req ); connect( reply, SIGNAL( finished() ), SLOT( onCoverArtDownloaded() ) ); } reply->deleteLater(); } + + +void +AlbumModel::onDataChanged() +{ + AlbumItem* p = (AlbumItem*)sender(); + emit dataChanged( p->index, p->index.sibling( p->index.row(), columnCount( QModelIndex() ) - 1 ) ); +} diff --git a/src/playlist/albummodel.h b/src/libtomahawk/playlist/albummodel.h similarity index 61% rename from src/playlist/albummodel.h rename to src/libtomahawk/playlist/albummodel.h index 1f96442cd..dc080a84c 100644 --- a/src/playlist/albummodel.h +++ b/src/libtomahawk/playlist/albummodel.h @@ -1,19 +1,39 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef ALBUMMODEL_H #define ALBUMMODEL_H #include #include -#include "tomahawk/album.h" -#include "tomahawk/collection.h" -#include "tomahawk/playlistinterface.h" +#include "album.h" +#include "collection.h" +#include "playlistinterface.h" #include "database/databasecommand_allalbums.h" #include "albumitem.h" +#include "dllmacro.h" + class QMetaData; -class AlbumModel : public QAbstractItemModel, public PlaylistInterface +class DLLEXPORT AlbumModel : public QAbstractItemModel { Q_OBJECT @@ -38,17 +58,18 @@ public: virtual void removeIndex( const QModelIndex& index ); virtual void removeIndexes( const QList& indexes ); - virtual Tomahawk::result_ptr siblingItem( int direction ) { return Tomahawk::result_ptr(); } - - virtual PlaylistInterface::RepeatMode repeatMode() const { return PlaylistInterface::NoRepeat; } - virtual bool shuffled() const { return false; } - virtual QMimeData* mimeData( const QModelIndexList& indexes ) const; virtual QStringList mimeTypes() const; virtual Qt::ItemFlags flags( const QModelIndex& index ) const; + void addCollection( const Tomahawk::collection_ptr& collection ); void addFilteredCollection( const Tomahawk::collection_ptr& collection, unsigned int amount, DatabaseCommand_AllAlbums::SortOrder order ); + virtual QString title() const { return m_title; } + virtual QString description() const { return m_description; } + virtual void setTitle( const QString& title ) { m_title = title; } + virtual void setDescription( const QString& description ) { m_description = description; } + AlbumItem* itemFromIndex( const QModelIndex& index ) const { if ( index.isValid() ) @@ -74,11 +95,15 @@ protected: private slots: void onAlbumsAdded( const QList& albums, const Tomahawk::collection_ptr& collection ); void onCoverArtDownloaded(); + void onDataChanged(); private: QPersistentModelIndex m_currentIndex; AlbumItem* m_rootItem; QPixmap m_defaultCover; + + QString m_title; + QString m_description; }; #endif // ALBUMMODEL_H diff --git a/src/libtomahawk/playlist/albumproxymodel.cpp b/src/libtomahawk/playlist/albumproxymodel.cpp new file mode 100644 index 000000000..8a9f66493 --- /dev/null +++ b/src/libtomahawk/playlist/albumproxymodel.cpp @@ -0,0 +1,146 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "albumproxymodel.h" + +#include +#include + +#include "query.h" +#include "collectionmodel.h" + + +AlbumProxyModel::AlbumProxyModel( QObject* parent ) + : QSortFilterProxyModel( parent ) + , PlaylistInterface( this ) + , m_model( 0 ) + , m_repeatMode( PlaylistInterface::NoRepeat ) + , m_shuffled( false ) +{ + qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); + + setFilterCaseSensitivity( Qt::CaseInsensitive ); + setSortCaseSensitivity( Qt::CaseInsensitive ); + setDynamicSortFilter( true ); + + setSourceModel( 0 ); +} + + +void +AlbumProxyModel::setSourceModel( AlbumModel* sourceModel ) +{ + m_model = sourceModel; + + connect( m_model, SIGNAL( trackCountChanged( unsigned int ) ), + SIGNAL( sourceTrackCountChanged( unsigned int ) ) ); + + QSortFilterProxyModel::setSourceModel( sourceModel ); +} + + +void +AlbumProxyModel::setFilter( const QString& pattern ) +{ + qDebug() << Q_FUNC_INFO; + setFilterRegExp( pattern ); + + emit filterChanged( pattern ); +} + + +bool +AlbumProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const +{ + if ( filterRegExp().isEmpty() ) + return true; + + AlbumItem* pi = sourceModel()->itemFromIndex( sourceModel()->index( sourceRow, 0, sourceParent ) ); + if ( !pi ) + return false; + + const Tomahawk::album_ptr& q = pi->album(); + + QStringList sl = filterRegExp().pattern().split( " ", QString::SkipEmptyParts ); + + bool found = true; + foreach( const QString& s, sl ) + { + if ( !q->name().contains( s, Qt::CaseInsensitive ) && !q->artist()->name().contains( s, Qt::CaseInsensitive ) ) + { + found = false; + } + } + + return found; +} + + +bool +AlbumProxyModel::lessThan( const QModelIndex& left, const QModelIndex& right ) const +{ + AlbumItem* p1 = sourceModel()->itemFromIndex( left ); + AlbumItem* p2 = sourceModel()->itemFromIndex( right ); + + if ( !p1 ) + return true; + if ( !p2 ) + return false; + + if ( p1->album()->artist()->name() == p2->album()->artist()->name() ) + { + return QString::localeAwareCompare( p1->album()->name(), p2->album()->name() ) < 0; + } + + return QString::localeAwareCompare( p1->album()->artist()->name(), p2->album()->artist()->name() ) < 0; +} + + +void +AlbumProxyModel::removeIndex( const QModelIndex& index ) +{ + qDebug() << Q_FUNC_INFO; + + if ( !sourceModel() ) + return; + if ( index.column() > 0 ) + return; + + sourceModel()->removeIndex( mapToSource( index ) ); +} + + +void +AlbumProxyModel::removeIndexes( const QList& indexes ) +{ + if ( !sourceModel() ) + return; + + foreach( const QModelIndex& idx, indexes ) + { + removeIndex( idx ); + } +} + + +Tomahawk::result_ptr +AlbumProxyModel::siblingItem( int itemsAway ) +{ + qDebug() << Q_FUNC_INFO; + return Tomahawk::result_ptr( 0 ); +} diff --git a/src/libtomahawk/playlist/albumproxymodel.h b/src/libtomahawk/playlist/albumproxymodel.h new file mode 100644 index 000000000..151ba51c8 --- /dev/null +++ b/src/libtomahawk/playlist/albumproxymodel.h @@ -0,0 +1,79 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef ALBUMPROXYMODEL_H +#define ALBUMPROXYMODEL_H + +#include + +#include "playlistinterface.h" +#include "playlist/albummodel.h" + +#include "dllmacro.h" + +class DLLEXPORT AlbumProxyModel : public QSortFilterProxyModel, public PlaylistInterface +{ +Q_OBJECT + +public: + explicit AlbumProxyModel( QObject* parent = 0 ); + + virtual AlbumModel* sourceModel() const { return m_model; } + virtual void setSourceModel( AlbumModel* sourceModel ); + + virtual QList tracks() { Q_ASSERT( FALSE ); QList queries; return queries; } + + virtual int unfilteredTrackCount() const { return sourceModel()->rowCount( QModelIndex() ); } + virtual int trackCount() const { return rowCount( QModelIndex() ); } + virtual int albumCount() const { return rowCount( QModelIndex() ); } + + virtual void removeIndex( const QModelIndex& index ); + virtual void removeIndexes( const QList& indexes ); + + virtual Tomahawk::result_ptr siblingItem( int direction ); + + virtual void setFilter( const QString& pattern ); + + virtual PlaylistInterface::RepeatMode repeatMode() const { return m_repeatMode; } + virtual bool shuffled() const { return m_shuffled; } + virtual PlaylistInterface::ViewMode viewMode() const { return PlaylistInterface::Album; } + +signals: + void repeatModeChanged( PlaylistInterface::RepeatMode mode ); + void shuffleModeChanged( bool enabled ); + + void trackCountChanged( unsigned int tracks ); + void sourceTrackCountChanged( unsigned int tracks ); + + void filterChanged( const QString& filter ); + +public slots: + virtual void setRepeatMode( RepeatMode mode ) { m_repeatMode = mode; emit repeatModeChanged( mode ); } + virtual void setShuffled( bool enabled ) { m_shuffled = enabled; emit shuffleModeChanged( enabled ); } + +protected: + bool filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const; + bool lessThan( const QModelIndex& left, const QModelIndex& right ) const; + +private: + AlbumModel* m_model; + RepeatMode m_repeatMode; + bool m_shuffled; +}; + +#endif // ALBUMPROXYMODEL_H diff --git a/src/playlist/albumview.cpp b/src/libtomahawk/playlist/albumview.cpp similarity index 67% rename from src/playlist/albumview.cpp rename to src/libtomahawk/playlist/albumview.cpp index 889e588c3..1f8affea7 100644 --- a/src/playlist/albumview.cpp +++ b/src/libtomahawk/playlist/albumview.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "albumview.h" #include @@ -6,12 +24,10 @@ #include #include -#include "tomahawk/tomahawkapp.h" -#include "audioengine.h" -#include "tomahawksettings.h" +#include "audio/audioengine.h" -#include "albummodel.h" -#include "albumproxymodel.h" +#include "tomahawksettings.h" +#include "albumitemdelegate.h" #include "playlistmanager.h" using namespace Tomahawk; @@ -27,11 +43,11 @@ AlbumView::AlbumView( QWidget* parent ) setDropIndicatorShown( false ); setDragDropOverwriteMode( false ); setUniformItemSizes( true ); - setSpacing( 8 ); + setSpacing( 20 ); setResizeMode( Adjust ); setViewMode( IconMode ); - setIconSize( QSize( 64, 64 ) ); + setVerticalScrollMode( QAbstractItemView::ScrollPerPixel ); setProxyModel( new AlbumProxyModel( this ) ); @@ -49,8 +65,7 @@ void AlbumView::setProxyModel( AlbumProxyModel* model ) { m_proxyModel = model; -// m_delegate = new PlaylistItemDelegate( this, m_proxyModel ); -// setItemDelegate( m_delegate ); + setItemDelegate( new AlbumItemDelegate( this, m_proxyModel ) ); QListView::setModel( m_proxyModel ); } @@ -62,7 +77,10 @@ AlbumView::setModel( AlbumModel* model ) m_model = model; if ( m_proxyModel ) + { m_proxyModel->setSourceModel( model ); + m_proxyModel->sort( 0 ); + } connect( m_proxyModel, SIGNAL( filterChanged( QString ) ), SLOT( onFilterChanged( QString ) ) ); @@ -79,7 +97,7 @@ AlbumView::onItemActivated( const QModelIndex& index ) // qDebug() << "Result activated:" << item->album()->tracks().first()->toString() << item->album()->tracks().first()->results().first()->url(); // APP->audioEngine()->playItem( item->album().data(), item->album()->tracks().first()->results().first() ); - APP->playlistManager()->show( item->album() ); + PlaylistManager::instance()->show( item->album() ); } } diff --git a/src/libtomahawk/playlist/albumview.h b/src/libtomahawk/playlist/albumview.h new file mode 100644 index 000000000..953863ab2 --- /dev/null +++ b/src/libtomahawk/playlist/albumview.h @@ -0,0 +1,79 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef ALBUMVIEW_H +#define ALBUMVIEW_H + +#include +#include + +#include "albummodel.h" +#include "albumproxymodel.h" +#include "viewpage.h" + +#include "dllmacro.h" + +class DLLEXPORT AlbumView : public QListView, public Tomahawk::ViewPage +{ +Q_OBJECT + +public: + explicit AlbumView( QWidget* parent = 0 ); + ~AlbumView(); + + void setProxyModel( AlbumProxyModel* model ); + + AlbumModel* model() const { return m_model; } + AlbumProxyModel* proxyModel() const { return m_proxyModel; } +// PlaylistItemDelegate* delegate() { return m_delegate; } + + void setModel( AlbumModel* model ); + + virtual QWidget* widget() { return this; } + virtual PlaylistInterface* playlistInterface() const { return proxyModel(); } + + virtual QString title() const { return m_model->title(); } + virtual QString description() const { return m_model->description(); } + + virtual bool showModes() const { return true; } + + virtual bool jumpToCurrentTrack() { return false; } + +public slots: + void onItemActivated( const QModelIndex& index ); + +protected: + virtual void startDrag( Qt::DropActions supportedActions ); + virtual void dragEnterEvent( QDragEnterEvent* event ); + virtual void dragMoveEvent( QDragMoveEvent* event ); + virtual void dropEvent( QDropEvent* event ); + + void paintEvent( QPaintEvent* event ); + +private slots: + void onFilterChanged( const QString& filter ); + +private: + QPixmap createDragPixmap( int itemCount ) const; + + AlbumModel* m_model; + AlbumProxyModel* m_proxyModel; +// PlaylistItemDelegate* m_delegate; +}; + +#endif // ALBUMVIEW_H diff --git a/src/libtomahawk/playlist/collectionflatmodel.cpp b/src/libtomahawk/playlist/collectionflatmodel.cpp new file mode 100644 index 000000000..83d4a6386 --- /dev/null +++ b/src/libtomahawk/playlist/collectionflatmodel.cpp @@ -0,0 +1,295 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "collectionflatmodel.h" + +#include +#include +#include + +#include "database/database.h" +#include "sourcelist.h" + +using namespace Tomahawk; + + +CollectionFlatModel::CollectionFlatModel( QObject* parent ) + : TrackModel( parent ) +{ + qDebug() << Q_FUNC_INFO; + + connect( SourceList::instance(), SIGNAL( sourceRemoved( Tomahawk::source_ptr ) ), SLOT( onSourceOffline( Tomahawk::source_ptr ) ) ); +} + + +CollectionFlatModel::~CollectionFlatModel() +{ +} + + +void +CollectionFlatModel::addCollections( const QList< collection_ptr >& collections ) +{ + qDebug() << Q_FUNC_INFO << "Adding collections!"; + foreach( const collection_ptr& col, collections ) + { + addCollection( col ); + } + + // we are waiting for some to load + if( !m_loadingCollections.isEmpty() ) + emit loadingStarted(); +} + + +void +CollectionFlatModel::addCollection( const collection_ptr& collection, bool sendNotifications ) +{ + qDebug() << Q_FUNC_INFO << collection->name() + << collection->source()->id() + << collection->source()->userName(); + + connect( collection.data(), SIGNAL( tracksAdded( QList ) ), + SLOT( onTracksAdded( QList ) ) ); + connect( collection.data(), SIGNAL( tracksRemoved( QList ) ), + SLOT( onTracksRemoved( QList ) ) ); + + if( sendNotifications ) + emit loadingStarted(); + + if ( collection->isLoaded() ) + { + onTracksAdded( collection->tracks() ); + } + else + { + collection->tracks(); // data will arrive via signals + m_loadingCollections << collection.data(); + } + + if ( collection->source()->isLocal() ) + setTitle( tr( "Your Collection" ) ); + else + setTitle( tr( "Collection of %1" ).arg( collection->source()->friendlyName() ) ); +} + + +void +CollectionFlatModel::addFilteredCollection( const collection_ptr& collection, unsigned int amount, DatabaseCommand_AllTracks::SortOrder order ) +{ + qDebug() << Q_FUNC_INFO << collection->name() + << collection->source()->id() + << collection->source()->userName() + << amount << order; + + DatabaseCommand_AllTracks* cmd = new DatabaseCommand_AllTracks( collection ); + cmd->setLimit( amount ); + cmd->setSortOrder( order ); + cmd->setSortDescending( true ); + + connect( cmd, SIGNAL( tracks( QList, Tomahawk::collection_ptr ) ), + SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ), Qt::QueuedConnection ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + + +void +CollectionFlatModel::removeCollection( const collection_ptr& collection ) +{ + return; // FIXME + + disconnect( collection.data(), SIGNAL( tracksAdded( QList ) ), + this, SLOT( onTracksAdded( QList ) ) ); + + QTime timer; + timer.start(); + +// QList plitems = m_collectionIndex.values( collection ); + QList< QPair< int, int > > rows; + QList< QPair< int, int > > sortrows; + QPair< int, int > row; + QPair< int, int > rowf; + rows = m_collectionRows.values( collection ); + + while ( rows.count() ) + { + int x = -1; + int j = 0; + foreach( row, rows ) + { + if ( x < 0 || row.first > rows.at( x ).first ) + x = j; + + j++; + } + + sortrows.append( rows.at( x ) ); + rows.removeAt( x ); + } + + foreach( row, sortrows ) + { + QMap< Tomahawk::collection_ptr, QPair< int, int > > newrows; + foreach ( const collection_ptr& col, m_collectionRows.uniqueKeys() ) + { + if ( col.data() == collection.data() ) + continue; + + foreach ( rowf, m_collectionRows.values( col ) ) + { + if ( rowf.first > row.first ) + { + rowf.first -= ( row.second - row.first ) + 1; + rowf.second -= ( row.second - row.first ) + 1; + } + newrows.insertMulti( col, rowf ); + } + } + m_collectionRows = newrows; + + qDebug() << "Removing rows:" << row.first << row.second; + emit beginRemoveRows( QModelIndex(), row.first, row.second ); + for ( int i = row.second; i >= row.first; i-- ) + { + PlItem* item = itemFromIndex( index( i, 0, QModelIndex() ) ); + delete item; + } + emit endRemoveRows(); + } + + qDebug() << "Collection removed, time elapsed:" << timer.elapsed(); + +// emit trackCountChanged( rowCount( QModelIndex() ) ); +} + + +void +CollectionFlatModel::onTracksAdded( const QList& tracks ) +{ + qDebug() << Q_FUNC_INFO << tracks.count() << rowCount( QModelIndex() ); + + if( !m_loadingCollections.isEmpty() && sender() && qobject_cast< Collection* >( sender() ) ) + { + // we are keeping track and are called as a slot + m_loadingCollections.removeAll( qobject_cast< Collection* >( sender() ) ); + } + + bool kickOff = m_tracksToAdd.isEmpty(); + m_tracksToAdd << tracks; + + emit trackCountChanged( trackCount() ); + + if ( m_tracksToAdd.count() && kickOff ) + processTracksToAdd(); + else if ( m_tracksToAdd.isEmpty() && m_loadingCollections.isEmpty() ) + emit loadingFinished(); +} + + +void +CollectionFlatModel::processTracksToAdd() +{ + int chunkSize = 5000; + int maxc = qMin( chunkSize, m_tracksToAdd.count() ); + int c = rowCount( QModelIndex() ); + + emit beginInsertRows( QModelIndex(), c, c + maxc - 1 ); + //beginResetModel(); + + PlItem* plitem; + QList< Tomahawk::query_ptr >::iterator iter = m_tracksToAdd.begin(); + + for( int i = 0; i < maxc; ++i ) + { + plitem = new PlItem( *iter, m_rootItem ); + plitem->index = createIndex( m_rootItem->children.count() - 1, 0, plitem ); + + connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); + + ++iter; + } + + m_tracksToAdd.erase( m_tracksToAdd.begin(), iter ); + + if ( m_tracksToAdd.isEmpty() && m_loadingCollections.isEmpty() ) + emit loadingFinished(); + + //endResetModel(); + emit endInsertRows(); + qDebug() << Q_FUNC_INFO << rowCount( QModelIndex() ); + + if ( m_tracksToAdd.count() ) + QTimer::singleShot( 250, this, SLOT( processTracksToAdd() ) ); +} + + +void +CollectionFlatModel::onTracksRemoved( const QList& tracks ) +{ + QList t = tracks; + for ( int i = rowCount( QModelIndex() ); i >= 0 && t.count(); i-- ) + { + PlItem* item = itemFromIndex( index( i, 0, QModelIndex() ) ); + if ( !item ) + continue; + + int j = 0; + foreach ( const query_ptr& query, t ) + { + if ( item->query().data() == query.data() ) + { + qDebug() << "Removing row:" << i << query->toString(); + emit beginRemoveRows( QModelIndex(), i, i ); + delete item; + emit endRemoveRows(); + + t.removeAt( j ); + break; + } + + j++; + } + } + +// emit trackCountChanged( rowCount( QModelIndex() ) ); + qDebug() << Q_FUNC_INFO << rowCount( QModelIndex() ); +} + + +void +CollectionFlatModel::onDataChanged() +{ + PlItem* p = (PlItem*)sender(); +// emit itemSizeChanged( p->index ); + + if ( p ) + emit dataChanged( p->index, p->index.sibling( p->index.row(), columnCount( QModelIndex() ) - 1 ) ); +} + + +void +CollectionFlatModel::onSourceOffline( const Tomahawk::source_ptr& src ) +{ + qDebug() << Q_FUNC_INFO; + + if ( m_collectionRows.contains( src->collection() ) ) + { + removeCollection( src->collection() ); + } +} diff --git a/src/libtomahawk/playlist/collectionflatmodel.h b/src/libtomahawk/playlist/collectionflatmodel.h new file mode 100644 index 000000000..cbfee0d36 --- /dev/null +++ b/src/libtomahawk/playlist/collectionflatmodel.h @@ -0,0 +1,82 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef COLLECTIONFLATMODEL_H +#define COLLECTIONFLATMODEL_H + +#include +#include +#include + +#include "plitem.h" +#include "trackmodel.h" +#include "collection.h" +#include "query.h" +#include "typedefs.h" +#include "playlist.h" +#include "playlistinterface.h" + +#include "database/databasecommand_alltracks.h" + +#include "dllmacro.h" + +class QMetaData; + +class DLLEXPORT CollectionFlatModel : public TrackModel +{ +Q_OBJECT + +public: + explicit CollectionFlatModel( QObject* parent = 0 ); + ~CollectionFlatModel(); + + virtual int trackCount() const { return rowCount( QModelIndex() ) + m_tracksToAdd.count(); } + + void addCollections( const QList< Tomahawk::collection_ptr >& collections ); + + void addCollection( const Tomahawk::collection_ptr& collection, bool sendNotifications = true ); + void removeCollection( const Tomahawk::collection_ptr& collection ); + + void addFilteredCollection( const Tomahawk::collection_ptr& collection, unsigned int amount, DatabaseCommand_AllTracks::SortOrder order ); + + virtual void append( const Tomahawk::query_ptr& query ) {} + +signals: + void repeatModeChanged( PlaylistInterface::RepeatMode mode ); + void shuffleModeChanged( bool enabled ); + + void itemSizeChanged( const QModelIndex& index ); + +private slots: + void onDataChanged(); + + void onTracksAdded( const QList& tracks ); + void onTracksRemoved( const QList& tracks ); + + void onSourceOffline( const Tomahawk::source_ptr& src ); + + void processTracksToAdd(); + +private: + QMap< Tomahawk::collection_ptr, QPair< int, int > > m_collectionRows; + QList m_tracksToAdd; + // just to keep track of what we are waiting to be loaded + QList m_loadingCollections; +}; + +#endif // COLLECTIONFLATMODEL_H diff --git a/src/playlist/collectionmodel.cpp b/src/libtomahawk/playlist/collectionmodel.cpp similarity index 77% rename from src/playlist/collectionmodel.cpp rename to src/libtomahawk/playlist/collectionmodel.cpp index d127bba9c..4ba38d861 100644 --- a/src/playlist/collectionmodel.cpp +++ b/src/libtomahawk/playlist/collectionmodel.cpp @@ -1,9 +1,30 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "collectionmodel.h" #include #include #include +#include "sourcelist.h" +#include "utils/tomahawkutils.h" + using namespace Tomahawk; @@ -13,7 +34,7 @@ CollectionModel::CollectionModel( QObject* parent ) { qDebug() << Q_FUNC_INFO; - connect( &APP->sourcelist(), SIGNAL( sourceRemoved( Tomahawk::source_ptr ) ), SLOT( onSourceOffline( Tomahawk::source_ptr ) ) ); + connect( SourceList::instance(), SIGNAL( sourceRemoved( Tomahawk::source_ptr ) ), SLOT( onSourceOffline( Tomahawk::source_ptr ) ) ); } @@ -163,18 +184,18 @@ CollectionModel::addCollection( const collection_ptr& collection ) emit loadingStarts(); - connect( collection.data(), SIGNAL( tracksAdded( QList, Tomahawk::collection_ptr ) ), - SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ) ); + connect( collection.data(), SIGNAL( tracksAdded( QList, Tomahawk::collection_ptr ) ), + SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ) ); connect( collection.data(), SIGNAL( tracksFinished( Tomahawk::collection_ptr ) ), - SLOT( onTracksAddingFinished( Tomahawk::collection_ptr ) ) ); + SLOT( onTracksAddingFinished( Tomahawk::collection_ptr ) ) ); } void CollectionModel::removeCollection( const collection_ptr& collection ) { - disconnect( collection.data(), SIGNAL( tracksAdded( QList, Tomahawk::collection_ptr ) ), - this, SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ) ); + disconnect( collection.data(), SIGNAL( tracksAdded( QList, Tomahawk::collection_ptr ) ), + this, SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ) ); disconnect( collection.data(), SIGNAL( tracksFinished( Tomahawk::collection_ptr ) ), this, SLOT( onTracksAddingFinished( Tomahawk::collection_ptr ) ) ); @@ -185,24 +206,13 @@ CollectionModel::removeCollection( const collection_ptr& collection ) void -CollectionModel::onTracksAdded( const QList& tracks, const collection_ptr& collection ) +CollectionModel::onTracksAdded( const QList& tracks, const collection_ptr& collection ) { // int c = rowCount( QModelIndex() ); PlItem* plitem; - foreach( const QVariant& v, tracks ) + foreach( const Tomahawk::query_ptr& query, tracks ) { - Tomahawk::query_ptr query = query_ptr( new Query( v ) ); - - // FIXME: needs merging - // Manually add a result, since it's coming from the local collection - QVariantMap t = query->toVariant().toMap(); - t["score"] = 1.0; - QList results; - result_ptr result = result_ptr( new Result( t, collection ) ); - results << result; - query->addResults( results ); - PlItem* parent = m_rootItem; if ( parent->hash.contains( query->artist() ) ) { diff --git a/src/playlist/collectionmodel.h b/src/libtomahawk/playlist/collectionmodel.h similarity index 60% rename from src/playlist/collectionmodel.h rename to src/libtomahawk/playlist/collectionmodel.h index 3454c59af..346133013 100644 --- a/src/playlist/collectionmodel.h +++ b/src/libtomahawk/playlist/collectionmodel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef COLLECTIONMODEL_H #define COLLECTIONMODEL_H @@ -6,16 +24,17 @@ #include #include "plitem.h" -#include "tomahawk/tomahawkapp.h" -#include "tomahawk/collection.h" -#include "tomahawk/query.h" -#include "tomahawk/typedefs.h" -#include "tomahawk/playlist.h" -#include "tomahawk/playlistinterface.h" +#include "collection.h" +#include "query.h" +#include "typedefs.h" +#include "playlist.h" +#include "playlistinterface.h" + +#include "dllmacro.h" class QMetaData; -class CollectionModel : public QAbstractItemModel, public PlaylistInterface +class DLLEXPORT CollectionModel : public QAbstractItemModel { Q_OBJECT @@ -37,12 +56,6 @@ public: void addCollection( const Tomahawk::collection_ptr& collection ); void removeCollection( const Tomahawk::collection_ptr& collection ); - virtual Tomahawk::result_ptr previousItem() { return Tomahawk::result_ptr(); } - virtual Tomahawk::result_ptr nextItem() { return Tomahawk::result_ptr(); } - virtual Tomahawk::result_ptr siblingItem( int direction ) { return Tomahawk::result_ptr(); } - - virtual void setCurrentItem( const QModelIndex& index ) {} - virtual PlaylistInterface::RepeatMode repeatMode() const { return PlaylistInterface::NoRepeat; } virtual bool shuffled() const { return false; } @@ -60,7 +73,7 @@ signals: void trackCountChanged( unsigned int tracks ); private slots: - void onTracksAdded( const QList& tracks, const Tomahawk::collection_ptr& collection ); + void onTracksAdded( const QList& tracks, const Tomahawk::collection_ptr& collection ); void onTracksAddingFinished( const Tomahawk::collection_ptr& collection ); void onSourceOffline( Tomahawk::source_ptr src ); diff --git a/src/playlist/collectionproxymodel.cpp b/src/libtomahawk/playlist/collectionproxymodel.cpp similarity index 72% rename from src/playlist/collectionproxymodel.cpp rename to src/libtomahawk/playlist/collectionproxymodel.cpp index 1bd3d7033..f25532d1f 100644 --- a/src/playlist/collectionproxymodel.cpp +++ b/src/libtomahawk/playlist/collectionproxymodel.cpp @@ -1,10 +1,28 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "collectionproxymodel.h" #include #include -#include "tomahawk/album.h" -#include "tomahawk/query.h" +#include "album.h" +#include "query.h" #include "collectionmodel.h" @@ -67,11 +85,6 @@ CollectionProxyModel::lessThan( const QModelIndex& left, const QModelIndex& righ id2 = r->dbid(); } - if ( album1 == "Gern Geschehen" ) - { - qDebug() << artist1 << artist2 << album1 << album2 << id1 << id2; - } - if ( left.column() == 0 ) // sort by artist { if ( artist1 == artist2 ) @@ -79,9 +92,7 @@ CollectionProxyModel::lessThan( const QModelIndex& left, const QModelIndex& righ if ( album1 == album2 ) { if ( albumpos1 == albumpos2 ) - { return id1 < id2; - } return albumpos1 < albumpos2; } @@ -95,6 +106,9 @@ CollectionProxyModel::lessThan( const QModelIndex& left, const QModelIndex& righ { if ( album1 == album2 ) { + if ( albumpos1 == albumpos2 ) + return id1 < id2; + return albumpos1 < albumpos2; } @@ -102,10 +116,16 @@ CollectionProxyModel::lessThan( const QModelIndex& left, const QModelIndex& righ } else if ( left.column() == 4 ) // sort by bitrate { + if ( bitrate1 == bitrate2 ) + return id1 < id2; + return bitrate1 < bitrate2; } else if ( left.column() == 5 ) // sort by mtime { + if ( mtime1 == mtime2 ) + return id1 < id2; + return mtime1 < mtime2; } diff --git a/src/libtomahawk/playlist/collectionproxymodel.h b/src/libtomahawk/playlist/collectionproxymodel.h new file mode 100644 index 000000000..63c615f40 --- /dev/null +++ b/src/libtomahawk/playlist/collectionproxymodel.h @@ -0,0 +1,39 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef COLLECTIONPROXYMODEL_H +#define COLLECTIONPROXYMODEL_H + +#include "trackproxymodel.h" + +#include "dllmacro.h" + +class DLLEXPORT CollectionProxyModel : public TrackProxyModel +{ +Q_OBJECT + +public: + explicit CollectionProxyModel( QObject* parent = 0 ); + + virtual PlaylistInterface::ViewMode viewMode() const { return PlaylistInterface::Flat; } + +protected: + bool lessThan( const QModelIndex& left, const QModelIndex& right ) const; +}; + +#endif // COLLECTIONPROXYMODEL_H diff --git a/src/libtomahawk/playlist/collectionview.cpp b/src/libtomahawk/playlist/collectionview.cpp new file mode 100644 index 000000000..d67b02535 --- /dev/null +++ b/src/libtomahawk/playlist/collectionview.cpp @@ -0,0 +1,121 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "collectionview.h" + +#include +#include +#include + +#include "playlist/collectionproxymodel.h" +#include "widgets/overlaywidget.h" + +using namespace Tomahawk; + + +CollectionView::CollectionView( QWidget* parent ) + : TrackView( parent ) +{ + setProxyModel( new CollectionProxyModel( this ) ); + + setSortingEnabled( true ); + sortByColumn( 0, Qt::AscendingOrder ); + + setDragDropMode( QAbstractItemView::DragOnly ); + setAcceptDrops( false ); + + setContextMenuPolicy( Qt::CustomContextMenu ); + connect( this, SIGNAL( customContextMenuRequested( const QPoint& ) ), SLOT( onCustomContextMenu( const QPoint& ) ) ); +} + + +CollectionView::~CollectionView() +{ + qDebug() << Q_FUNC_INFO; +} + + +void +CollectionView::setModel( TrackModel* model ) +{ + TrackView::setModel( model ); + setGuid( "collectionview" ); + + connect( model, SIGNAL( trackCountChanged( unsigned int ) ), SLOT( onTrackCountChanged( unsigned int ) ) ); +} + + +void +CollectionView::dragEnterEvent( QDragEnterEvent* event ) +{ + event->ignore(); +} + + +void +CollectionView::setupMenus() +{ + m_itemMenu.clear(); + + m_playItemAction = m_itemMenu.addAction( tr( "&Play" ) ); + m_addItemsToQueueAction = m_itemMenu.addAction( tr( "Add to &Queue" ) ); +// m_itemMenu.addSeparator(); +// m_addItemsToPlaylistAction = m_itemMenu.addAction( tr( "&Add to Playlist" ) ); + + connect( m_playItemAction, SIGNAL( triggered() ), SLOT( playItem() ) ); + connect( m_addItemsToQueueAction, SIGNAL( triggered() ), SLOT( addItemsToQueue() ) ); +// connect( m_addItemsToPlaylistAction, SIGNAL( triggered() ), SLOT( addItemsToPlaylist() ) ); +} + + +void +CollectionView::onCustomContextMenu( const QPoint& pos ) +{ + qDebug() << Q_FUNC_INFO; + setupMenus(); + + QModelIndex idx = indexAt( pos ); + idx = idx.sibling( idx.row(), 0 ); + setContextMenuIndex( idx ); + + if ( !idx.isValid() ) + return; + + m_itemMenu.exec( mapToGlobal( pos ) ); +} + + +void +CollectionView::onTrackCountChanged( unsigned int tracks ) +{ + if ( tracks == 0 ) + { + overlay()->setText( tr( "This collection is empty." ) ); + overlay()->show(); + } + else + overlay()->hide(); +} + + +bool +CollectionView::jumpToCurrentTrack() +{ + scrollTo( proxyModel()->currentItem(), QAbstractItemView::PositionAtCenter ); + return true; +} diff --git a/src/libtomahawk/playlist/collectionview.h b/src/libtomahawk/playlist/collectionview.h new file mode 100644 index 000000000..ea26fc5af --- /dev/null +++ b/src/libtomahawk/playlist/collectionview.h @@ -0,0 +1,69 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef COLLECTIONVIEW_H +#define COLLECTIONVIEW_H + +#include + +#include "trackproxymodel.h" +#include "trackmodel.h" +#include "trackview.h" +#include "viewpage.h" + +#include "dllmacro.h" + +class DLLEXPORT CollectionView : public TrackView, public Tomahawk::ViewPage +{ +Q_OBJECT + +public: + explicit CollectionView( QWidget* parent = 0 ); + ~CollectionView(); + + virtual void setModel( TrackModel* model ); + + virtual QWidget* widget() { return this; } + virtual PlaylistInterface* playlistInterface() const { return proxyModel(); } + + virtual QString title() const { return model()->title(); } + virtual QString description() const { return model()->description(); } + virtual QPixmap pixmap() const { return QPixmap( RESPATH "images/music-icon.png" ); } + + virtual bool showModes() const { return true; } + + virtual bool jumpToCurrentTrack(); + +private slots: + void onCustomContextMenu( const QPoint& pos ); + void onTrackCountChanged( unsigned int tracks ); + +protected: + virtual void dragEnterEvent( QDragEnterEvent* event ); + +private: + void setupMenus(); + + QMenu m_itemMenu; + + QAction* m_playItemAction; + QAction* m_addItemsToQueueAction; + QAction* m_addItemsToPlaylistAction; +}; + +#endif // COLLECTIONVIEW_H diff --git a/src/libtomahawk/playlist/dynamic/DynamicControl.cpp b/src/libtomahawk/playlist/dynamic/DynamicControl.cpp new file mode 100644 index 000000000..ae81f8665 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/DynamicControl.cpp @@ -0,0 +1,38 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "DynamicControl.h" + +Tomahawk::DynamicControl::DynamicControl( const QStringList& typeSelectors ) + : m_typeSelectors( typeSelectors ) +{ + +} + +Tomahawk::DynamicControl::~DynamicControl() +{ + +} + +Tomahawk::DynamicControl::DynamicControl(const QString& selectedType, const QStringList& typeSelectors, QObject* parent) + : QObject(parent) + , m_selectedType( selectedType ) + , m_typeSelectors( typeSelectors ) +{ + +} diff --git a/src/libtomahawk/playlist/dynamic/DynamicControl.h b/src/libtomahawk/playlist/dynamic/DynamicControl.h new file mode 100644 index 000000000..42b62d07d --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/DynamicControl.h @@ -0,0 +1,120 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DYNAMIC_PLAYLIST_CONTROL +#define DYNAMIC_PLAYLIST_CONTROL + +#include +#include +#include +#include +#include "typedefs.h" + +namespace Tomahawk +{ + +/** + * A Dynamic Control is a single constraint that limits a dynamic playlist. Each generator creates controls specific to that generator. + * Each control has 3 pieces: + * - Type (string selector for what this control is matching) + * - Match selector (how to match the type to the input) + * - Input field (the user input field). + * + * Each control also has a list of TypeSelectors that comes from the generator, and only one is selected at once. + * + */ +class DynamicControl : public QObject +{ + Q_OBJECT + Q_PROPERTY( QString type READ type WRITE setType ) // the generator type associated with this control + Q_PROPERTY( QString id READ id WRITE setId ) + Q_PROPERTY( QString selectedType READ selectedType WRITE setSelectedType ) + Q_PROPERTY( QString match READ match WRITE setMatch ) + Q_PROPERTY( QString input READ input WRITE setInput ) + Q_PROPERTY( QString summary READ summary ) // a summary of the control in phrase form + +public: + DynamicControl( const QStringList& typeSelectors = QStringList() ); + virtual ~DynamicControl(); + + + /// The current type of this control + QString selectedType() const { return m_selectedType; } + /** + * The match selector widget based on this control's type + * + * The control manages the lifetime of the widget. + */ + virtual QWidget* matchSelector() { Q_ASSERT( false ); return 0; } + /** + * The input field widget that is associated with this type + * + * The control manages the lifetime of the widget. + */ + virtual QWidget* inputField() { Q_ASSERT( false ); return 0; } + + /// The user-readable match value, for showing in read-only playlists + virtual QString matchString() const { Q_ASSERT( false ); return QString(); } + + /// the serializable value of the match + virtual QString match() const { Q_ASSERT( false ); return QString(); } + /// the serializable value of the input + virtual QString input() const { Q_ASSERT( false ); return QString(); } + /// the user-readable summary phrase + virtual QString summary() const { Q_ASSERT( false ); return QString(); } + + // used by JSON serialization + virtual void setMatch( const QString& match ) { Q_ASSERT( false ); } + virtual void setInput( const QString& input ) { Q_ASSERT( false ); } + /// All the potential type selectors for this control + QStringList typeSelectors() const { return m_typeSelectors; } + + QString id() { + if( m_id.isEmpty() ) + m_id = uuid(); + return m_id; + }; + void setId( const QString& id ) { m_id = id; } + + void setType( const QString& type ) { m_type = type; } + QString type() const { return m_type; } + +signals: + void changed(); + +public slots: + /** + * Sets the type to the newly specified one. Note that this will update the matchSelector + * and inputField widgets, so you should fetch the new widgets for use immediately. + */ + virtual void setSelectedType( const QString& selectedType ) { m_selectedType = selectedType; } + +protected: + // Private constructor, you can't make one. Get it from your Generator. + explicit DynamicControl( const QString& selectedType, const QStringList& typeSelectors, QObject* parent = 0 ); + +private: + QString m_type; + QString m_selectedType; + QStringList m_typeSelectors; + QString m_id; +}; + +}; + +#endif diff --git a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp new file mode 100644 index 000000000..43fceeaac --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp @@ -0,0 +1,257 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "playlist/dynamic/DynamicModel.h" +#include "GeneratorInterface.h" +#include "audio/audioengine.h" +#include + +using namespace Tomahawk; + +DynamicModel::DynamicModel( QObject* parent ) + : PlaylistModel( parent ) + , m_onDemandRunning( false ) + , m_changeOnNext( false ) + , m_searchingForNext( false ) + , m_filterUnresolvable( true ) + , m_startingAfterFailed( false ) + , m_currentAttempts( 0 ) + , m_lastResolvedRow( 0 ) +{ + +} + +DynamicModel::~DynamicModel() +{ + +} + +void +DynamicModel::loadPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) +{ + if( !m_playlist.isNull() ) { + disconnect( m_playlist->generator().data(), SIGNAL( nextTrackGenerated( Tomahawk::query_ptr ) ), this, SLOT( newTrackGenerated( Tomahawk::query_ptr ) ) ); + } + m_playlist = playlist; + + if( m_playlist->mode() == OnDemand ) + setFilterUnresolvable( true ); + + connect( m_playlist->generator().data(), SIGNAL( nextTrackGenerated( Tomahawk::query_ptr ) ), this, SLOT( newTrackGenerated( Tomahawk::query_ptr ) ) ); + PlaylistModel::loadPlaylist( m_playlist, m_playlist->mode() == Static ); + + if( m_playlist->mode() == OnDemand ) + emit trackCountChanged( rowCount( QModelIndex() ) ); +} + +QString +DynamicModel::description() const +{ + return m_playlist->generator()->sentenceSummary(); +} + + +void +DynamicModel::startOnDemand() +{ + connect( AudioEngine::instance(), SIGNAL( loading( Tomahawk::result_ptr ) ), this, SLOT( newTrackLoading() ) ); + + m_playlist->generator()->startOnDemand(); + + m_onDemandRunning = true; +} + +void +DynamicModel::newTrackGenerated( const Tomahawk::query_ptr& query ) +{ + if( m_onDemandRunning ) { + connect( query.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( trackResolveFinished( bool ) ) ); + + append( query ); + } +} + +void +DynamicModel::stopOnDemand( bool stopPlaying ) +{ + m_onDemandRunning = false; + if( stopPlaying ) + AudioEngine::instance()->stop(); + + disconnect( AudioEngine::instance(), SIGNAL( loading( Tomahawk::result_ptr ) ), this, SLOT( newTrackLoading() ) ); +} + +void +DynamicModel::changeStation() +{ + if( m_onDemandRunning ) + m_changeOnNext = true; + else // if we're not running, just start + m_playlist->generator()->startOnDemand(); +} + +void +DynamicModel::trackResolveFinished( bool success ) +{ + Query* q = qobject_cast(sender()); + + if( !q->playable() ) { + qDebug() << "Got not resolved track:" << q->track() << q->artist() << m_lastResolvedRow << m_currentAttempts; + m_currentAttempts++; + + int curAttempts = m_startingAfterFailed ? m_currentAttempts - 20 : m_currentAttempts; // if we just failed, m_currentAttempts includes those failures + if( curAttempts < 20 ) { + qDebug() << "FETCHING MORE!"; + m_playlist->generator()->fetchNext(); + } else { + m_startingAfterFailed = true; + emit trackGenerationFailure( tr( "Could not find a playable track.\n\nPlease change the filters or try again." ) ); + } + } + else { + qDebug() << "Got successful resolved track:" << q->track() << q->artist() << m_lastResolvedRow << m_currentAttempts; + + if( m_currentAttempts > 0 ) { + qDebug() << "EMITTING AN ASK FOR COLLAPSE:" << m_lastResolvedRow << m_currentAttempts; + emit collapseFromTo( m_lastResolvedRow, m_currentAttempts ); + } + m_currentAttempts = 0; + m_searchingForNext = false; + + emit checkForOverflow(); + } +} + + +void +DynamicModel::newTrackLoading() +{ + qDebug() << "Got NEW TRACK LOADING signal"; + if( m_changeOnNext ) { // reset instead of getting the next one + m_lastResolvedRow = rowCount( QModelIndex() ); + m_searchingForNext = true; + m_playlist->generator()->startOnDemand(); + } else if( m_onDemandRunning && m_currentAttempts == 0 && !m_searchingForNext ) { // if we're in dynamic mode and we're also currently idle + m_lastResolvedRow = rowCount( QModelIndex() ); + m_searchingForNext = true; + qDebug() << "IDLE fetching new track!"; + m_playlist->generator()->fetchNext(); + } +} + +void +DynamicModel::tracksGenerated( const QList< query_ptr > entries, int limitResolvedTo ) +{ + if( m_filterUnresolvable && m_playlist->mode() == OnDemand ) { // wait till we get them resolved (for previewing stations) + m_limitResolvedTo = limitResolvedTo; + filterUnresolved( entries ); + } else { + addToPlaylist( entries, m_playlist->mode() == OnDemand ); // if ondemand, we're previewing, so clear old + + if( m_playlist->mode() == OnDemand ) { + m_lastResolvedRow = rowCount( QModelIndex() ); + } + } +} + +void +DynamicModel::filterUnresolved( const QList< query_ptr >& entries ) +{ + m_toResolveList = entries; + + foreach( const query_ptr& q, entries ) { + connect( q.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( filteringTrackResolved( bool ) ) ); + } + Pipeline::instance()->resolve( entries, true ); +} + +void +DynamicModel::filteringTrackResolved( bool successful ) +{ + // arg, we don't have the query_ptr, just the Query + Query* q = qobject_cast< Query* >( sender() ); + Q_ASSERT( q ); + + query_ptr realptr; + foreach( const query_ptr& qptr, m_toResolveList ) { + if( qptr.data() == q ) { + realptr = qptr; + break; + } + } + if( realptr.isNull() ) // we already finished + return; + + m_toResolveList.removeAll( realptr ); + + if( successful ) + m_resolvedList << realptr; + + if( m_toResolveList.isEmpty() || m_resolvedList.size() == m_limitResolvedTo ) { // done, add to playlist + if( m_limitResolvedTo < m_resolvedList.count() ) // limit to how many we were asked for + m_resolvedList = m_resolvedList.mid( 0, m_limitResolvedTo ); + + addToPlaylist( m_resolvedList, true ); + m_toResolveList.clear(); + m_resolvedList.clear(); + + if( m_playlist->mode() == OnDemand ) { + m_lastResolvedRow = rowCount( QModelIndex() ); + } + } + if( m_toResolveList.isEmpty() && rowCount( QModelIndex() ) == 0 ) // we failed + emit trackGenerationFailure( tr( "Could not find a playable track.\n\nPlease change the filters or try again." ) ); +} + + +void +DynamicModel::addToPlaylist( const QList< query_ptr >& entries, bool clearFirst ) +{ + if( clearFirst ) + clear(); + + if( m_playlist->author()->isLocal() && m_playlist->mode() == Static ) { + m_playlist->addEntries( entries, m_playlist->currentrevision() ); + } else { // read-only, so add tracks only in the GUI, not to the playlist itself + foreach( const query_ptr& query, entries ) { + append( query ); + } + } + + emit tracksAdded(); +} + + +void +DynamicModel::removeIndex(const QModelIndex& idx, bool moreToCome) +{ + if ( m_playlist->mode() == Static && isReadOnly() ) + return; + + if( m_playlist->mode() == OnDemand ) { + if( !moreToCome && idx == index( rowCount( QModelIndex() ) - 1, 0, QModelIndex() ) ) { // if the user is manually removing the last one, re-add as we're a station + newTrackLoading(); + } + TrackModel::removeIndex( idx ); + } else + PlaylistModel::removeIndex( idx, moreToCome ); + // don't call onPlaylistChanged. + + if( !moreToCome ) + m_lastResolvedRow = rowCount( QModelIndex() ); +} diff --git a/src/libtomahawk/playlist/dynamic/DynamicModel.h b/src/libtomahawk/playlist/dynamic/DynamicModel.h new file mode 100644 index 000000000..a3cdc5f65 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.h @@ -0,0 +1,94 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DYNAMIC_MODEL_H +#define DYNAMIC_MODEL_H + +#include "playlistmodel.h" +#include "query.h" + +namespace Tomahawk +{ + +class StationModelItem; + + +/** + * Extends PlaylistModel with support for handling stations + */ +class DynamicModel : public PlaylistModel +{ + Q_OBJECT +public: + DynamicModel( QObject* parent = 0 ); + virtual ~DynamicModel(); + + void startOnDemand(); + void stopOnDemand( bool stopPlaying = true ); + void changeStation(); + + virtual QString description() const; + + void loadPlaylist( const dynplaylist_ptr& playlist ); + + virtual void removeIndex( const QModelIndex& index, bool moreToCome = false ); + + bool searchingForNext() const { return m_searchingForNext; } + + void setFilterUnresolvable( bool filter ) { m_filterUnresolvable = filter; } + bool filterUnresolvable() const { return m_filterUnresolvable; } + + // a batchof static tracks wre generated + void tracksGenerated( const QList< query_ptr > entries, int limitResolvedTo = -1 ); +signals: + void collapseFromTo( int startRow, int num ); + void checkForOverflow(); + + void trackGenerationFailure( const QString& msg ); + + void tracksAdded(); +private slots: + void newTrackGenerated( const Tomahawk::query_ptr& query ); + + void trackResolveFinished( bool ); + void newTrackLoading(); + + void filteringTrackResolved( bool successful ); +private: + void filterUnresolved( const QList< query_ptr >& entries ); + void addToPlaylist( const QList< query_ptr >& entries, bool clearFirst ); + + dynplaylist_ptr m_playlist; + // for filtering unresolvable + int m_limitResolvedTo; + QList< query_ptr > m_toResolveList; + QList< query_ptr > m_resolvedList; + + bool m_onDemandRunning; + bool m_changeOnNext; + bool m_searchingForNext; + bool m_firstTrackGenerated; + bool m_filterUnresolvable; + bool m_startingAfterFailed; + int m_currentAttempts; + int m_lastResolvedRow; +}; + +}; + +#endif diff --git a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp new file mode 100644 index 000000000..554ff5f98 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.cpp @@ -0,0 +1,440 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "DynamicPlaylist.h" + +#include "sourcelist.h" +#include "GeneratorFactory.h" +#include "database/database.h" +#include "database/databasecommand.h" +#include "database/databasecommand_createdynamicplaylist.h" +#include "database/databasecommand_setdynamicplaylistrevision.h" +#include "database/databasecommand_loaddynamicplaylist.h" +#include "database/databasecommand_deletedynamicplaylist.h" + +using namespace Tomahawk; + +DynamicPlaylist::DynamicPlaylist(const Tomahawk::source_ptr& author, const QString& type ) + : Playlist(author) +{ + qDebug() << Q_FUNC_INFO << "JSON"; + m_generator = geninterface_ptr( GeneratorFactory::create( type ) ); +} + + +DynamicPlaylist::~DynamicPlaylist() +{ + +} + +// Called by loadAllPlaylists command +DynamicPlaylist::DynamicPlaylist ( const Tomahawk::source_ptr& src, + const QString& currentrevision, + const QString& title, + const QString& info, + const QString& creator, + const QString& type, + GeneratorMode mode, + bool shared, + int lastmod, + const QString& guid ) + : Playlist( src, currentrevision, title, info, creator, shared, lastmod, guid ) +{ + qDebug() << "Creating Dynamic Playlist 1"; + // TODO instantiate generator + m_generator = geninterface_ptr( GeneratorFactory::create( type ) ); + m_generator->setMode( mode ); +} + + +// called when a new playlist is created (no currentrevision, new guid) +DynamicPlaylist::DynamicPlaylist ( const Tomahawk::source_ptr& author, + const QString& guid, + const QString& title, + const QString& info, + const QString& creator, + const QString& type, + bool shared ) + : Playlist ( author, guid, title, info, creator, shared ) +{ + qDebug() << "Creating Dynamic Playlist 2"; + m_generator = geninterface_ptr( GeneratorFactory::create( type ) ); +} + +geninterface_ptr +DynamicPlaylist::generator() const +{ + return m_generator; +} + +int +DynamicPlaylist::mode() const +{ + return m_generator->mode(); +} + +void +DynamicPlaylist::setGenerator(const Tomahawk::geninterface_ptr& gen_ptr) +{ + m_generator = gen_ptr; +} + +QString +DynamicPlaylist::type() const +{ + return m_generator->type(); +} + +void +DynamicPlaylist::setMode( int mode ) +{ + m_generator->setMode( (GeneratorMode)mode ); +} + + + +dynplaylist_ptr +DynamicPlaylist::create( const Tomahawk::source_ptr& author, + const QString& guid, + const QString& title, + const QString& info, + const QString& creator, + bool shared ) +{ + // default generator + QString type = ""; + dynplaylist_ptr dynplaylist = dynplaylist_ptr( new DynamicPlaylist( author, guid, title, info, creator, type, shared ) ); + + DatabaseCommand_CreateDynamicPlaylist* cmd = new DatabaseCommand_CreateDynamicPlaylist( author, dynplaylist ); + connect( cmd, SIGNAL(finished()), dynplaylist.data(), SIGNAL(created()) ); + Database::instance()->enqueue( QSharedPointer(cmd) ); + dynplaylist->reportCreated( dynplaylist ); + return dynplaylist; + +} + +void +DynamicPlaylist::createNewRevision( const QString& newUuid ) +{ + if( mode() == Static ) + { + createNewRevision( newUuid.isEmpty() ? uuid() : newUuid, currentrevision(), type(), generator()->controls(), entries() ); + } else if( mode() == OnDemand ) + { + createNewRevision( newUuid.isEmpty() ? uuid() : newUuid, currentrevision(), type(), generator()->controls()); + } +} + + +// create a new revision that will be a static playlist, as it has entries +void +DynamicPlaylist::createNewRevision( const QString& newrev, + const QString& oldrev, + const QString& type, + const QList< dyncontrol_ptr>& controls, + const QList< plentry_ptr >& entries ) +{ + // get the newly added tracks + QList< plentry_ptr > added = newEntries( entries ); + + QStringList orderedguids; + for( int i = 0; i < entries.size(); ++i ) + orderedguids << entries.at(i)->guid(); + + // no conflict resolution or partial updating for controls. all or nothing baby + + // source making the change (local user in this case) + source_ptr author = SourceList::instance()->getLocal(); + // command writes new rev to DB and calls setRevision, which emits our signal + DatabaseCommand_SetDynamicPlaylistRevision* cmd = + new DatabaseCommand_SetDynamicPlaylistRevision( author, + guid(), + newrev, + oldrev, + orderedguids, + added, + entries, + type, + Static, + controls ); + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + +// create a new revision that will be an ondemand playlist, as it has no entries +void +DynamicPlaylist::createNewRevision( const QString& newrev, + const QString& oldrev, + const QString& type, + const QList< dyncontrol_ptr>& controls ) +{ + // can skip the entry stuff. just overwrite with new info + source_ptr author = SourceList::instance()->getLocal(); + // command writes new rev to DB and calls setRevision, which emits our signal + DatabaseCommand_SetDynamicPlaylistRevision* cmd = + new DatabaseCommand_SetDynamicPlaylistRevision( author, + guid(), + newrev, + oldrev, + type, + OnDemand, + controls ); + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + +void +DynamicPlaylist::loadRevision( const QString& rev ) +{ + qDebug() << Q_FUNC_INFO << "Loading with:" << ( rev.isEmpty() ? currentrevision() : rev ); + + DatabaseCommand_LoadDynamicPlaylist* cmd = new DatabaseCommand_LoadDynamicPlaylist( rev.isEmpty() ? currentrevision() : rev ); + + if( m_generator->mode() == OnDemand ) { + connect( cmd, SIGNAL( done( QString, + bool, + QString, + QList< QVariantMap >, + bool ) ), + SLOT( setRevision( QString, + bool, + QString, + QList< QVariantMap >, + bool) ) ); + } else if( m_generator->mode() == Static ) { + connect( cmd, SIGNAL( done( QString, + QList< QString >, + QList< QString >, + QString, + QList< QVariantMap >, + bool, + QMap< QString, Tomahawk::plentry_ptr >, + bool ) ), + SLOT( setRevision( QString, + QList< QString >, + QList< QString >, + QString, + QList< QVariantMap >, + bool, + QMap< QString, Tomahawk::plentry_ptr >, + bool ) ) ); + + } + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + +bool +DynamicPlaylist::remove( const Tomahawk::dynplaylist_ptr& playlist ) +{ + DatabaseCommand_DeletePlaylist* cmd = new DatabaseCommand_DeleteDynamicPlaylist( playlist->author(), playlist->guid() ); + Database::instance()->enqueue( QSharedPointer(cmd) ); + + return false; +} + +void +DynamicPlaylist::reportCreated( const Tomahawk::dynplaylist_ptr& self ) +{ + qDebug() << Q_FUNC_INFO; + Q_ASSERT( self.data() == this ); + Q_ASSERT( !author().isNull() ); + Q_ASSERT( !author()->collection().isNull() ); + // will emit Collection::playlistCreated(...) + qDebug() << "Creating dynplaylist belonging to:" << author().data() << author().isNull(); + qDebug() << "REPORTING DYNAMIC PLAYLIST CREATED:" << this << author()->friendlyName(); + author()->collection()->addDynamicPlaylist( self ); +} + +void +DynamicPlaylist::reportDeleted( const Tomahawk::dynplaylist_ptr& self ) +{ + qDebug() << Q_FUNC_INFO; + Q_ASSERT( self.data() == this ); + // will emit Collection::playlistDeleted(...) + author()->collection()->deleteDynamicPlaylist( self ); +} + +void DynamicPlaylist::addEntries(const QList< query_ptr >& queries, const QString& oldrev) +{ + Q_ASSERT( m_generator->mode() == Static ); + + QList el = addEntriesInternal( queries ); + + QString newrev = uuid(); + createNewRevision( newrev, oldrev, m_generator->type(), m_generator->controls(), el ); +} + +void DynamicPlaylist::addEntry(const Tomahawk::query_ptr& query, const QString& oldrev) +{ + QList queries; + queries << query; + + addEntries( queries, oldrev ); +} + +void DynamicPlaylist::setRevision( const QString& rev, + const QList< QString >& neworderedguids, + const QList< QString >& oldorderedguids, + const QString& type, + const QList< dyncontrol_ptr >& controls, + bool is_newest_rev, + const QMap< QString, plentry_ptr >& addedmap, + bool applied) +{ + // we're probably being called by a database worker thread + if( QThread::currentThread() != thread() ) + { + QMetaObject::invokeMethod( this, + "setRevision", + Qt::BlockingQueuedConnection, + Q_ARG( QString, rev ), + Q_ARG( QList , neworderedguids ), + Q_ARG( QList , oldorderedguids ), + Q_ARG( QString , type ), + QGenericArgument( "QList< Tomahawk::dyncontrol_ptr > " , (const void*)&controls ), + Q_ARG( bool, is_newest_rev ), + QGenericArgument( "QMap< QString,Tomahawk::plentry_ptr > " , (const void*)&addedmap ), + Q_ARG( bool, applied ) ); + return; + } + if( m_generator->type() != type ) { // new generator needed + m_generator = GeneratorFactory::create( type ); + } + + m_generator->setControls( controls ); + m_generator->setMode( Static ); + + DynamicPlaylistRevision dpr = setNewRevision( rev, neworderedguids, oldorderedguids, is_newest_rev, addedmap ); + dpr.applied = applied; + dpr.controls = controls; + dpr.type = type; + dpr.mode = Static; + + if( applied ) { + setCurrentrevision( rev ); + } + // qDebug() << "EMITTING REVISION LOADED 1!"; + emit dynamicRevisionLoaded( dpr ); +} + + +void +DynamicPlaylist::setRevision( const QString& rev, + const QList< QString >& neworderedguids, + const QList< QString >& oldorderedguids, + const QString& type, + const QList< QVariantMap>& controlsV, + bool is_newest_rev, + const QMap< QString, Tomahawk::plentry_ptr >& addedmap, + bool applied ) +{ + if( QThread::currentThread() != thread() ) + { + QMetaObject::invokeMethod( this, + "setRevision", + Qt::BlockingQueuedConnection, + Q_ARG( QString, rev ), + Q_ARG( QList , neworderedguids ), + Q_ARG( QList , oldorderedguids ), + Q_ARG( QString , type ), + QGenericArgument( "QList< QVariantMap > " , (const void*)&controlsV ), + Q_ARG( bool, is_newest_rev ), + QGenericArgument( "QMap< QString,Tomahawk::plentry_ptr > " , (const void*)&addedmap ), + Q_ARG( bool, applied ) ); + return; + } + + QList controls = variantsToControl( controlsV ); + setRevision( rev, neworderedguids, oldorderedguids, type, controls, is_newest_rev, addedmap, applied ); + +} + +void DynamicPlaylist::setRevision( const QString& rev, + bool is_newest_rev, + const QString& type, + const QList< dyncontrol_ptr >& controls, + bool applied ) +{ + if( QThread::currentThread() != thread() ) + { + QMetaObject::invokeMethod( this, + "setRevision", + Qt::BlockingQueuedConnection, + Q_ARG( QString, rev ), + Q_ARG( bool, is_newest_rev ), + Q_ARG( QString, type ), + QGenericArgument( "QList< Tomahawk::dyncontrol_ptr >" , (const void*)&controls ), + Q_ARG( bool, applied ) ); + return; + } + if( m_generator->type() != type ) { // new generator needed + m_generator = geninterface_ptr( GeneratorFactory::create( type ) ); + } + + m_generator->setControls( controls ); + m_generator->setMode( OnDemand ); + + DynamicPlaylistRevision dpr; + dpr.oldrevisionguid = currentrevision(); + dpr.revisionguid = rev; + dpr.controls = controls; + dpr.type = type; + dpr.mode = OnDemand; + + if( applied ) { + setCurrentrevision( rev ); + } + // qDebug() << "EMITTING REVISION LOADED 2!"; + emit dynamicRevisionLoaded( dpr ); +} + + +void +DynamicPlaylist::setRevision( const QString& rev, + bool is_newest_rev, + const QString& type, + const QList< QVariantMap >& controlsV, + bool applied ) +{ + if( QThread::currentThread() != thread() ) + { + QMetaObject::invokeMethod( this, + "setRevision", + Qt::BlockingQueuedConnection, + Q_ARG( QString, rev ), + Q_ARG( bool, is_newest_rev ), + Q_ARG( QString, type ), + QGenericArgument( "QList< QVariantMap >" , (const void*)&controlsV ), + Q_ARG( bool, applied ) ); + return; + } + + QList controls = variantsToControl( controlsV ); + setRevision( rev, is_newest_rev, type, controls, applied ); +} + +QList< dyncontrol_ptr > DynamicPlaylist::variantsToControl( const QList< QVariantMap >& controlsV ) +{ + QList realControls; + foreach( QVariantMap controlV, controlsV ) { + dyncontrol_ptr control = GeneratorFactory::createControl( controlV.value( "type" ).toString(), controlV.value( "selectedType" ).toString() ); + qDebug() << "CReating control with data:" << controlV; + QJson::QObjectHelper::qvariant2qobject( controlV, control.data() ); + realControls << control; + } + return realControls; +} + diff --git a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h new file mode 100644 index 000000000..864d6a9f4 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h @@ -0,0 +1,190 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DYNAMIC_PLAYLIST_H +#define DYNAMIC_PLAYLIST_H + +#include +#include +#include + +#include "playlist.h" +#include "typedefs.h" +#include "playlist/dynamic/DynamicControl.h" + +#include "dllmacro.h" + +class DatabaseCommand_LoadAllDynamicPlaylists; +class DatabaseCommand_SetDynamicPlaylistRevision; +class DatabaseCommand_CreateDynamicPlaylist; +class DatabaseCollection; + +namespace Tomahawk { + +/** + * Subclass of playlist that adds the information needed to store a dynamic playlist. + * It uses normal PlaylistEntries but also has a mode, a generator, and a list of controls +*/ + +struct DLLEXPORT DynamicPlaylistRevision : PlaylistRevision +{ + QList< dyncontrol_ptr > controls; + Tomahawk::GeneratorMode mode; + QString type; + + DynamicPlaylistRevision( const PlaylistRevision& other ) + { + revisionguid = other.revisionguid; + oldrevisionguid = other.oldrevisionguid; + newlist = other.newlist; + added = other.added; + removed = other.removed; + applied = other.applied; + } + + DynamicPlaylistRevision() {} +}; + +class DLLEXPORT DynamicPlaylist : public Playlist +{ + Q_OBJECT + + // :-( int becuase qjson chokes on my enums + Q_PROPERTY( int mode WRITE setMode READ mode ) + Q_PROPERTY( QString type WRITE setType READ type ) + + friend class ::DatabaseCommand_SetDynamicPlaylistRevision; + friend class ::DatabaseCommand_CreateDynamicPlaylist; + friend class ::DatabaseCollection; /// :-( + +public: + virtual ~DynamicPlaylist(); + + /// Generate an empty dynamic playlist with default generator + static Tomahawk::dynplaylist_ptr create( const source_ptr& author, + const QString& guid, + const QString& title, + const QString& info, + const QString& creator, + bool shared + ); + static bool remove( const dynplaylist_ptr& playlist ); + + virtual void loadRevision( const QString& rev = "" ); + + // :-( int becuase qjson chokes on my enums + int mode() const; + QString type() const; + geninterface_ptr generator() const; + + // Creates a new revision from the playlist in memory. Use this is you change the controls or + // mode of a playlist and want to save it to db/others. + void createNewRevision( const QString& uuid = QString() ); + + virtual void addEntries( const QList< query_ptr >& queries, const QString& oldrev ); + virtual void addEntry( const Tomahawk::query_ptr& query, const QString& oldrev ); + + // + // these need to exist and be public for the json serialization stuff + // you SHOULD NOT call them. They are used for an alternate CTOR method from json. + // maybe friend QObjectHelper and make them private? + explicit DynamicPlaylist( const source_ptr& author, const QString& type ); + void setMode( int mode ); + void setType( const QString& type ) { /** TODO */; } + void setGenerator( const geninterface_ptr& gen_ptr ); + // + +signals: + /// emitted when the playlist revision changes (whenever the playlist changes) + void dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ); + +public slots: + // want to update the playlist from the model? + // generate a newrev using uuid() and call this: + // if this is a static playlist, pass it a new list of entries. implicitly sets mode to static + void createNewRevision( const QString& newrev, const QString& oldrev, const QString& type, const QList< dyncontrol_ptr>& controls, const QList< plentry_ptr >& entries ); + // if it is ondemand, no entries are needed implicitly sets mode to ondemand + void createNewRevision( const QString& newrev, const QString& oldrev, const QString& type, const QList< dyncontrol_ptr>& controls ); + + void reportCreated( const Tomahawk::dynplaylist_ptr& self ); + void reportDeleted( const Tomahawk::dynplaylist_ptr& self ); + + // called from setdynamicplaylistrevision db cmd + // 4 options, because dbcmds can't create qwidgets: + // static version, qvariant controls + // static version, dyncontrol_ptr controls + // ondemand version, qvariant controls + // ondemand version, dyncontrol_ptr controls + void setRevision( const QString& rev, + const QList& neworderedguids, + const QList& oldorderedguids, + const QString& type, + const QList< QVariantMap >& controls, + bool is_newest_rev, + const QMap< QString, Tomahawk::plentry_ptr >& addedmap, + bool applied ); + void setRevision( const QString& rev, + const QList& neworderedguids, + const QList& oldorderedguids, + const QString& type, + const QList< Tomahawk::dyncontrol_ptr >& controls, + bool is_newest_rev, + const QMap< QString, Tomahawk::plentry_ptr >& addedmap, + bool applied ); + // ondemand version + void setRevision( const QString& rev, + bool is_newest_rev, + const QString& type, + const QList< QVariantMap>& controls, + bool applied ); + void setRevision( const QString& rev, + bool is_newest_rev, + const QString& type, + const QList< Tomahawk::dyncontrol_ptr>& controls, + bool applied ); +private: + // called from loadAllPlaylists DB cmd via databasecollection (in GUI thread) + explicit DynamicPlaylist( const source_ptr& src, + const QString& currentrevision, + const QString& title, + const QString& info, + const QString& creator, + const QString& type, + GeneratorMode mode, + bool shared, + int lastmod, + const QString& guid = "" ); // populate db + + // called when creating new playlist + explicit DynamicPlaylist( const source_ptr& author, + const QString& guid, + const QString& title, + const QString& info, + const QString& creator, + const QString& type, + bool shared ); + +private: + QList< dyncontrol_ptr > variantsToControl( const QList< QVariantMap >& controlsV ); + geninterface_ptr m_generator; + +}; + +}; // namespace + +#endif \ No newline at end of file diff --git a/src/libtomahawk/playlist/dynamic/DynamicView.cpp b/src/libtomahawk/playlist/dynamic/DynamicView.cpp new file mode 100644 index 000000000..05b2c9c9d --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/DynamicView.cpp @@ -0,0 +1,329 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "DynamicView.h" + +#include "widgets/overlaywidget.h" +#include "playlistmodel.h" +#include "trackproxymodel.h" +#include "trackheader.h" + +#include +#include +#include +#include + +#include "DynamicModel.h" +#include + +using namespace Tomahawk; + +#define FADE_LENGTH 800 +#define SLIDE_LENGTH 300 +#define SLIDE_OFFSET 500 +#define LONG_MULT 0 // to avoid superfast slides when the length is long, make it longer incrementally + +DynamicView::DynamicView( QWidget* parent ) + : PlaylistView( parent ) + , m_onDemand( false ) + , m_checkOnCollapse( false ) + , m_working( false ) + , m_fadebg( false ) + , m_fadeOnly( false ) +{ + setContentsMargins( 0, 0, 0, 0 ); + setFrameShape( QFrame::NoFrame ); + setAttribute( Qt::WA_MacShowFocusRect, 0 ); + + m_fadeOutAnim.setDuration( FADE_LENGTH ); + m_fadeOutAnim.setCurveShape( QTimeLine::LinearCurve ); + m_fadeOutAnim.setFrameRange( 100, 0 ); + m_fadeOutAnim.setUpdateInterval( 5 ); + + QEasingCurve curve( QEasingCurve::OutBounce ); + curve.setAmplitude( .25 ); + m_slideAnim.setEasingCurve( curve ); + m_slideAnim.setDirection( QTimeLine::Forward ); + m_fadeOutAnim.setUpdateInterval( 5 ); + + connect( &m_fadeOutAnim, SIGNAL( frameChanged( int ) ), viewport(), SLOT( update() ) ); + connect( &m_fadeOutAnim, SIGNAL( finished() ), this, SLOT( animFinished() ) ); +} + +DynamicView::~DynamicView() +{ + +} + +void +DynamicView::setModel( DynamicModel* model) +{ + m_model = model; + PlaylistView::setModel( model ); + + connect( model, SIGNAL( trackCountChanged( unsigned int ) ), SLOT( onTrackCountChanged( unsigned int ) ) ); + connect( model, SIGNAL( checkForOverflow() ), this, SLOT( checkForOverflow() ) ); +} + +void +DynamicView::setOnDemand( bool onDemand ) +{ + m_onDemand = onDemand; + + if( m_onDemand ) + setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + else + setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded ); +} + +void +DynamicView::setReadOnly( bool readOnly ) +{ + m_readOnly = readOnly; +} + +void +DynamicView::showMessageTimeout( const QString& title, const QString& body ) +{ + m_title = title; + m_body = body; + + overlay()->setText( QString( "%1:\n\n%2" ).arg( m_title, m_body ) ); + overlay()->show( 10 ); +} + +void +DynamicView::showMessage(const QString& message) +{ + overlay()->setText( message ); + overlay()->show(); +} + +void +DynamicView::setDynamicWorking(bool working) +{ + m_working = working; + if( working ) + overlay()->hide(); + else + onTrackCountChanged( proxyModel()->rowCount() ); +} + + +void +DynamicView::onTrackCountChanged( unsigned int tracks ) +{ + if ( tracks == 0 && !m_working ) + { + if( m_onDemand ) { + if( !m_readOnly ) + overlay()->setText( tr( "Add some filters above to seed this station!" ) ); + else + return; // when viewing a read-only station, don't show anything + } else + if( m_readOnly ) + overlay()->setText( tr( "Press Generate to get started!" ) ); + else + overlay()->setText( tr( "Add some filters above, and press Generate to get started!" ) ); + if( !overlay()->shown() ) + overlay()->show(); + } + else { + overlay()->hide(); + } +} + +void +DynamicView::checkForOverflow() +{ + if( !m_onDemand || proxyModel()->rowCount( QModelIndex() ) == 0 ) + return; + + if( m_fadeOutAnim.state() == QTimeLine::Running ) + m_checkOnCollapse = true; + + /// We don't want stations to grow forever, because we don't want the view to have to scroll + /// So if there are too many tracks, we remove some that have already been played + /// Our threshold is 4 rows to the end. That's when we collapse. + QModelIndex last = proxyModel()->index( proxyModel()->rowCount( QModelIndex() ) - 1, 0, QModelIndex() ); + QRect lastRect = visualRect( last ); + qDebug() << "Checking viewport height of" << viewport()->height() << "and last track bottom:" << lastRect.bottomLeft().y() << "under threshold" << 4 * lastRect.height(); + if( viewport()->height() - lastRect.bottomLeft().y() <= ( 4 * lastRect.height() ) ) { + qDebug() << "Deciding to remove some tracks from this station"; + + // figure out how many to remove. lets get rid of 1/3rd of the backlog, visually. + int toRemove = ( viewport()->height() / 3 ) / lastRect.height(); + qDebug() << "Decided to remove" << toRemove << "rows!"; + collapseEntries( 0, toRemove, proxyModel()->rowCount( QModelIndex() ) - toRemove ); + } +} + +void +DynamicView::collapseEntries( int startRow, int num, int numToKeep ) +{ + qDebug() << "BEGINNING TO COLLAPSE FROM" << startRow << num << numToKeep; + if( m_fadeOutAnim.state() == QTimeLine::Running ) { + qDebug() << "COLLAPSING TWICE, aborting!"; + return; + } + + /// Two options: Either we are overflowing our view, or we're not. If we are, it's because the search for a playable track + /// went past the limit of the view. Just fade out from the beginning to the end in that case. otherwise, animate a slide + int realNum = num; + QModelIndex last = indexAt( QPoint( 3, viewport()->height() - 3 ) ); + if( last.isValid() && last.row() < startRow + num ) { + m_fadeOnly = true; + realNum = last.row() - startRow; + } else { + m_fadeOnly = false; + } + + // we capture the image of the rows we're going to collapse + // then we capture the image of the target row we're going to animate downwards + // then we fade the first image out while sliding the second image up. + QModelIndex topLeft = proxyModel()->index( startRow, 0, QModelIndex() ); + QModelIndex bottomRight = proxyModel()->index( startRow + realNum - 1, proxyModel()->columnCount( QModelIndex() ) - 1, QModelIndex() ); + QItemSelection sel( topLeft, bottomRight ); + qDebug() << "Created selection from:" << startRow << "to" << startRow + realNum - 1; + QRect fadingRect = visualRegionForSelection( sel ).boundingRect(); + QRect fadingRectViewport = fadingRect; // all values that we use in paintEvent() have to be in viewport coords + fadingRect.moveTo( viewport()->mapTo( this, fadingRect.topLeft() ) ); + //fadingRect.setBottom( qMin( fadingRect.bottom(), viewport()->mapTo( this, viewport()->rect().bottomLeft() ).y() ) ); // limit what we capture to the viewport rect, if the last item is partially obscured + + m_fadingIndexes = QPixmap::grabWidget( this, fadingRect ); // but all values we use to grab the widgetr have to be in scrollarea coords :( + m_fadingPointAnchor = QPoint( 0, fadingRectViewport.topLeft().y() ); + + // get the background + m_bg = backgroundBetween( m_fadingIndexes.rect(), startRow ); + + m_fadeOutAnim.start(); + + qDebug() << "Grabbed fading indexes from rect:" << fadingRect << m_fadingIndexes.size() << "ANCHORED:" << m_fadingPointAnchor; + + if( !m_fadeOnly ) { + /// sanity checks. make sure we have all the rows we need + int firstSlider = startRow + realNum; + qDebug() << "Sliding from" << firstSlider << "number:" << numToKeep - 1 << "rowcount is:" << proxyModel()->rowCount(); + // we may have removed some rows since we first started counting, so adjust + //Q_ASSERT( firstSlider + numToKeep - 1 <= proxyModel()->rowCount() ); + if( firstSlider + numToKeep - 1 >= proxyModel()->rowCount() ) { + if( numToKeep == 1 ) { // we just want the last row + firstSlider = proxyModel()->rowCount(); + } + } + + topLeft = proxyModel()->index( startRow + realNum, 0, QModelIndex() ); + bottomRight = proxyModel()->index( startRow + realNum + numToKeep - 1, proxyModel()->columnCount( QModelIndex() ) - 1, QModelIndex() ); + QRect slidingRect = visualRegionForSelection( QItemSelection( topLeft, bottomRight ) ).boundingRect(); + QRect slidingRectViewport = slidingRect; + // map internal view coord to external qscrollarea + slidingRect.moveTo( viewport()->mapTo( this, slidingRect.topLeft() ) ); + + m_slidingIndex = QPixmap::grabWidget( this, slidingRect ); + m_bottomAnchor = QPoint( 0, slidingRectViewport.topLeft().y() ); + m_bottomOfAnim = QPoint( 0, slidingRectViewport.bottomLeft().y() ); + qDebug() << "Grabbed sliding index from rect:" << slidingRect << m_slidingIndex.size(); + + // slide from the current position to the new one + int frameRange = fadingRect.topLeft().y() - slidingRect.topLeft().y(); + m_slideAnim.setDuration( SLIDE_LENGTH + frameRange * LONG_MULT ); + m_slideAnim.setFrameRange( slidingRectViewport.topLeft().y(), fadingRectViewport.topLeft().y() ); + + QTimer::singleShot( SLIDE_OFFSET, &m_slideAnim, SLOT( start() ) ); + } + + // delete the actual indices + QModelIndexList todel; + for( int i = 0; i < num; i++ ) { + for( int k = 0; k < proxyModel()->columnCount( QModelIndex() ); k++ ) { + todel << proxyModel()->index( startRow + i, k ); + } + } + proxyModel()->removeIndexes( todel ); +} + +QPixmap +DynamicView::backgroundBetween( QRect rect, int rowStart ) +{ + QPixmap bg = QPixmap( rect.size() ); + bg.fill( Qt::white ); + QPainter p( &bg ); + QStyleOptionViewItemV4 opt = viewOptions(); + // code taken from QTreeViewPrivate::paintAlternatingRowColors + m_fadebg = !style()->styleHint( QStyle::SH_ItemView_PaintAlternatingRowColorsForEmptyArea, &opt ); + // qDebug() << "PAINTING ALTERNATING ROW BG!: " << fadingRectViewport; + int rowHeight = itemDelegate()->sizeHint( opt, QModelIndex() ).height() + 1; + int y = 0; + int current = rowStart; + while( y <= rect.bottomLeft().y() ) { + opt.rect.setRect(0, y, viewport()->width(), rowHeight); + // qDebug() << "PAINTING BG ROW IN RECT" << y << "to" << y + rowHeight << ":" << opt.rect; + if( current & 1 ) { + opt.features |= QStyleOptionViewItemV2::Alternate; + } else { + opt.features &= ~QStyleOptionViewItemV2::Alternate; + } + ++current; + style()->drawPrimitive( QStyle::PE_PanelItemViewRow, &opt, &p ); + y += rowHeight; + } + + return bg; +} + +void +DynamicView::animFinished() +{ + if( m_checkOnCollapse ) + checkForOverflow(); + m_checkOnCollapse = false; +} + +void +DynamicView::paintEvent( QPaintEvent* event ) +{ + TrackView::paintEvent(event); + + QPainter p( viewport() ); + if( m_fadeOutAnim.state() == QTimeLine::Running ) { // both run together + p.save(); + QRect bg = m_fadingIndexes.rect(); + bg.moveTo( m_fadingPointAnchor ); // cover up the background + p.fillRect( bg, Qt::white ); + if( m_fadebg ) { + p.save(); + p.setOpacity( 1 - m_fadeOutAnim.currentValue() ); + } + p.drawPixmap( bg, m_bg ); + if( m_fadebg ) { + p.restore(); + } + // qDebug() << "FAST SETOPACITY:" << p.paintEngine()->hasFeature(QPaintEngine::ConstantOpacity); + p.setOpacity( 1 - m_fadeOutAnim.currentValue() ); + p.drawPixmap( m_fadingPointAnchor, m_fadingIndexes ); + p.restore(); + + if( m_slideAnim.state() == QTimeLine::Running ) { + // draw the collapsing entry + p.drawPixmap( 0, m_slideAnim.currentFrame(), m_slidingIndex ); + } else if( m_fadeOutAnim.state() == QTimeLine::Running && !m_fadeOnly ) { + p.drawPixmap( 0, m_bottomAnchor.y(), m_slidingIndex ); + } + } +} + diff --git a/src/libtomahawk/playlist/dynamic/DynamicView.h b/src/libtomahawk/playlist/dynamic/DynamicView.h new file mode 100644 index 000000000..3ea371d3a --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/DynamicView.h @@ -0,0 +1,95 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DYNAMIC_VIEW_H +#define DYNAMIC_VIEW_H + +#include "playlist/playlistview.h" +#include +#include +#include +#include + +class PlaylistModel; +class TrackModel; +namespace Tomahawk +{ + +class DynamicModel; + + +class DynamicView : public PlaylistView +{ + Q_OBJECT +public: + explicit DynamicView( QWidget* parent = 0 ); + virtual ~DynamicView(); + + virtual void setModel( DynamicModel* model ); + + void setOnDemand( bool onDemand ); + void setReadOnly( bool readOnly ); + + void setDynamicWorking( bool working ); + + virtual void paintEvent( QPaintEvent* event ); + +public slots: + void showMessageTimeout( const QString& title, const QString& body ); + void showMessage( const QString& message ); + + // collapse and animate the transition + // there MUST be a row *after* startRow + num. that is, you can't collapse + // entries unless there is at least one entry after the last collapsed row + // optionally you can specify how many rows are past the block of collapsed rows + void collapseEntries( int startRow, int num, int numToKeep = 1 ); + +private slots: + void onTrackCountChanged( unsigned int ); + void checkForOverflow(); + void animFinished(); + +private: + QPixmap backgroundBetween( QRect rect, int rowStart ); + + DynamicModel* m_model; + QString m_title; + QString m_body; + + bool m_onDemand; + bool m_readOnly; + bool m_checkOnCollapse; + bool m_working; + + // for collapsing animation + QPoint m_fadingPointAnchor; + QPoint m_bottomAnchor; + QPoint m_bottomOfAnim; + QPixmap m_fadingIndexes; + QPixmap m_slidingIndex; + QPixmap m_bg; + bool m_fadebg; + bool m_fadeOnly; + QTimeLine m_fadeOutAnim; + QTimeLine m_slideAnim; +}; + +}; + + +#endif diff --git a/src/libtomahawk/playlist/dynamic/GeneratorFactory.cpp b/src/libtomahawk/playlist/dynamic/GeneratorFactory.cpp new file mode 100644 index 000000000..dd4e44a96 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/GeneratorFactory.cpp @@ -0,0 +1,67 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "dynamic/GeneratorFactory.h" +#include "dynamic/GeneratorInterface.h" + +using namespace Tomahawk; + +QHash< QString, GeneratorFactoryInterface* > GeneratorFactory::s_factories = QHash< QString, GeneratorFactoryInterface* >(); + +geninterface_ptr +GeneratorFactory::create ( const QString& type ) +{ + if( type.isEmpty() && !s_factories.isEmpty() ) // default, return first + return geninterface_ptr( s_factories.begin().value()->create() ); + + if( !s_factories.contains( type ) ) + return geninterface_ptr(); + + return geninterface_ptr( s_factories.value( type )->create() ); +} + +dyncontrol_ptr +GeneratorFactory::createControl(const QString& generatorType, const QString& controlType) +{ + if( generatorType.isEmpty() || !s_factories.contains( generatorType ) ) + return dyncontrol_ptr(); + + return s_factories.value( generatorType )->createControl( controlType ); +} + + +void +GeneratorFactory::registerFactory ( const QString& type, GeneratorFactoryInterface* interface ) +{ + s_factories.insert( type, interface ); +} + +QStringList +GeneratorFactory::types() +{ + return s_factories.keys(); +} + +QStringList +GeneratorFactory::typeSelectors(const QString& type) +{ + if( !s_factories.contains( type ) ) + return QStringList(); + + return s_factories.value( type )->typeSelectors(); +} diff --git a/src/libtomahawk/playlist/dynamic/GeneratorFactory.h b/src/libtomahawk/playlist/dynamic/GeneratorFactory.h new file mode 100644 index 000000000..5687dbb91 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/GeneratorFactory.h @@ -0,0 +1,72 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef GENERATOR_FACTORY_H +#define GENERATOR_FACTORY_H + +#include +#include + +#include "playlist/dynamic/GeneratorInterface.h" +#include "typedefs.h" + +#include "dllmacro.h" + +namespace Tomahawk { + +/** + * Generators should subclass this and have it create the custom Generator + */ +class DLLEXPORT GeneratorFactoryInterface +{ +public: + GeneratorFactoryInterface() {} + + virtual GeneratorInterface* create() = 0; + /** + * Create a control for this generator, not tied to this generator itself. Used when loading dynamic + * playlists from a dbcmd. + */ + virtual dyncontrol_ptr createControl( const QString& controlType = QString() ) = 0; + + virtual QStringList typeSelectors() const = 0; +}; + +/** + * Simple factory that generates Generators from string type descriptors + */ +class DLLEXPORT GeneratorFactory +{ +public: + static geninterface_ptr create( const QString& type ); + // only used when loading from dbcmd + static dyncontrol_ptr createControl( const QString& generatorType, const QString& controlType = QString() ); + + static void registerFactory( const QString& type, GeneratorFactoryInterface* interface ); + static QStringList types(); + static QStringList typeSelectors( const QString& type ); + +private: + static QHash s_factories; + +}; + + +}; + +#endif diff --git a/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp b/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp new file mode 100644 index 000000000..999d8aa45 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp @@ -0,0 +1,76 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "dynamic/GeneratorInterface.h" + +Tomahawk::GeneratorInterface::GeneratorInterface( QObject* parent ): QObject( parent ) +{ + +} + +Tomahawk::GeneratorInterface::~GeneratorInterface() +{ + +} + +QList< Tomahawk::dyncontrol_ptr > +Tomahawk::GeneratorInterface::controls() +{ +// if( m_controls.isEmpty() ) { // return a default control (so the user can add more) +// return QList< Tomahawk::dyncontrol_ptr >() << createControl(); +// } + + return m_controls; +} + +QPixmap +Tomahawk::GeneratorInterface::logo() +{ + return QPixmap(); +} + +void +Tomahawk::GeneratorInterface::addControl( const Tomahawk::dyncontrol_ptr& control ) +{ + m_controls << control; +} + +void +Tomahawk::GeneratorInterface::clearControls() +{ + m_controls.clear(); +} + +void +Tomahawk::GeneratorInterface::setControls( const QList< Tomahawk::dyncontrol_ptr >& controls ) +{ + m_controls = controls; +} + +void Tomahawk::GeneratorInterface::removeControl(const Tomahawk::dyncontrol_ptr& control) +{ + m_controls.removeAll( control ); +} + + +Tomahawk::dyncontrol_ptr +Tomahawk::GeneratorInterface::createControl(const QString& type) +{ + Q_ASSERT( false ); + return dyncontrol_ptr(); +} diff --git a/src/libtomahawk/playlist/dynamic/GeneratorInterface.h b/src/libtomahawk/playlist/dynamic/GeneratorInterface.h new file mode 100644 index 000000000..b08634104 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/GeneratorInterface.h @@ -0,0 +1,135 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef GENERATOR_INTERFACE_H +#define GENERATOR_INTERFACE_H + +#include +#include +#include + +#include "typedefs.h" +#include "query.h" +#include "playlist/dynamic/DynamicControl.h" + +#include "dllmacro.h" + +namespace Tomahawk { + +/** + * The abstract interface for Dynamic Playlist Generators. Generators have the following features: + * - They create new DynamicControls that are appropriate for the generator + * - They expose a list of controls that this generator currently is operating on + * - They have a mode of OnDemand or Static + * + * And they generate tracks in two ways: + * - Statically (ask for X tracks, get X tracks) + * - On Demand (as for next track, ask for next track again, etc) + */ +class DLLEXPORT GeneratorInterface : public QObject +{ + Q_OBJECT + Q_PROPERTY( QString type READ type ) + /// oh qjson. + Q_PROPERTY( int mode READ mode WRITE setMode ) + +public: + // can't inline constructors/destructors for forward declared shared pointer types + explicit GeneratorInterface( QObject* parent = 0 ); + virtual ~GeneratorInterface(); + + // Can't make it pure otherwise we can't shove it in QVariants :-/ + // empty QString means use default + /// The generator will keep track of all the controls it creates. No need to tell it about controls + /// you ask it to create + virtual dyncontrol_ptr createControl( const QString& type = QString() ); + + /// A logo to display for this generator, if it has one + virtual QPixmap logo(); + + /** + * Generate tracks from the controls in this playlist. If this generator is in static + * mode, then it will return the desired number of tracks. If the generator is in OnDemand + * mode, this will do nothing. + * + * Connect to the generated() signal for the results. + * + */ + virtual void generate( int number = -1 ) {} + + /** + * Starts an on demand session for this generator. Listen to the nextTrack() signal to get + * the first generated track + */ + virtual void startOnDemand() {} + + /** + * Get the next on demand track. + * \param rating Rating from 1-5, -1 for none + */ + virtual void fetchNext( int rating = -1 ) {} + + /** + * Return a sentence that describes this generator's controls. TODO english only ATM + */ + virtual QString sentenceSummary() { return QString(); } + + /** + * If an OnDemand playlist can be steered, this returns true. + * If so, the generator should also provide a steering widget + * in steeringWidget() + */ + virtual bool onDemandSteerable() const { return false; } + + /** + * Returns a widget used to steer the OnDemand dynamic playlist. + * If this generator doesn't support this (and returns false for + * \c onDemandSteerable) this will be null. The generator is responsible + * for reacting to changes in the widget. + */ + virtual QWidget* steeringWidget() { return 0; } + + /// The type of this generator + QString type() const { return m_type; } + + int mode() const { return (int)m_mode; } + void setMode( int mode ) { m_mode = (GeneratorMode)mode; } + + // control functions + QList< dyncontrol_ptr > controls(); + void addControl( const dyncontrol_ptr& control ); + void clearControls(); + void setControls( const QList< dyncontrol_ptr>& controls ); + void removeControl( const dyncontrol_ptr& control ); + +signals: + void error( const QString& title, const QString& body); + void generated( const QList< Tomahawk::query_ptr>& queries ); + void nextTrackGenerated( const Tomahawk::query_ptr& track ); + +protected: + QString m_type; + GeneratorMode m_mode; + QList< dyncontrol_ptr > m_controls; +}; + +typedef QSharedPointer geninterface_ptr; + +}; + +#endif diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp new file mode 100644 index 000000000..7a4c3fb47 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp @@ -0,0 +1,565 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "dynamic/echonest/EchonestControl.h" + +#include "dynamic/widgets/MiscControlWidgets.h" + +#include + +#include +#include +#include + + +Tomahawk::EchonestControl::EchonestControl( const QString& selectedType, const QStringList& typeSelectors, QObject* parent ) + : DynamicControl ( selectedType.isEmpty() ? "Artist" : selectedType, typeSelectors, parent ) +{ + setType( "echonest" ); + m_editingTimer.setInterval( 500 ); //timeout to edits + m_editingTimer.setSingleShot( true ); + + connect( &m_editingTimer, SIGNAL( timeout() ), this, SLOT( editTimerFired() ) ); + updateWidgets(); +} + +QWidget* +Tomahawk::EchonestControl::inputField() +{ + return m_input.data(); +} + +QWidget* +Tomahawk::EchonestControl::matchSelector() +{ + return m_match.data(); +} + +void +Tomahawk::EchonestControl::setSelectedType ( const QString& type ) +{ + if( type != selectedType() ) { + if( !m_input.isNull() ) + delete m_input.data(); + if( !m_match.isNull() ) + delete m_match.data(); + + Tomahawk::DynamicControl::setSelectedType ( type ); + updateWidgets(); + updateData(); + qDebug() << "Setting new type, set data to:" << m_data.first << m_data.second; + } +} + +Echonest::DynamicPlaylist::PlaylistParamData +Tomahawk::EchonestControl::toENParam() const +{ + if( m_overrideType != -1 ) { + Echonest::DynamicPlaylist::PlaylistParamData newData = m_data; + newData.first = static_cast( m_overrideType ); + return newData; + } + return m_data; +} + +QString +Tomahawk::EchonestControl::input() const +{ + return m_data.second.toString(); +} + +QString +Tomahawk::EchonestControl::match() const +{ + return m_matchData; +} + +QString +Tomahawk::EchonestControl::matchString() const +{ + return m_matchString; +} + +QString +Tomahawk::EchonestControl::summary() const +{ + if( m_summary.isEmpty() ) + const_cast< EchonestControl* >( this )->calculateSummary(); + + return m_summary; +} + +void +Tomahawk::EchonestControl::setInput(const QString& input) +{ + // TODO generate widgets + m_data.second = input; + updateWidgetsFromData(); +} + +void +Tomahawk::EchonestControl::setMatch(const QString& match) +{ + // TODO generate widgets + m_matchData = match; + updateWidgetsFromData(); +} + +void +Tomahawk::EchonestControl::updateWidgets() +{ + if( !m_input.isNull() ) + delete m_input.data(); + if( !m_match.isNull() ) + delete m_match.data(); + m_overrideType = -1; + + // make sure the widgets are the proper kind for the selected type, and hook up to their slots + if( selectedType() == "Artist" ) { + m_currentType = Echonest::DynamicPlaylist::Artist; + + QComboBox* match = new QComboBox(); + QLineEdit* input = new QLineEdit(); + + match->addItem( "Limit To", Echonest::DynamicPlaylist::ArtistType ); + match->addItem( "Similar To", Echonest::DynamicPlaylist::ArtistRadioType ); + m_matchString = match->currentText(); + m_matchData = match->itemData( match->currentIndex() ).toString(); + + input->setPlaceholderText( "Artist name" ); + input->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Fixed ); + + connect( match, SIGNAL( currentIndexChanged(int) ), this, SLOT( updateData() ) ); + connect( match, SIGNAL( currentIndexChanged(int) ), this, SIGNAL( changed() ) ); + connect( input, SIGNAL( textChanged(QString) ), this, SLOT( updateData() ) ); + connect( input, SIGNAL( editingFinished() ), this, SLOT( editingFinished() ) ); + connect( input, SIGNAL( textEdited( QString ) ), &m_editingTimer, SLOT( stop() ) ); + + match->hide(); + input->hide(); + m_match = QWeakPointer< QWidget >( match ); + m_input = QWeakPointer< QWidget >( input ); + } else if( selectedType() == "Artist Description" ) { + m_currentType = Echonest::DynamicPlaylist::Description; + + QLabel* match = new QLabel( tr( "is" ) ); + QLineEdit* input = new QLineEdit(); + + m_matchString = QString(); + m_matchData = QString::number( (int)Echonest::DynamicPlaylist::ArtistDescriptionType ); + + connect( input, SIGNAL( textChanged(QString) ), this, SLOT( updateData() ) ); + connect( input, SIGNAL( editingFinished() ), this, SLOT( editingFinished() ) ); + connect( input, SIGNAL( textEdited( QString ) ), &m_editingTimer, SLOT( stop() ) ); + + match->hide(); + input->hide(); + m_match = QWeakPointer< QWidget >( match ); + m_input = QWeakPointer< QWidget >( input ); + } else if( selectedType() == "Variety" ) { + m_currentType = Echonest::DynamicPlaylist::Variety; + + QLabel* match = new QLabel( tr( "is" ) ); + LabeledSlider* input = new LabeledSlider( tr( "Less" ), tr( "More" ) ); + input->slider()->setRange( 0, 10000 ); + input->slider()->setTickInterval( 1 ); + input->slider()->setTracking( false ); + + m_matchString = match->text(); + m_matchData = match->text(); + + + connect( input->slider(), SIGNAL( valueChanged( int ) ), this, SLOT( updateData() ) ); + connect( input->slider(), SIGNAL( valueChanged( int ) ), this, SLOT( editingFinished() ) ); + + match->hide(); + input->hide(); + m_match = QWeakPointer< QWidget >( match ); + m_input = QWeakPointer< QWidget >( input ); + } else if( selectedType() == "Tempo" ) { + m_currentType = Echonest::DynamicPlaylist::MinTempo; + + setupMinMaxWidgets( Echonest::DynamicPlaylist::MinTempo, Echonest::DynamicPlaylist::MaxTempo, tr( "0 BPM" ), tr( "500 BPM" ), 500 ); + } else if( selectedType() == "Duration" ) { + m_currentType = Echonest::DynamicPlaylist::MinDuration; + + setupMinMaxWidgets( Echonest::DynamicPlaylist::MinDuration, Echonest::DynamicPlaylist::MaxDuration, tr( "0 secs" ), tr( "3600 secs" ), 3600 ); + } else if( selectedType() == "Loudness" ) { + m_currentType = Echonest::DynamicPlaylist::MinLoudness; + + setupMinMaxWidgets( Echonest::DynamicPlaylist::MinLoudness, Echonest::DynamicPlaylist::MaxLoudness, tr( "-100 dB" ), tr( "100 dB" ), 100 ); + qobject_cast< LabeledSlider* >( m_input.data() )->slider()->setMinimum( -100 ); + } else if( selectedType() == "Danceability" ) { + m_currentType = Echonest::DynamicPlaylist::MinDanceability; + + setupMinMaxWidgets( Echonest::DynamicPlaylist::MinDanceability, Echonest::DynamicPlaylist::MaxDanceability, tr( "Less" ), tr( "More" ), 10000 ); + } else if( selectedType() == "Energy" ) { + m_currentType = Echonest::DynamicPlaylist::MinEnergy; + + setupMinMaxWidgets( Echonest::DynamicPlaylist::MinEnergy, Echonest::DynamicPlaylist::MaxEnergy, tr( "Less" ), tr( "More" ), 10000 ); + } else if( selectedType() == "Artist Familiarity" ) { + m_currentType = Echonest::DynamicPlaylist::ArtistMinFamiliarity; + + setupMinMaxWidgets( Echonest::DynamicPlaylist::ArtistMinFamiliarity, Echonest::DynamicPlaylist::ArtistMaxFamiliarity, tr( "Less" ), tr( "More" ), 10000 ); + } else if( selectedType() == "Artist Hotttnesss" ) { + m_currentType = Echonest::DynamicPlaylist::ArtistMinHotttnesss; + + setupMinMaxWidgets( Echonest::DynamicPlaylist::ArtistMinHotttnesss, Echonest::DynamicPlaylist::ArtistMaxHotttnesss, tr( "Less" ), tr( "More" ), 10000 ); + } else if( selectedType() == "Song Hotttnesss" ) { + m_currentType = Echonest::DynamicPlaylist::SongMinHotttnesss; + + setupMinMaxWidgets( Echonest::DynamicPlaylist::SongMinHotttnesss, Echonest::DynamicPlaylist::SongMaxHotttnesss, tr( "Less" ), tr( "More" ), 10000 ); + } else if( selectedType() == "Latitude" ) { + m_currentType = Echonest::DynamicPlaylist::ArtistMinLatitude; + QString deg = QString( QChar( 0x00B0 ) ); + setupMinMaxWidgets( Echonest::DynamicPlaylist::ArtistMinLatitude, Echonest::DynamicPlaylist::ArtistMaxLatitude, QString( "-180%1" ).arg( deg ), QString( "180%1" ).arg( deg ), 180 ); + qobject_cast< LabeledSlider* >( m_input.data() )->slider()->setMinimum( -180 ); + } else if( selectedType() == "Longitude" ) { + m_currentType = Echonest::DynamicPlaylist::ArtistMinLongitude; + QString deg = QString( QChar( 0x00B0 ) ); + setupMinMaxWidgets( Echonest::DynamicPlaylist::ArtistMinLongitude, Echonest::DynamicPlaylist::ArtistMaxLongitude, QString( "-180%1" ).arg( deg ), QString( "180%1" ).arg( deg ), 180 ); + qobject_cast< LabeledSlider* >( m_input.data() )->slider()->setMinimum( -180 ); + } else if( selectedType() == "Mode" ) { + m_currentType = Echonest::DynamicPlaylist::Mode; + + QLabel* match = new QLabel( tr( "is" ) ); + QComboBox* combo = new QComboBox; + combo->addItem( tr( "Major" ), QString::number( 1 ) ); + combo->addItem( tr( "Minor" ), QString::number( 0 ) ); + + m_matchString = match->text(); + m_matchData = match->text(); + + + connect( combo, SIGNAL( activated( int ) ), this, SLOT( updateData() ) ); + connect( combo, SIGNAL( activated( int ) ), this, SLOT( editingFinished() ) ); + + match->hide(); + combo->hide(); + m_match = QWeakPointer< QWidget >( match ); + m_input = QWeakPointer< QWidget >( combo ); + } else if( selectedType() == "Key" ) { + m_currentType = Echonest::DynamicPlaylist::Key; + + QLabel* match = new QLabel( tr( "is" ) ); + QComboBox* combo = new QComboBox; + combo->addItem( tr( "C" ), QString::number( 0 ) ); + combo->addItem( tr( "C Sharp" ), QString::number( 1 ) ); + combo->addItem( tr( "D" ), QString::number( 2 ) ); + combo->addItem( tr( "E Flat" ), QString::number( 3 ) ); + combo->addItem( tr( "E" ), QString::number( 4 ) ); + combo->addItem( tr( "F" ), QString::number( 5 ) ); + combo->addItem( tr( "F Sharp" ), QString::number( 6 ) ); + combo->addItem( tr( "G" ), QString::number( 7 ) ); + combo->addItem( tr( "A Flat" ), QString::number( 8 ) ); + combo->addItem( tr( "A" ), QString::number( 9 ) ); + combo->addItem( tr( "B Flat" ), QString::number( 10 ) ); + combo->addItem( tr( "B" ), QString::number( 11 ) ); + + m_matchString = match->text(); + m_matchData = match->text(); + + + connect( combo, SIGNAL( activated( int ) ), this, SLOT( updateData() ) ); + connect( combo, SIGNAL( activated( int ) ), this, SLOT( editingFinished() ) ); + + match->hide(); + combo->hide(); + m_match = QWeakPointer< QWidget >( match ); + m_input = QWeakPointer< QWidget >( combo ); + } else if( selectedType() == "Sorting" ) { + m_currentType = Echonest::DynamicPlaylist::Key; + + QComboBox* match = new QComboBox(); + match->addItem( tr( "Ascending" ), 0 ); + match->addItem( tr( "Descending" ), 1 ); + + QComboBox* combo = new QComboBox; + combo->addItem( tr( "Tempo" ), QString::number( Echonest::DynamicPlaylist::SortTempoAscending ) ); + combo->addItem( tr( "Duration" ), QString::number( Echonest::DynamicPlaylist::SortDurationAscending ) ); + combo->addItem( tr( "Loudness" ), QString::number( Echonest::DynamicPlaylist::SortLoudnessAscending ) ); + combo->addItem( tr( "Artist Familiarity" ), QString::number( Echonest::DynamicPlaylist::SortArtistFamiliarityAscending ) ); + combo->addItem( tr( "Artist Hotttnesss" ), QString::number( Echonest::DynamicPlaylist::SortArtistHotttnessAscending ) ); + combo->addItem( tr( "Song Hotttnesss" ), QString::number( Echonest::DynamicPlaylist::SortSongHotttnesssAscending ) ); + combo->addItem( tr( "Latitude" ), QString::number( Echonest::DynamicPlaylist::SortLatitudeAscending ) ); + combo->addItem( tr( "Longitude" ), QString::number( Echonest::DynamicPlaylist::SortLongitudeAscending ) ); + combo->addItem( tr( "Mode" ), QString::number( Echonest::DynamicPlaylist::SortModeAscending ) ); + combo->addItem( tr( "Key" ), QString::number( Echonest::DynamicPlaylist::SortKeyAscending ) ); + combo->addItem( tr( "Energy" ), QString::number( Echonest::DynamicPlaylist::SortEnergyAscending ) ); + combo->addItem( tr( "Danceability" ), QString::number( Echonest::DynamicPlaylist::SortDanceabilityAscending ) ); + + m_matchString = "Ascending"; // default + m_matchData = Echonest::DynamicPlaylist::SortTempoAscending; + + connect( match, SIGNAL( activated( int ) ), this, SLOT( updateData() ) ); + connect( match, SIGNAL( activated( int ) ), this, SLOT( editingFinished() ) ); + connect( combo, SIGNAL( activated( int ) ), this, SLOT( updateData() ) ); + connect( combo, SIGNAL( activated( int ) ), this, SLOT( editingFinished() ) ); + + match->hide(); + combo->hide(); + m_match = QWeakPointer< QWidget >( match ); + m_input = QWeakPointer< QWidget >( combo ); + } else { + m_match = QWeakPointer( new QWidget ); + m_input = QWeakPointer( new QWidget ); + } + updateData(); + calculateSummary(); +} + +void +Tomahawk::EchonestControl::setupMinMaxWidgets( Echonest::DynamicPlaylist::PlaylistParam min, Echonest::DynamicPlaylist::PlaylistParam max, const QString& leftL, const QString& rightL, int maxRange ) +{ + QComboBox* match = new QComboBox; + match->addItem( "At Least", min ); + match->addItem( "At Most", max ); + + LabeledSlider* input = new LabeledSlider( leftL, rightL ); + input->slider()->setRange( 0, maxRange ); + input->slider()->setTickInterval( 1 ); + input->slider()->setTracking( false ); + + m_matchString = match->currentText(); + m_matchData = match->itemData( match->currentIndex() ).toString(); + + connect( match, SIGNAL( activated( int ) ), this, SLOT( updateData() ) ); + connect( match, SIGNAL( activated( int ) ), this, SLOT( editingFinished() ) ); + connect( input->slider(), SIGNAL( valueChanged( int ) ), this, SLOT( updateData() ) ); + connect( input->slider(), SIGNAL( valueChanged( int ) ), this, SLOT( editingFinished() ) ); + + match->hide(); + input->hide(); + m_match = QWeakPointer< QWidget >( match ); + m_input = QWeakPointer< QWidget >( input ); +} + + +void +Tomahawk::EchonestControl::updateData() +{ + if( selectedType() == "Artist" ) { + QComboBox* combo = qobject_cast( m_match.data() ); + if( combo ) { + m_matchString = combo->currentText(); + m_matchData = combo->itemData( combo->currentIndex() ).toString(); + } + QLineEdit* edit = qobject_cast( m_input.data() ); + if( edit && !edit->text().isEmpty() ) { + m_data.first = m_currentType; + m_data.second = edit->text(); + } + } else if( selectedType() == "Artist Description" ) { + QLineEdit* edit = qobject_cast( m_input.data() ); + if( edit && !edit->text().isEmpty() ) { + m_data.first = m_currentType; + m_data.second = edit->text(); + } + } else if( selectedType() == "Variety" ) { + LabeledSlider* s = qobject_cast( m_input.data() ); + if( s ) { + m_data.first = m_currentType; + m_data.second = (qreal)s->slider()->value() / 10000.0; + } + } else if( selectedType() == "Tempo" || selectedType() == "Duration" || selectedType() == "Loudness" || selectedType() == "Latitude" || selectedType() == "Longitude" ) { + updateFromComboAndSlider(); + } else if( selectedType() == "Danceability" || selectedType() == "Energy" || selectedType() == "Artist Familiarity" || selectedType() == "Artist Hotttnesss" || selectedType() == "Song Hotttnesss" ) { + updateFromComboAndSlider( true ); + } else if( selectedType() == "Mode" || selectedType() == "Key" ) { + updateFromLabelAndCombo(); + } else if( selectedType() == "Sorting" ) { + QComboBox* match = qobject_cast( m_match.data() ); + QComboBox* input = qobject_cast< QComboBox* >( m_input.data() ); + if( match && input ) { + m_matchString = match->currentText(); + m_matchData = match->itemData( match->currentIndex() ).toString(); + + // what a HACK + int enumVal = input->itemData( input->currentIndex() ).toInt() + m_matchData.toInt(); + m_data.first = Echonest::DynamicPlaylist::Sort; + m_data.second = enumVal; +// qDebug() << "SAVING" << input->currentIndex() << "AS" << enumVal << "(" << input->itemData( input->currentIndex() ).toInt() << "+" << m_matchData.toInt() << ")"; + } + } + + calculateSummary(); +} + +void +Tomahawk::EchonestControl::updateFromComboAndSlider( bool smooth ) +{ + QComboBox* combo = qobject_cast( m_match.data() ); + if( combo ) { + m_matchString = combo->currentText(); + m_matchData = combo->itemData( combo->currentIndex() ).toString(); + } + LabeledSlider* ls = qobject_cast( m_input.data() ); + if( ls && ls->slider() ) { + m_data.first = static_cast< Echonest::DynamicPlaylist::PlaylistParam >( combo->itemData( combo->currentIndex() ).toInt() ); + m_data.second = ls->slider()->value() / ( smooth ? 10000. : 1.0 ); + } +} + +void +Tomahawk::EchonestControl::updateFromLabelAndCombo() +{ + QComboBox* s = qobject_cast( m_input.data() ); + if( s ) { + m_data.first = m_currentType; + m_data.second = s->itemData( s->currentIndex() ); + } +} + + +// fills in the current widget with the data from json or dbcmd (m_data.second and m_matchData) +void +Tomahawk::EchonestControl::updateWidgetsFromData() +{ + if( selectedType() == "Artist" ) { + QComboBox* combo = qobject_cast( m_match.data() ); + if( combo ) + combo->setCurrentIndex( combo->findData( m_matchData ) ); + QLineEdit* edit = qobject_cast( m_input.data() ); + if( edit ) + edit->setText( m_data.second.toString() ); + } else if( selectedType() == "Artist Description" ) { + QLineEdit* edit = qobject_cast( m_input.data() ); + if( edit ) + edit->setText( m_data.second.toString() ); + } else if( selectedType() == "Variety" ) { + LabeledSlider* s = qobject_cast( m_input.data() ); + if( s ) + s->slider()->setValue( m_data.second.toDouble() * 10000 ); + } else if( selectedType() == "Tempo" || selectedType() == "Duration" || selectedType() == "Loudness" || selectedType() == "Latitude" || selectedType() == "Longitude" ) { + updateToComboAndSlider(); + } else if( selectedType() == "Danceability" || selectedType() == "Energy" || selectedType() == "Artist Familiarity" || selectedType() == "Artist Hotttnesss" || selectedType() == "Song Hotttnesss" ) { + updateToComboAndSlider( true ); + } else if( selectedType() == "Mode" || selectedType() == "Key" ) { + updateToLabelAndCombo(); + } else if( selectedType() == "Sorting" ) { + QComboBox* match = qobject_cast( m_match.data() ); + QComboBox* input = qobject_cast< QComboBox* >( m_input.data() ); + if( match && input ) { + match->setCurrentIndex( match->findData( m_matchData )); + + // HACK alert. if it's odd, subtract 1 + int val = ( m_data.second.toInt() - ( m_data.second.toInt() % 2 ) ) / 2; + input->setCurrentIndex( val ); +// qDebug() << "LOADING" << m_data.second.toInt() << "AS" << val; + } + } + calculateSummary(); +} + +void +Tomahawk::EchonestControl::updateToComboAndSlider( bool smooth ) +{ + QComboBox* combo = qobject_cast( m_match.data() ); + if( combo ) + combo->setCurrentIndex( combo->findData( m_matchData ) ); + LabeledSlider* ls = qobject_cast( m_input.data() ); + if( ls ) + ls->slider()->setValue( m_data.second.toDouble() * ( smooth ? 10000. : 1 ) ); +} + +void Tomahawk::EchonestControl::updateToLabelAndCombo() +{ + QComboBox* s = qobject_cast< QComboBox* >( m_input.data() ); + if( s ) { + s->setCurrentIndex( s->findData( m_data.second ) ); + } +} + +void +Tomahawk::EchonestControl::editingFinished() +{ + qDebug() << Q_FUNC_INFO; + m_editingTimer.start(); +} + +void +Tomahawk::EchonestControl::editTimerFired() +{ + // make sure it's really changed + if( m_cacheData != m_data.second ) { // new, so emit changed + emit changed(); + } + + m_cacheData = m_data.second; +} + + +void +Tomahawk::EchonestControl::calculateSummary() +{ + // turns the current control into an english phrase suitable for embedding into a sentence summary + QString summary; + if( selectedType() == "Artist" ) { + // magic char is used by EchonestGenerator to split the prefix from the artist name + if( static_cast< Echonest::DynamicPlaylist::ArtistTypeEnum >( m_matchData.toInt() ) == Echonest::DynamicPlaylist::ArtistType ) + summary = QString( "only by ~%1" ).arg( m_data.second.toString() ); + else if( static_cast< Echonest::DynamicPlaylist::ArtistTypeEnum >( m_matchData.toInt() ) == Echonest::DynamicPlaylist::ArtistRadioType ) + summary = QString( "similar to ~%1" ).arg( m_data.second.toString() ); + } else if( selectedType() == "Artist Description" ) { + summary = QString( "with genre ~%1" ).arg( m_data.second.toString() ); + } else if( selectedType() == "Variety" || selectedType() == "Danceability" || selectedType() == "Artist Hotttnesss" || selectedType() == "Energy" || selectedType() == "Artist Familiarity" || selectedType() == "Song Hotttnesss" ) { + QString modifier; + qreal sliderVal = m_data.second.toReal(); + // divide into avpproximate chunks + if( 0.0 <= sliderVal && sliderVal < 0.2 ) + modifier = "very low"; + else if( 0.2 <= sliderVal && sliderVal < 0.4 ) + modifier = "low"; + else if( 0.4 <= sliderVal && sliderVal < 0.6 ) + modifier = "moderate"; + else if( 0.6 <= sliderVal && sliderVal < 0.8 ) + modifier = "high"; + else if( 0.8 <= sliderVal && sliderVal <= 1 ) + modifier = "very high"; + summary = QString( "with %1 %2" ).arg( modifier ).arg( selectedType().toLower() ); + } else if( selectedType() == "Tempo" ) { + summary = QString( "about %1 BPM" ).arg( m_data.second.toString() ); + } else if( selectedType() == "Duration" ) { + summary = QString( "about %1 minutes long" ).arg( m_data.second.toInt() / 60 ); + } else if( selectedType() == "Loudness" ) { + summary = QString( "about %1 dB" ).arg( m_data.second.toString() ); + } else if( selectedType() == "Latitude" || selectedType() == "Longitude" ) { + summary = QString( "at around %1%2 %3" ).arg( m_data.second.toString() ).arg( QString( QChar( 0x00B0 ) ) ).arg( selectedType().toLower() ); + } else if( selectedType() == "Key" ) { + Q_ASSERT( !m_input.isNull() ); + Q_ASSERT( qobject_cast< QComboBox* >( m_input.data() ) ); + QString keyName = qobject_cast< QComboBox* >( m_input.data() )->currentText().toLower(); + summary = QString( "in %1" ).arg( keyName ); + } else if( selectedType() == "Mode" ) { + Q_ASSERT( !m_input.isNull() ); + Q_ASSERT( qobject_cast< QComboBox* >( m_input.data() ) ); + QString modeName = qobject_cast< QComboBox* >( m_input.data() )->currentText().toLower(); + summary = QString( "in a %1 key" ).arg( modeName ); + } else if( selectedType() == "Sorting" ) { + Q_ASSERT( !m_input.isNull() ); + Q_ASSERT( qobject_cast< QComboBox* >( m_input.data() ) ); + QString sortType = qobject_cast< QComboBox* >( m_input.data() )->currentText().toLower(); + + Q_ASSERT( !m_match.isNull() ); + Q_ASSERT( qobject_cast< QComboBox* >( m_match.data() ) ); + QString ascdesc = qobject_cast< QComboBox* >( m_match.data() )->currentText().toLower(); + + summary = QString( "sorted in %1 %2 order" ).arg( ascdesc ).arg( sortType ); + } + m_summary = summary; +} diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.h b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.h new file mode 100644 index 000000000..405bed0c5 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.h @@ -0,0 +1,92 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef ECHONEST_CONTROL_H +#define ECHONEST_CONTROL_H + +#include + +#include "dynamic/DynamicControl.h" +#include + +namespace Tomahawk +{ + +class EchonestControl : public DynamicControl +{ + Q_OBJECT +public: + virtual QWidget* inputField(); + virtual QWidget* matchSelector(); + + /// Converts this to an echonest suitable parameter + Echonest::DynamicPlaylist::PlaylistParamData toENParam() const; + + virtual QString input() const; + virtual QString match() const; + virtual QString matchString() const; + virtual QString summary() const; + + virtual void setInput(const QString& input); + virtual void setMatch(const QString& match); + + /// DO NOT USE IF YOU ARE NOT A DBCMD + EchonestControl( const QString& type, const QStringList& typeSelectors, QObject* parent = 0 ); + +public slots: + virtual void setSelectedType ( const QString& type ); + +private slots: + void updateData(); + void editingFinished(); + void editTimerFired(); + +private: + void updateWidgets(); + void updateWidgetsFromData(); + + // utility + void setupMinMaxWidgets( Echonest::DynamicPlaylist::PlaylistParam min, Echonest::DynamicPlaylist::PlaylistParam max, const QString& leftL, const QString& rightL, int maxRange ); + void updateFromComboAndSlider( bool smooth = false ); + void updateFromLabelAndCombo(); + + void updateToComboAndSlider( bool smooth = false ); + void updateToLabelAndCombo(); + + void calculateSummary(); + + Echonest::DynamicPlaylist::PlaylistParam m_currentType; + int m_overrideType; + + QWeakPointer< QWidget > m_input; + QWeakPointer< QWidget > m_match; + QString m_matchData; + QString m_matchString; + QString m_summary; + + QTimer m_editingTimer; + + Echonest::DynamicPlaylist::PlaylistParamData m_data; + QVariant m_cacheData; + + friend class EchonestGenerator; +}; + +}; + +#endif diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp new file mode 100644 index 000000000..6f07beb1d --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp @@ -0,0 +1,410 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "dynamic/echonest/EchonestGenerator.h" +#include "dynamic/echonest/EchonestControl.h" +#include "dynamic/echonest/EchonestSteerer.h" +#include "query.h" +#include "utils/tomahawkutils.h" + +using namespace Tomahawk; + +EchonestFactory::EchonestFactory() +{} + +GeneratorInterface* +EchonestFactory::create() +{ + return new EchonestGenerator(); +} + +dyncontrol_ptr +EchonestFactory::createControl( const QString& controlType ) +{ + return dyncontrol_ptr( new EchonestControl( controlType, typeSelectors() ) ); +} + +QStringList +EchonestFactory::typeSelectors() const +{ + return QStringList() << "Artist" << "Artist Description" << "Variety" << "Tempo" << "Duration" << "Loudness" + << "Danceability" << "Energy" << "Artist Familiarity" << "Artist Hotttnesss" << "Song Hotttnesss" + << "Longitude" << "Latitude" << "Mode" << "Key" << "Sorting"; +} + +EchonestGenerator::EchonestGenerator ( QObject* parent ) + : GeneratorInterface ( parent ) + , m_dynPlaylist( new Echonest::DynamicPlaylist() ) + , m_steeredSinceLastTrack( false ) +{ + m_type = "echonest"; + m_mode = OnDemand; + m_logo.load( RESPATH "/images/echonest_logo.png" ); + qDebug() << "ECHONEST:" << m_logo.size(); +} + +EchonestGenerator::~EchonestGenerator() +{ + delete m_dynPlaylist; +} + +dyncontrol_ptr +EchonestGenerator::createControl( const QString& type ) +{ + m_controls << dyncontrol_ptr( new EchonestControl( type, GeneratorFactory::typeSelectors( m_type ) ) ); + return m_controls.last(); +} + +QPixmap EchonestGenerator::logo() +{ + return m_logo; +} + + +void +EchonestGenerator::generate ( int number ) +{ + // convert to an echonest query, and fire it off + qDebug() << Q_FUNC_INFO; + qDebug() << "Generating playlist with " << m_controls.size(); + foreach( const dyncontrol_ptr& ctrl, m_controls ) + qDebug() << ctrl->selectedType() << ctrl->match() << ctrl->input(); + + try { + Echonest::DynamicPlaylist::PlaylistParams params = getParams(); + + params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Results, number ) ); + QNetworkReply* reply = Echonest::DynamicPlaylist::staticPlaylist( params ); + qDebug() << "Generating a static playlist from echonest!" << reply->url().toString(); + connect( reply, SIGNAL( finished() ), this, SLOT( staticFinished() ) ); + } catch( std::runtime_error& e ) { + qWarning() << "Got invalid controls!" << e.what(); + emit error( "Filters are not valid", e.what() ); + } +} + +void +EchonestGenerator::startOnDemand() +{ + try { + Echonest::DynamicPlaylist::PlaylistParams params = getParams(); + + QNetworkReply* reply = m_dynPlaylist->start( params ); + qDebug() << "starting a dynamic playlist from echonest!" << reply->url().toString(); + connect( reply, SIGNAL( finished() ), this, SLOT( dynamicStarted() ) ); + } catch( std::runtime_error& e ) { + qWarning() << "Got invalid controls!" << e.what(); + emit error( "Filters are not valid", e.what() ); + } +} + +void +EchonestGenerator::fetchNext( int rating ) +{ + if( m_dynPlaylist->sessionId().isEmpty() ) { + // we're not currently playing, oops! + qWarning() << Q_FUNC_INFO << "asked to fetch next dynamic song when we're not in the middle of a playlist!"; + return; + } + + QNetworkReply* reply; + if( m_steeredSinceLastTrack ) { + qDebug() << "Steering dynamic playlist!" << m_steerData.first << m_steerData.second; + reply = m_dynPlaylist->fetchNextSong( Echonest::DynamicPlaylist::DynamicControls() << m_steerData ); + m_steeredSinceLastTrack = false; + } else { + reply = m_dynPlaylist->fetchNextSong( rating ); + } + qDebug() << "getting next song from echonest" << reply->url().toString(); + connect( reply, SIGNAL( finished() ), this, SLOT( dynamicFetched() ) ); +} + + +void +EchonestGenerator::staticFinished() +{ + Q_ASSERT( sender() ); + Q_ASSERT( qobject_cast< QNetworkReply* >( sender() ) ); + + QNetworkReply* reply = qobject_cast< QNetworkReply* >( sender() ); + + Echonest::SongList songs; + try { + songs = Echonest::DynamicPlaylist::parseStaticPlaylist( reply ); + } catch( const Echonest::ParseError& e ) { + qWarning() << "libechonest threw an error trying to parse the static playlist! code" << e.errorType() << "error desc:" << e.what(); + + emit error( "The Echo Nest returned an error creating the playlist", e.what() ); + return; + } + + QList< query_ptr > queries; + foreach( const Echonest::Song& song, songs ) { + qDebug() << "EchonestGenerator got song:" << song; + queries << queryFromSong( song ); + } + + emit generated( queries ); +} + +Echonest::DynamicPlaylist::PlaylistParams +EchonestGenerator::getParams() const throw( std::runtime_error ) +{ + Echonest::DynamicPlaylist::PlaylistParams params; + foreach( const dyncontrol_ptr& control, m_controls ) { + params.append( control.dynamicCast()->toENParam() ); + } + appendRadioType( params ); + return params; +} + +void +EchonestGenerator::dynamicStarted() +{ + Q_ASSERT( sender() ); + Q_ASSERT( qobject_cast< QNetworkReply* >( sender() ) ); + QNetworkReply* reply = qobject_cast< QNetworkReply* >( sender() ); + + try + { + Echonest::Song song = m_dynPlaylist->parseStart( reply ); + query_ptr songQuery = queryFromSong( song ); + emit nextTrackGenerated( songQuery ); + } catch( const Echonest::ParseError& e ) { + qWarning() << "libechonest threw an error parsing the start of the dynamic playlist:" << e.errorType() << e.what(); + emit error( "The Echo Nest returned an error starting the station", e.what() ); + } +} + +void +EchonestGenerator::dynamicFetched() +{ + Q_ASSERT( sender() ); + Q_ASSERT( qobject_cast< QNetworkReply* >( sender() ) ); + QNetworkReply* reply = qobject_cast< QNetworkReply* >( sender() ); + + resetSteering(); + + if( !m_steerer.isNull() ) + m_steerer.data()->resetSteering( true ); + + try + { + Echonest::Song song = m_dynPlaylist->parseNextSong( reply ); + query_ptr songQuery = queryFromSong( song ); + emit nextTrackGenerated( songQuery ); + } catch( const Echonest::ParseError& e ) { + qWarning() << "libechonest threw an error parsing the next song of the dynamic playlist:" << e.errorType() << e.what(); + emit error( "The Echo Nest returned an error getting the next song", e.what() ); + } +} + +void +EchonestGenerator::steerDescription( const QString& desc ) +{ + m_steeredSinceLastTrack = true; + m_steerData.first = Echonest::DynamicPlaylist::SteerDescription; + m_steerData.second = desc; +} + +void +EchonestGenerator::steerField( const QString& field ) +{ + m_steeredSinceLastTrack = true; + m_steerData.first = Echonest::DynamicPlaylist::Steer; + m_steerData.second = field; +} + +void +EchonestGenerator::resetSteering() +{ + m_steeredSinceLastTrack = false; + m_steerData.first = Echonest::DynamicPlaylist::Steer; + m_steerData.second = QString(); +} + + +bool +EchonestGenerator::onlyThisArtistType( Echonest::DynamicPlaylist::ArtistTypeEnum type ) const throw( std::runtime_error ) +{ + bool only = true; + bool some = false; + + foreach( const dyncontrol_ptr& control, m_controls ) { + if( ( control->selectedType() == "Artist" || control->selectedType() == "Artist Description" ) && static_cast( control->match().toInt() ) != type ) { + only = false; + } else if( ( control->selectedType() == "Artist" || control->selectedType() == "Artist Description" ) && static_cast( control->match().toInt() ) == type ) { + some = true; + } + } + if( some && only ) { + return true; + } else if( some && !only ) { + throw std::runtime_error( "All artist match types must be the same" ); + } + + return false; +} + +void +EchonestGenerator::appendRadioType( Echonest::DynamicPlaylist::PlaylistParams& params ) const throw( std::runtime_error ) +{ + /** + * So we try to match the best type of echonest playlist, based on the controls + * the types are artist, artist-radio, artist-description, catalog, catalog-radio, song-radio. we don't care about the catalog ones, and + * we can't use the song ones since for the moment EN only accepts Song IDs, not names, and we don't want to insert an extra song.search + * call first. + * + */ + + /// 1. artist: If all the artist controls are Limit-To. If some were but not all, error out. + /// 2. artist-description: If all the artist entries are Description. If some were but not all, error out. + /// 3. artist-radio: If all the artist entries are Similar To. If some were but not all, error out. + if( onlyThisArtistType( Echonest::DynamicPlaylist::ArtistType ) ) + params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::ArtistType ) ); + else if( onlyThisArtistType( Echonest::DynamicPlaylist::ArtistDescriptionType ) ) + params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::ArtistDescriptionType ) ); + else if( onlyThisArtistType( Echonest::DynamicPlaylist::ArtistRadioType ) ) + params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::ArtistRadioType ) ); +} + +query_ptr +EchonestGenerator::queryFromSong(const Echonest::Song& song) +{ + // track[ "album" ] = song.release(); // TODO should we include it? can be quite specific + return Query::get( song.artistName(), song.title(), QString(), uuid() ); +} + +QWidget* +EchonestGenerator::steeringWidget() +{ + if( m_steerer.isNull() ) { + m_steerer = QWeakPointer< EchonestSteerer >( new EchonestSteerer ); + + connect( m_steerer.data(), SIGNAL( steerField( QString ) ), this, SLOT( steerField( QString ) ) ); + connect( m_steerer.data(), SIGNAL( steerDescription( QString ) ), this, SLOT( steerDescription( QString ) ) ); + connect( m_steerer.data(), SIGNAL( reset() ), this, SLOT( resetSteering() ) ); + } + + return m_steerer.data(); +} + + +QString +EchonestGenerator::sentenceSummary() +{ + /** + * The idea is we generate an english sentence from the individual phrases of the controls. We have to follow a few rules, but othewise it's quite straightforward. + * + * Rules: + * - Sentence starts with "Songs " + * - Artists always go first + * - Separate phrases by comma, and before last phrase + * - sorting always at end + * - collapse artists. "Like X, like Y, like Z, ..." -> "Like X, Y, and Z" + * - skip empty artist entries + * + * NOTE / TODO: In order for the sentence to be grammatically correct, we must follow the EN API rules. That means we can't have multiple of some types of filters, + * and all Artist types must be the same. The filters aren't checked at the moment until Generate / Play is pressed. Consider doing a check on hide as well. + */ + QList< dyncontrol_ptr > allcontrols = m_controls; + QString sentence = "Songs "; + + /// 1. Collect all artist filters + /// 2. Get the sorted by filter if it exists. + QList< dyncontrol_ptr > artists; + dyncontrol_ptr sorting; + foreach( const dyncontrol_ptr& control, allcontrols ) { + if( control->selectedType() == "Artist" || control->selectedType() == "Artist Description" ) + artists << control; + else if( control->selectedType() == "Sorting" ) + sorting = control; + } + if( !sorting.isNull() ) + allcontrols.removeAll( sorting ); + + /// Skip empty artists + QList< dyncontrol_ptr > empty; + foreach( const dyncontrol_ptr& artist, artists ) { + QString summary = artist.dynamicCast< EchonestControl >()->summary(); + if( summary.lastIndexOf( "~" ) == summary.length() - 1 ) + empty << artist; + } + foreach( const dyncontrol_ptr& toremove, empty ) { + artists.removeAll( toremove ); + allcontrols.removeAll( toremove ); + } + + /// If there are no artists and no filters, show some help text + if( artists.isEmpty() && allcontrols.isEmpty() ) + sentence = "No configured filters!"; + + /// Do the assembling. Start with the artists if there are any, then do all the rest. + for( int i = 0; i < artists.size(); i++ ) { + dyncontrol_ptr artist = artists.value( i ); + allcontrols.removeAll( artist ); // remove from pool while we're here + + /// Collapse artist lists + QString center, suffix; + QString summary = artist.dynamicCast< EchonestControl >()->summary(); + + if( i == 0 ) { // if it's the first.. special casez + center = summary.remove( "~" ); + if( artists.size() == 2 ) // special case for 2, no comma. ( X and Y ) + suffix = " and "; + else if( artists.size() > 2 ) // in a list with more after + suffix = ", "; + else if( allcontrols.isEmpty() && sorting.isNull() ) // the last one, and no more controls, so put a period + suffix = "."; + else + suffix = " "; + } else { + center = summary.mid( summary.indexOf( "~" ) + 1 ); + if( i == artists.size() - 1 ) { // if there are more, add an " and " + if( !( allcontrols.isEmpty() && sorting.isNull() ) ) + suffix = ", "; + else + suffix = "."; + } else + suffix += ", and "; + } + sentence += center + suffix; + } + /// Add each filter individually + for( int i = 0; i < allcontrols.size(); i++ ) { + /// end case: if this is the last AND there is not a sorting filter (so this is the real last one) + const bool last = ( i == allcontrols.size() - 1 && sorting.isNull() ); + QString prefix, suffix; + if( last ) { // only if there is not just 1 + if( !( artists.isEmpty() && allcontrols.size() == 1 ) ) + prefix = "and "; + suffix = "."; + } else + suffix = ", "; + sentence += prefix + allcontrols.value( i ).dynamicCast< EchonestControl >()->summary() + suffix; + } + qDebug() << "Got artists and contents:" << sentence; + + if( !sorting.isNull() ) { + sentence += "and " + sorting.dynamicCast< EchonestControl >()->summary() + "."; + } + qDebug() << "Got full summary:" << sentence; + + return sentence; +} + diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h new file mode 100644 index 000000000..30fbfba73 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h @@ -0,0 +1,88 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef ECHONEST_GENERATOR_H +#define ECHONEST_GENERATOR_H + +#include +#include + +#include "playlist/dynamic/GeneratorInterface.h" +#include "playlist/dynamic/GeneratorFactory.h" +#include "playlist/dynamic/DynamicControl.h" + +#include "dllmacro.h" + +namespace Tomahawk +{ + +class EchonestSteerer; + +class DLLEXPORT EchonestFactory : public GeneratorFactoryInterface +{ +public: + EchonestFactory(); + + virtual GeneratorInterface* create(); + virtual dyncontrol_ptr createControl( const QString& controlType = QString() ); + virtual QStringList typeSelectors() const; +}; + +class EchonestGenerator : public GeneratorInterface +{ + Q_OBJECT +public: + explicit EchonestGenerator( QObject* parent = 0 ); + virtual ~EchonestGenerator(); + + virtual dyncontrol_ptr createControl( const QString& type = QString() ); + virtual QPixmap logo(); + virtual void generate ( int number = -1 ); + virtual void startOnDemand(); + virtual void fetchNext( int rating = -1 ); + virtual QString sentenceSummary(); + virtual bool onDemandSteerable() const { return true; } + virtual QWidget* steeringWidget(); + +private slots: + void staticFinished(); + void dynamicStarted(); + void dynamicFetched(); + + // steering controls + void steerField( const QString& field ); + void steerDescription( const QString& desc ); + void resetSteering(); + +private: + Echonest::DynamicPlaylist::PlaylistParams getParams() const throw( std::runtime_error ); + query_ptr queryFromSong( const Echonest::Song& song ); + void appendRadioType( Echonest::DynamicPlaylist::PlaylistParams& params ) const throw( std::runtime_error ); + bool onlyThisArtistType( Echonest::DynamicPlaylist::ArtistTypeEnum type ) const throw( std::runtime_error ); + + Echonest::DynamicPlaylist* m_dynPlaylist; + QPixmap m_logo; + + QWeakPointer m_steerer; + bool m_steeredSinceLastTrack; + Echonest::DynamicPlaylist::DynamicControl m_steerData; +}; + +}; + +#endif diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.cpp new file mode 100644 index 000000000..f8f36a34a --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.cpp @@ -0,0 +1,245 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "dynamic/echonest/EchonestSteerer.h" + +#include "utils/tomahawkutils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Tomahawk; + +#define ANIM_DURATION 300 + +EchonestSteerer::EchonestSteerer( QWidget* parent ) + : QWidget( parent ) + , m_layout( new QHBoxLayout ) + , m_amplifier( 0 ) + , m_field( 0 ) + , m_description( 0 ) + , m_textL( new QVBoxLayout ) + , m_steerTop( 0 ) + , m_steerBottom( 0 ) + , m_reset( 0 ) + , m_expanding( true ) + +{ + m_layout->setContentsMargins( 8, 8, 8, 8 ); + + m_textL->setSpacing( 0 ); + m_steerTop = new QLabel( tr( "Steer this station:" ), this ); + QFont f = m_steerTop->font(); + f.setPointSize( f.pointSize() + 2 ); + f.setBold( true ); + m_steerTop->setFont( f ); + m_textL->addWidget( m_steerTop ); + m_steerBottom = new QLabel( tr( "Takes effect on track change" ), this ); + f.setPointSize( f.pointSize() - 3 ); + m_steerBottom->setFont( f ); + m_textL->addWidget( m_steerBottom ); + + m_layout->addLayout( m_textL, 1 ); + + m_amplifier = new QComboBox( this ); + m_amplifier->addItem( tr( "Much less" ), "^.1" ); + m_amplifier->addItem( tr( "Less" ), "^.5" ); + m_amplifier->addItem( tr( "A bit less" ), "^.75" ); + m_amplifier->addItem( tr( "Keep at current", "" ) ); + m_amplifier->addItem( tr( "A bit more" ), "^1.25" ); + m_amplifier->addItem( tr( "More" ), "^1.5" ); + m_amplifier->addItem( tr( "Much more" ), "^2" ); + m_amplifier->setCurrentIndex( 3 ); + m_field = new QComboBox( this ); + m_field->addItem( tr( "Tempo" ), "tempo"); + m_field->addItem( tr( "Loudness" ), "loudness"); + m_field->addItem( tr( "Danceability" ), "danceability"); + m_field->addItem( tr( "Energy" ), "energy"); + m_field->addItem( tr( "Song Hotttnesss" ), "tempo"); + m_field->addItem( tr( "Artist Hotttnesss" ), "artist_hotttnesss"); + m_field->addItem( tr( "Artist Familiarity" ), "artist_familiarity"); + m_field->addItem( tr( "By Description" ), "desc"); + m_layout->addWidget( m_amplifier ); + m_layout->addWidget( m_field ); + + connect( m_amplifier, SIGNAL( currentIndexChanged( int ) ), this, SLOT( changed() ) ); + connect( m_field, SIGNAL( currentIndexChanged( int ) ), this, SLOT( changed() ) ); + + m_description = new QLineEdit( this ); + m_description->setPlaceholderText( tr( "Enter a description" ) ); + m_description->hide(); + + connect( m_description, SIGNAL( textChanged( QString ) ), this, SLOT( changed() ) ); + + m_reset = initButton( this ); + m_reset->setIcon( QIcon( RESPATH "images/view-refresh.png" ) ); + m_reset->setToolTip( tr( "Reset all steering commands" ) ); + m_layout->addWidget( m_reset ); + + connect( m_reset, SIGNAL( clicked( bool ) ), this, SLOT( resetSteering( bool ) ) ); + + setLayout( m_layout ); + setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + m_resizeAnim.setDuration( ANIM_DURATION ); + m_resizeAnim.setEasingCurve( QEasingCurve::InOutQuad ); + m_resizeAnim.setDirection( QTimeLine::Forward ); + m_resizeAnim.setUpdateInterval( 8 ); + + connect( &m_resizeAnim, SIGNAL( frameChanged( int ) ), this, SLOT( resizeFrame( int ) ) ); + + + m_fadeAnim = new QPropertyAnimation( this, "opacity", this ); + m_fadeAnim->setDuration( ANIM_DURATION ); + m_fadeAnim->setStartValue( 0 ); + m_fadeAnim->setEndValue( .86 ); + resize( sizeHint() ); +} + +void +EchonestSteerer::paintEvent( QPaintEvent* ) +{ + QPainter p( this ); + QRect r = contentsRect(); + QPalette pal = palette(); + + DynamicWidget::paintRoundedFilledRect( p, pal, r, m_opacity ); +} + +void +EchonestSteerer::setOpacity( qreal opacity ) +{ + m_opacity = opacity; + if( m_opacity == 0 ) + hide(); + repaint(); +} + +void +EchonestSteerer::fadeIn() +{ + m_fadeAnim->setDirection( QAbstractAnimation::Forward ); + m_fadeAnim->start(); + + show(); +} + +void +EchonestSteerer::fadeOut() +{ + m_fadeAnim->setDirection( QAbstractAnimation::Backward ); + m_fadeAnim->start(); +} + + +void +EchonestSteerer::changed() +{ + bool keep = false; + if( m_amplifier->itemData( m_amplifier->currentIndex() ).toString().isEmpty() ) { // Keep Current + keep = true; + + emit reset(); + } + + if( m_field->itemData( m_field->currentIndex() ).toString() != "desc" ) { + if( !keep ) { + QString steer = m_field->itemData( m_field->currentIndex() ).toString() + m_amplifier->itemData( m_amplifier->currentIndex() ).toString(); + emit steerField( steer ); + } + + // if description was shown, animate to shrink + if( m_layout->indexOf( m_description ) > 0 ) { + m_expanding = false; + int start = width(); + int end = start - m_layout->spacing() - m_description->sizeHint().width();; + + m_layout->removeWidget( m_description ); + m_description->hide(); + m_layout->setStretchFactor( m_textL, 1 ); + + m_resizeAnim.setFrameRange( start, end ); + m_resizeAnim.start(); + + qDebug() << "COLLAPSING FROM" << start << "TO" << end; + } + } else { // description, so put in the description field + if( !m_description->text().isEmpty() && !keep ) { + QString steer = m_description->text() + m_amplifier->itemData( m_amplifier->currentIndex() ).toString(); + emit steerDescription( steer ); + } + + if( m_layout->indexOf( m_description ) == -1 ) { + // animate to expand + m_layout->insertWidget( m_layout->count() - 1, m_description, 1 ); + m_layout->setStretchFactor( m_textL, 0 ); + m_description->show(); + + m_expanding = true; + int start = width(); + int end = start + m_layout->spacing() + m_description->sizeHint().width(); + m_resizeAnim.setFrameRange( start, end ); + m_resizeAnim.start(); + + qDebug() << "EXPANDING FROM" << start << "TO" << end; + } + } +} + +void +EchonestSteerer::resizeFrame( int width ) +{ +// qDebug() << "RESIZING TO:" << width; + resize( width, sizeHint().height() ); + repaint(); + + emit resized(); +} + +void +EchonestSteerer::resetSteering( bool automatic ) +{ + m_amplifier->setCurrentIndex( 3 ); + + if( !automatic ) { + m_description->clear(); + m_field->setCurrentIndex( 0 ); + emit reset(); + } +} + + +QToolButton* +EchonestSteerer::initButton( QWidget* parent ) +{ + QToolButton* btn = new QToolButton( parent ); + btn->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + btn->setIconSize( QSize( 14, 14 ) ); + btn->setToolButtonStyle( Qt::ToolButtonIconOnly ); + btn->setAutoRaise( true ); + btn->setContentsMargins( 0, 0, 0, 0 ); + return btn; +} diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.h b/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.h new file mode 100644 index 000000000..614b12007 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.h @@ -0,0 +1,92 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef ECHONEST_STEERER_H +#define ECHONEST_STEERER_H + +#include +#include + +class QPropertyAnimation; +class QToolButton; +class QLabel; +class QComboBox; +class QVBoxLayout; +class QLineEdit; +class QHBoxLayout; + +namespace Tomahawk +{ + +class EchonestSteerer : public QWidget +{ + Q_OBJECT + Q_PROPERTY( qreal opacity READ opacity WRITE setOpacity ) + +public: + EchonestSteerer( QWidget* parent = 0 ); + + virtual void paintEvent(QPaintEvent* ); + +public slots: + void resetSteering( bool automatic = false ); + + void fadeIn(); + void fadeOut(); + qreal opacity() const { return m_opacity; } + void setOpacity( qreal opacity ); +signals: + void steerField( const QString& field ); + void steerDescription( const QString& desc ); + void reset(); + + void resized(); +private slots: + void changed(); + + void resizeFrame( int ); + +private: + QToolButton* initButton( QWidget* parent ); + + QHBoxLayout* m_layout; + + QComboBox* m_amplifier; + QComboBox* m_field; + + QLineEdit* m_description; + + // text on the left + QVBoxLayout* m_textL; + QLabel* m_steerTop; + QLabel* m_steerBottom; + + // icons on the right + QToolButton* m_reset; + + // animations + QTimeLine m_resizeAnim; + bool m_expanding; + + QPropertyAnimation* m_fadeAnim; + qreal m_opacity; +}; + +}; + +#endif diff --git a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp new file mode 100644 index 000000000..65ac1564b --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.cpp @@ -0,0 +1,203 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "CollapsibleControls.h" + +#include "DynamicControlList.h" +#include "DynamicControlWrapper.h" +#include "dynamic/GeneratorInterface.h" +#include "dynamic/DynamicControl.h" +#include "utils/tomahawkutils.h" +#include "utils/elidedlabel.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Tomahawk; + +CollapsibleControls::CollapsibleControls( QWidget* parent ) + : QWidget( parent ) + , m_isLocal( true ) +{ + init(); +} + +CollapsibleControls::CollapsibleControls( const dynplaylist_ptr& playlist, bool isLocal, QWidget* parent ) + : QWidget( parent ) + , m_dynplaylist( playlist ) + , m_isLocal( isLocal ) +{ + init(); + setControls( m_dynplaylist, m_isLocal ); +} + +Tomahawk::CollapsibleControls::~CollapsibleControls() +{ + +} + +void +CollapsibleControls::init() +{ + m_timeline = new QTimeLine( 250, this ); + m_timeline->setUpdateInterval( 5 ); + m_animHeight = -1; + m_collapseAnimation = false; + + connect( m_timeline, SIGNAL( frameChanged( int ) ), this, SLOT( onAnimationStep( int ) ) ); + connect( m_timeline, SIGNAL( finished() ), this, SLOT( onAnimationFinished() ) ); + + m_layout = new QStackedLayout; + setContentsMargins( 0, 0, 0, 0 ); + m_layout->setContentsMargins( 0, 0, 0, 0 ); + m_layout->setSpacing( 0 ); + + m_controls = new Tomahawk::DynamicControlList( this ); + m_layout->addWidget( m_controls ); + connect( m_controls, SIGNAL( toggleCollapse() ), this, SLOT( toggleCollapse() ) ); + + m_summaryWidget = new QWidget( this ); + m_summaryWidget->setMinimumHeight( 24 ); + m_summaryWidget->setMaximumHeight( 24 ); + m_summaryWidget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); + m_summaryLayout = new QHBoxLayout; + m_summaryWidget->setLayout( m_summaryLayout ); + m_summaryLayout->setMargin( 0 ); + m_summaryWidget->setContentsMargins( 3, 0, 0, 0 ); + + m_summary = new ElidedLabel( m_summaryWidget ); + QFont f = m_summary->font(); + f.setPointSize( f.pointSize() + 1 ); + f.setBold( true ); + m_summary->setFont( f ); + m_summaryLayout->addWidget( m_summary, 1 ); + m_summaryExpand = DynamicControlWrapper::initButton( this ); + m_summaryExpand->setIcon( QIcon( RESPATH "images/arrow-down-double.png" ) ); + m_expandL = new QStackedLayout; + m_expandL->setContentsMargins( 0, 0, 0, 0 ); + m_expandL->setMargin( 0 ); + m_expandL->addWidget( m_summaryExpand ); + m_expandL->addWidget( DynamicControlWrapper::createDummy( m_summaryExpand, this ) ); + m_summaryLayout->addLayout( m_expandL ); + if( m_isLocal ) + m_expandL->setCurrentIndex( 0 ); + else + m_expandL->setCurrentIndex( 1 ); + + m_layout->addWidget( m_summaryWidget ); + connect( m_summaryExpand, SIGNAL( clicked( bool ) ), this, SLOT( toggleCollapse() ) ); + + if( m_isLocal ) + m_layout->setCurrentWidget( m_controls ); + else + m_layout->setCurrentWidget( m_summary ); + + connect( m_controls, SIGNAL( controlChanged( Tomahawk::dyncontrol_ptr ) ), SIGNAL( controlChanged( Tomahawk::dyncontrol_ptr ) ) ); + connect( m_controls, SIGNAL( controlsChanged() ), SIGNAL( controlsChanged() ) ); + + setLayout( m_layout ); + + setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); +} + + +QList< DynamicControlWrapper* > +Tomahawk::CollapsibleControls::controls() const +{ + return m_controls->controls(); +} + +void +CollapsibleControls::setControls( const dynplaylist_ptr& playlist, bool isLocal ) +{ + m_dynplaylist = playlist; + m_isLocal = isLocal; + m_controls->setControls( m_dynplaylist->generator(), m_dynplaylist->generator()->controls() ); + + if( !m_isLocal ) { + m_expandL->setCurrentIndex( 1 ); + m_summary->setText( m_dynplaylist->generator()->sentenceSummary() ); + m_layout->setCurrentWidget( m_summaryWidget ); + setMaximumHeight( m_summaryWidget->sizeHint().height() ); + } else { + m_expandL->setCurrentIndex( 0 ); + } +} + +void +CollapsibleControls::toggleCollapse() +{ +// qDebug() << "TOGGLING SIZEHINTS:" << m_controls->height() << m_summaryWidget->sizeHint(); + m_timeline->setEasingCurve( QEasingCurve::OutBack ); + m_timeline->setFrameRange( m_summaryWidget->sizeHint().height(), m_controls->height() ); + if( m_layout->currentWidget() == m_controls ) { + m_summary->setText( m_dynplaylist->generator()->sentenceSummary() ); + m_controls->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored ); + + m_timeline->setDirection( QTimeLine::Backward ); + m_timeline->start(); + + m_collapseAnimation = true; + } else { + m_summaryWidget->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored ); + m_layout->setCurrentWidget( m_controls ); + + m_timeline->setDirection( QTimeLine::Forward ); + m_timeline->start(); + + m_collapseAnimation = false; + } +} + +void +CollapsibleControls::onAnimationStep( int step ) +{ +// qDebug() << "ANIMATION STEP:" << step; + resize( width(), step ); + m_animHeight = step; + setMaximumHeight( m_animHeight ); +} + +void +CollapsibleControls::onAnimationFinished() +{ +// qDebug() << "ANIMATION DONE:" << m_animHeight; + setMaximumHeight( m_animHeight ); + m_animHeight = -1; + + if( m_collapseAnimation ) { + m_layout->setCurrentWidget( m_summaryWidget ); + } else { + setMaximumHeight( QWIDGETSIZE_MAX ); + } +} + +QSize CollapsibleControls::sizeHint() const +{ + if( m_animHeight >= 0 ) { + return QSize( QWidget::sizeHint().width(), m_animHeight ); + } else { + return QWidget::sizeHint(); + } +} diff --git a/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.h b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.h new file mode 100644 index 000000000..a242769d0 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/CollapsibleControls.h @@ -0,0 +1,81 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef COLLAPSIBLE_CONTROLS_H +#define COLLAPSIBLE_CONTROLS_H + +#include "typedefs.h" + +#include + +class QPaintEvent; +class QHBoxLayout; +class QTimeLine; +class QToolButton; +class ElidedLabel; +class QStackedLayout; +namespace Tomahawk +{ + +class DynamicControlWrapper; +class DynamicControlList; + +class CollapsibleControls : public QWidget +{ + Q_OBJECT +public: + CollapsibleControls( QWidget* parent ); + CollapsibleControls( const dynplaylist_ptr& playlist, bool isLocal, QWidget* parent = 0 ); + virtual ~CollapsibleControls(); + + void setControls( const dynplaylist_ptr& playlist, bool isLocal ); + QList< DynamicControlWrapper* > controls() const; + + virtual QSize sizeHint() const; +signals: + void controlsChanged(); + void controlChanged( const Tomahawk::dyncontrol_ptr& control ); + +private slots: + void toggleCollapse(); + + void onAnimationStep( int ); + void onAnimationFinished(); + +private: + void init(); + + dynplaylist_ptr m_dynplaylist; + QStackedLayout* m_layout; + DynamicControlList* m_controls; + bool m_isLocal; + + QWidget* m_summaryWidget; + QHBoxLayout* m_summaryLayout; + ElidedLabel* m_summary; + QStackedLayout* m_expandL; + QToolButton* m_summaryExpand; + + // animations! + QTimeLine* m_timeline; + int m_animHeight; + bool m_collapseAnimation; +}; + +} +#endif diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.cpp new file mode 100644 index 000000000..5128562af --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.cpp @@ -0,0 +1,171 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "DynamicControlList.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DynamicControlWrapper.h" +#include "dynamic/GeneratorInterface.h" +#include "utils/tomahawkutils.h" + +using namespace Tomahawk; + +DynamicControlList::DynamicControlList( QWidget* parent ) + : QWidget( parent ) + , m_layout( new QGridLayout ) +{ + init(); +} + +DynamicControlList::DynamicControlList( const geninterface_ptr& generator, const QList< dyncontrol_ptr >& controls, QWidget* parent ) + : QWidget( parent ) + , m_generator( generator ) + , m_layout( new QGridLayout ) +{ + init(); + setControls( generator, controls ); +} + +DynamicControlList::~DynamicControlList() +{ + +} + +void +DynamicControlList::init() +{ + qDebug() << "GRIDLAYOUT: " << m_layout->rowCount(); + setContentsMargins( 0, 0, 0, 0 ); + setLayout( m_layout ); + m_layout->setColumnStretch( 2, 1 ); + m_layout->setMargin( 0 ); + m_layout->setVerticalSpacing( 0 ); + m_layout->setContentsMargins( 0, 0, 0, 0 ); + m_layout->setSizeConstraint( QLayout::SetMinimumSize ); + + m_collapseLayout = new QHBoxLayout(); + m_collapseLayout->setContentsMargins( 0, 0, 0, 0 ); + m_collapseLayout->setMargin( 0 ); + m_collapseLayout->setSpacing( 0 ); + m_collapse = new QPushButton( tr( "Click to collapse" ), this ); + m_collapse->setAttribute( Qt::WA_LayoutUsesWidgetRect ); + m_collapseLayout->addWidget( m_collapse ); + m_addControl = new QToolButton( this ); + m_addControl->setAttribute( Qt::WA_LayoutUsesWidgetRect ); + m_addControl->setIcon( QIcon( RESPATH "images/list-add.png" ) ); + m_addControl->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + m_addControl->setIconSize( QSize( 16, 16 ) ); + m_addControl->setToolButtonStyle( Qt::ToolButtonIconOnly ); + m_addControl->setAutoRaise( true ); + m_addControl->setContentsMargins( 0, 0, 0, 0 ); + m_collapseLayout->addWidget( m_addControl ); + m_collapse->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); + + connect( m_collapse, SIGNAL( clicked() ), this, SIGNAL( toggleCollapse() ) ); + connect( m_addControl, SIGNAL( clicked() ), this, SLOT( addNewControl() ) ); + + setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); +} + +void +DynamicControlList::setControls( const geninterface_ptr& generator, const QList< dyncontrol_ptr >& controls ) +{ + if( m_controls.size() == controls.size() && controls.size() > 0 ) { // check if we're setting the same controls we already have, and exit if we are + bool different = false; + for( int i = 0; i < m_controls.size(); i++ ) { + if( m_controls.value( i )->control().data() != controls.value( i ).data() ) { + different = true; + break; + } + } + if( !different ) { // no work to do + return; + } + } + + if( !m_controls.isEmpty() ) { + qDeleteAll( m_controls ); + m_controls.clear(); + } + + m_layout->removeItem( m_collapseLayout ); + + m_generator = generator; + if( controls.isEmpty() ) { + qDebug() << "CREATING DEFAULT CONTROL"; + DynamicControlWrapper* ctrlW = new DynamicControlWrapper( generator->createControl(), m_layout, m_controls.size(), this ); + connect( ctrlW, SIGNAL( removeControl() ), this, SLOT( removeControl() ) ); + connect( ctrlW, SIGNAL( changed() ), this, SLOT( controlChanged() ) ); + m_controls << ctrlW; + } else + { + foreach( const dyncontrol_ptr& control, controls ) { + DynamicControlWrapper* ctrlW = new DynamicControlWrapper( control, m_layout, m_controls.size(), this ); + connect( ctrlW, SIGNAL( removeControl() ), this, SLOT( removeControl() ) ); + connect( ctrlW, SIGNAL( changed() ), this, SLOT( controlChanged() ) ); + + m_controls << ctrlW; + } + } + m_layout->addItem( m_collapseLayout, m_layout->rowCount(), 0, 1, 4, Qt::AlignCenter ); + +} + +void DynamicControlList::addNewControl() +{ + m_layout->removeItem( m_collapseLayout ); + + dyncontrol_ptr control = m_generator->createControl(); + m_controls.append( new DynamicControlWrapper( control, m_layout, m_controls.size(), this ) ); + connect( m_controls.last(), SIGNAL( removeControl() ), this, SLOT( removeControl() ) ); + connect( m_controls.last(), SIGNAL( changed() ), this, SLOT( controlChanged() ) ); + + m_layout->addItem( m_collapseLayout, m_layout->rowCount(), 0, 1, 4, Qt::AlignCenter ); + emit controlsChanged(); +} + +void DynamicControlList::removeControl() +{ + DynamicControlWrapper* w = qobject_cast( sender() ); + w->removeFromLayout(); + m_controls.removeAll( w ); + + m_generator->removeControl( w->control() ); + delete w; + + emit controlsChanged(); +} + +void DynamicControlList::controlChanged() +{ + Q_ASSERT( sender() && qobject_cast(sender()) ); + DynamicControlWrapper* widget = qobject_cast(sender()); + + qDebug() << "control changed!"; + foreach( DynamicControlWrapper* c, m_controls ) + qDebug() << c->control()->id() << c->control()->selectedType() << c->control()->match() << c->control()->input(); + emit controlChanged( widget->control() ); +} diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.h new file mode 100644 index 000000000..985a1ccba --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlList.h @@ -0,0 +1,81 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DYNAMIC_CONTROL_LIST_H +#define DYNAMIC_CONTROL_LIST_H + +#include "typedefs.h" +#include "dynamic/DynamicPlaylist.h" + +#include + +class QEvent; +class QGridLayout; +class QPushButton; +class QHBoxLayout; +class QVBoxLayout; +class QToolButton; + +namespace Tomahawk +{ + +class DynamicControlWrapper; + + +/** + * This widget encapsulates the list of dynamic controls. It can hide or show the controls. + */ + +class DynamicControlList : public QWidget +{ + Q_OBJECT +public: + DynamicControlList( QWidget* parent = 0 ); + explicit DynamicControlList( const geninterface_ptr& generator, const QList< dyncontrol_ptr >& controls, QWidget* parent = 0 ); + virtual ~DynamicControlList(); + + void setControls( const geninterface_ptr& generator, const QList< dyncontrol_ptr >& controls ); + QList< DynamicControlWrapper* > controls() const { return m_controls; } + +signals: + void controlsChanged(); + void controlChanged( const Tomahawk::dyncontrol_ptr& control ); + void toggleCollapse(); + +public slots: + void addNewControl(); + void removeControl(); + void controlChanged(); + +private: + void init(); + + geninterface_ptr m_generator; + + QGridLayout* m_layout; + QList< DynamicControlWrapper* > m_controls; + + QHBoxLayout* m_collapseLayout; + QPushButton* m_collapse; + QToolButton* m_addControl; + +}; + +}; + +#endif diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.cpp new file mode 100644 index 000000000..c3ed4fed6 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.cpp @@ -0,0 +1,196 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "DynamicControlWrapper.h" + +#include "dynamic/DynamicControl.h" +#include "utils/tomahawkutils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Tomahawk; + +DynamicControlWrapper::DynamicControlWrapper( const Tomahawk::dyncontrol_ptr& control, QGridLayout* layout, int row, QWidget* parent ) + : QObject( parent ) + , m_parent( parent ) + , m_row( row ) + , m_minusButton( 0 ) + , m_control( control ) + , m_typeSelector( 0 ) + , m_layout( QWeakPointer< QGridLayout >( layout ) ) +{ + + qDebug() << "CREATING DYNAMIC CONTROL WRAPPER WITH ROW:" << row << layout; + + m_typeSelector = new QComboBox( m_parent ); + + m_matchSelector = QWeakPointer( control->matchSelector() ); + m_entryWidget = QWeakPointer( control->inputField() ); + + m_minusButton = initButton( m_parent ); + m_minusButton->setIcon( QIcon( RESPATH "images/list-remove.png" ) ); + connect( m_minusButton, SIGNAL( clicked( bool ) ), this, SIGNAL( removeControl() ) ); + + + m_plusL = new QStackedLayout(); + m_plusL->setContentsMargins( 0, 0, 0, 0 ); + m_plusL->setMargin( 0 ); + m_plusL->addWidget( m_minusButton ); + m_plusL->addWidget( createDummy( m_minusButton, m_parent ) ); // :-( + + connect( m_typeSelector, SIGNAL( activated( QString) ), SLOT( typeSelectorChanged( QString ) ) ); + connect( m_control.data(), SIGNAL( changed() ), this, SIGNAL( changed() ) ); + + m_layout.data()->addWidget( m_typeSelector, row, 0, Qt::AlignLeft ); + + if( !control.isNull() ) { + foreach( const QString& type, control->typeSelectors() ) + m_typeSelector->addItem( type ); + } + + typeSelectorChanged( m_control.isNull() ? "" : m_control->selectedType(), true ); + + m_layout.data()->addLayout( m_plusL, m_row, 3, Qt::AlignCenter ); + m_plusL->setCurrentIndex( 0 ); + + +} + +DynamicControlWrapper::~DynamicControlWrapper() +{ + // remove the controls widgets from our layout so they are not parented + // we don't want to auto-delete them since the control should own them + // if we delete them, then the control will be holding on to null ptrs + removeFromLayout(); + + if( !m_entryWidget.isNull() ) + m_control->inputField()->setParent( 0 ); + if( !m_matchSelector.isNull() ) + m_control->matchSelector()->setParent( 0 ); + + delete m_typeSelector; + delete m_minusButton; +} + +dyncontrol_ptr +DynamicControlWrapper::control() const +{ + return m_control; +} + +void +DynamicControlWrapper::removeFromLayout() +{ + if( m_layout.isNull() ) + return; + + if( !m_matchSelector.isNull() ) + m_layout.data()->removeWidget( m_matchSelector.data() ); + if( !m_entryWidget.isNull() ) + m_layout.data()->removeWidget( m_entryWidget.data() ); + m_layout.data()->removeWidget( m_typeSelector ); + m_layout.data()->removeItem( m_plusL ); +} + + +QToolButton* +DynamicControlWrapper::initButton( QWidget* parent ) +{ + QToolButton* btn = new QToolButton( parent ); + btn->setAttribute( Qt::WA_LayoutUsesWidgetRect ); + btn->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + btn->setIconSize( QSize( 16, 16 ) ); + btn->setToolButtonStyle( Qt::ToolButtonIconOnly ); + btn->setAutoRaise( true ); + btn->setContentsMargins( 0, 0, 0, 0 ); + return btn; +} + +QWidget* +DynamicControlWrapper::createDummy( QWidget* fromW, QWidget* parent ) +{ + QWidget* dummy = new QWidget( parent ); + dummy->setAttribute( Qt::WA_LayoutUsesWidgetRect ); + dummy->setContentsMargins( 0, 0, 0, 0 ); + dummy->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + dummy->setMinimumSize( fromW->sizeHint() ); + dummy->setMaximumSize( fromW->sizeHint() ); + return dummy; +} + + +void +DynamicControlWrapper::typeSelectorChanged( const QString& type, bool firstLoad ) +{ + Q_ASSERT( !m_layout.isNull() ); + m_layout.data()->removeWidget( m_matchSelector.data() ); + m_layout.data()->removeWidget( m_entryWidget.data() ); + + if( m_control->selectedType() != type && !firstLoad ) + m_control->setSelectedType( type ); + + + int idx = m_typeSelector->findText( type ); + if( idx > -1 ) + m_typeSelector->setCurrentIndex( idx ); + + + if( m_control->matchSelector() ) { + m_matchSelector = QWeakPointer( m_control->matchSelector() ); + m_layout.data()->addWidget( m_matchSelector.data(), m_row, 1, Qt::AlignCenter ); + m_matchSelector.data()->show(); + } + if( m_control->inputField() ) { + m_entryWidget = QWeakPointer( m_control->inputField() ); + m_layout.data()->addWidget( m_entryWidget.data(), m_row, 2 ); + m_entryWidget.data()->show(); + } + + emit changed(); +} +/* +void +DynamicControlWrapper::enterEvent(QEvent* ev) +{ + m_mouseOver = true; + if( m_isLocal ) + m_plusL->setCurrentIndex( 0 ); + + if( ev ) + QObject::enterEvent( ev ); +} + +void +DynamicControlWrapper::leaveEvent(QEvent* ev) +{ + m_mouseOver = true; + if( m_isLocal ) + m_plusL->setCurrentIndex( 1 ); + + if( ev ) + QWidget::leaveEvent( ev ); +} +*/ + diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.h new file mode 100644 index 000000000..b0c3df109 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicControlWrapper.h @@ -0,0 +1,84 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DYNAMIC_CONTROL_WRAPPER_H +#define DYNAMIC_CONTROL_WRAPPER_H + +#include + +#include "typedefs.h" + +class QGridLayout; +class QStackedLayout; +class QEvent; +class QToolButton; +class QHBoxLayout; +class QComboBox; +class QLabel;; + +namespace Tomahawk +{ + +/** +* This abstraction object manages the widgets for 1 dynamic playlist control, laid out in the desired layout +*/ +class DynamicControlWrapper : public QObject +{ + Q_OBJECT +public: + explicit DynamicControlWrapper( const dyncontrol_ptr& control, QGridLayout* layout, int row, QWidget* parent = 0 ); + virtual ~DynamicControlWrapper(); + +// virtual void enterEvent(QEvent* ); +// virtual void leaveEvent(QEvent* ); + + dyncontrol_ptr control() const; + + void removeFromLayout(); + + + static QToolButton* initButton( QWidget* parent ); + static QWidget* createDummy( QWidget* fromW, QWidget* parent ); +signals: + void collapse(); + void removeControl(); + void changed(); + +private slots: + void typeSelectorChanged( const QString& selectedType, bool firstLoad = false ); + +private: + QWidget* m_parent; + int m_row; + QStackedLayout* m_plusL; + QToolButton* m_minusButton; + + dyncontrol_ptr m_control; + QComboBox* m_typeSelector; + QWeakPointer m_matchSelector; + QWeakPointer m_entryWidget; + QWeakPointer m_layout; +}; + +}; + +#endif + +class QPaintEvent; + +class QMouseEvent; diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp new file mode 100644 index 000000000..062fa6485 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp @@ -0,0 +1,153 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "DynamicSetupWidget.h" + +#include "ReadOrWriteWidget.h" +#include "playlist/dynamic/DynamicPlaylist.h" +#include "playlist/dynamic/GeneratorFactory.h" +#include "DynamicWidget.h" +#include "source.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Tomahawk; + + +DynamicSetupWidget::DynamicSetupWidget( const Tomahawk::dynplaylist_ptr& playlist, QWidget* parent ) + : QWidget( parent ) + , m_playlist( playlist ) + , m_headerText( 0 ) + , m_layout( new QHBoxLayout ) + , m_generatorCombo( 0 ) + , m_logo( 0 ) + , m_generateButton( 0 ) + , m_genNumber( 0 ) +{ + + setContentsMargins( 0, 0, 0, 0 ); + m_headerText = new QLabel( tr( "Type:" ), this ); + m_layout->addWidget( m_headerText ); + + QComboBox * genCombo = new QComboBox( this ); + foreach( const QString& type, GeneratorFactory::types() ) + genCombo->addItem( type ); + m_generatorCombo = new ReadOrWriteWidget( genCombo, m_playlist->author()->isLocal(), this ); + m_generatorCombo->setLabel( playlist->generator()->type().replace( 0, 1, playlist->generator()->type().at( 0 ).toUpper() ) ); + + m_layout->addWidget( m_generatorCombo ); + + m_generateButton = new QPushButton( tr( "Generate" ), this ); + m_generateButton->setAttribute( Qt::WA_LayoutUsesWidgetRect ); + connect( m_generateButton, SIGNAL( clicked( bool ) ), this, SLOT( generatePressed( bool ) ) ); + if( m_playlist->mode() == OnDemand ) + m_generateButton->hide(); + else + m_layout->addWidget( m_generateButton ); + + + m_genNumber = new QSpinBox( this ); + m_genNumber->setValue( 15 ); + m_genNumber->setMinimum( 0 ); + if( m_playlist->mode() == OnDemand ) + m_genNumber->hide(); + else + m_layout->addWidget( m_genNumber ); + + m_layout->addSpacing( 30 ); + + m_logo = new QLabel( this ); + if( !m_playlist->generator()->logo().isNull() ) { + QPixmap p = m_playlist->generator()->logo().scaledToHeight( 22, Qt::SmoothTransformation ); + m_logo->setPixmap( p ); + } + m_layout->addWidget(m_logo); + + setLayout( m_layout ); + + m_fadeAnim = new QPropertyAnimation( this, "opacity" ); + m_fadeAnim->setDuration( 250 ); + m_fadeAnim->setStartValue( 0.00 ); + m_fadeAnim->setEndValue( .86 ); + + setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); + resize( sizeHint() ); +} + +DynamicSetupWidget::~DynamicSetupWidget() +{ + +} + +void +DynamicSetupWidget::setPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) +{ + +} + +void +DynamicSetupWidget::fadeIn() +{ + m_fadeAnim->setDirection( QAbstractAnimation::Forward ); + m_fadeAnim->start(); + + show(); +} + +void +DynamicSetupWidget::fadeOut() +{ + m_fadeAnim->setDirection( QAbstractAnimation::Backward ); + m_fadeAnim->start(); + +} + +void +DynamicSetupWidget::generatePressed( bool ) +{ + emit generatePressed( m_genNumber->value() ); +} + +void +DynamicSetupWidget::setOpacity( qreal opacity ) +{ + m_opacity = opacity; + + if( m_opacity == 0 ) + hide(); + repaint(); +} + +void +DynamicSetupWidget::paintEvent( QPaintEvent* e ) +{ + QPainter p( this ); + QRect r = contentsRect(); + QPalette pal = palette(); + + DynamicWidget::paintRoundedFilledRect( p, pal, r, m_opacity ); + + QWidget::paintEvent( e ); +} diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.h new file mode 100644 index 000000000..78568b19e --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.h @@ -0,0 +1,82 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DYNAMIC_SETUP_WIDGET_H +#define DYNAMIC_SETUP_WIDGET_H + +#include +#include + +class QPropertyAnimation; +class QPaintEvent; +class QHBoxLayout; +class QSpinBox; +class QPushButton; +class QLabel; +class ReadOrWriteWidget; +class QLabel; + +namespace Tomahawk +{ + +/** + * Widget used to choose a type of dynamic playlist, and to set the number/generate if it's a static one. + */ +class DynamicSetupWidget : public QWidget +{ + Q_OBJECT + Q_PROPERTY( qreal opacity READ opacity WRITE setOpacity ) +public: + DynamicSetupWidget( const Tomahawk::dynplaylist_ptr& playlist, QWidget* parent = 0 ); + virtual ~DynamicSetupWidget(); + + void setPlaylist( const dynplaylist_ptr& playlist ); + + qreal opacity() const { return m_opacity; } + void setOpacity( qreal opacity ); + + virtual void paintEvent( QPaintEvent* ); + +public slots: + void fadeIn(); + void fadeOut(); + +signals: + void generatePressed( int num ); + void typeChanged( const QString& playlistType ); + +private slots: + void generatePressed( bool ); + +private: + dynplaylist_ptr m_playlist; + + QLabel* m_headerText; + QHBoxLayout* m_layout; + ReadOrWriteWidget* m_generatorCombo; + QLabel* m_logo; + QPushButton* m_generateButton; + QSpinBox* m_genNumber; + + QPropertyAnimation* m_fadeAnim; + qreal m_opacity; +}; + +}; + +#endif diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp new file mode 100644 index 000000000..75fc46351 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp @@ -0,0 +1,405 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "DynamicWidget.h" + +#include +#include +#include +#include +#include + +#include "DynamicControlList.h" +#include "dynamic/DynamicModel.h" +#include "trackproxymodel.h" +#include "dynamic/GeneratorInterface.h" +#include "dynamic/GeneratorFactory.h" +#include "pipeline.h" +#include "audio/audioengine.h" +#include "ReadOrWriteWidget.h" +#include "CollapsibleControls.h" +#include "DynamicControlWrapper.h" +#include "playlistmanager.h" +#include "dynamic/DynamicView.h" +#include +#include "DynamicSetupWidget.h" +#include + +#include "audiocontrols.h" +#include "LoadingSpinner.h" + +using namespace Tomahawk; + +DynamicWidget::DynamicWidget( const Tomahawk::dynplaylist_ptr& playlist, QWidget* parent ) + : QWidget(parent) + , m_layout( new QVBoxLayout ) + , m_resolveOnNextLoad( false ) + , m_seqRevLaunched( 0 ) + , m_activePlaylist( false ) + , m_setup( 0 ) + , m_runningOnDemand( false ) + , m_controlsChanged( false ) + , m_steering( 0 ) + , m_controls( 0 ) + , m_view( 0 ) + , m_model() +{ + m_controls = new CollapsibleControls( this ); + m_layout->addWidget( m_controls ); + setContentsMargins( 0, 0, 0, 1 ); // to align the bottom with the bottom of the sourcelist + + m_model = new DynamicModel( this ); + m_view = new DynamicView( this ); + m_view->setModel( m_model ); + m_view->setContentsMargins( 0, 0, 0, 0 ); + m_layout->addWidget( m_view, 1 ); + + connect( m_model, SIGNAL( collapseFromTo( int, int ) ), m_view, SLOT( collapseEntries( int, int ) ) ); + connect( m_model, SIGNAL( trackGenerationFailure( QString ) ), this, SLOT( stationFailed( QString ) ) ); + + m_loading = new LoadingSpinner( m_view ); + connect( m_model, SIGNAL( tracksAdded() ), m_loading, SLOT( fadeOut() ) ); + + m_setup = new DynamicSetupWidget( playlist, this ); + m_setup->fadeIn(); + + connect( m_model, SIGNAL( tracksAdded() ), this, SLOT( tracksAdded() ) ); + + loadDynamicPlaylist( playlist ); + + m_layout->setContentsMargins( 0, 0, 0, 0 ); + m_layout->setMargin( 0 ); + m_layout->setSpacing( 0 ); + setLayout( m_layout ); + + connect( m_setup, SIGNAL( generatePressed( int ) ), this, SLOT( generate( int ) ) ); + connect( m_setup, SIGNAL( typeChanged( QString ) ), this, SLOT( playlistTypeChanged( QString ) ) ); + + layoutFloatingWidgets(); + + connect( m_controls, SIGNAL( controlChanged( Tomahawk::dyncontrol_ptr ) ), this, SLOT( controlChanged( Tomahawk::dyncontrol_ptr ) ), Qt::QueuedConnection ); + connect( m_controls, SIGNAL( controlsChanged() ), this, SLOT( controlsChanged() ), Qt::QueuedConnection ); + + connect( AudioEngine::instance(), SIGNAL( started( Tomahawk::result_ptr ) ), this, SLOT( trackStarted() ) ); + connect( AudioEngine::instance(), SIGNAL( playlistChanged( PlaylistInterface* ) ), this, SLOT( playlistChanged( PlaylistInterface* ) ) ); +} + +DynamicWidget::~DynamicWidget() +{ +} + +void +DynamicWidget::loadDynamicPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) +{ + // special case: if we have launched multiple setRevision calls, and the number of controls is different, it means that we're getting an intermediate setRevision + // called after the user has already created more revisions. ignore in that case. + if( m_playlist.data() == playlist.data() && m_seqRevLaunched > 0 + && m_controls->controls().size() != playlist->generator()->controls().size() // different number of controls + && qAbs( m_playlist->generator()->controls().size() - playlist->generator()->controls().size() ) < m_seqRevLaunched ) { // difference in controls has to be less than how many revisions we launched + return; + } + m_seqRevLaunched = 0; + + // if we're being told to load the same dynamic playlist over again, only do it if the controls have a different number + if( !m_playlist.isNull() && ( m_playlist.data() == playlist.data() ) // same playlist pointer + && m_playlist->generator()->controls().size() == playlist->generator()->controls().size() ) { + // we can skip our work. just let the dynamiccontrollist show the difference + m_controls->setControls( m_playlist, m_playlist->author()->isLocal() ); + + m_playlist = playlist; + + if( !m_runningOnDemand ) { + m_model->loadPlaylist( m_playlist ); + } else if( !m_controlsChanged ) { // if the controls changed, we already dealt with that and don't want to change station yet + m_model->changeStation(); + } + m_controlsChanged = false; + + return; + } + + if( !m_playlist.isNull() ) { + disconnect( m_playlist->generator().data(), SIGNAL( generated( QList ) ), this, SLOT( tracksGenerated( QList ) ) ); + disconnect( m_playlist.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision) ), this, SLOT(onRevisionLoaded( Tomahawk::DynamicPlaylistRevision) ) ); + disconnect( m_playlist->generator().data(), SIGNAL( error( QString, QString ) ), this, SLOT( generatorError( QString, QString ) ) ); + } + + + m_playlist = playlist; + m_view->setOnDemand( m_playlist->mode() == OnDemand ); + m_view->setReadOnly( !m_playlist->author()->isLocal() ); + m_model->loadPlaylist( m_playlist ); + m_controlsChanged = false; + m_setup->setPlaylist( m_playlist ); + + + if( !m_playlist->author()->isLocal() ) { // hide controls, as we show the description in the summary + m_layout->removeWidget( m_controls ); + } else if( m_layout->indexOf( m_controls ) == -1 ) { + m_layout->insertWidget( 0, m_controls ); + } + + if( m_playlist->mode() == OnDemand && !m_playlist->generator()->controls().isEmpty() ) + showPreview(); + + if( !m_playlist.isNull() ) + m_controls->setControls( m_playlist, m_playlist->author()->isLocal() ); + + connect( m_playlist->generator().data(), SIGNAL( generated( QList ) ), this, SLOT( tracksGenerated( QList ) ) ); + connect( m_playlist.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ), this, SLOT( onRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ) ); + connect( m_playlist->generator().data(), SIGNAL( error( QString, QString ) ), this, SLOT( generatorError( QString, QString ) ) ); +} + + +void +DynamicWidget::onRevisionLoaded( const Tomahawk::DynamicPlaylistRevision& rev ) +{ + qDebug() << "DynamicWidget::onRevisionLoaded"; + loadDynamicPlaylist( m_playlist ); + if( m_resolveOnNextLoad || !m_playlist->author()->isLocal() ) + { + m_playlist->resolve(); + m_resolveOnNextLoad = false; + } +} + +PlaylistInterface* +DynamicWidget::playlistInterface() const +{ + return m_view->proxyModel(); +} + +QSize +DynamicWidget::sizeHint() const +{ + // We want to take up as much room as the animated splitter containing us and the queue editor will allow. So we return a bogus huge sizehint + // to avoid having to calculate it which is slow + return QSize( 5000, 5000 ); +} + +void +DynamicWidget::resizeEvent(QResizeEvent* ) +{ + layoutFloatingWidgets(); +} + +void +DynamicWidget::layoutFloatingWidgets() +{ + if( !m_runningOnDemand ) { + int x = ( width() / 2 ) - ( m_setup->size().width() / 2 ); + int y = height() - m_setup->size().height() - 40; // padding + + m_setup->move( x, y ); + } else if( m_runningOnDemand && m_steering ) { + int x = ( width() / 2 ) - ( m_steering->size().width() / 2 ); + int y = height() - m_steering->size().height() - 40; // padding + + m_steering->move( x, y ); + } +} + +void +DynamicWidget::playlistChanged( PlaylistInterface* pl ) +{ + if( pl == static_cast< PlaylistInterface* >( m_view->proxyModel() ) ) { // same playlist + m_activePlaylist = true; + } else { + m_activePlaylist = false; + + // user started playing something somewhere else, so give it a rest + if( m_runningOnDemand ) { + stopStation( false ); + } + } +} + +void +DynamicWidget::showEvent(QShowEvent* ) +{ + if( !m_playlist.isNull() && !m_runningOnDemand ) { + m_setup->fadeIn(); + } +} + + +void +DynamicWidget::generate( int num ) +{ + // get the items from the generator, and put them in the playlist + m_view->setDynamicWorking( true ); + m_loading->fadeIn(); + m_playlist->generator()->generate( num ); +} + +void +DynamicWidget::stationFailed( const QString& msg ) +{ + m_view->setDynamicWorking( false ); + m_view->showMessage( msg ); + m_loading->fadeOut(); + + stopStation( false ); +} + +void +DynamicWidget::trackStarted() +{ + if( m_activePlaylist && !m_playlist.isNull() && + m_playlist->mode() == OnDemand && !m_runningOnDemand ) { + + startStation(); + } +} + +void +DynamicWidget::tracksAdded() +{ + if( m_playlist->mode() == OnDemand && m_runningOnDemand && m_setup->isVisible() ) + m_setup->fadeOut(); +} + + +void +DynamicWidget::stopStation( bool stopPlaying ) +{ + m_model->stopOnDemand( stopPlaying ); + m_runningOnDemand = false; + + // TODO until i add a qwidget interface + QMetaObject::invokeMethod( m_steering, "fadeOut", Qt::DirectConnection ); + m_setup->fadeIn(); +} + +void +DynamicWidget::startStation() +{ + m_runningOnDemand = true; + m_model->startOnDemand(); + + m_setup->fadeOut(); + // show the steering controls + if( m_playlist->generator()->onDemandSteerable() ) { + // position it horizontally centered, above the botton. + m_steering = m_playlist->generator()->steeringWidget(); + Q_ASSERT( m_steering ); + + int x = ( width() / 2 ) - ( m_steering->size().width() / 2 ); + int y = height() - m_steering->size().height() - 40; // padding + + m_steering->setParent( this ); + m_steering->move( x, y ); + + // TODO until i add a qwidget interface + QMetaObject::invokeMethod( m_steering, "fadeIn", Qt::DirectConnection ); + + connect( m_steering, SIGNAL( resized() ), this, SLOT( layoutFloatingWidgets() ) ); + } +} + +void +DynamicWidget::playlistTypeChanged( QString ) +{ + // TODO +} + +void +DynamicWidget::tracksGenerated( const QList< query_ptr >& queries ) +{ + int limit = -1; // only limit the "preview" of a station + if( m_playlist->author()->isLocal() && m_playlist->mode() == Static ) { + m_resolveOnNextLoad = true; + } else if( m_playlist->mode() == OnDemand ) + limit = 5; + + if( m_playlist->mode() != OnDemand ) + m_loading->fadeOut(); + m_model->tracksGenerated( queries, limit ); +} + + +void +DynamicWidget::controlsChanged() +{ + // controlsChanged() is emitted when a control is added or removed + // in the case of addition, it's blank by default... so to avoid an error + // when playing a station just ignore it till we're ready and get a controlChanged() + m_controlsChanged = true; + + if( !m_playlist->author()->isLocal() ) + return; + m_playlist->createNewRevision(); + m_seqRevLaunched++; +} + +void +DynamicWidget::controlChanged( const Tomahawk::dyncontrol_ptr& control ) +{ + if( !m_playlist->author()->isLocal() ) + return; + m_playlist->createNewRevision(); + m_seqRevLaunched++; + + showPreview(); +} + +void +DynamicWidget::showPreview() +{ + if( m_playlist->mode() == OnDemand && !m_runningOnDemand && m_model->rowCount( QModelIndex() ) == 0 ) { // if this is a not running station, preview matching tracks + generate( 20 ); // ask for more, we'll filter how many we actually want + } +} + + +void +DynamicWidget::generatorError( const QString& title, const QString& content ) +{ + if( m_runningOnDemand ) { + stopStation( false ); + } + m_view->setDynamicWorking( false ); + m_loading->fadeOut(); + m_view->showMessageTimeout( title, content ); +} + +void +DynamicWidget::paintRoundedFilledRect( QPainter& p, QPalette& pal, QRect& r, qreal opacity ) +{ + p.setBackgroundMode( Qt::TransparentMode ); + p.setRenderHint( QPainter::Antialiasing ); + p.setOpacity( opacity ); + + QPen pen( pal.dark().color(), .5 ); + p.setPen( pen ); + p.setBrush( pal.highlight() ); + + p.drawRoundedRect( r, 10, 10 ); + + p.setOpacity( opacity + .2 ); + p.setBrush( QBrush() ); + p.setPen( pen ); + p.drawRoundedRect( r, 10, 10 ); +} + +bool +DynamicWidget::jumpToCurrentTrack() +{ + m_view->scrollTo( m_view->proxyModel()->currentItem(), QAbstractItemView::PositionAtCenter ); + return true; +} diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h new file mode 100644 index 000000000..441db1e13 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h @@ -0,0 +1,134 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef DYNAMIC_WIDGET_H +#define DYNAMIC_WIDGET_H + +#include + +#include "typedefs.h" +#include "viewpage.h" + +#include "dynamic/DynamicPlaylist.h" +#include "dynamic/DynamicControl.h" +#include "dynamic/DynamicModel.h" + +class LoadingSpinner; +class QShowEvent; +class QHideEvent; +class QSpinBox; +class QVBoxLayout; +class QHBoxLayout; +class QPushButton; +class QComboBox; +class PlaylistInterface; +class PlaylistModel; +class PlaylistView; +class AnimatedSplitter; +class QLabel; +class ReadOrWriteWidget; + +namespace Tomahawk +{ + +class DynamicSetupWidget; + +class DynamicView; + +class CollapsibleControls; + + +/** + * This class contains the dynamic playlist config and the playlist view itself + */ +class DynamicWidget : public QWidget, public Tomahawk::ViewPage +{ +Q_OBJECT +public: + explicit DynamicWidget( const dynplaylist_ptr& playlist, QWidget* parent = 0); + virtual ~DynamicWidget(); + + void loadDynamicPlaylist( const dynplaylist_ptr& playlist ); + + virtual PlaylistInterface* playlistInterface() const; + + virtual QSize sizeHint() const; + virtual void resizeEvent( QResizeEvent* ); + virtual void showEvent(QShowEvent* ); + + static void paintRoundedFilledRect( QPainter& p, QPalette& pal, QRect& r, qreal opacity = .95 ); + + virtual QWidget* widget() { return this; } + + virtual QString title() const { return m_model->title(); } + virtual QString description() const { return m_model->description(); } + virtual QPixmap pixmap() const { return QPixmap( RESPATH "images/playlist-icon.png" ); } + + virtual bool jumpToCurrentTrack(); + +public slots: + void onRevisionLoaded( const Tomahawk::DynamicPlaylistRevision& rev ); + void playlistTypeChanged(QString); + + void startStation(); + void stopStation( bool stopPlaying = true ); + + void trackStarted(); + void stationFailed( const QString& ); + + void playlistChanged( PlaylistInterface* ); + void tracksAdded(); + +private slots: + void generate( int = -1 ); + void tracksGenerated( const QList< Tomahawk::query_ptr>& queries ); + void generatorError( const QString& title, const QString& content ); + + void controlsChanged(); + void controlChanged( const Tomahawk::dyncontrol_ptr& control ); + void showPreview(); + + void layoutFloatingWidgets(); + +private: + dynplaylist_ptr m_playlist; + QVBoxLayout* m_layout; + bool m_resolveOnNextLoad; + int m_seqRevLaunched; // if we shoot off multiple createRevision calls, we don'y want to set one of the middle ones + bool m_activePlaylist; + + // loading animation + LoadingSpinner* m_loading; + + // setup controls + DynamicSetupWidget* m_setup; + + // used in OnDemand mode + bool m_runningOnDemand; + bool m_controlsChanged; + QWidget* m_steering; + + CollapsibleControls* m_controls; + + DynamicView* m_view; + DynamicModel* m_model; +}; + +}; + +#endif diff --git a/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp b/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp new file mode 100644 index 000000000..f3814c238 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.cpp @@ -0,0 +1,119 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "LoadingSpinner.h" + +#include +#include +#include +#include +#include + +#include "utils/tomahawkutils.h" + +#define ANIM_LENGTH 300 + +LoadingSpinner::LoadingSpinner( QWidget* parent ) + : QWidget(parent) + , m_showHide( new QTimeLine ) +{ + m_showHide->setDuration( 300 ); + m_showHide->setStartFrame( 0 ); + m_showHide->setEndFrame( 100 ); + m_showHide->setUpdateInterval( 20 ); + connect( m_showHide, SIGNAL( frameChanged( int ) ), this, SLOT( update() ) ); + connect( m_showHide, SIGNAL( finished() ), this, SLOT( hideFinished() ) ); + + m_anim = new QMovie( RESPATH "/images/loading-animation.gif" ); + + connect( m_anim, SIGNAL( frameChanged( int ) ), this, SLOT( update() ) ); + + setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + hide(); +} + +LoadingSpinner::~LoadingSpinner() +{ + +} + +void +LoadingSpinner::fadeIn() +{ + show(); + m_anim->start(); + m_showHide->setDirection( QTimeLine::Forward ); + m_showHide->start(); +} + +void +LoadingSpinner::fadeOut() +{ + m_showHide->setDirection( QTimeLine::Backward ); + m_showHide->start(); +} + +void +LoadingSpinner::hideFinished() +{ + if( m_showHide->direction() == QTimeLine::Backward ) { + hide(); + m_anim->stop(); + } +} + + +QSize +LoadingSpinner::sizeHint() const +{ + return QSize( 64, 64 ); +} + +void +LoadingSpinner::resizeEvent( QResizeEvent* ) +{ + reposition(); +} + +void +LoadingSpinner::reposition() +{ + if( !parentWidget() ) + return; + + int x = ( parentWidget()->width() / 2 ) - ( width() / 2 ); + int y = ( parentWidget()->height() / 2 ) - ( height() / 2 ); + move( x, y ); + resize( 64, 64 ); +} + + +void +LoadingSpinner::paintEvent( QPaintEvent* ev ) +{ + QPainter p( this ); + +// qDebug() << "FADING" << ( m_showHide->state() == QTimeLine::Running ) << "at frame:" << m_showHide->currentValue(); + if( m_showHide->state() == QTimeLine::Running ) { // showing or hiding + p.setOpacity( (qreal)m_showHide->currentValue() ); + } + p.drawPixmap( rect(), m_anim->currentPixmap() ); + + +} + diff --git a/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.h b/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.h new file mode 100644 index 000000000..0dde3520d --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/LoadingSpinner.h @@ -0,0 +1,55 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef LOADING_SPINNER_H +#define LOADING_SPINNER_H + +#include + +class QMovie; +class QTimeLine; +/** + * A small widget that displays an animated loading spinner + */ +class LoadingSpinner : public QWidget { + Q_OBJECT +public: + LoadingSpinner( QWidget* parent ); + virtual ~LoadingSpinner(); + + virtual QSize sizeHint() const; + virtual void paintEvent( QPaintEvent* ); + virtual void resizeEvent( QResizeEvent* ); + +public slots: + void fadeIn(); + void fadeOut(); + +private slots: + void hideFinished(); + +private: + void reposition(); + + QTimeLine* m_showHide; + QMovie* m_anim; +}; + +#endif + +class QPaintEvent; \ No newline at end of file diff --git a/src/libtomahawk/playlist/dynamic/widgets/MiscControlWidgets.cpp b/src/libtomahawk/playlist/dynamic/widgets/MiscControlWidgets.cpp new file mode 100644 index 000000000..7ac128829 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/MiscControlWidgets.cpp @@ -0,0 +1,41 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "MiscControlWidgets.h" + +#include +#include +#include + +using namespace Tomahawk; + +LabeledSlider::LabeledSlider( const QString& leftT, const QString& rightT, QWidget* parent ) + : QWidget( parent ) +{ + setLayout( new QHBoxLayout ); + layout()->setMargin( 0 ); + + m_leftLabel = new QLabel( leftT, this ); + layout()->addWidget( m_leftLabel ); + + m_slider = new QSlider( Qt::Horizontal, this ); + layout()->addWidget( m_slider ); + + m_rightLabel = new QLabel( rightT, this ); + layout()->addWidget( m_rightLabel ); +} diff --git a/src/libtomahawk/playlist/dynamic/widgets/MiscControlWidgets.h b/src/libtomahawk/playlist/dynamic/widgets/MiscControlWidgets.h new file mode 100644 index 000000000..245868264 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/MiscControlWidgets.h @@ -0,0 +1,46 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef MISC_CONTROL_WIDGETS_H +#define MISC_CONTROL_WIDGETS_H + +#include + +class QLabel; +class QSlider; +namespace Tomahawk +{ + +class LabeledSlider : public QWidget +{ + Q_OBJECT +public: + explicit LabeledSlider( const QString& leftT, const QString& rightT, QWidget* parent = 0 ); + + QSlider* slider() { return m_slider; } + +private: + QSlider* m_slider; + QLabel* m_leftLabel; + QLabel* m_rightLabel; +}; + + +} + +#endif diff --git a/src/libtomahawk/playlist/dynamic/widgets/ReadOrWriteWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/ReadOrWriteWidget.cpp new file mode 100644 index 000000000..2e3da1aad --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/ReadOrWriteWidget.cpp @@ -0,0 +1,101 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "ReadOrWriteWidget.h" + +#include +#include +#include + +ReadOrWriteWidget::ReadOrWriteWidget( QWidget* writableWidget, bool writable, QWidget* parent) + : QWidget( parent ) + , m_writableWidget( writableWidget ) + , m_label( 0 ) + , m_layout( 0 ) + , m_writable( writable ) +{ + m_label = new QLabel( QString(), this ); + + m_layout = new QStackedLayout( this ); + if( writableWidget ) + m_layout->addWidget( writableWidget ); + + m_layout->addWidget( m_label ); + + setWritable( m_writable ); + + setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); + setContentsMargins( 0, 0, 0, 0 ); + m_layout->setContentsMargins( 0, 0, 0, 0 ); + m_layout->setSpacing( 0 ); +} + +void +ReadOrWriteWidget::setWritable( bool write ) +{ + m_writable = write; + if( m_writableWidget && write ) + m_layout->setCurrentWidget( m_writableWidget ); + else + m_layout->setCurrentWidget( m_label ); +} + +void +ReadOrWriteWidget::setWritableWidget( QWidget* w ) +{ + if( m_writableWidget ) { + m_layout->removeWidget( m_writableWidget ); + } + + m_writableWidget = w; + m_layout->insertWidget( 0, m_writableWidget ); +} + +bool +ReadOrWriteWidget::writable() const +{ + return m_writable; +} + +QWidget* +ReadOrWriteWidget::writableWidget() const +{ + return m_writableWidget; +} + +QString +ReadOrWriteWidget::label() const +{ + return m_label->text(); +} + +void +ReadOrWriteWidget::setLabel( const QString& label ) +{ + m_label->setText( label ); +} + +QSize ReadOrWriteWidget::sizeHint() const +{ + if( m_writableWidget ) { + return m_writableWidget->sizeHint(); + } else { + return m_label->sizeHint(); + } +} + diff --git a/src/libtomahawk/playlist/dynamic/widgets/ReadOrWriteWidget.h b/src/libtomahawk/playlist/dynamic/widgets/ReadOrWriteWidget.h new file mode 100644 index 000000000..5abbbca7a --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/ReadOrWriteWidget.h @@ -0,0 +1,56 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef READ_OR_WRITE_WIDGET_H +#define READ_OR_WRITE_WIDGET_H + +#include + +class QStackedLayout; +class QLabel; + +/** + * Utility class for encapsulating either a QLabel (read-only) or an editable widget (combobox, lineedit, etc) + */ + +class ReadOrWriteWidget : public QWidget +{ + Q_OBJECT +public: + explicit ReadOrWriteWidget( QWidget* writableWidget, bool writable, QWidget* parent = 0 ); + + void setWritable( bool write ); + bool writable() const; + + void setWritableWidget( QWidget* w ); + QWidget* writableWidget() const; + + void setLabel( const QString& label ); + QString label() const; + + virtual QSize sizeHint() const; + +private: + QWidget* m_writableWidget; + QLabel* m_label; + QStackedLayout* m_layout; + + bool m_writable; +}; + +#endif diff --git a/src/libtomahawk/playlist/infobar/infobar.cpp b/src/libtomahawk/playlist/infobar/infobar.cpp new file mode 100644 index 000000000..569c8a247 --- /dev/null +++ b/src/libtomahawk/playlist/infobar/infobar.cpp @@ -0,0 +1,119 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "infobar.h" +#include "ui_infobar.h" + +#include +#include +#include + +#include "utils/tomahawkutils.h" + +#define IMAGE_HEIGHT 64 + + +InfoBar::InfoBar( QWidget* parent ) + : QWidget( parent ) + , ui( new Ui::InfoBar ) +{ + ui->setupUi( this ); + layout()->setSpacing( 0 ); + layout()->setContentsMargins( 0, 0, 0, 0 ); + + QFont boldFont = ui->captionLabel->font(); + boldFont.setPixelSize( 18 ); + boldFont.setBold( true ); + ui->captionLabel->setFont( boldFont ); + + boldFont.setPixelSize( 12 ); + ui->descriptionLabel->setFont( boldFont ); + ui->descriptionLabel->setMargin( 10 ); + + QPalette whitePal = ui->captionLabel->palette(); + whitePal.setColor( QPalette::Foreground, Qt::white ); + + ui->captionLabel->setPalette( whitePal ); + ui->descriptionLabel->setPalette( whitePal ); + + ui->captionLabel->setText( QString() ); + ui->captionLabel->setMargin( 6 ); + + ui->descriptionLabel->setText( QString() ); + ui->imageLabel->setText( QString() ); + + setAutoFillBackground( true ); +} + + +InfoBar::~InfoBar() +{ + delete ui; +} + + +void +InfoBar::setCaption( const QString& s ) +{ + ui->captionLabel->setText( s ); +} + + +void +InfoBar::setDescription( const QString& s ) +{ + ui->descriptionLabel->setText( s ); +} + + +void +InfoBar::setPixmap( const QPixmap& p ) +{ + ui->imageLabel->setPixmap( p.scaledToHeight( IMAGE_HEIGHT, Qt::SmoothTransformation ) ); +} + + +void +InfoBar::changeEvent( QEvent* e ) +{ + QWidget::changeEvent( e ); + switch ( e->type() ) + { + case QEvent::LanguageChange: +// ui->retranslateUi( this ); + break; + + default: + break; + } +} + + +void +InfoBar::resizeEvent( QResizeEvent* e ) +{ + QWidget::resizeEvent( e ); + + QLinearGradient gradient = QLinearGradient( contentsRect().topLeft(), contentsRect().bottomRight() ); + gradient.setColorAt( 0.0, QColor( 100, 100, 100 ) ); + gradient.setColorAt( 1.0, QColor( 63, 63, 63 ) ); + + QPalette p = palette(); + p.setBrush( QPalette::Window, QBrush( gradient ) ); + setPalette( p ); +} diff --git a/src/libtomahawk/playlist/infobar/infobar.h b/src/libtomahawk/playlist/infobar/infobar.h new file mode 100644 index 000000000..4bd677c78 --- /dev/null +++ b/src/libtomahawk/playlist/infobar/infobar.h @@ -0,0 +1,52 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef INFOBAR_H +#define INFOBAR_H + +#include + +#include "dllmacro.h" + +namespace Ui +{ + class InfoBar; +} + +class DLLEXPORT InfoBar : public QWidget +{ +Q_OBJECT + +public: + InfoBar( QWidget* parent = 0 ); + ~InfoBar(); + +public slots: + void setCaption( const QString& s ); + void setDescription( const QString& s ); + void setPixmap( const QPixmap& p ); + +protected: + void changeEvent( QEvent* e ); + void resizeEvent( QResizeEvent* e ); + +private: + Ui::InfoBar* ui; +}; + +#endif // INFOBAR_H diff --git a/src/libtomahawk/playlist/infobar/infobar.ui b/src/libtomahawk/playlist/infobar/infobar.ui new file mode 100644 index 000000000..764fd040a --- /dev/null +++ b/src/libtomahawk/playlist/infobar/infobar.ui @@ -0,0 +1,131 @@ + + + InfoBar + + + + 0 + 0 + 800 + 72 + + + + + 0 + 0 + + + + + 0 + 72 + + + + InfoBar + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 32 + 20 + + + + + + + + + 64 + 64 + + + + TextLabel + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 32 + 20 + + + + + + + + + + + 0 + 0 + + + + TextLabel + + + + + + + + 0 + 0 + + + + TextLabel + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 32 + 20 + + + + + + + + + ElidedLabel + QLabel +
utils/elidedlabel.h
+
+
+ + +
diff --git a/src/libtomahawk/playlist/playlistitemdelegate.cpp b/src/libtomahawk/playlist/playlistitemdelegate.cpp new file mode 100644 index 000000000..d014691c5 --- /dev/null +++ b/src/libtomahawk/playlist/playlistitemdelegate.cpp @@ -0,0 +1,120 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "playlistitemdelegate.h" + +#include +#include + +#include "query.h" +#include "result.h" + +#include "playlist/plitem.h" +#include "playlist/trackproxymodel.h" +#include "playlist/trackview.h" +#include "playlist/trackheader.h" + +#include "utils/tomahawkutils.h" + +#define PLAYING_ICON QString( RESPATH "images/now-playing-speaker.png" ) + + +PlaylistItemDelegate::PlaylistItemDelegate( TrackView* parent, TrackProxyModel* proxy ) + : QStyledItemDelegate( (QObject*)parent ) + , m_view( parent ) + , m_model( proxy ) +{ + m_nowPlayingIcon = QPixmap( PLAYING_ICON ); +} + + +void +PlaylistItemDelegate::updateRowSize( const QModelIndex& index ) +{ + emit sizeHintChanged( index ); +} + + +QSize +PlaylistItemDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + QSize size = QStyledItemDelegate::sizeHint( option, index ); + return size; +} + + +QWidget* +PlaylistItemDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + return 0; +} + + +void +PlaylistItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + PlItem* item = m_model->itemFromIndex( m_model->mapToSource( index ) ); + if ( !item || item->query().isNull() ) + return; + + painter->save(); + if ( item->query()->results().count() ) + painter->setOpacity( item->query()->results().at( 0 )->score() ); + else + painter->setOpacity( 0.0 ); + + if ( painter->opacity() < 0.3 ) + painter->setOpacity( 0.3 ); + + if ( item->isPlaying() ) + { +// painter->setRenderHint( QPainter::Antialiasing ); + + { + QRect r = option.rect.adjusted( 3, 0, 0, 0 ); + if ( m_view->header()->visualIndex( index.column() ) == 0 ) + { + r.adjust( 0, 0, 0, -3 ); + painter->drawPixmap( r.adjusted( 3, 3, 18 - r.width(), 0 ), m_nowPlayingIcon ); + r.adjust( 22, 0, 0, 3 ); + } + + painter->setPen( option.palette.text().color() ); + + QTextOption to( Qt::AlignVCenter ); + QString text = painter->fontMetrics().elidedText( index.data().toString(), Qt::ElideRight, r.width() - 3 ); + painter->drawText( r.adjusted( 0, 1, 0, 0 ), text, to ); + } + +// if ( m_view->header()->visualIndex( index.column() ) == m_view->header()->visibleSectionCount() - 1 ) + { + QRect r = QRect( 3, option.rect.y() + 1, m_view->viewport()->width() - 6, option.rect.height() - 2 ); + painter->setPen( option.palette.highlight().color() ); + QPen pen = painter->pen(); + pen.setWidth( 1.0 ); + painter->setPen( pen ); + painter->drawRoundedRect( r, 3.0, 3.0 ); + } + } + else + { + QStyledItemDelegate::paint( painter, option, index ); + } + + painter->restore(); +} diff --git a/src/libtomahawk/playlist/playlistitemdelegate.h b/src/libtomahawk/playlist/playlistitemdelegate.h new file mode 100644 index 000000000..5ba66bbd9 --- /dev/null +++ b/src/libtomahawk/playlist/playlistitemdelegate.h @@ -0,0 +1,55 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef PLAYLISTITEMDELEGATE_H +#define PLAYLISTITEMDELEGATE_H + +#include + +#include "dllmacro.h" + +class TrackProxyModel; +class TrackView; + +class DLLEXPORT PlaylistItemDelegate : public QStyledItemDelegate +{ +Q_OBJECT + +public: + PlaylistItemDelegate( TrackView* parent = 0, TrackProxyModel* proxy = 0 ); + + void updateRowSize( const QModelIndex& index ); + +public slots: + void setRemovalProgress( unsigned int progress ) { m_removalProgress = progress; } + +protected: + void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; + QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const; + + QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const; + +private: + unsigned int m_removalProgress; + QPixmap m_nowPlayingIcon; + + TrackView* m_view; + TrackProxyModel* m_model; +}; + +#endif // PLAYLISTITEMDELEGATE_H diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp new file mode 100644 index 000000000..31570a41f --- /dev/null +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -0,0 +1,840 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "playlistmanager.h" + +#include + +#include "audio/audioengine.h" +#include "utils/animatedsplitter.h" +#include "infobar/infobar.h" +#include "topbar/topbar.h" +#include "widgets/infowidgets/sourceinfowidget.h" +#include "widgets/welcomewidget.h" + +#include "collectionmodel.h" +#include "collectionflatmodel.h" +#include "collectionview.h" +#include "playlistmodel.h" +#include "playlistview.h" +#include "queueview.h" +#include "trackproxymodel.h" +#include "trackmodel.h" +#include "albumview.h" +#include "albumproxymodel.h" +#include "albummodel.h" +#include "sourcelist.h" +#include "tomahawksettings.h" + +#include "dynamic/widgets/DynamicWidget.h" + +#include "widgets/welcomewidget.h" +#include "widgets/infowidgets/sourceinfowidget.h" + +#define FILTER_TIMEOUT 280 + +using namespace Tomahawk; + +PlaylistManager* PlaylistManager::s_instance = 0; + + +PlaylistManager* +PlaylistManager::instance() +{ + return s_instance; +} + + +PlaylistManager::PlaylistManager( QObject* parent ) + : QObject( parent ) + , m_widget( new QWidget() ) + , m_welcomeWidget( new WelcomeWidget() ) + , m_currentMode( 0 ) +{ + s_instance = this; + + setHistoryPosition( -1 ); + m_widget->setLayout( new QVBoxLayout() ); + + m_topbar = new TopBar(); + m_infobar = new InfoBar(); + m_stack = new QStackedWidget(); + + QFrame* line = new QFrame(); + line->setFrameStyle( QFrame::HLine ); + line->setStyleSheet( "border: 1px solid gray;" ); + line->setMaximumHeight( 1 ); + + m_splitter = new AnimatedSplitter(); + m_splitter->setOrientation( Qt::Vertical ); + m_splitter->setChildrenCollapsible( false ); + m_splitter->setGreedyWidget( 0 ); + m_splitter->addWidget( m_stack ); + + m_queueView = new QueueView( m_splitter ); + m_queueModel = new PlaylistModel( m_queueView ); + m_queueView->queue()->setModel( m_queueModel ); + AudioEngine::instance()->setQueue( m_queueView->queue()->proxyModel() ); + + m_splitter->addWidget( m_queueView ); + m_splitter->hide( 1, false ); + + m_widget->layout()->addWidget( m_infobar ); + m_widget->layout()->addWidget( m_topbar ); + m_widget->layout()->addWidget( line ); + m_widget->layout()->addWidget( m_splitter ); + + m_superCollectionView = new CollectionView(); + m_superCollectionFlatModel = new CollectionFlatModel( m_superCollectionView ); + m_superCollectionView->setModel( m_superCollectionFlatModel ); + m_superCollectionView->setFrameShape( QFrame::NoFrame ); + m_superCollectionView->setAttribute( Qt::WA_MacShowFocusRect, 0 ); + m_superCollectionView->proxyModel()->setShowOfflineResults( false ); + + m_superAlbumView = new AlbumView(); + m_superAlbumModel = new AlbumModel( m_superAlbumView ); + m_superAlbumView->setModel( m_superAlbumModel ); + m_superAlbumView->setFrameShape( QFrame::NoFrame ); + m_superAlbumView->setAttribute( Qt::WA_MacShowFocusRect, 0 ); + + m_stack->setContentsMargins( 0, 0, 0, 0 ); + m_widget->setContentsMargins( 0, 0, 0, 0 ); + m_widget->layout()->setContentsMargins( 0, 0, 0, 0 ); + m_widget->layout()->setMargin( 0 ); + m_widget->layout()->setSpacing( 0 ); + + connect( &m_filterTimer, SIGNAL( timeout() ), SLOT( applyFilter() ) ); + + connect( m_topbar, SIGNAL( filterTextChanged( QString ) ), + SLOT( setFilter( QString ) ) ); + + connect( m_topbar, SIGNAL( flatMode() ), + SLOT( setTableMode() ) ); + + connect( m_topbar, SIGNAL( artistMode() ), + SLOT( setTreeMode() ) ); + + connect( m_topbar, SIGNAL( albumMode() ), + SLOT( setAlbumMode() ) ); +} + + +PlaylistManager::~PlaylistManager() +{ + delete m_widget; +} + + +PlaylistView* +PlaylistManager::queue() const +{ + return m_queueView->queue(); +} + + +bool +PlaylistManager::show( const Tomahawk::playlist_ptr& playlist ) +{ + PlaylistView* view; + if ( !m_playlistViews.contains( playlist ) ) + { + view = new PlaylistView(); + PlaylistModel* model = new PlaylistModel(); + view->setModel( model ); + view->setFrameShape( QFrame::NoFrame ); + view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); + model->loadPlaylist( playlist ); + playlist->resolve(); + + m_playlistViews.insert( playlist, view ); + } + else + { + view = m_playlistViews.value( playlist ); + } + + setPage( view ); + TomahawkSettings::instance()->appendRecentlyPlayedPlaylist( playlist ); + emit numSourcesChanged( SourceList::instance()->count() ); + + return true; +} + + +bool +PlaylistManager::show( const Tomahawk::dynplaylist_ptr& playlist ) +{ + if ( !m_dynamicWidgets.contains( playlist ) ) + { + m_dynamicWidgets[ playlist ] = new Tomahawk::DynamicWidget( playlist, m_stack ); + + playlist->resolve(); + } + + setPage( m_dynamicWidgets.value( playlist ) ); + + if ( playlist->mode() == Tomahawk::OnDemand ) + m_queueView->hide(); + else + m_queueView->show(); + + TomahawkSettings::instance()->appendRecentlyPlayedPlaylist( playlist ); + emit numSourcesChanged( SourceList::instance()->count() ); + + return true; +} + + +bool +PlaylistManager::show( const Tomahawk::artist_ptr& artist ) +{ + PlaylistView* view; + + if ( !m_artistViews.contains( artist ) ) + { + view = new PlaylistView(); + PlaylistModel* model = new PlaylistModel(); + view->setModel( model ); + view->setFrameShape( QFrame::NoFrame ); + view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); + model->append( artist ); + + m_artistViews.insert( artist, view ); + } + else + { + view = m_artistViews.value( artist ); + } + + setPage( view ); + emit numSourcesChanged( 1 ); + + return true; +} + + +bool +PlaylistManager::show( const Tomahawk::album_ptr& album ) +{ + PlaylistView* view; + if ( !m_albumViews.contains( album ) ) + { + view = new PlaylistView(); + PlaylistModel* model = new PlaylistModel(); + view->setModel( model ); + view->setFrameShape( QFrame::NoFrame ); + view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); + model->append( album ); + + m_albumViews.insert( album, view ); + } + else + { + view = m_albumViews.value( album ); + } + + setPage( view ); + emit numSourcesChanged( 1 ); + + return true; +} + + +bool +PlaylistManager::show( const Tomahawk::collection_ptr& collection ) +{ + m_currentCollection = collection; + if ( m_currentMode == 0 ) + { + CollectionView* view; + if ( !m_collectionViews.contains( collection ) ) + { + view = new CollectionView(); + CollectionFlatModel* model = new CollectionFlatModel(); + view->setModel( model ); + view->setFrameShape( QFrame::NoFrame ); + view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); + + model->addCollection( collection ); + + m_collectionViews.insert( collection, view ); + } + else + { + view = m_collectionViews.value( collection ); + } + + setPage( view ); + } + + if ( m_currentMode == 2 ) + { + AlbumView* aview; + if ( !m_collectionAlbumViews.contains( collection ) ) + { + aview = new AlbumView(); + AlbumModel* amodel = new AlbumModel( aview ); + aview->setModel( amodel ); + aview->setFrameShape( QFrame::NoFrame ); + aview->setAttribute( Qt::WA_MacShowFocusRect, 0 ); + amodel->addCollection( collection ); + + m_collectionAlbumViews.insert( collection, aview ); + } + else + { + aview = m_collectionAlbumViews.value( collection ); + } + + setPage( aview ); + } + + emit numSourcesChanged( 1 ); + + return true; +} + + +bool +PlaylistManager::show( const Tomahawk::source_ptr& source ) +{ + SourceInfoWidget* swidget; + if ( !m_sourceViews.contains( source ) ) + { + swidget = new SourceInfoWidget( source ); + m_sourceViews.insert( source, swidget ); + } + else + { + swidget = m_sourceViews.value( source ); + } + + setPage( swidget ); + emit numSourcesChanged( 1 ); + + return true; +} + + +bool +PlaylistManager::show( ViewPage* page ) +{ + if ( m_stack->indexOf( page->widget() ) < 0 ) + { + connect( page->widget(), SIGNAL( destroyed( QWidget* ) ), SLOT( onWidgetDestroyed( QWidget* ) ) ); + } + + setPage( page ); + + return true; +} + + +bool +PlaylistManager::showSuperCollection() +{ + QList< collection_ptr > toAdd; + foreach( const Tomahawk::source_ptr& source, SourceList::instance()->sources() ) + { + if ( !m_superCollections.contains( source->collection() ) ) + { + m_superCollections.append( source->collection() ); + toAdd << source->collection(); + m_superAlbumModel->addCollection( source->collection() ); + } + } + m_superCollectionFlatModel->addCollections( toAdd ); + + m_superCollectionFlatModel->setTitle( tr( "All available tracks" ) ); + m_superAlbumModel->setTitle( tr( "All available albums" ) ); + + if ( m_currentMode == 0 ) + { + setPage( m_superCollectionView ); + } + else if ( m_currentMode == 2 ) + { + setPage( m_superAlbumView ); + } + + emit numSourcesChanged( m_superCollections.count() ); + + return true; +} + + +void +PlaylistManager::showWelcomePage() +{ + show( m_welcomeWidget ); +} + + +void +PlaylistManager::setTableMode() +{ + qDebug() << Q_FUNC_INFO; + + m_currentMode = 0; + + if ( isSuperCollectionVisible() ) + showSuperCollection(); + else + show( m_currentCollection ); +} + + +void +PlaylistManager::setTreeMode() +{ + return; + + qDebug() << Q_FUNC_INFO; + + m_currentMode = 1; + + if ( isSuperCollectionVisible() ) + showSuperCollection(); + else + show( m_currentCollection ); +} + + +void +PlaylistManager::setAlbumMode() +{ + qDebug() << Q_FUNC_INFO; + + m_currentMode = 2; + + if ( isSuperCollectionVisible() ) + showSuperCollection(); + else + show( m_currentCollection ); +} + + +void +PlaylistManager::showQueue() +{ + if ( QThread::currentThread() != thread() ) + { + qDebug() << "Reinvoking in correct thread:" << Q_FUNC_INFO; + QMetaObject::invokeMethod( this, "showQueue", Qt::QueuedConnection ); + return; + } + + m_splitter->show( 1 ); +} + + +void +PlaylistManager::hideQueue() +{ + if ( QThread::currentThread() != thread() ) + { + qDebug() << "Reinvoking in correct thread:" << Q_FUNC_INFO; + QMetaObject::invokeMethod( this, "hideQueue", Qt::QueuedConnection ); + return; + } + + m_splitter->hide( 1 ); +} + + +void +PlaylistManager::historyBack() +{ + if ( m_historyPosition < 1 ) + return; + + showHistory( m_historyPosition - 1 ); +} + + +void +PlaylistManager::historyForward() +{ + if ( m_historyPosition >= m_pageHistory.count() - 1 ) + return; + + showHistory( m_historyPosition + 1 ); +} + + +void +PlaylistManager::showHistory( int historyPosition ) +{ + if ( historyPosition < 0 || historyPosition >= m_pageHistory.count() ) + { + qDebug() << "History position out of bounds!" << historyPosition << m_pageHistory.count(); + Q_ASSERT( false ); + return; + } + + setHistoryPosition( historyPosition ); + ViewPage* page = m_pageHistory.at( historyPosition ); + setPage( page, false ); +} + + +void +PlaylistManager::setFilter( const QString& filter ) +{ + m_filter = filter; + + m_filterTimer.stop(); + m_filterTimer.setInterval( FILTER_TIMEOUT ); + m_filterTimer.setSingleShot( true ); + m_filterTimer.start(); +} + + +void +PlaylistManager::applyFilter() +{ + qDebug() << Q_FUNC_INFO; + + if ( currentPlaylistInterface() && currentPlaylistInterface()->filter() != m_filter ) + currentPlaylistInterface()->setFilter( m_filter ); +} + + +void +PlaylistManager::setPage( ViewPage* page, bool trackHistory ) +{ + if ( !page ) + return; + + unlinkPlaylist(); + + if ( !m_pageHistory.contains( page ) ) + { + m_stack->addWidget( page->widget() ); + } + else + { + if ( trackHistory ) + m_pageHistory.removeAll( page ); + } + + if ( trackHistory ) + { + m_pageHistory << page; + setHistoryPosition( m_pageHistory.count() - 1 ); + } + + if ( playlistForInterface( currentPlaylistInterface() ) ) + emit playlistActivated( playlistForInterface( currentPlaylistInterface() ) ); + if ( dynamicPlaylistForInterface( currentPlaylistInterface() ) ) + emit dynamicPlaylistActivated( dynamicPlaylistForInterface( currentPlaylistInterface() ) ); + if ( collectionForInterface( currentPlaylistInterface() ) ) + emit collectionActivated( collectionForInterface( currentPlaylistInterface() ) ); + if ( isSuperCollectionVisible() ) + emit superCollectionActivated(); + if ( !currentPlaylistInterface() ) + emit tempPageActivated(); + + if ( !AudioEngine::instance()->isPlaying() ) + AudioEngine::instance()->setPlaylist( currentPlaylistInterface() ); + + m_stack->setCurrentWidget( page->widget() ); + updateView(); +} + + +void +PlaylistManager::unlinkPlaylist() +{ + if ( currentPlaylistInterface() ) + { + disconnect( currentPlaylistInterface()->object(), SIGNAL( sourceTrackCountChanged( unsigned int ) ), + this, SIGNAL( numTracksChanged( unsigned int ) ) ); + + disconnect( currentPlaylistInterface()->object(), SIGNAL( trackCountChanged( unsigned int ) ), + this, SIGNAL( numShownChanged( unsigned int ) ) ); + + disconnect( currentPlaylistInterface()->object(), SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ), + this, SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ) ); + + disconnect( currentPlaylistInterface()->object(), SIGNAL( shuffleModeChanged( bool ) ), + this, SIGNAL( shuffleModeChanged( bool ) ) ); + } +} + + +void +PlaylistManager::updateView() +{ + if ( currentPlaylistInterface() ) + { + connect( currentPlaylistInterface()->object(), SIGNAL( sourceTrackCountChanged( unsigned int ) ), + SIGNAL( numTracksChanged( unsigned int ) ) ); + + connect( currentPlaylistInterface()->object(), SIGNAL( trackCountChanged( unsigned int ) ), + SIGNAL( numShownChanged( unsigned int ) ) ); + + connect( currentPlaylistInterface()->object(), SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ), + SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ) ); + + connect( currentPlaylistInterface()->object(), SIGNAL( shuffleModeChanged( bool ) ), + SIGNAL( shuffleModeChanged( bool ) ) ); + + m_topbar->setFilter( currentPlaylistInterface()->filter() ); + } + + if ( currentPage()->showStatsBar() && currentPlaylistInterface() ) + { + emit numTracksChanged( currentPlaylistInterface()->unfilteredTrackCount() ); + + if ( !currentPlaylistInterface()->filter().isEmpty() ) + emit numShownChanged( currentPlaylistInterface()->trackCount() ); + else + emit numShownChanged( currentPlaylistInterface()->unfilteredTrackCount() ); + + emit repeatModeChanged( currentPlaylistInterface()->repeatMode() ); + emit shuffleModeChanged( currentPlaylistInterface()->shuffled() ); + emit modeChanged( currentPlaylistInterface()->viewMode() ); + } + + if ( currentPage()->queueVisible() ) + m_queueView->show(); + else + m_queueView->hide(); + + emit statsAvailable( currentPage()->showStatsBar() ); + emit modesAvailable( currentPage()->showModes() ); + + if ( !currentPage()->showStatsBar() && !currentPage()->showModes() ) + m_topbar->setVisible( false ); + else + m_topbar->setVisible( true ); + + m_infobar->setCaption( currentPage()->title() ); + m_infobar->setDescription( currentPage()->description() ); + m_infobar->setPixmap( currentPage()->pixmap() ); +} + + +void +PlaylistManager::onWidgetDestroyed( QWidget* widget ) +{ + qDebug() << "Destroyed child:" << widget; + + bool resetWidget = ( m_stack->currentWidget() == widget ); + m_stack->removeWidget( widget ); + + for ( int i = 0; i < m_pageHistory.count(); i++ ) + { + ViewPage* page = m_pageHistory.at( i ); + if ( page->widget() == widget ) + { + m_pageHistory.removeAt( i ); + if ( m_historyPosition > i ) + m_historyPosition--; + break; + } + } + + if ( resetWidget ) + { + if ( m_pageHistory.count() ) + showHistory( m_pageHistory.count() - 1 ); + } +} + + +void +PlaylistManager::setRepeatMode( PlaylistInterface::RepeatMode mode ) +{ + if ( currentPlaylistInterface() ) + currentPlaylistInterface()->setRepeatMode( mode ); +} + + +void +PlaylistManager::setShuffled( bool enabled ) +{ + if ( currentPlaylistInterface() ) + currentPlaylistInterface()->setShuffled( enabled ); +} + + +void +PlaylistManager::createPlaylist( const Tomahawk::source_ptr& src, + const QVariant& contents ) +{ + Tomahawk::playlist_ptr p = Tomahawk::playlist_ptr( new Tomahawk::Playlist( src ) ); + QJson::QObjectHelper::qvariant2qobject( contents.toMap(), p.data() ); + p->reportCreated( p ); +} + + +void +PlaylistManager::createDynamicPlaylist( const Tomahawk::source_ptr& src, + const QVariant& contents ) +{ + Tomahawk::dynplaylist_ptr p = Tomahawk::dynplaylist_ptr( new Tomahawk::DynamicPlaylist( src, contents.toMap().value( "type", QString() ).toString() ) ); + QJson::QObjectHelper::qvariant2qobject( contents.toMap(), p.data() ); + p->reportCreated( p ); +} + + +ViewPage* +PlaylistManager::pageForInterface( PlaylistInterface* interface ) const +{ + for ( int i = 0; i < m_pageHistory.count(); i++ ) + { + ViewPage* page = m_pageHistory.at( i ); + if ( page->playlistInterface() == interface ) + return page; + } + + return 0; +} + + +int +PlaylistManager::positionInHistory( ViewPage* page ) const +{ + for ( int i = 0; i < m_pageHistory.count(); i++ ) + { + if ( page == m_pageHistory.at( i ) ) + return i; + } + + return -1; +} + + +PlaylistInterface* +PlaylistManager::currentPlaylistInterface() const +{ + if ( currentPage() ) + return currentPage()->playlistInterface(); + else + return 0; +} + + +Tomahawk::ViewPage* +PlaylistManager::currentPage() const +{ + if ( m_historyPosition >= 0 ) + return m_pageHistory.at( m_historyPosition ); + else + return 0; +} + + +void +PlaylistManager::setHistoryPosition( int position ) +{ + m_historyPosition = position; + + emit historyBackAvailable( m_historyPosition > 0 ); + emit historyForwardAvailable( m_historyPosition < m_pageHistory.count() - 1 ); +} + + +Tomahawk::playlist_ptr +PlaylistManager::playlistForInterface( PlaylistInterface* interface ) const +{ + foreach ( PlaylistView* view, m_playlistViews.values() ) + { + if ( view->playlistInterface() == interface ) + { + return m_playlistViews.key( view ); + } + } + + return playlist_ptr(); +} + + +Tomahawk::dynplaylist_ptr +PlaylistManager::dynamicPlaylistForInterface( PlaylistInterface* interface ) const +{ + foreach ( DynamicWidget* view, m_dynamicWidgets.values() ) + { + if ( view->playlistInterface() == interface ) + { + return m_dynamicWidgets.key( view ); + } + } + + return dynplaylist_ptr(); +} + + +Tomahawk::collection_ptr +PlaylistManager::collectionForInterface( PlaylistInterface* interface ) const +{ + foreach ( CollectionView* view, m_collectionViews.values() ) + { + if ( view->playlistInterface() == interface ) + { + return m_collectionViews.key( view ); + } + } + foreach ( AlbumView* view, m_collectionAlbumViews.values() ) + { + if ( view->playlistInterface() == interface ) + { + return m_collectionAlbumViews.key( view ); + } + } + + return collection_ptr(); +} + + +bool +PlaylistManager::isSuperCollectionVisible() const +{ + return ( m_pageHistory.count() && + ( currentPage()->playlistInterface() == m_superCollectionView->playlistInterface() || + currentPage()->playlistInterface() == m_superAlbumView->playlistInterface() ) ); +} + + +void +PlaylistManager::showCurrentTrack() +{ + ViewPage* page = pageForInterface( AudioEngine::instance()->currentTrackPlaylist() ); + + if ( page ) + { + setPage( page ); + page->jumpToCurrentTrack(); + } +} + + +void +PlaylistManager::onPlayClicked() +{ + emit playClicked(); +} + + +void +PlaylistManager::onPauseClicked() +{ + emit pauseClicked(); +} diff --git a/src/libtomahawk/playlist/playlistmanager.h b/src/libtomahawk/playlist/playlistmanager.h new file mode 100644 index 000000000..278037e98 --- /dev/null +++ b/src/libtomahawk/playlist/playlistmanager.h @@ -0,0 +1,187 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef PLAYLISTMANAGER_H +#define PLAYLISTMANAGER_H + +#include +#include +#include + +#include "collection.h" +#include "playlistinterface.h" +#include "viewpage.h" + +#include "dllmacro.h" + +class AnimatedSplitter; +class AlbumModel; +class AlbumView; +class CollectionModel; +class CollectionFlatModel; +class CollectionView; +class PlaylistModel; +class PlaylistView; +class QueueView; +class TrackProxyModel; +class TrackModel; +class TrackView; +class SourceInfoWidget; +class InfoBar; +class TopBar; +class WelcomeWidget; + +namespace Tomahawk +{ + class DynamicWidget; +} + +class DLLEXPORT PlaylistManager : public QObject +{ +Q_OBJECT + +public: + static PlaylistManager* instance(); + + explicit PlaylistManager( QObject* parent = 0 ); + ~PlaylistManager(); + + QWidget* widget() const { return m_widget; } + PlaylistView* queue() const; + + bool isSuperCollectionVisible() const; + + PlaylistInterface* currentPlaylistInterface() const; + Tomahawk::ViewPage* currentPage() const; + Tomahawk::ViewPage* pageForInterface( PlaylistInterface* interface ) const; + int positionInHistory( Tomahawk::ViewPage* page ) const; + + bool show( const Tomahawk::playlist_ptr& playlist ); + bool show( const Tomahawk::dynplaylist_ptr& playlist ); + bool show( const Tomahawk::artist_ptr& artist ); + bool show( const Tomahawk::album_ptr& album ); + bool show( const Tomahawk::collection_ptr& collection ); + bool show( const Tomahawk::source_ptr& source ); + + bool show( Tomahawk::ViewPage* page ); + +signals: + void numSourcesChanged( unsigned int sources ); + void numTracksChanged( unsigned int tracks ); + void numArtistsChanged( unsigned int artists ); + void numShownChanged( unsigned int shown ); + + void repeatModeChanged( PlaylistInterface::RepeatMode mode ); + void shuffleModeChanged( bool enabled ); + + void statsAvailable( bool b ); + void modesAvailable( bool b ); + void modeChanged( PlaylistInterface::ViewMode mode ); + + void playClicked(); + void pauseClicked(); + + void historyBackAvailable( bool avail ); + void historyForwardAvailable( bool avail ); + + void tempPageActivated(); + void superCollectionActivated(); + void collectionActivated( const Tomahawk::collection_ptr& collection ); + void playlistActivated( const Tomahawk::playlist_ptr& playlist ); + void dynamicPlaylistActivated( const Tomahawk::dynplaylist_ptr& playlist ); + +public slots: + bool showSuperCollection(); + void showWelcomePage(); + void showCurrentTrack(); + + void historyBack(); + void historyForward(); + void showHistory( int historyPosition ); + + void setTreeMode(); + void setTableMode(); + void setAlbumMode(); + + void showQueue(); + void hideQueue(); + + void setRepeatMode( PlaylistInterface::RepeatMode mode ); + void setShuffled( bool enabled ); + + // called by the playlist creation dbcmds + void createPlaylist( const Tomahawk::source_ptr& src, const QVariant& contents ); + void createDynamicPlaylist( const Tomahawk::source_ptr& src, const QVariant& contents ); + + // ugh need to set up the connection in tomahawk to libtomahawk + void onPlayClicked(); + void onPauseClicked(); + +private slots: + void setFilter( const QString& filter ); + void applyFilter(); + void onWidgetDestroyed( QWidget* widget ); + +private: + void setHistoryPosition( int position ); + void setPage( Tomahawk::ViewPage* page, bool trackHistory = true ); + void updateView(); + void unlinkPlaylist(); + + Tomahawk::playlist_ptr playlistForInterface( PlaylistInterface* interface ) const; + Tomahawk::dynplaylist_ptr dynamicPlaylistForInterface( PlaylistInterface* interface ) const; + Tomahawk::collection_ptr collectionForInterface( PlaylistInterface* interface ) const; + + QWidget* m_widget; + InfoBar* m_infobar; + TopBar* m_topbar; + QStackedWidget* m_stack; + AnimatedSplitter* m_splitter; + + PlaylistModel* m_queueModel; + QueueView* m_queueView; + + AlbumModel* m_superAlbumModel; + AlbumView* m_superAlbumView; + CollectionFlatModel* m_superCollectionFlatModel; + CollectionView* m_superCollectionView; + WelcomeWidget* m_welcomeWidget; + + QList< Tomahawk::collection_ptr > m_superCollections; + + QHash< Tomahawk::dynplaylist_ptr, Tomahawk::DynamicWidget* > m_dynamicWidgets; + QHash< Tomahawk::collection_ptr, CollectionView* > m_collectionViews; + QHash< Tomahawk::collection_ptr, AlbumView* > m_collectionAlbumViews; + QHash< Tomahawk::artist_ptr, PlaylistView* > m_artistViews; + QHash< Tomahawk::album_ptr, PlaylistView* > m_albumViews; + QHash< Tomahawk::playlist_ptr, PlaylistView* > m_playlistViews; + QHash< Tomahawk::source_ptr, SourceInfoWidget* > m_sourceViews; + + QList m_pageHistory; + int m_historyPosition; + + Tomahawk::collection_ptr m_currentCollection; + int m_currentMode; + + QTimer m_filterTimer; + QString m_filter; + + static PlaylistManager* s_instance; +}; + +#endif // PLAYLISTMANAGER_H diff --git a/src/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp similarity index 51% rename from src/playlist/playlistmodel.cpp rename to src/libtomahawk/playlist/playlistmodel.cpp index be7c1e2af..8e4d24ae6 100644 --- a/src/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -1,10 +1,32 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "playlistmodel.h" #include #include #include -#include "tomahawk/album.h" +#include "album.h" + +#include "database/database.h" +#include "database/databasecommand_playbackhistory.h" +#include "dynamic/GeneratorInterface.h" using namespace Tomahawk; @@ -14,6 +36,8 @@ PlaylistModel::PlaylistModel( QObject* parent ) , m_waitForUpdate( false ) { qDebug() << Q_FUNC_INFO; + + setReadOnly( false ); } @@ -44,100 +68,182 @@ PlaylistModel::headerData( int section, Qt::Orientation orientation, int role ) void -PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist ) +PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEntries ) { if ( !m_playlist.isNull() ) disconnect( m_playlist.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), this, SLOT( onRevisionLoaded( Tomahawk::PlaylistRevision ) ) ); - if ( rowCount( QModelIndex() ) ) + if ( rowCount( QModelIndex() ) && loadEntries ) { - emit beginRemoveRows( QModelIndex(), 0, rowCount( QModelIndex() ) - 1 ); - delete m_rootItem; - emit endRemoveRows(); - m_rootItem = new PlItem( 0, this ); + clear(); } m_playlist = playlist; connect( playlist.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), SLOT( onRevisionLoaded( Tomahawk::PlaylistRevision ) ) ); setReadOnly( !m_playlist->author()->isLocal() ); + setTitle( playlist->title() ); + setDescription( tr( "A playlist by %1" ).arg( playlist->author()->isLocal() ? tr( "you" ) : playlist->author()->friendlyName() ) ); + + if ( !loadEntries ) + return; PlItem* plitem; QList entries = playlist->entries(); - int c = rowCount( QModelIndex() ); - - qDebug() << "starting loading" << playlist->title(); - emit loadingStarts(); - emit beginInsertRows( QModelIndex(), c, c + entries.count() - 1 ); - - foreach( const plentry_ptr& entry, entries ) + if ( entries.count() ) { - qDebug() << entry->query()->toString(); - plitem = new PlItem( entry, m_rootItem ); - plitem->index = createIndex( m_rootItem->children.count() - 1, 0, plitem ); + int c = rowCount( QModelIndex() ); - connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); + qDebug() << "Starting loading" << playlist->title(); + emit loadingStarts(); + emit beginInsertRows( QModelIndex(), c, c + entries.count() - 1 ); + + foreach( const plentry_ptr& entry, entries ) + { + qDebug() << entry->query()->toString(); + plitem = new PlItem( entry, m_rootItem ); + plitem->index = createIndex( m_rootItem->children.count() - 1, 0, plitem ); + + connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); + } + + emit endInsertRows(); } + else + qDebug() << "Playlist seems empty:" << playlist->title(); - emit endInsertRows(); - - qDebug() << rowCount( QModelIndex() ); emit loadingFinished(); + emit trackCountChanged( rowCount( QModelIndex() ) ); } void -PlaylistModel::loadAlbum( const Tomahawk::album_ptr& album ) +PlaylistModel::loadHistory( const Tomahawk::source_ptr& source, unsigned int amount ) +{ + if ( rowCount( QModelIndex() ) ) + { + clear(); + } + + m_playlist.clear(); + setReadOnly( true ); + + DatabaseCommand_PlaybackHistory* cmd = new DatabaseCommand_PlaybackHistory( source ); + cmd->setLimit( amount ); + + connect( cmd, SIGNAL( tracks( QList ) ), + SLOT( onTracksAdded( QList ) ), Qt::QueuedConnection ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + + +void +PlaylistModel::clear() +{ + if ( rowCount( QModelIndex() ) ) + { + emit beginResetModel(); + delete m_rootItem; + m_rootItem = 0; + emit endResetModel(); + m_rootItem = new PlItem( 0, this ); + } +} + + +void +PlaylistModel::append( const Tomahawk::query_ptr& query ) +{ + if ( query.isNull() ) + return; + + QList< Tomahawk::query_ptr > ql; + ql << query; + + onTracksAdded( ql ); +} + + +void +PlaylistModel::append( const Tomahawk::album_ptr& album ) { if ( album.isNull() ) return; - if ( rowCount( QModelIndex() ) ) - { - emit beginRemoveRows( QModelIndex(), 0, rowCount( QModelIndex() ) - 1 ); - delete m_rootItem; - emit endRemoveRows(); - m_rootItem = new PlItem( 0, this ); - } + connect( album.data(), SIGNAL( tracksAdded( QList ) ), + SLOT( onTracksAdded( QList ) ) ); - m_playlist.clear(); - setReadOnly( false ); - - connect( album.data(), SIGNAL( tracksAdded( QList, Tomahawk::collection_ptr ) ), - SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ) ); - - onTracksAdded( album->tracks(), album->collection() ); + onTracksAdded( album->tracks() ); } void -PlaylistModel::onTracksAdded( const QList& tracks, const Tomahawk::collection_ptr& collection ) +PlaylistModel::append( const Tomahawk::artist_ptr& artist ) { - if ( !tracks.count() ) + if ( artist.isNull() ) return; - int c = rowCount( QModelIndex() ); + connect( artist.data(), SIGNAL( tracksAdded( QList ) ), + SLOT( onTracksAdded( QList ) ) ); + + onTracksAdded( artist->tracks() ); +} + + +void +PlaylistModel::insert( unsigned int row, const Tomahawk::query_ptr& query ) +{ + if ( query.isNull() ) + return; + + QList< Tomahawk::query_ptr > ql; + ql << query; + + onTracksInserted( row, ql ); +} + + +void +PlaylistModel::onTracksAdded( const QList& tracks ) +{ + onTracksInserted( rowCount( QModelIndex() ), tracks ); +} + + +void +PlaylistModel::onTracksInserted( unsigned int row, const QList& tracks ) +{ + if ( !tracks.count() ) + { + emit trackCountChanged( rowCount( QModelIndex() ) ); + return; + } + + int c = row; QPair< int, int > crows; crows.first = c; crows.second = c + tracks.count() - 1; emit beginInsertRows( QModelIndex(), crows.first, crows.second ); + int i = 0; PlItem* plitem; foreach( const query_ptr& query, tracks ) { plentry_ptr entry = plentry_ptr( new PlaylistEntry() ); entry->setQuery( query ); - plitem = new PlItem( entry, m_rootItem ); - plitem->index = createIndex( m_rootItem->children.count() - 1, 0, plitem ); + plitem = new PlItem( entry, m_rootItem, row + i ); + plitem->index = createIndex( row + i, 0, plitem ); + + i++; connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); } emit endInsertRows(); emit trackCountChanged( rowCount( QModelIndex() ) ); - qDebug() << rowCount( QModelIndex() ); } @@ -145,8 +251,8 @@ void PlaylistModel::onDataChanged() { PlItem* p = (PlItem*)sender(); -// emit itemSizeChanged( p->index ); - emit dataChanged( p->index, p->index.sibling( p->index.row(), columnCount() - 1 ) ); + if ( p && p->index.isValid() ) + emit dataChanged( p->index, p->index.sibling( p->index.row(), columnCount() - 1 ) ); } @@ -171,7 +277,9 @@ PlaylistModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int r if ( action == Qt::IgnoreAction || isReadOnly() ) return true; - if ( !data->hasFormat( "application/tomahawk.query.list" ) && !data->hasFormat( "application/tomahawk.plentry.list" ) ) + if ( !data->hasFormat( "application/tomahawk.query.list" ) + && !data->hasFormat( "application/tomahawk.plentry.list" ) + && !data->hasFormat( "application/tomahawk.result.list" ) ) return false; int beginRow; @@ -198,7 +306,7 @@ PlaylistModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int r Tomahawk::query_ptr* query = reinterpret_cast(qptr); if ( query && !query->isNull() ) { - qDebug() << "Dropped query item:" << query->data()->artist() << "-" << query->data()->track(); + qDebug() << "Dropped query item:" << query->data()->artist() << "-" << query->data()->track() << action; queries << *query; } } @@ -224,6 +332,11 @@ PlaylistModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int r connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); } emit endInsertRows(); + + if ( action == Qt::CopyAction ) + { + onPlaylistChanged(); + } } return true; @@ -231,7 +344,7 @@ PlaylistModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int r void -PlaylistModel::onPlaylistChanged() +PlaylistModel::onPlaylistChanged( bool waitForUpdate ) { qDebug() << Q_FUNC_INFO; @@ -239,16 +352,28 @@ PlaylistModel::onPlaylistChanged() return; QList l = playlistEntries(); - if ( !l.count() ) - return; foreach( const plentry_ptr& ple, l ) { qDebug() << "updateinternal:" << ple->query()->toString(); } + m_waitForUpdate = waitForUpdate; QString newrev = uuid(); - m_playlist->createNewRevision( newrev, m_playlist->currentrevision(), l ); + if( dynplaylist_ptr dynplaylist = m_playlist.dynamicCast() ) + { + if( dynplaylist->mode() == OnDemand ) + { + dynplaylist->createNewRevision( newrev ); + } + else if( dynplaylist->mode() == Static ) + { + dynplaylist->createNewRevision( newrev, dynplaylist->currentrevision(), dynplaylist->type(), dynplaylist->generator()->controls(), l ); + } + } else + { + m_playlist->createNewRevision( newrev, m_playlist->currentrevision(), l ); + } } @@ -272,13 +397,15 @@ PlaylistModel::playlistEntries() const void -PlaylistModel::removeIndex( const QModelIndex& index ) +PlaylistModel::removeIndex( const QModelIndex& index, bool moreToCome ) { if ( isReadOnly() ) return; TrackModel::removeIndex( index ); - m_waitForUpdate = true; - onPlaylistChanged(); + if ( !moreToCome && !m_playlist.isNull() ) + { + onPlaylistChanged(); + } } diff --git a/src/libtomahawk/playlist/playlistmodel.h b/src/libtomahawk/playlist/playlistmodel.h new file mode 100644 index 000000000..e69bf59e1 --- /dev/null +++ b/src/libtomahawk/playlist/playlistmodel.h @@ -0,0 +1,92 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef PLAYLISTMODEL_H +#define PLAYLISTMODEL_H + +#include +#include + +#include "plitem.h" +#include "trackmodel.h" +#include "collection.h" +#include "query.h" +#include "typedefs.h" +#include "playlist.h" +#include "playlistinterface.h" + +#include "dllmacro.h" + +class QMetaData; + +class DLLEXPORT PlaylistModel : public TrackModel +{ +Q_OBJECT + +public: + explicit PlaylistModel( QObject* parent = 0 ); + ~PlaylistModel(); + + int columnCount( const QModelIndex& parent = QModelIndex() ) const; + + QVariant data( const QModelIndex& index, int role ) const; + QVariant headerData( int section, Qt::Orientation orientation, int role ) const; + + virtual bool dropMimeData( const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent ); + + Tomahawk::playlist_ptr playlist() const { return m_playlist; } + + virtual void loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEntries = true ); + void loadHistory( const Tomahawk::source_ptr& source, unsigned int amount = 50 ); + + void clear(); + + void append( const Tomahawk::query_ptr& query ); + void append( const Tomahawk::album_ptr& album ); + void append( const Tomahawk::artist_ptr& artist ); + + void insert( unsigned int row, const Tomahawk::query_ptr& query ); + + virtual void removeIndex( const QModelIndex& index, bool moreToCome = false ); + +signals: + void repeatModeChanged( PlaylistInterface::RepeatMode mode ); + void shuffleModeChanged( bool enabled ); + + void itemSizeChanged( const QModelIndex& index ); + + void loadingStarts(); + void loadingFinished(); + +private slots: + void onDataChanged(); + + void onRevisionLoaded( Tomahawk::PlaylistRevision revision ); + void onPlaylistChanged( bool waitForUpdate = true ); + + void onTracksAdded( const QList& tracks ); + void onTracksInserted( unsigned int row, const QList& tracks ); + +private: + QList playlistEntries() const; + + Tomahawk::playlist_ptr m_playlist; + bool m_waitForUpdate; +}; + +#endif // PLAYLISTMODEL_H diff --git a/src/libtomahawk/playlist/playlistproxymodel.cpp b/src/libtomahawk/playlist/playlistproxymodel.cpp new file mode 100644 index 000000000..272721234 --- /dev/null +++ b/src/libtomahawk/playlist/playlistproxymodel.cpp @@ -0,0 +1,25 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "playlistproxymodel.h" + + +PlaylistProxyModel::PlaylistProxyModel( QObject* parent ) + : TrackProxyModel( parent ) +{ +} diff --git a/src/libtomahawk/playlist/playlistproxymodel.h b/src/libtomahawk/playlist/playlistproxymodel.h new file mode 100644 index 000000000..f0cddc616 --- /dev/null +++ b/src/libtomahawk/playlist/playlistproxymodel.h @@ -0,0 +1,34 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef PLAYLISTPROXYMODEL_H +#define PLAYLISTPROXYMODEL_H + +#include "trackproxymodel.h" + +#include "dllmacro.h" + +class DLLEXPORT PlaylistProxyModel : public TrackProxyModel +{ +Q_OBJECT + +public: + explicit PlaylistProxyModel( QObject* parent = 0 ); +}; + +#endif // PLAYLISTPROXYMODEL_H diff --git a/src/libtomahawk/playlist/playlistview.cpp b/src/libtomahawk/playlist/playlistview.cpp new file mode 100644 index 000000000..9b66257ad --- /dev/null +++ b/src/libtomahawk/playlist/playlistview.cpp @@ -0,0 +1,156 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "playlistview.h" + +#include +#include +#include + +#include "playlist/playlistproxymodel.h" +#include "widgets/overlaywidget.h" + +using namespace Tomahawk; + + +PlaylistView::PlaylistView( QWidget* parent ) + : TrackView( parent ) +{ + setProxyModel( new PlaylistProxyModel( this ) ); + + setContextMenuPolicy( Qt::CustomContextMenu ); + connect( this, SIGNAL( customContextMenuRequested( const QPoint& ) ), SLOT( onCustomContextMenu( const QPoint& ) ) ); +} + + +PlaylistView::~PlaylistView() +{ + qDebug() << Q_FUNC_INFO; +} + + +void +PlaylistView::setModel( PlaylistModel* model ) +{ + m_model = model; + + TrackView::setModel( model ); + setColumnHidden( 5, true ); // Hide age column per default + + if ( !model->playlist().isNull() ) + setGuid( QString( "playlistview/%1" ).arg( model->playlist()->guid() ) ); + else + setGuid( "playlistview" ); + + connect( model, SIGNAL( trackCountChanged( unsigned int ) ), SLOT( onTrackCountChanged( unsigned int ) ) ); +} + + +void +PlaylistView::setupMenus() +{ + m_itemMenu.clear(); + + unsigned int i = 0; + foreach( const QModelIndex& idx, selectedIndexes() ) + if ( idx.column() == 0 ) + i++; + + m_playItemAction = m_itemMenu.addAction( tr( "&Play" ) ); + m_addItemsToQueueAction = m_itemMenu.addAction( tr( "Add to &Queue" ) ); + m_itemMenu.addSeparator(); +// m_addItemsToPlaylistAction = m_itemMenu.addAction( tr( "&Add to Playlist" ) ); +// m_itemMenu.addSeparator(); + m_deleteItemsAction = m_itemMenu.addAction( i > 1 ? tr( "&Delete Items" ) : tr( "&Delete Item" ) ); + + if ( model() ) + m_deleteItemsAction->setEnabled( !model()->isReadOnly() ); + + connect( m_playItemAction, SIGNAL( triggered() ), SLOT( playItem() ) ); + connect( m_addItemsToQueueAction, SIGNAL( triggered() ), SLOT( addItemsToQueue() ) ); +// connect( m_addItemsToPlaylistAction, SIGNAL( triggered() ), SLOT( addItemsToPlaylist() ) ); + connect( m_deleteItemsAction, SIGNAL( triggered() ), SLOT( deleteItems() ) ); +} + + +void +PlaylistView::onCustomContextMenu( const QPoint& pos ) +{ + qDebug() << Q_FUNC_INFO; + setupMenus(); + + QModelIndex idx = indexAt( pos ); + idx = idx.sibling( idx.row(), 0 ); + setContextMenuIndex( idx ); + + if ( !idx.isValid() ) + return; + + m_itemMenu.exec( mapToGlobal( pos ) ); +} + + +void +PlaylistView::keyPressEvent( QKeyEvent* event ) +{ + qDebug() << Q_FUNC_INFO; + TrackView::keyPressEvent( event ); + + if ( !model() ) + return; + + if ( event->key() == Qt::Key_Delete ) + { + qDebug() << "Removing selected items"; + proxyModel()->removeIndexes( selectedIndexes() ); + } +} + + +void +PlaylistView::addItemsToPlaylist() +{ +} + + +void +PlaylistView::deleteItems() +{ + proxyModel()->removeIndexes( selectedIndexes() ); +} + + +void +PlaylistView::onTrackCountChanged( unsigned int tracks ) +{ + if ( tracks == 0 ) + { + overlay()->setText( tr( "This playlist is currently empty. Add some tracks to it and enjoy the music!" ) ); + overlay()->show(); + } + else + overlay()->hide(); +} + + +bool +PlaylistView::jumpToCurrentTrack() +{ + scrollTo( proxyModel()->currentItem(), QAbstractItemView::PositionAtCenter ); + return true; +} diff --git a/src/libtomahawk/playlist/playlistview.h b/src/libtomahawk/playlist/playlistview.h new file mode 100644 index 000000000..3629099b1 --- /dev/null +++ b/src/libtomahawk/playlist/playlistview.h @@ -0,0 +1,76 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef PLAYLISTVIEW_H +#define PLAYLISTVIEW_H + +#include + +#include "playlist/trackproxymodel.h" +#include "playlist/playlistmodel.h" +#include "trackview.h" +#include "viewpage.h" + +#include "dllmacro.h" + +class PlaylistModel; + +class DLLEXPORT PlaylistView : public TrackView, public Tomahawk::ViewPage +{ +Q_OBJECT + +public: + explicit PlaylistView( QWidget* parent = 0 ); + ~PlaylistView(); + + PlaylistModel* playlistModel() const { return m_model; } + virtual void setModel( PlaylistModel* model ); + + virtual QWidget* widget() { return this; } + virtual PlaylistInterface* playlistInterface() const { return proxyModel(); } + + virtual QString title() const { return playlistModel()->title(); } + virtual QString description() const { return m_model->description(); } + virtual QPixmap pixmap() const { return QPixmap( RESPATH "images/playlist-icon.png" ); } + + virtual bool jumpToCurrentTrack(); + +protected: + void keyPressEvent( QKeyEvent* event ); + +private slots: + void onCustomContextMenu( const QPoint& pos ); + void onTrackCountChanged( unsigned int tracks ); + + void addItemsToPlaylist(); + void deleteItems(); + +private: + void setupMenus(); + + PlaylistModel* m_model; + + QMenu m_itemMenu; + + QAction* m_playItemAction; + QAction* m_addItemsToQueueAction; + QAction* m_addItemsToPlaylistAction; + QAction* m_deleteItemsAction; +}; + +#endif // PLAYLISTVIEW_H diff --git a/src/playlist/plitem.cpp b/src/libtomahawk/playlist/plitem.cpp similarity index 53% rename from src/playlist/plitem.cpp rename to src/libtomahawk/playlist/plitem.cpp index efaa0eb70..853df520a 100644 --- a/src/playlist/plitem.cpp +++ b/src/libtomahawk/playlist/plitem.cpp @@ -1,6 +1,26 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "plitem.h" #include "utils/tomahawkutils.h" +#include "playlist.h" +#include "query.h" #include @@ -12,13 +32,13 @@ PlItem::~PlItem() // Don't use qDeleteAll here! The children will remove themselves // from the list when they get deleted and the qDeleteAll iterator // will fail badly! - for ( int i = children.count() - 1; i >= 0; i-- ) - delete children.at( i ); - if ( parent && index.isValid() ) { - parent->children.removeAt( index.row() ); + parent->children.remove( index.row() ); } + + for ( int i = children.count() - 1; i >= 0; i-- ) + delete children.at( i ); } @@ -66,6 +86,18 @@ PlItem::PlItem( const Tomahawk::plentry_ptr& entry, PlItem* parent, int row ) setupItem( entry->query(), parent, row ); } +const Tomahawk::plentry_ptr& +PlItem::entry() const +{ + return m_entry; +} + +const Tomahawk::query_ptr& +PlItem::query() const +{ + if ( !m_entry.isNull() ) return m_entry->query(); else return m_query; +} + void PlItem::setupItem( const Tomahawk::query_ptr& query, PlItem* parent, int row ) @@ -90,26 +122,18 @@ PlItem::setupItem( const Tomahawk::query_ptr& query, PlItem* parent, int row ) toberemoved = false; m_query = query; if ( query->numResults() ) - onResultsAdded( query->results() ); + { + emit dataChanged(); + } + else + { + connect( query.data(), SIGNAL( resultsAdded( QList ) ), + SIGNAL( dataChanged() ) ); - connect( query.data(), SIGNAL( resultsAdded( QList ) ), - SLOT( onResultsAdded( QList ) ), Qt::DirectConnection ); - - connect( query.data(), SIGNAL( resultsRemoved( Tomahawk::result_ptr ) ), - SLOT( onResultsRemoved( Tomahawk::result_ptr ) ), Qt::DirectConnection ); -} - - -void -PlItem::onResultsAdded( const QList& results ) -{ -// qDebug() << "Found results for playlist item:" << this; - emit dataChanged(); -} - - -void -PlItem::onResultsRemoved( const Tomahawk::result_ptr& result ) -{ - emit dataChanged(); + connect( query.data(), SIGNAL( resultsRemoved( Tomahawk::result_ptr ) ), + SIGNAL( dataChanged() ) ); + + connect( query.data(), SIGNAL( resultsChanged() ), + SIGNAL( dataChanged() ) ); + } } diff --git a/src/libtomahawk/playlist/plitem.h b/src/libtomahawk/playlist/plitem.h new file mode 100644 index 000000000..3d28957fc --- /dev/null +++ b/src/libtomahawk/playlist/plitem.h @@ -0,0 +1,70 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef PLITEM_H +#define PLITEM_H + +#include +#include +#include +#include + +#include "query.h" +#include "typedefs.h" + +#include "dllmacro.h" + +class DLLEXPORT PlItem : public QObject +{ +Q_OBJECT + +public: + virtual ~PlItem(); + + explicit PlItem( PlItem* parent = 0, QAbstractItemModel* model = 0 ); + explicit PlItem( const QString& caption, PlItem* parent = 0 ); + explicit PlItem( const Tomahawk::query_ptr& query, PlItem* parent = 0, int row = -1 ); + explicit PlItem( const Tomahawk::plentry_ptr& entry, PlItem* parent = 0, int row = -1 ); + + const Tomahawk::plentry_ptr& entry() const; + const Tomahawk::query_ptr& query() const; + + bool isPlaying() { return m_isPlaying; } + void setIsPlaying( bool b ) { m_isPlaying = b; emit dataChanged(); } + + PlItem* parent; + QVector children; + QHash hash; + QString caption; + int childCount; + QPersistentModelIndex index; + QAbstractItemModel* model; + bool toberemoved; + +signals: + void dataChanged(); + +private: + void setupItem( const Tomahawk::query_ptr& query, PlItem* parent, int row = -1 ); + + Tomahawk::plentry_ptr m_entry; + Tomahawk::query_ptr m_query; + bool m_isPlaying; +}; + +#endif // PLITEM_H diff --git a/src/libtomahawk/playlist/queueproxymodel.cpp b/src/libtomahawk/playlist/queueproxymodel.cpp new file mode 100644 index 000000000..88950089e --- /dev/null +++ b/src/libtomahawk/playlist/queueproxymodel.cpp @@ -0,0 +1,55 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "queueproxymodel.h" + +#include + +#include "playlist/playlistmanager.h" + +using namespace Tomahawk; + + +QueueProxyModel::QueueProxyModel( QObject* parent ) + : PlaylistProxyModel( parent ) +{ + qDebug() << Q_FUNC_INFO; +} + + +QueueProxyModel::~QueueProxyModel() +{ +} + + +Tomahawk::result_ptr +QueueProxyModel::siblingItem( int itemsAway ) +{ + qDebug() << Q_FUNC_INFO << rowCount( QModelIndex() ); + + setCurrentItem( QModelIndex() ); + Tomahawk::result_ptr res = PlaylistProxyModel::siblingItem( itemsAway ); + + qDebug() << "new rowcount:" << rowCount( QModelIndex() ); + if ( rowCount( QModelIndex() ) == 1 ) + PlaylistManager::instance()->hideQueue(); + + removeIndex( currentItem() ); + + return res; +} diff --git a/src/libtomahawk/playlist/queueproxymodel.h b/src/libtomahawk/playlist/queueproxymodel.h new file mode 100644 index 000000000..fd240dfb8 --- /dev/null +++ b/src/libtomahawk/playlist/queueproxymodel.h @@ -0,0 +1,39 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef QUEUEPROXYMODEL_H +#define QUEUEPROXYMODEL_H + +#include "playlistproxymodel.h" + +#include "dllmacro.h" + +class QMetaData; + +class DLLEXPORT QueueProxyModel : public PlaylistProxyModel +{ +Q_OBJECT + +public: + explicit QueueProxyModel( QObject* parent = 0 ); + ~QueueProxyModel(); + + virtual Tomahawk::result_ptr siblingItem( int itemsAway ); +}; + +#endif // QUEUEPROXYMODEL_H diff --git a/src/libtomahawk/playlist/queueview.cpp b/src/libtomahawk/playlist/queueview.cpp new file mode 100644 index 000000000..1d060db73 --- /dev/null +++ b/src/libtomahawk/playlist/queueview.cpp @@ -0,0 +1,93 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "queueview.h" + +#include +#include + +#include "playlist/queueproxymodel.h" +#include "widgets/overlaywidget.h" + +#ifdef Q_WS_MAC +#define MINIMUM_HEIGHT 38 +#else +#define MINIMUM_HEIGHT 27 +#endif + +using namespace Tomahawk; + + +QueueView::QueueView( AnimatedSplitter* parent ) + : AnimatedWidget( parent ) +{ + setHiddenSize( QSize( 0, MINIMUM_HEIGHT ) ); + setLayout( new QVBoxLayout() ); + + m_queue = new PlaylistView( this ); + m_queue->setProxyModel( new QueueProxyModel( this ) ); + m_queue->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Ignored ); + m_queue->setFrameShape( QFrame::NoFrame ); + m_queue->setAttribute( Qt::WA_MacShowFocusRect, 0 ); + m_queue->overlay()->setEnabled( false ); + + m_button = new QPushButton(); + m_button->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ); + m_button->setText( tr( "Click to show queue" ) ); + connect( m_button, SIGNAL( clicked() ), SIGNAL( showWidget() ) ); + + layout()->setMargin( 0 ); + layout()->addWidget( m_queue ); + layout()->addWidget( m_button ); +} + + +QueueView::~QueueView() +{ + qDebug() << Q_FUNC_INFO; +} + + +void +QueueView::onShown( QWidget* widget ) +{ + qDebug() << Q_FUNC_INFO << widget; + if ( widget != this ) + return; + + AnimatedWidget::onShown( widget ); + + m_button->setText( tr( "Click to hide queue" ) ); + disconnect( m_button, SIGNAL( clicked() ), this, SIGNAL( showWidget() ) ); + connect( m_button, SIGNAL( clicked() ), SIGNAL( hideWidget() ) ); +} + + +void +QueueView::onHidden( QWidget* widget ) +{ + qDebug() << Q_FUNC_INFO << widget; + if ( widget != this ) + return; + + AnimatedWidget::onHidden( widget ); + + m_button->setText( tr( "Click to show queue" ) ); + disconnect( m_button, SIGNAL( clicked() ), this, SIGNAL( hideWidget() ) ); + connect( m_button, SIGNAL( clicked() ), SIGNAL( showWidget() ) ); +} diff --git a/src/libtomahawk/playlist/queueview.h b/src/libtomahawk/playlist/queueview.h new file mode 100644 index 000000000..f617831f9 --- /dev/null +++ b/src/libtomahawk/playlist/queueview.h @@ -0,0 +1,50 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef QUEUEVIEW_H +#define QUEUEVIEW_H + +#include + +#include "utils/animatedsplitter.h" +#include "playlistview.h" + +#include "dllmacro.h" + +class DLLEXPORT QueueView : public AnimatedWidget +{ +Q_OBJECT + +public: + explicit QueueView( AnimatedSplitter* parent ); + ~QueueView(); + + PlaylistView* queue() const { return m_queue; } + + QSize sizeHint() const { return QSize( 0, 200 ); } + +public slots: + virtual void onShown( QWidget* ); + virtual void onHidden( QWidget* ); + +private: + PlaylistView* m_queue; + QPushButton* m_button; +}; + +#endif // QUEUEVIEW_H diff --git a/src/topbar/clearbutton.cpp b/src/libtomahawk/playlist/topbar/clearbutton.cpp similarity index 100% rename from src/topbar/clearbutton.cpp rename to src/libtomahawk/playlist/topbar/clearbutton.cpp diff --git a/src/topbar/clearbutton.h b/src/libtomahawk/playlist/topbar/clearbutton.h similarity index 100% rename from src/topbar/clearbutton.h rename to src/libtomahawk/playlist/topbar/clearbutton.h diff --git a/src/topbar/lineedit.cpp b/src/libtomahawk/playlist/topbar/lineedit.cpp similarity index 100% rename from src/topbar/lineedit.cpp rename to src/libtomahawk/playlist/topbar/lineedit.cpp diff --git a/src/topbar/lineedit.h b/src/libtomahawk/playlist/topbar/lineedit.h similarity index 97% rename from src/topbar/lineedit.h rename to src/libtomahawk/playlist/topbar/lineedit.h index 1eeec3a7c..5999ddb2b 100644 --- a/src/topbar/lineedit.h +++ b/src/libtomahawk/playlist/topbar/lineedit.h @@ -29,8 +29,9 @@ #ifndef LINEEDIT_H #define LINEEDIT_H -#include +#include +#include "dllmacro.h" class QHBoxLayout; /* @@ -44,7 +45,7 @@ class QHBoxLayout; into the center of the widget. */ class SideWidget; -class LineEdit : public QLineEdit +class DLLEXPORT LineEdit : public QLineEdit { Q_OBJECT Q_PROPERTY(QString inactiveText READ inactiveText WRITE setInactiveText) diff --git a/src/topbar/lineedit_p.h b/src/libtomahawk/playlist/topbar/lineedit_p.h similarity index 100% rename from src/topbar/lineedit_p.h rename to src/libtomahawk/playlist/topbar/lineedit_p.h diff --git a/src/topbar/searchbutton.cpp b/src/libtomahawk/playlist/topbar/searchbutton.cpp similarity index 100% rename from src/topbar/searchbutton.cpp rename to src/libtomahawk/playlist/topbar/searchbutton.cpp diff --git a/src/topbar/searchbutton.h b/src/libtomahawk/playlist/topbar/searchbutton.h similarity index 94% rename from src/topbar/searchbutton.h rename to src/libtomahawk/playlist/topbar/searchbutton.h index 3f9c0ac92..e57da62b1 100644 --- a/src/topbar/searchbutton.h +++ b/src/libtomahawk/playlist/topbar/searchbutton.h @@ -22,8 +22,10 @@ #include +#include "dllmacro.h" + class QCompleter; -class SearchButton : public QAbstractButton +class DLLEXPORT SearchButton : public QAbstractButton { Q_OBJECT diff --git a/src/topbar/searchlineedit.cpp b/src/libtomahawk/playlist/topbar/searchlineedit.cpp similarity index 100% rename from src/topbar/searchlineedit.cpp rename to src/libtomahawk/playlist/topbar/searchlineedit.cpp diff --git a/src/topbar/searchlineedit.h b/src/libtomahawk/playlist/topbar/searchlineedit.h similarity index 100% rename from src/topbar/searchlineedit.h rename to src/libtomahawk/playlist/topbar/searchlineedit.h diff --git a/src/topbar/topbar.cpp b/src/libtomahawk/playlist/topbar/topbar.cpp similarity index 56% rename from src/topbar/topbar.cpp rename to src/libtomahawk/playlist/topbar/topbar.cpp index cf06c18c7..5018092f3 100644 --- a/src/topbar/topbar.cpp +++ b/src/libtomahawk/playlist/topbar/topbar.cpp @@ -1,13 +1,29 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "topbar.h" #include "ui_topbar.h" -#include #include #include #include -#include "tomahawk/functimeout.h" -#include "tomahawk/tomahawkapp.h" +#include "utils/tomahawkutils.h" #define MAXDUDES 3 #define DUDEWIDTH 10 @@ -56,17 +72,39 @@ TopBar::TopBar( QWidget* parent ) ui->radioDetailed->setFocusPolicy( Qt::NoFocus ); ui->radioCloud->setFocusPolicy( Qt::NoFocus ); + ui->radioDetailed->setEnabled( false ); + connect( ui->radioNormal, SIGNAL( clicked() ), SIGNAL( flatMode() ) ); connect( ui->radioDetailed, SIGNAL( clicked() ), SIGNAL( artistMode() ) ); - - ui->widgetRadio->hide(); // FIXME + connect( ui->radioCloud, SIGNAL( clicked() ), SIGNAL( albumMode() ) ); setNumSources( 0 ); setNumTracks( 0 ); setNumArtists( 0 ); setNumShown( 0 ); - ui->radioNormal->setChecked( true ); + onFlatMode(); + + connect( PlaylistManager::instance(), SIGNAL( numSourcesChanged( unsigned int ) ), + SLOT( setNumSources( unsigned int ) ) ); + + connect( PlaylistManager::instance(), SIGNAL( numTracksChanged( unsigned int ) ), + SLOT( setNumTracks( unsigned int ) ) ); + + connect( PlaylistManager::instance(), SIGNAL( numArtistsChanged( unsigned int ) ), + SLOT( setNumArtists( unsigned int ) ) ); + + connect( PlaylistManager::instance(), SIGNAL( numShownChanged( unsigned int ) ), + SLOT( setNumShown( unsigned int ) ) ); + + connect( PlaylistManager::instance(), SIGNAL( statsAvailable( bool ) ), + SLOT( setStatsVisible( bool ) ) ); + + connect( PlaylistManager::instance(), SIGNAL( modesAvailable( bool ) ), + SLOT( setModesVisible( bool ) ) ); + + connect( PlaylistManager::instance(), SIGNAL( modeChanged( PlaylistInterface::ViewMode ) ), + SLOT( onModeChanged( PlaylistInterface::ViewMode ) ) ); } @@ -77,7 +115,7 @@ TopBar::~TopBar() void -TopBar::changeEvent( QEvent *e ) +TopBar::changeEvent( QEvent* e ) { QWidget::changeEvent( e ); switch ( e->type() ) @@ -92,6 +130,13 @@ TopBar::changeEvent( QEvent *e ) } +void +TopBar::resizeEvent( QResizeEvent* e ) +{ + QWidget::resizeEvent( e ); +} + + void TopBar::fadeInDude( unsigned int i ) { @@ -185,7 +230,7 @@ void TopBar::setNumShown( unsigned int i ) { m_shown = i; - ui->statsLabelNumShown->setVisible( m_shown != m_tracks ); + ui->statsLabelNumShown->setVisible( m_shown != m_tracks && ui->statsLabelNumTracks->isVisible() ); ui->statsLabelNumShown->setText( QString( "%L1 %2" ).arg( i ).arg( tr( "Shown" ) ) ); } @@ -205,3 +250,75 @@ TopBar::removeSource() Q_ASSERT( m_sources > 0 ); setNumSources( m_sources - 1 ); } + + +void +TopBar::setStatsVisible( bool b ) +{ + foreach( QLabel* dude, m_dudes ) + dude->setVisible( b ); + +// ui->statsLabelNumArtists->setVisible( b ); +// ui->statsLabelNumShown->setVisible( b ); + ui->statsLabelNumSources->setVisible( b ); + ui->statsLabelNumTracks->setVisible( b ); +} + + +void +TopBar::setModesVisible( bool b ) +{ + ui->widgetRadio->setVisible( b ); +} + + +void +TopBar::setFilter( const QString& filter ) +{ + ui->filterEdit->setText( filter ); +} + + +void +TopBar::onModeChanged( PlaylistInterface::ViewMode mode ) +{ + qDebug() << Q_FUNC_INFO << mode; + switch ( mode ) + { + case PlaylistInterface::Flat: + onFlatMode(); + break; + + case PlaylistInterface::Tree: + onArtistMode(); + break; + + case PlaylistInterface::Album: + onAlbumMode(); + break; + + default: + break; + } +} + + +void +TopBar::onFlatMode() +{ + ui->radioNormal->setChecked( true ); +} + + +void +TopBar::onArtistMode() +{ + ui->radioDetailed->setChecked( true ); +} + + +void +TopBar::onAlbumMode() +{ + ui->radioCloud->setChecked( true ); +} diff --git a/src/libtomahawk/playlist/topbar/topbar.h b/src/libtomahawk/playlist/topbar/topbar.h new file mode 100644 index 000000000..c29b8b959 --- /dev/null +++ b/src/libtomahawk/playlist/topbar/topbar.h @@ -0,0 +1,86 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TOPBAR_H +#define TOPBAR_H + +#include +#include +#include + +#include "playlist/playlistmanager.h" +#include "sourcelist.h" +#include "dllmacro.h" + +namespace Ui +{ + class TopBar; +} + +class DLLEXPORT TopBar : public QWidget +{ +Q_OBJECT + +public: + TopBar( QWidget* parent = 0 ); + ~TopBar(); + +signals: + void filterTextChanged( const QString& newtext ); + + void flatMode(); + void artistMode(); + void albumMode(); + +public slots: + void setNumSources( unsigned int ); + void setNumTracks( unsigned int ); + void setNumArtists( unsigned int ); + void setNumShown( unsigned int ); + + void setStatsVisible( bool b ); + void setModesVisible( bool b ); + + void addSource(); + void removeSource(); + + void setFilter( const QString& filter ); + +private slots: + void onModeChanged( PlaylistInterface::ViewMode mode ); + void onFlatMode(); + void onArtistMode(); + void onAlbumMode(); + +protected: + void changeEvent( QEvent* e ); + void resizeEvent( QResizeEvent* e ); + +private: + void fadeOutDude( unsigned int i ); + void fadeInDude( unsigned int i ); + + Ui::TopBar* ui; + + unsigned int m_sources, m_tracks, m_artists, m_shown; + QList m_dudes; + + Tomahawk::source_ptr m_onesource; +}; + +#endif // TOPBAR_H diff --git a/src/topbar/topbar.ui b/src/libtomahawk/playlist/topbar/topbar.ui similarity index 98% rename from src/topbar/topbar.ui rename to src/libtomahawk/playlist/topbar/topbar.ui index c609d1c70..b817c341c 100644 --- a/src/topbar/topbar.ui +++ b/src/libtomahawk/playlist/topbar/topbar.ui @@ -19,7 +19,7 @@ 0 - 27 + 38 @@ -311,12 +311,12 @@ AnimatedCounterLabel QLabel -
animatedcounterlabel.h
+
utils/animatedcounterlabel.h
SearchLineEdit QLineEdit -
topbar/searchlineedit.h
+
playlist/topbar/searchlineedit.h
diff --git a/src/libtomahawk/playlist/trackheader.cpp b/src/libtomahawk/playlist/trackheader.cpp new file mode 100644 index 000000000..750f207d2 --- /dev/null +++ b/src/libtomahawk/playlist/trackheader.cpp @@ -0,0 +1,145 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "trackheader.h" + +#include +#include +#include + +#include "tomahawksettings.h" +#include "playlist/trackmodel.h" +#include "playlist/trackview.h" + + +TrackHeader::TrackHeader( TrackView* parent ) + : QHeaderView( Qt::Horizontal, parent ) + , m_parent( parent ) + , m_menu( new QMenu( this ) ) + , m_sigmap( new QSignalMapper( this ) ) + , m_init( false ) +{ + setResizeMode( QHeaderView::Interactive ); + setMinimumSectionSize( 60 ); + setDefaultAlignment( Qt::AlignLeft ); + setMovable( true ); + setStretchLastSection( true ); +// setCascadingSectionResizes( true ); + +// m_menu->addAction( tr( "Resize columns to fit window" ), this, SLOT( onToggleResizeColumns() ) ); +// m_menu->addSeparator(); + + connect( this, SIGNAL( sectionResized( int, int, int ) ), SLOT( onSectionResized() ) ); + connect( m_sigmap, SIGNAL( mapped( int ) ), SLOT( toggleVisibility( int ) ) ); +} + + +TrackHeader::~TrackHeader() +{ +} + + +void +TrackHeader::onSectionResized() +{ + if ( !m_init ) + return; + + TomahawkSettings::instance()->setPlaylistColumnSizes( m_parent->guid(), saveState() ); +} + + +int +TrackHeader::visibleSectionCount() const +{ + return count() - hiddenSectionCount(); +} + + +void +TrackHeader::checkState() +{ + if ( !count() || m_init ) + return; + + QByteArray state = TomahawkSettings::instance()->playlistColumnSizes( m_parent->guid() ); + if ( !state.isEmpty() ) + restoreState( state ); + else + { + QList< double > m_columnWeights; + m_columnWeights << 0.21 << 0.22 << 0.20 << 0.05 << 0.05 << 0.05 << 0.05 << 0.05; // << 0.12; + + for ( int i = 0; i < count() - 1; i++ ) + { + if ( isSectionHidden( i ) ) + continue; + + double nw = (double)m_parent->width() * m_columnWeights.at( i ); + qDebug() << "Setting default size:" << i << nw; + resizeSection( i, qMax( minimumSectionSize(), int( nw - 0.5 ) ) ); + } + } + + m_init = true; +} + + +void +TrackHeader::addColumnToMenu( int index ) +{ + QString title = m_parent->model()->headerData( index, Qt::Horizontal, Qt::DisplayRole ).toString(); + + QAction* action = m_menu->addAction( title, m_sigmap, SLOT( map() ) ); + action->setCheckable( true ); + action->setChecked( !isSectionHidden( index ) ); + m_visActions << action; + + m_sigmap->setMapping( action, index ); +} + + +void +TrackHeader::contextMenuEvent( QContextMenuEvent* e ) +{ + qDeleteAll( m_visActions ); + m_visActions.clear(); + + for ( int i = 0; i < count(); i++ ) + addColumnToMenu( i ); + + m_menu->popup( e->globalPos() ); +} + + +void +TrackHeader::onToggleResizeColumns() +{ +} + + +void +TrackHeader::toggleVisibility( int index ) +{ + qDebug() << Q_FUNC_INFO << index; + + if ( isSectionHidden( index ) ) + showSection( index ); + else + hideSection( index ); +} diff --git a/src/libtomahawk/playlist/trackheader.h b/src/libtomahawk/playlist/trackheader.h new file mode 100644 index 000000000..2d62a1170 --- /dev/null +++ b/src/libtomahawk/playlist/trackheader.h @@ -0,0 +1,61 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TRACKHEADER_H +#define TRACKHEADER_H + +#include +#include + +#include "dllmacro.h" + +class TrackView; + +class DLLEXPORT TrackHeader : public QHeaderView +{ +Q_OBJECT + +public: + explicit TrackHeader( TrackView* parent = 0 ); + ~TrackHeader(); + + int visibleSectionCount() const; + +public slots: + void toggleVisibility( int index ); + void checkState(); + +protected: + void contextMenuEvent( QContextMenuEvent* e ); + +private slots: + void onSectionResized(); + void onToggleResizeColumns(); + +private: + void addColumnToMenu( int index ); + + TrackView* m_parent; + + QMenu* m_menu; + QSignalMapper* m_sigmap; + QList m_visActions; + bool m_init; +}; + +#endif diff --git a/src/playlist/trackmodel.cpp b/src/libtomahawk/playlist/trackmodel.cpp similarity index 66% rename from src/playlist/trackmodel.cpp rename to src/libtomahawk/playlist/trackmodel.cpp index 99d3ac959..eceadd401 100644 --- a/src/playlist/trackmodel.cpp +++ b/src/libtomahawk/playlist/trackmodel.cpp @@ -1,11 +1,31 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "trackmodel.h" #include #include #include -#include "tomahawk/tomahawkapp.h" -#include "tomahawk/album.h" +#include "audio/audioengine.h" +#include "utils/tomahawkutils.h" + +#include "album.h" using namespace Tomahawk; @@ -16,6 +36,9 @@ TrackModel::TrackModel( QObject* parent ) , m_readOnly( true ) { qDebug() << Q_FUNC_INFO; + + connect( AudioEngine::instance(), SIGNAL( finished( Tomahawk::result_ptr ) ), SLOT( onPlaybackFinished( Tomahawk::result_ptr ) ), Qt::DirectConnection ); + connect( AudioEngine::instance(), SIGNAL( stopped() ), SLOT( onPlaybackStopped() ), Qt::DirectConnection ); } @@ -57,7 +80,7 @@ TrackModel::rowCount( const QModelIndex& parent ) const int TrackModel::columnCount( const QModelIndex& parent ) const { - return 7; + return 9; } @@ -90,9 +113,6 @@ TrackModel::data( const QModelIndex& index, int role ) const if ( role == Qt::DecorationRole ) { - if ( index.column() == 0 && entry->isPlaying() ) - return QString( RESPATH "images/now-playing-speaker.png" ); - return QVariant(); } @@ -124,15 +144,15 @@ TrackModel::data( const QModelIndex& index, int role ) const { switch( index.column() ) { - case 0: + case Artist: return query->artist(); break; - case 1: + case Track: return query->track(); break; - case 2: + case Album: return query->album(); break; } @@ -141,32 +161,40 @@ TrackModel::data( const QModelIndex& index, int role ) const { switch( index.column() ) { - case 0: + case Artist: return query->results().first()->artist()->name(); break; - case 1: + case Track: return query->results().first()->track(); break; - case 2: + case Album: return query->results().first()->album()->name(); break; - case 3: + case Duration: return TomahawkUtils::timeToString( query->results().first()->duration() ); break; - case 4: + case Bitrate: return query->results().first()->bitrate(); break; - case 5: + case Age: return TomahawkUtils::ageToString( QDateTime::fromTime_t( query->results().first()->modificationTime() ) ); break; - case 6: - return query->results().first()->collection()->source()->friendlyName(); + case Year: + return query->results().first()->year(); + break; + + case Filesize: + return TomahawkUtils::filesizeToString( query->results().first()->size() ); + break; + + case Origin: + return query->results().first()->friendlySource(); break; } } @@ -179,8 +207,8 @@ QVariant TrackModel::headerData( int section, Qt::Orientation orientation, int role ) const { QStringList headers; - headers << tr( "Artist" ) << tr( "Track" ) << tr( "Album" ) << tr( "Duration" ) << tr( "Bitrate" ) << tr( "Age" ) << tr( "Origin" ); - if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 ) + headers << tr( "Artist" ) << tr( "Track" ) << tr( "Album" ) << tr( "Duration" ) << tr( "Bitrate" ) << tr( "Age" ) << tr( "Year" ) << tr( "Size" ) << tr( "Origin" ); + if ( role == Qt::DisplayRole && section >= 0 ) { return headers.at( section ); } @@ -270,8 +298,19 @@ TrackModel::mimeData( const QModelIndexList &indexes ) const void -TrackModel::removeIndex( const QModelIndex& index ) +TrackModel::removeIndex( const QModelIndex& index, bool moreToCome ) { + if ( QThread::currentThread() != thread() ) + { + qDebug() << "Reinvoking in correct thread:" << Q_FUNC_INFO; + QMetaObject::invokeMethod( this, "removeIndex", + Qt::QueuedConnection, + Q_ARG(const QModelIndex, index), + Q_ARG(bool, moreToCome) + ); + return; + } + qDebug() << Q_FUNC_INFO; if ( index.column() > 0 ) @@ -284,6 +323,8 @@ TrackModel::removeIndex( const QModelIndex& index ) delete item; emit endRemoveRows(); } + + emit trackCountChanged( rowCount( QModelIndex() ) ); } @@ -307,3 +348,25 @@ TrackModel::itemFromIndex( const QModelIndex& index ) const return m_rootItem; } } + + +void +TrackModel::onPlaybackFinished( const Tomahawk::result_ptr& result ) +{ + PlItem* oldEntry = itemFromIndex( m_currentIndex ); + if ( oldEntry && !oldEntry->query().isNull() && oldEntry->query()->results().contains( result ) ) + { + oldEntry->setIsPlaying( false ); + } +} + + +void +TrackModel::onPlaybackStopped() +{ + PlItem* oldEntry = itemFromIndex( m_currentIndex ); + if ( oldEntry ) + { + oldEntry->setIsPlaying( false ); + } +} diff --git a/src/playlist/trackmodel.h b/src/libtomahawk/playlist/trackmodel.h similarity index 56% rename from src/playlist/trackmodel.h rename to src/libtomahawk/playlist/trackmodel.h index 2ecd7088d..d9cec03d8 100644 --- a/src/playlist/trackmodel.h +++ b/src/libtomahawk/playlist/trackmodel.h @@ -1,14 +1,34 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef TRACKMODEL_H #define TRACKMODEL_H #include -#include "tomahawk/playlistinterface.h" +#include "playlistinterface.h" #include "playlist/plitem.h" +#include "dllmacro.h" + class QMetaData; -class TrackModel : public QAbstractItemModel, public PlaylistInterface +class DLLEXPORT TrackModel : public QAbstractItemModel { Q_OBJECT @@ -20,7 +40,9 @@ public: Duration = 3, Bitrate = 4, Age = 5, - Origin = 6 + Year = 6, + Filesize = 7, + Origin = 8 }; explicit TrackModel( QObject* parent = 0 ); @@ -31,6 +53,11 @@ public: virtual bool isReadOnly() const { return m_readOnly; } + virtual QString title() const { return m_title; } + virtual void setTitle( const QString& title ) { m_title = title; } + virtual QString description() const { return m_description; } + virtual void setDescription( const QString& description ) { m_description = description; } + virtual int trackCount() const { return rowCount( QModelIndex() ); } virtual int rowCount( const QModelIndex& parent ) const; @@ -39,11 +66,6 @@ public: virtual QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const; virtual QVariant headerData( int section, Qt::Orientation orientation, int role ) const; - virtual void removeIndex( const QModelIndex& index ); - virtual void removeIndexes( const QList& indexes ); - - virtual Tomahawk::result_ptr siblingItem( int direction ) { return Tomahawk::result_ptr(); } - virtual QMimeData* mimeData( const QModelIndexList& indexes ) const; virtual QStringList mimeTypes() const; virtual Qt::DropActions supportedDropActions() const; @@ -54,6 +76,8 @@ public: virtual PlaylistInterface::RepeatMode repeatMode() const { return PlaylistInterface::NoRepeat; } virtual bool shuffled() const { return false; } + virtual void append( const Tomahawk::query_ptr& query ) = 0; + PlItem* itemFromIndex( const QModelIndex& index ) const; PlItem* m_rootItem; @@ -64,18 +88,31 @@ signals: void trackCountChanged( unsigned int tracks ); + void loadingStarted(); + void loadingFinished(); + public slots: virtual void setCurrentItem( const QModelIndex& index ); + virtual void removeIndex( const QModelIndex& index, bool moreToCome = false ); + virtual void removeIndexes( const QList& indexes ); + virtual void setRepeatMode( PlaylistInterface::RepeatMode mode ) {} virtual void setShuffled( bool shuffled ) {} protected: virtual void setReadOnly( bool b ) { m_readOnly = b; } +private slots: + void onPlaybackFinished( const Tomahawk::result_ptr& result ); + void onPlaybackStopped(); + private: QPersistentModelIndex m_currentIndex; bool m_readOnly; + + QString m_title; + QString m_description; }; #endif // TRACKMODEL_H diff --git a/src/playlist/trackproxymodel.cpp b/src/libtomahawk/playlist/trackproxymodel.cpp similarity index 52% rename from src/playlist/trackproxymodel.cpp rename to src/libtomahawk/playlist/trackproxymodel.cpp index 34379f843..3b66b70de 100644 --- a/src/playlist/trackproxymodel.cpp +++ b/src/libtomahawk/playlist/trackproxymodel.cpp @@ -1,18 +1,38 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "trackproxymodel.h" #include #include -#include "tomahawk/album.h" -#include "tomahawk/query.h" +#include "album.h" +#include "query.h" #include "collectionmodel.h" TrackProxyModel::TrackProxyModel( QObject* parent ) : QSortFilterProxyModel( parent ) + , PlaylistInterface( this ) , m_model( 0 ) , m_repeatMode( PlaylistInterface::NoRepeat ) , m_shuffled( false ) + , m_showOfflineResults( true ) { qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); @@ -29,21 +49,42 @@ TrackProxyModel::setSourceModel( TrackModel* sourceModel ) { m_model = sourceModel; + if ( m_model ) + connect( m_model, SIGNAL( trackCountChanged( unsigned int ) ), + SIGNAL( sourceTrackCountChanged( unsigned int ) ) ); + QSortFilterProxyModel::setSourceModel( sourceModel ); } void -TrackProxyModel::setFilterRegExp( const QString& pattern ) +TrackProxyModel::setFilter( const QString& pattern ) { qDebug() << Q_FUNC_INFO; - QSortFilterProxyModel::setFilterRegExp( pattern ); + PlaylistInterface::setFilter( pattern ); + setFilterRegExp( pattern ); emit filterChanged( pattern ); emit trackCountChanged( trackCount() ); } +QList< Tomahawk::query_ptr > +TrackProxyModel::tracks() +{ + QList queries; + + for ( int i = 0; i < rowCount( QModelIndex() ); i++ ) + { + PlItem* item = itemFromIndex( mapToSource( index( i, 0 ) ) ); + if ( item ) + queries << item->query(); + } + + return queries; +} + + Tomahawk::result_ptr TrackProxyModel::siblingItem( int itemsAway ) { @@ -96,7 +137,7 @@ TrackProxyModel::siblingItem( int itemsAway ) { PlItem* item = itemFromIndex( mapToSource( idx ) ); qDebug() << item->query()->toString(); - if ( item && item->query()->numResults() ) + if ( item && item->query()->playable() ) { qDebug() << "Next PlaylistItem found:" << item->query()->toString() << item->query()->results().at( 0 )->url(); setCurrentItem( idx ); @@ -115,44 +156,49 @@ TrackProxyModel::siblingItem( int itemsAway ) bool TrackProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const { - if ( filterRegExp().isEmpty() ) - return true; - PlItem* pi = itemFromIndex( sourceModel()->index( sourceRow, 0, sourceParent ) ); if ( !pi ) return false; const Tomahawk::query_ptr& q = pi->query(); + if( q.isNull() ) // uh oh? filter out invalid queries i guess + return false; + Tomahawk::result_ptr r; if ( q->numResults() ) - r = q->results().at( 0 ); + r = q->results().first(); + + if ( !m_showOfflineResults && !r.isNull() && !r->collection()->source()->isOnline() ) + return false; + + if ( filterRegExp().isEmpty() ) + return true; QStringList sl = filterRegExp().pattern().split( " ", QString::SkipEmptyParts ); - bool found = true; - - foreach( const QString& s, sl ) + foreach( QString s, sl ) { + s = s.toLower(); if ( !r.isNull() ) { - if ( !r->artist()->name().contains( s, Qt::CaseInsensitive ) && - !r->album()->name().contains( s, Qt::CaseInsensitive ) && - !r->track() .contains( s, Qt::CaseInsensitive ) ) + if ( !r->artist()->name().toLower().contains( s ) && + !r->album()->name().toLower().contains( s ) && + !r->track().toLower().contains( s ) ) { - found = false; + return false; } } else { - if ( !q->artist().contains( s, Qt::CaseInsensitive ) && - !q->album() .contains( s, Qt::CaseInsensitive ) && - !q->track() .contains( s, Qt::CaseInsensitive ) ) + if ( !q->artist().toLower().contains( s ) && + !q->album().toLower().contains( s ) && + !q->track().toLower().contains( s ) ) { - found = false; + return false; } } } - return found; + return true; } @@ -163,7 +209,7 @@ TrackProxyModel::removeIndex( const QModelIndex& index ) if ( !sourceModel() ) return; - if ( index.column() > 0 ) + if ( !index.isValid() ) return; sourceModel()->removeIndex( mapToSource( index ) ); @@ -171,13 +217,50 @@ TrackProxyModel::removeIndex( const QModelIndex& index ) void -TrackProxyModel::removeIndexes( const QList& indexes ) +TrackProxyModel::removeIndexes( const QModelIndexList& indexes ) { if ( !sourceModel() ) return; + QList pil; foreach( const QModelIndex& idx, indexes ) { - removeIndex( idx ); + if ( idx.isValid() && idx.column() == 0 ) + pil << mapToSource( idx ); + } + + bool b = true; + foreach( const QPersistentModelIndex& idx, pil ) + { + if ( idx == pil.last() ) + b = false; + + qDebug() << "b is:" << b; + sourceModel()->removeIndex( idx, b ); + } +} + + +void +TrackProxyModel::removeIndexes( const QList& indexes ) +{ + if ( !sourceModel() ) + return; + + QList pil; + foreach( const QModelIndex& idx, indexes ) + { + if ( idx.isValid() && idx.column() == 0 ) + pil << mapToSource( idx ); + } + + bool b = true; + foreach( const QPersistentModelIndex& idx, pil ) + { + if ( idx == pil.last() ) + b = false; + + qDebug() << "b is:" << b; + sourceModel()->removeIndex( idx, b ); } } diff --git a/src/playlist/trackproxymodel.h b/src/libtomahawk/playlist/trackproxymodel.h similarity index 50% rename from src/playlist/trackproxymodel.h rename to src/libtomahawk/playlist/trackproxymodel.h index 90e9fbeff..4452ef20d 100644 --- a/src/playlist/trackproxymodel.h +++ b/src/libtomahawk/playlist/trackproxymodel.h @@ -1,12 +1,32 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef TRACKPROXYMODEL_H #define TRACKPROXYMODEL_H #include -#include "tomahawk/playlistinterface.h" +#include "playlistinterface.h" #include "playlist/trackmodel.h" -class TrackProxyModel : public QSortFilterProxyModel, public PlaylistInterface +#include "dllmacro.h" + +class DLLEXPORT TrackProxyModel : public QSortFilterProxyModel, public PlaylistInterface { Q_OBJECT @@ -19,18 +39,26 @@ public: virtual QPersistentModelIndex currentItem() const { return mapFromSource( m_model->currentItem() ); } virtual void setCurrentItem( const QModelIndex& index ) { m_model->setCurrentItem( mapToSource( index ) ); } + virtual QList tracks(); + + virtual int unfilteredTrackCount() const { return sourceModel()->trackCount(); } virtual int trackCount() const { return rowCount( QModelIndex() ); } virtual void removeIndex( const QModelIndex& index ); - virtual void removeIndexes( const QList& indexes ); + virtual void removeIndexes( const QModelIndexList& indexes ); + virtual void removeIndexes( const QList& indexes ); virtual Tomahawk::result_ptr siblingItem( int itemsAway ); - void setFilterRegExp( const QString& pattern ); + virtual QString filter() const { return filterRegExp().pattern(); } + virtual void setFilter( const QString& pattern ); virtual PlaylistInterface::RepeatMode repeatMode() const { return m_repeatMode; } virtual bool shuffled() const { return m_shuffled; } + bool showOfflineResults() const { return m_showOfflineResults; } + void setShowOfflineResults( bool b ) { m_showOfflineResults = b; } + PlItem* itemFromIndex( const QModelIndex& index ) const { return sourceModel()->itemFromIndex( index ); } signals: @@ -38,6 +66,7 @@ signals: void shuffleModeChanged( bool enabled ); void trackCountChanged( unsigned int tracks ); + void sourceTrackCountChanged( unsigned int tracks ); void filterChanged( const QString& filter ); @@ -52,6 +81,7 @@ private: TrackModel* m_model; RepeatMode m_repeatMode; bool m_shuffled; + bool m_showOfflineResults; }; #endif // TRACKPROXYMODEL_H diff --git a/src/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp similarity index 54% rename from src/playlist/trackview.cpp rename to src/libtomahawk/playlist/trackview.cpp index 56da6f29b..d3d064ec2 100644 --- a/src/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -1,14 +1,36 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "trackview.h" #include -#include #include #include #include -#include "tomahawk/tomahawkapp.h" -#include "audioengine.h" -#include "tomahawksettings.h" +#include "audio/audioengine.h" +#include "utils/tomahawkutils.h" +#include "widgets/overlaywidget.h" +#include "dynamic/widgets/LoadingSpinner.h" + +#include "trackheader.h" +#include "playlistmanager.h" +#include "queueview.h" #include "trackmodel.h" #include "trackproxymodel.h" @@ -20,6 +42,9 @@ TrackView::TrackView( QWidget* parent ) , m_model( 0 ) , m_proxyModel( 0 ) , m_delegate( 0 ) + , m_header( new TrackHeader( this ) ) + , m_overlay( new OverlayWidget( this ) ) + , m_loadingSpinner( new LoadingSpinner( this ) ) , m_resizing( false ) { setSortingEnabled( false ); @@ -31,16 +56,26 @@ TrackView::TrackView( QWidget* parent ) setDragDropMode( QAbstractItemView::InternalMove ); setDragDropOverwriteMode( false ); setAllColumnsShowFocus( true ); + setVerticalScrollMode( QAbstractItemView::ScrollPerPixel ); + setRootIsDecorated( false ); + setUniformRowHeights( true ); + setMinimumWidth( 300 ); +// setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn ); + setHeader( m_header ); + +#ifndef Q_WS_WIN QFont f = font(); f.setPointSize( f.pointSize() - 1 ); setFont( f ); - - header()->setMinimumSectionSize( 60 ); - restoreColumnsState(); - +#endif + +#ifdef Q_WS_MAC + f.setPointSize( f.pointSize() - 2 ); + setFont( f ); +#endif + connect( this, SIGNAL( doubleClicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); - connect( header(), SIGNAL( sectionResized( int, int, int ) ), SLOT( onSectionResized( int, int, int ) ) ); } @@ -48,7 +83,14 @@ TrackView::~TrackView() { qDebug() << Q_FUNC_INFO; - saveColumnsState(); + delete m_header; +} + + +void +TrackView::setGuid( const QString& guid ) +{ + m_guid = guid; } @@ -56,6 +98,7 @@ void TrackView::setProxyModel( TrackProxyModel* model ) { m_proxyModel = model; + m_delegate = new PlaylistItemDelegate( this, m_proxyModel ); setItemDelegate( m_delegate ); @@ -67,62 +110,19 @@ void TrackView::setModel( TrackModel* model ) { m_model = model; - m_modelInterface = (PlaylistInterface*)model; if ( m_proxyModel ) + { m_proxyModel->setSourceModel( model ); + } connect( m_model, SIGNAL( itemSizeChanged( QModelIndex ) ), SLOT( onItemResized( QModelIndex ) ) ); + connect( m_model, SIGNAL( loadingStarted() ), m_loadingSpinner, SLOT( fadeIn() ) ); + connect( m_model, SIGNAL( loadingFinished() ), m_loadingSpinner, SLOT( fadeOut() ) ); + connect( m_proxyModel, SIGNAL( filterChanged( QString ) ), SLOT( onFilterChanged( QString ) ) ); setAcceptDrops( true ); - setRootIsDecorated( false ); - setUniformRowHeights( true ); -} - - -void -TrackView::restoreColumnsState() -{ - TomahawkSettings* s = APP->settings(); - QList list = s->playlistColumnSizes(); - - if ( list.count() != 7 ) // FIXME: const - { - m_columnWeights << 0.19 << 0.24 << 0.18 << 0.07 << 0.07 << 0.11 << 0.14; - } - else - { - foreach( const QVariant& v, list ) - m_columnWeights << v.toDouble(); - } - - for ( int i = 0; i < m_columnWeights.count(); i++ ) - m_columnWidths << 0; -} - - -void -TrackView::saveColumnsState() -{ - TomahawkSettings *s = APP->settings(); - QList wlist; -// int i = 0; - - foreach( double w, m_columnWeights ) - { - wlist << QVariant( w ); -// qDebug() << "Storing weight for column" << i++ << w; - } - - s->setPlaylistColumnSizes( wlist ); -} - - -void -TrackView::onSectionResized( int logicalIndex, int oldSize, int newSize ) -{ - return; } @@ -134,7 +134,23 @@ TrackView::onItemActivated( const QModelIndex& index ) { qDebug() << "Result activated:" << item->query()->toString() << item->query()->results().first()->url(); m_proxyModel->setCurrentItem( index ); - APP->audioEngine()->playItem( m_proxyModel, item->query()->results().first() ); + AudioEngine::instance()->playItem( m_proxyModel, item->query()->results().first() ); + } +} + + +void +TrackView::keyPressEvent( QKeyEvent* event ) +{ + qDebug() << Q_FUNC_INFO; + QTreeView::keyPressEvent( event ); + + if ( !model() ) + return; + + if ( event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return ) + { + onItemActivated( currentIndex() ); } } @@ -148,45 +164,35 @@ TrackView::onItemResized( const QModelIndex& index ) void -TrackView::resizeEvent( QResizeEvent* event ) +TrackView::playItem() { -// qDebug() << Q_FUNC_INFO; - resizeColumns(); + onItemActivated( m_contextMenuIndex ); } void -TrackView::resizeColumns() +TrackView::addItemsToQueue() { - double cw = contentsRect().width(); - int i = 0; - int total = 0; - - QList mcw = m_columnWeights; - - if ( verticalScrollBar() && verticalScrollBar()->isVisible() ) + foreach( const QModelIndex& idx, selectedIndexes() ) { - cw -= verticalScrollBar()->width() + 1; + if ( idx.column() ) + continue; + + PlItem* item = model()->itemFromIndex( proxyModel()->mapToSource( idx ) ); + if ( item && item->query()->numResults() ) + { + PlaylistManager::instance()->queue()->model()->append( item->query() ); + PlaylistManager::instance()->showQueue(); + } } +} - m_resizing = true; - foreach( double w, mcw ) - { - int fw = (int)( cw * w ); - if ( fw < header()->minimumSectionSize() ) - fw = header()->minimumSectionSize(); - if ( i + 1 == header()->count() ) - fw = cw - total; - - total += fw; -// qDebug() << "Resizing column:" << i << fw; - - m_columnWidths[ i ] = fw; - - header()->resizeSection( i++, fw ); - } - m_resizing = false; +void +TrackView::resizeEvent( QResizeEvent* event ) +{ + QTreeView::resizeEvent( event ); + m_header->checkState(); } @@ -196,7 +202,9 @@ TrackView::dragEnterEvent( QDragEnterEvent* event ) qDebug() << Q_FUNC_INFO; QTreeView::dragEnterEvent( event ); - if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) || event->mimeData()->hasFormat( "application/tomahawk.plentry.list" ) ) + if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) + || event->mimeData()->hasFormat( "application/tomahawk.plentry.list" ) + || event->mimeData()->hasFormat( "application/tomahawk.result.list" ) ) { m_dragging = true; m_dropRect = QRect(); @@ -218,7 +226,9 @@ TrackView::dragMoveEvent( QDragMoveEvent* event ) return; } - if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) || event->mimeData()->hasFormat( "application/tomahawk.plentry.list" ) ) + if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) + || event->mimeData()->hasFormat( "application/tomahawk.plentry.list" ) + || event->mimeData()->hasFormat( "application/tomahawk.result.list" ) ) { setDirtyRegion( m_dropRect ); const QPoint pos = event->pos(); @@ -244,21 +254,29 @@ TrackView::dragMoveEvent( QDragMoveEvent* event ) void TrackView::dropEvent( QDropEvent* event ) { -/* const QPoint pos = event->pos(); - const QModelIndex index = indexAt( pos ); - - if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) ) - { - const QPoint pos = event->pos(); - const QModelIndex index = indexAt( pos ); - - if ( index.isValid() ) - { - event->acceptProposedAction(); - } - }*/ - QTreeView::dropEvent( event ); + + if ( event->isAccepted() ) + { + qDebug() << "Ignoring accepted event!"; + } + else + { + if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) ) + { + const QPoint pos = event->pos(); + const QModelIndex index = indexAt( pos ); + + qDebug() << "Drop Event accepted at row:" << index.row(); + event->acceptProposedAction(); + + if ( !model()->isReadOnly() ) + { + model()->dropMimeData( event->mimeData(), event->proposedAction(), index.row(), 0, index.parent() ); + } + } + } + m_dragging = false; } @@ -267,11 +285,11 @@ void TrackView::paintEvent( QPaintEvent* event ) { QTreeView::paintEvent( event ); + QPainter painter( viewport() ); if ( m_dragging ) { // draw drop indicator - QPainter painter( viewport() ); { // draw indicator for inserting items QBrush blendedBrush = viewOptions().palette.brush( QPalette::Normal, QPalette::Highlight ); @@ -300,6 +318,16 @@ TrackView::onFilterChanged( const QString& ) { if ( selectedIndexes().count() ) scrollTo( selectedIndexes().at( 0 ), QAbstractItemView::PositionAtCenter ); + + if ( !proxyModel()->filter().isEmpty() && !proxyModel()->trackCount() && + model()->trackCount() ) + { + m_overlay->setText( tr( "Sorry, your filter '%1' did not match any results." ).arg( proxyModel()->filter() ) ); + m_overlay->show(); + } + else + if ( model()->trackCount() ) + m_overlay->hide(); } @@ -307,13 +335,14 @@ void TrackView::startDrag( Qt::DropActions supportedActions ) { QList pindexes; - QModelIndexList indexes = selectedIndexes(); - for( int i = indexes.count() - 1 ; i >= 0; --i ) + QModelIndexList indexes; + foreach( const QModelIndex& idx, selectedIndexes() ) { - if ( !( m_proxyModel->flags( indexes.at( i ) ) & Qt::ItemIsDragEnabled ) ) - indexes.removeAt( i ); - else - pindexes << indexes.at( i ); + if ( ( m_proxyModel->flags( idx ) & Qt::ItemIsDragEnabled ) ) + { + indexes << idx; + pindexes << idx; + } } if ( indexes.count() == 0 ) @@ -326,82 +355,13 @@ TrackView::startDrag( Qt::DropActions supportedActions ) QDrag* drag = new QDrag( this ); drag->setMimeData( data ); - const QPixmap p = createDragPixmap( indexes.count() ); + const QPixmap p = TomahawkUtils::createDragPixmap( indexes.count() ); drag->setPixmap( p ); drag->setHotSpot( QPoint( -20, -20 ) ); - // NOTE: if we support moving items in the model - // in the future, if exec() returns Qt::MoveAction - // we need to clean up ourselves. Qt::DropAction action = drag->exec( supportedActions, Qt::CopyAction ); - if ( action == Qt::MoveAction ) { - foreach ( const QPersistentModelIndex& idx, pindexes ) - { - m_proxyModel->removeIndex( idx ); - } + m_proxyModel->removeIndexes( pindexes ); } } - - -// Inspired from dolphin's draganddrophelper.cpp -QPixmap -TrackView::createDragPixmap( int itemCount ) const -{ - // If more than one item is dragged, align the items inside a - // rectangular grid. The maximum grid size is limited to 5 x 5 items. - int xCount = 3; - int size = 32; - - if ( itemCount > 16 ) - { - xCount = 5; - size = 16; - } else if( itemCount > 9 ) - { - xCount = 4; - size = 22; - } - - if( itemCount < xCount ) - { - xCount = itemCount; - } - - int yCount = itemCount / xCount; - if( itemCount % xCount != 0 ) - { - ++yCount; - } - if( yCount > xCount ) - { - yCount = xCount; - } - // Draw the selected items into the grid cells - QPixmap dragPixmap( xCount * size + xCount - 1, yCount * size + yCount - 1 ); - dragPixmap.fill( Qt::transparent ); - - QPainter painter( &dragPixmap ); - painter.setRenderHint( QPainter::Antialiasing ); - int x = 0; - int y = 0; - for( int i = 0; i < itemCount; ++i ) - { - const QPixmap pixmap = QPixmap( QString( ":/data/icons/audio-x-generic-%2x%2.png" ).arg( size ) ); - painter.drawPixmap( x, y, pixmap ); - - x += size + 1; - if ( x >= dragPixmap.width() ) - { - x = 0; - y += size + 1; - } - if ( y >= dragPixmap.height() ) - { - break; - } - } - - return dragPixmap; -} diff --git a/src/libtomahawk/playlist/trackview.h b/src/libtomahawk/playlist/trackview.h new file mode 100644 index 000000000..7777e24a3 --- /dev/null +++ b/src/libtomahawk/playlist/trackview.h @@ -0,0 +1,98 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TRACKVIEW_H +#define TRACKVIEW_H + +#include +#include + +#include "playlistitemdelegate.h" + +#include "dllmacro.h" + +class LoadingSpinner; +class PlaylistInterface; +class TrackHeader; +class TrackModel; +class TrackProxyModel; +class OverlayWidget; + +class DLLEXPORT TrackView : public QTreeView +{ +Q_OBJECT + +public: + explicit TrackView( QWidget* parent = 0 ); + ~TrackView(); + + virtual QString guid() const { return m_guid; } + virtual void setGuid( const QString& guid ); + + virtual void setModel( TrackModel* model ); + void setProxyModel( TrackProxyModel* model ); + + virtual TrackModel* model() const { return m_model; } + TrackProxyModel* proxyModel() const { return m_proxyModel; } + PlaylistItemDelegate* delegate() const { return m_delegate; } + TrackHeader* header() const { return m_header; } + OverlayWidget* overlay() const { return m_overlay; } + + QModelIndex contextMenuIndex() const { return m_contextMenuIndex; } + void setContextMenuIndex( const QModelIndex& idx ) { m_contextMenuIndex = idx; } + +public slots: + void onItemActivated( const QModelIndex& index ); + + void playItem(); + void addItemsToQueue(); + +protected: + virtual void resizeEvent( QResizeEvent* event ); + + virtual void startDrag( Qt::DropActions supportedActions ); + virtual void dragEnterEvent( QDragEnterEvent* event ); + virtual void dragLeaveEvent( QDragLeaveEvent* event ) { m_dragging = false; setDirtyRegion( m_dropRect ); } + virtual void dragMoveEvent( QDragMoveEvent* event ); + virtual void dropEvent( QDropEvent* event ); + + void paintEvent( QPaintEvent* event ); + void keyPressEvent( QKeyEvent* event ); + +private slots: + void onItemResized( const QModelIndex& index ); + + void onFilterChanged( const QString& filter ); + +private: + QString m_guid; + TrackModel* m_model; + TrackProxyModel* m_proxyModel; + PlaylistItemDelegate* m_delegate; + TrackHeader* m_header; + OverlayWidget* m_overlay; + LoadingSpinner* m_loadingSpinner; + + bool m_resizing; + bool m_dragging; + QRect m_dropRect; + + QModelIndex m_contextMenuIndex; +}; + +#endif // TRACKVIEW_H diff --git a/src/libtomahawk/playlistinterface.h b/src/libtomahawk/playlistinterface.h new file mode 100644 index 000000000..2743a7d13 --- /dev/null +++ b/src/libtomahawk/playlistinterface.h @@ -0,0 +1,73 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef PLAYLISTINTERFACE_H +#define PLAYLISTINTERFACE_H + +#include +#include + +#include "typedefs.h" + +#include "dllmacro.h" +#include "result.h" + +class DLLEXPORT PlaylistInterface +{ +public: + enum RepeatMode { NoRepeat, RepeatOne, RepeatAll }; + enum ViewMode { Unknown, Flat, Tree, Album }; + + PlaylistInterface( QObject* parent = 0 ) : m_object( parent ) {} + virtual ~PlaylistInterface() {} + + virtual QList< Tomahawk::query_ptr > tracks() = 0; + + virtual int unfilteredTrackCount() const = 0; + virtual int trackCount() const = 0; + + virtual Tomahawk::result_ptr previousItem() { return siblingItem( -1 ); } + virtual Tomahawk::result_ptr nextItem() { return siblingItem( 1 ); } + virtual Tomahawk::result_ptr siblingItem( int itemsAway ) = 0; + + virtual PlaylistInterface::RepeatMode repeatMode() const = 0; + virtual bool shuffled() const = 0; + virtual PlaylistInterface::ViewMode viewMode() const { return Unknown; } + + virtual QString filter() const { return m_filter; } + virtual void setFilter( const QString& pattern ) { m_filter = pattern; } + + QObject* object() const { return m_object; } + +public slots: + virtual void setRepeatMode( RepeatMode mode ) = 0; + virtual void setShuffled( bool enabled ) = 0; + +signals: + virtual void repeatModeChanged( PlaylistInterface::RepeatMode mode ) = 0; + virtual void shuffleModeChanged( bool enabled ) = 0; + virtual void trackCountChanged( unsigned int tracks ) = 0; + virtual void sourceTrackCountChanged( unsigned int tracks ) = 0; + +private: + QObject* m_object; + + QString m_filter; +}; + +#endif // PLAYLISTINTERFACE_H diff --git a/src/libtomahawk/qtsingleapp/qtlocalpeer.cpp b/src/libtomahawk/qtsingleapp/qtlocalpeer.cpp new file mode 100644 index 000000000..382d182dc --- /dev/null +++ b/src/libtomahawk/qtsingleapp/qtlocalpeer.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + + +#include "qtlocalpeer.h" +#include +#include + +#if defined(Q_OS_WIN) +#include +#include +typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); +static PProcessIdToSessionId pProcessIdToSessionId = 0; +#endif +#if defined(Q_OS_UNIX) +#include +#endif + +namespace QtLP_Private { +#include "qtlockedfile.cpp" +#if defined(Q_OS_WIN) +#include "qtlockedfile_win.cpp" +#else +#include "qtlockedfile_unix.cpp" +#endif +} + +const char* QtLocalPeer::ack = "ack"; + +QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId) + : QObject(parent), id(appId) +{ + QString prefix = id; + if (id.isEmpty()) { + id = QCoreApplication::applicationFilePath(); +#if defined(Q_OS_WIN) + id = id.toLower(); +#endif + prefix = id.section(QLatin1Char('/'), -1); + } + prefix.remove(QRegExp("[^a-zA-Z]")); + prefix.truncate(6); + + QByteArray idc = id.toUtf8(); + quint16 idNum = qChecksum(idc.constData(), idc.size()); + socketName = QLatin1String("qtsingleapp-") + prefix + + QLatin1Char('-') + QString::number(idNum, 16); + +#if defined(Q_OS_WIN) + if (!pProcessIdToSessionId) { + QLibrary lib("kernel32"); + pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); + } + if (pProcessIdToSessionId) { + DWORD sessionId = 0; + pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); + socketName += QLatin1Char('-') + QString::number(sessionId, 16); + } +#else + socketName += QLatin1Char('-') + QString::number(::getuid(), 16); +#endif + + server = new QLocalServer(this); + QString lockName = QDir(QDir::tempPath()).absolutePath() + + QLatin1Char('/') + socketName + + QLatin1String("-lockfile"); + lockFile.setFileName(lockName); + lockFile.open(QIODevice::ReadWrite); +} + + + +bool QtLocalPeer::isClient() +{ + if (lockFile.isLocked()) + return false; + + if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false)) + return true; + + bool res = server->listen(socketName); +#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0)) + // ### Workaround + if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { + QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); + res = server->listen(socketName); + } +#endif + if (!res) + qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); + QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection())); + return false; +} + + +bool QtLocalPeer::sendMessage(const QString &message, int timeout) +{ + if (!isClient()) + return false; + + QLocalSocket socket; + bool connOk = false; + for(int i = 0; i < 2; i++) { + // Try twice, in case the other instance is just starting up + socket.connectToServer(socketName); + connOk = socket.waitForConnected(timeout/2); + if (connOk || i) + break; + int ms = 250; +#if defined(Q_OS_WIN) + Sleep(DWORD(ms)); +#else + struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; + nanosleep(&ts, NULL); +#endif + } + if (!connOk) + return false; + + QByteArray uMsg(message.toUtf8()); + QDataStream ds(&socket); + ds.writeBytes(uMsg.constData(), uMsg.size()); + bool res = socket.waitForBytesWritten(timeout); + if (res) { + res &= socket.waitForReadyRead(timeout); // wait for ack + if (res) + res &= (socket.read(qstrlen(ack)) == ack); + } + return res; +} + + +void QtLocalPeer::receiveConnection() +{ + QLocalSocket* socket = server->nextPendingConnection(); + if (!socket) + return; + + while (socket->bytesAvailable() < (int)sizeof(quint32)) + socket->waitForReadyRead(); + QDataStream ds(socket); + QByteArray uMsg; + quint32 remaining; + ds >> remaining; + uMsg.resize(remaining); + int got = 0; + char* uMsgBuf = uMsg.data(); + do { + got = ds.readRawData(uMsgBuf, remaining); + remaining -= got; + uMsgBuf += got; + } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); + if (got < 0) { + qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData()); + delete socket; + return; + } + QString message(QString::fromUtf8(uMsg)); + socket->write(ack, qstrlen(ack)); + socket->waitForBytesWritten(1000); + delete socket; + emit messageReceived(message); //### (might take a long time to return) +} diff --git a/src/libtomahawk/qtsingleapp/qtlocalpeer.h b/src/libtomahawk/qtsingleapp/qtlocalpeer.h new file mode 100644 index 000000000..869af2ac2 --- /dev/null +++ b/src/libtomahawk/qtsingleapp/qtlocalpeer.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + + +#include +#include +#include + +#include "qtlockedfile.h" + +class QtLocalPeer : public QObject +{ + Q_OBJECT + +public: + QtLocalPeer(QObject *parent = 0, const QString &appId = QString()); + bool isClient(); + bool sendMessage(const QString &message, int timeout); + QString applicationId() const + { return id; } + +Q_SIGNALS: + void messageReceived(const QString &message); + +protected Q_SLOTS: + void receiveConnection(); + +protected: + QString id; + QString socketName; + QLocalServer* server; + QtLP_Private::QtLockedFile lockFile; + +private: + static const char* ack; +}; diff --git a/src/libtomahawk/qtsingleapp/qtlockedfile.cpp b/src/libtomahawk/qtsingleapp/qtlockedfile.cpp new file mode 100644 index 000000000..3e73ba652 --- /dev/null +++ b/src/libtomahawk/qtsingleapp/qtlockedfile.cpp @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +#include "qtlockedfile.h" + +/*! + \class QtLockedFile + + \brief The QtLockedFile class extends QFile with advisory locking + functions. + + A file may be locked in read or write mode. Multiple instances of + \e QtLockedFile, created in multiple processes running on the same + machine, may have a file locked in read mode. Exactly one instance + may have it locked in write mode. A read and a write lock cannot + exist simultaneously on the same file. + + The file locks are advisory. This means that nothing prevents + another process from manipulating a locked file using QFile or + file system functions offered by the OS. Serialization is only + guaranteed if all processes that access the file use + QLockedFile. Also, while holding a lock on a file, a process + must not open the same file again (through any API), or locks + can be unexpectedly lost. + + The lock provided by an instance of \e QtLockedFile is released + whenever the program terminates. This is true even when the + program crashes and no destructors are called. +*/ + +/*! \enum QtLockedFile::LockMode + + This enum describes the available lock modes. + + \value ReadLock A read lock. + \value WriteLock A write lock. + \value NoLock Neither a read lock nor a write lock. +*/ + +/*! + Constructs an unlocked \e QtLockedFile object. This constructor + behaves in the same way as \e QFile::QFile(). + + \sa QFile::QFile() +*/ +QtLockedFile::QtLockedFile() + : QFile() +{ +#ifdef Q_OS_WIN + wmutex = 0; + rmutex = 0; +#endif + m_lock_mode = NoLock; +} + +/*! + Constructs an unlocked QtLockedFile object with file \a name. This + constructor behaves in the same way as \e QFile::QFile(const + QString&). + + \sa QFile::QFile() +*/ +QtLockedFile::QtLockedFile(const QString &name) + : QFile(name) +{ +#ifdef Q_OS_WIN + wmutex = 0; + rmutex = 0; +#endif + m_lock_mode = NoLock; +} + +/*! + Opens the file in OpenMode \a mode. + + This is identical to QFile::open(), with the one exception that the + Truncate mode flag is disallowed. Truncation would conflict with the + advisory file locking, since the file would be modified before the + write lock is obtained. If truncation is required, use resize(0) + after obtaining the write lock. + + Returns true if successful; otherwise false. + + \sa QFile::open(), QFile::resize() +*/ +bool QtLockedFile::open(OpenMode mode) +{ + if (mode & QIODevice::Truncate) { + qWarning("QtLockedFile::open(): Truncate mode not allowed."); + return false; + } + return QFile::open(mode); +} + +/*! + Returns \e true if this object has a in read or write lock; + otherwise returns \e false. + + \sa lockMode() +*/ +bool QtLockedFile::isLocked() const +{ + return m_lock_mode != NoLock; +} + +/*! + Returns the type of lock currently held by this object, or \e + QtLockedFile::NoLock. + + \sa isLocked() +*/ +QtLockedFile::LockMode QtLockedFile::lockMode() const +{ + return m_lock_mode; +} + +/*! + \fn bool QtLockedFile::lock(LockMode mode, bool block = true) + + Obtains a lock of type \a mode. The file must be opened before it + can be locked. + + If \a block is true, this function will block until the lock is + aquired. If \a block is false, this function returns \e false + immediately if the lock cannot be aquired. + + If this object already has a lock of type \a mode, this function + returns \e true immediately. If this object has a lock of a + different type than \a mode, the lock is first released and then a + new lock is obtained. + + This function returns \e true if, after it executes, the file is + locked by this object, and \e false otherwise. + + \sa unlock(), isLocked(), lockMode() +*/ + +/*! + \fn bool QtLockedFile::unlock() + + Releases a lock. + + If the object has no lock, this function returns immediately. + + This function returns \e true if, after it executes, the file is + not locked by this object, and \e false otherwise. + + \sa lock(), isLocked(), lockMode() +*/ + +/*! + \fn QtLockedFile::~QtLockedFile() + + Destroys the \e QtLockedFile object. If any locks were held, they + are released. +*/ diff --git a/src/libtomahawk/qtsingleapp/qtlockedfile.h b/src/libtomahawk/qtsingleapp/qtlockedfile.h new file mode 100644 index 000000000..07a42bffb --- /dev/null +++ b/src/libtomahawk/qtsingleapp/qtlockedfile.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +#ifndef QTLOCKEDFILE_H +#define QTLOCKEDFILE_H + +#include +#ifdef Q_OS_WIN +#include +#endif + +#if defined(Q_WS_WIN) +# if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT) +# define QT_QTLOCKEDFILE_EXPORT +# elif defined(QT_QTLOCKEDFILE_IMPORT) +# if defined(QT_QTLOCKEDFILE_EXPORT) +# undef QT_QTLOCKEDFILE_EXPORT +# endif +# define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport) +# elif defined(QT_QTLOCKEDFILE_EXPORT) +# undef QT_QTLOCKEDFILE_EXPORT +# define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport) +# endif +#else +# define QT_QTLOCKEDFILE_EXPORT +#endif + +namespace QtLP_Private { + +class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile +{ +public: + enum LockMode { NoLock = 0, ReadLock, WriteLock }; + + QtLockedFile(); + QtLockedFile(const QString &name); + ~QtLockedFile(); + + bool open(OpenMode mode); + + bool lock(LockMode mode, bool block = true); + bool unlock(); + bool isLocked() const; + LockMode lockMode() const; + +private: +#ifdef Q_OS_WIN + Qt::HANDLE wmutex; + Qt::HANDLE rmutex; + QVector rmutexes; + QString mutexname; + + Qt::HANDLE getMutexHandle(int idx, bool doCreate); + bool waitMutex(Qt::HANDLE mutex, bool doBlock); + +#endif + LockMode m_lock_mode; +}; +} +#endif diff --git a/src/libtomahawk/qtsingleapp/qtlockedfile_unix.cpp b/src/libtomahawk/qtsingleapp/qtlockedfile_unix.cpp new file mode 100644 index 000000000..715c7d9b1 --- /dev/null +++ b/src/libtomahawk/qtsingleapp/qtlockedfile_unix.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +#include +#include +#include +#include + +#include "qtlockedfile.h" + +bool QtLockedFile::lock(LockMode mode, bool block) +{ + if (!isOpen()) { + qWarning("QtLockedFile::lock(): file is not opened"); + return false; + } + + if (mode == NoLock) + return unlock(); + + if (mode == m_lock_mode) + return true; + + if (m_lock_mode != NoLock) + unlock(); + + struct flock fl; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; + int cmd = block ? F_SETLKW : F_SETLK; + int ret = fcntl(handle(), cmd, &fl); + + if (ret == -1) { + if (errno != EINTR && errno != EAGAIN) + qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); + return false; + } + + + m_lock_mode = mode; + return true; +} + + +bool QtLockedFile::unlock() +{ + if (!isOpen()) { + qWarning("QtLockedFile::unlock(): file is not opened"); + return false; + } + + if (!isLocked()) + return true; + + struct flock fl; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_UNLCK; + int ret = fcntl(handle(), F_SETLKW, &fl); + + if (ret == -1) { + qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); + return false; + } + + m_lock_mode = NoLock; + return true; +} + +QtLockedFile::~QtLockedFile() +{ + if (isOpen()) + unlock(); +} + diff --git a/src/libtomahawk/qtsingleapp/qtlockedfile_win.cpp b/src/libtomahawk/qtsingleapp/qtlockedfile_win.cpp new file mode 100644 index 000000000..8090470cd --- /dev/null +++ b/src/libtomahawk/qtsingleapp/qtlockedfile_win.cpp @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +#include "qtlockedfile.h" +#include +#include + +#define MUTEX_PREFIX "QtLockedFile mutex " +// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS +#define MAX_READERS MAXIMUM_WAIT_OBJECTS + +#define TCHAR WCHAR + +Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate) +{ + if (mutexname.isEmpty()) { + QFileInfo fi(*this); + mutexname = QString::fromLatin1(MUTEX_PREFIX) + + fi.absoluteFilePath().toLower(); + } + QString mname(mutexname); + if (idx >= 0) + mname += QString::number(idx); + + Qt::HANDLE mutex; + if (doCreate) { + QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); }, + { mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } ); + if (!mutex) { + qErrnoWarning("QtLockedFile::lock(): CreateMutex failed"); + return 0; + } + } + else { + QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); }, + { mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } ); + if (!mutex) { + if (GetLastError() != ERROR_FILE_NOT_FOUND) + qErrnoWarning("QtLockedFile::lock(): OpenMutex failed"); + return 0; + } + } + return mutex; +} + +bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock) +{ + Q_ASSERT(mutex); + DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0); + switch (res) { + case WAIT_OBJECT_0: + case WAIT_ABANDONED: + return true; + break; + case WAIT_TIMEOUT: + break; + default: + qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed"); + } + return false; +} + + + +bool QtLockedFile::lock(LockMode mode, bool block) +{ + if (!isOpen()) { + qWarning("QtLockedFile::lock(): file is not opened"); + return false; + } + + if (mode == NoLock) + return unlock(); + + if (mode == m_lock_mode) + return true; + + if (m_lock_mode != NoLock) + unlock(); + + if (!wmutex && !(wmutex = getMutexHandle(-1, true))) + return false; + + if (!waitMutex(wmutex, block)) + return false; + + if (mode == ReadLock) { + int idx = 0; + for (; idx < MAX_READERS; idx++) { + rmutex = getMutexHandle(idx, false); + if (!rmutex || waitMutex(rmutex, false)) + break; + CloseHandle(rmutex); + } + bool ok = true; + if (idx >= MAX_READERS) { + qWarning("QtLockedFile::lock(): too many readers"); + rmutex = 0; + ok = false; + } + else if (!rmutex) { + rmutex = getMutexHandle(idx, true); + if (!rmutex || !waitMutex(rmutex, false)) + ok = false; + } + if (!ok && rmutex) { + CloseHandle(rmutex); + rmutex = 0; + } + ReleaseMutex(wmutex); + if (!ok) + return false; + } + else { + Q_ASSERT(rmutexes.isEmpty()); + for (int i = 0; i < MAX_READERS; i++) { + Qt::HANDLE mutex = getMutexHandle(i, false); + if (mutex) + rmutexes.append(mutex); + } + if (rmutexes.size()) { + DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(), + TRUE, block ? INFINITE : 0); + if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) { + if (res != WAIT_TIMEOUT) + qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed"); + m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky + unlock(); + return false; + } + } + } + + m_lock_mode = mode; + return true; +} + +bool QtLockedFile::unlock() +{ + if (!isOpen()) { + qWarning("QtLockedFile::unlock(): file is not opened"); + return false; + } + + if (!isLocked()) + return true; + + if (m_lock_mode == ReadLock) { + ReleaseMutex(rmutex); + CloseHandle(rmutex); + rmutex = 0; + } + else { + foreach(Qt::HANDLE mutex, rmutexes) { + ReleaseMutex(mutex); + CloseHandle(mutex); + } + rmutexes.clear(); + ReleaseMutex(wmutex); + } + + m_lock_mode = QtLockedFile::NoLock; + return true; +} + +QtLockedFile::~QtLockedFile() +{ + if (isOpen()) + unlock(); + if (wmutex) + CloseHandle(wmutex); +} diff --git a/src/libtomahawk/qtsingleapp/qtsingleapplication.cpp b/src/libtomahawk/qtsingleapp/qtsingleapplication.cpp new file mode 100644 index 000000000..5a8f1b035 --- /dev/null +++ b/src/libtomahawk/qtsingleapp/qtsingleapplication.cpp @@ -0,0 +1,344 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + + +#include "qtsingleapplication.h" +#include "qtlocalpeer.h" +#include + + +/*! + \class QtSingleApplication qtsingleapplication.h + \brief The QtSingleApplication class provides an API to detect and + communicate with running instances of an application. + + This class allows you to create applications where only one + instance should be running at a time. I.e., if the user tries to + launch another instance, the already running instance will be + activated instead. Another usecase is a client-server system, + where the first started instance will assume the role of server, + and the later instances will act as clients of that server. + + By default, the full path of the executable file is used to + determine whether two processes are instances of the same + application. You can also provide an explicit identifier string + that will be compared instead. + + The application should create the QtSingleApplication object early + in the startup phase, and call isRunning() to find out if another + instance of this application is already running. If isRunning() + returns false, it means that no other instance is running, and + this instance has assumed the role as the running instance. In + this case, the application should continue with the initialization + of the application user interface before entering the event loop + with exec(), as normal. + + The messageReceived() signal will be emitted when the running + application receives messages from another instance of the same + application. When a message is received it might be helpful to the + user to raise the application so that it becomes visible. To + facilitate this, QtSingleApplication provides the + setActivationWindow() function and the activateWindow() slot. + + If isRunning() returns true, another instance is already + running. It may be alerted to the fact that another instance has + started by using the sendMessage() function. Also data such as + startup parameters (e.g. the name of the file the user wanted this + new instance to open) can be passed to the running instance with + this function. Then, the application should terminate (or enter + client mode). + + If isRunning() returns true, but sendMessage() fails, that is an + indication that the running instance is frozen. + + Here's an example that shows how to convert an existing + application to use QtSingleApplication. It is very simple and does + not make use of all QtSingleApplication's functionality (see the + examples for that). + + \code + // Original + int main(int argc, char **argv) + { + QApplication app(argc, argv); + + MyMainWidget mmw; + mmw.show(); + return app.exec(); + } + + // Single instance + int main(int argc, char **argv) + { + QtSingleApplication app(argc, argv); + + if (app.isRunning()) + return !app.sendMessage(someDataString); + + MyMainWidget mmw; + app.setActivationWindow(&mmw); + mmw.show(); + return app.exec(); + } + \endcode + + Once this QtSingleApplication instance is destroyed (normally when + the process exits or crashes), when the user next attempts to run the + application this instance will not, of course, be encountered. The + next instance to call isRunning() or sendMessage() will assume the + role as the new running instance. + + For console (non-GUI) applications, QtSingleCoreApplication may be + used instead of this class, to avoid the dependency on the QtGui + library. + + \sa QtSingleCoreApplication +*/ + + +void QtSingleApplication::sysInit(const QString &appId) +{ + actWin = 0; + peer = new QtLocalPeer(this, appId); + connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); +} + + +/*! + Creates a QtSingleApplication object. The application identifier + will be QCoreApplication::applicationFilePath(). \a argc, \a + argv, and \a GUIenabled are passed on to the QAppliation constructor. + + If you are creating a console application (i.e. setting \a + GUIenabled to false), you may consider using + QtSingleCoreApplication instead. +*/ + +QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled) + : QApplication(argc, argv, GUIenabled) +{ + sysInit(); +} + + +/*! + Creates a QtSingleApplication object with the application + identifier \a appId. \a argc and \a argv are passed on to the + QAppliation constructor. +*/ + +QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv) + : QApplication(argc, argv) +{ + sysInit(appId); +} + + +/*! + Creates a QtSingleApplication object. The application identifier + will be QCoreApplication::applicationFilePath(). \a argc, \a + argv, and \a type are passed on to the QAppliation constructor. +*/ +QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type) + : QApplication(argc, argv, type) +{ + sysInit(); +} + + +#if defined(Q_WS_X11) +/*! + Special constructor for X11, ref. the documentation of + QApplication's corresponding constructor. The application identifier + will be QCoreApplication::applicationFilePath(). \a dpy, \a visual, + and \a cmap are passed on to the QApplication constructor. +*/ +QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap) + : QApplication(dpy, visual, cmap) +{ + sysInit(); +} + +/*! + Special constructor for X11, ref. the documentation of + QApplication's corresponding constructor. The application identifier + will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a + argv, \a visual, and \a cmap are passed on to the QApplication + constructor. +*/ +QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) + : QApplication(dpy, argc, argv, visual, cmap) +{ + sysInit(); +} + +/*! + Special constructor for X11, ref. the documentation of + QApplication's corresponding constructor. The application identifier + will be \a appId. \a dpy, \a argc, \a + argv, \a visual, and \a cmap are passed on to the QApplication + constructor. +*/ +QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) + : QApplication(dpy, argc, argv, visual, cmap) +{ + sysInit(appId); +} +#endif + + +/*! + Returns true if another instance of this application is running; + otherwise false. + + This function does not find instances of this application that are + being run by a different user (on Windows: that are running in + another session). + + \sa sendMessage() +*/ + +bool QtSingleApplication::isRunning() +{ + return peer->isClient(); +} + + +/*! + Tries to send the text \a message to the currently running + instance. The QtSingleApplication object in the running instance + will emit the messageReceived() signal when it receives the + message. + + This function returns true if the message has been sent to, and + processed by, the current instance. If there is no instance + currently running, or if the running instance fails to process the + message within \a timeout milliseconds, this function return false. + + \sa isRunning(), messageReceived() +*/ +bool QtSingleApplication::sendMessage(const QString &message, int timeout) +{ + return peer->sendMessage(message, timeout); +} + + +/*! + Returns the application identifier. Two processes with the same + identifier will be regarded as instances of the same application. +*/ +QString QtSingleApplication::id() const +{ + return peer->applicationId(); +} + + +/*! + Sets the activation window of this application to \a aw. The + activation window is the widget that will be activated by + activateWindow(). This is typically the application's main window. + + If \a activateOnMessage is true (the default), the window will be + activated automatically every time a message is received, just prior + to the messageReceived() signal being emitted. + + \sa activateWindow(), messageReceived() +*/ + +void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage) +{ + actWin = aw; + if (activateOnMessage) + connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); + else + disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); +} + + +/*! + Returns the applications activation window if one has been set by + calling setActivationWindow(), otherwise returns 0. + + \sa setActivationWindow() +*/ +QWidget* QtSingleApplication::activationWindow() const +{ + return actWin; +} + + +/*! + De-minimizes, raises, and activates this application's activation window. + This function does nothing if no activation window has been set. + + This is a convenience function to show the user that this + application instance has been activated when he has tried to start + another instance. + + This function should typically be called in response to the + messageReceived() signal. By default, that will happen + automatically, if an activation window has been set. + + \sa setActivationWindow(), messageReceived(), initialize() +*/ +void QtSingleApplication::activateWindow() +{ + if (actWin) { + actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); + actWin->raise(); + actWin->activateWindow(); + } +} + + +/*! + \fn void QtSingleApplication::messageReceived(const QString& message) + + This signal is emitted when the current instance receives a \a + message from another instance of this application. + + \sa sendMessage(), setActivationWindow(), activateWindow() +*/ + + +/*! + \fn void QtSingleApplication::initialize(bool dummy = true) + + \obsolete +*/ diff --git a/src/libtomahawk/qtsingleapp/qtsingleapplication.h b/src/libtomahawk/qtsingleapp/qtsingleapplication.h new file mode 100644 index 000000000..c696d60ce --- /dev/null +++ b/src/libtomahawk/qtsingleapp/qtsingleapplication.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + + +#include + +#include "dllmacro.h" + +class QtLocalPeer; + +class DLLEXPORT QtSingleApplication : public QApplication +{ + Q_OBJECT + +public: + QtSingleApplication(int &argc, char **argv, bool GUIenabled = true); + QtSingleApplication(const QString &id, int &argc, char **argv); + QtSingleApplication(int &argc, char **argv, Type type); +#if defined(Q_WS_X11) + QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); + QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0); + QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); +#endif + + bool isRunning(); + QString id() const; + + void setActivationWindow(QWidget* aw, bool activateOnMessage = true); + QWidget* activationWindow() const; + + // Obsolete: + void initialize(bool dummy = true) + { isRunning(); Q_UNUSED(dummy) } + +public Q_SLOTS: + bool sendMessage(const QString &message, int timeout = 5000); + void activateWindow(); + + +Q_SIGNALS: + void messageReceived(const QString &message); + + +private: + void sysInit(const QString &appId = QString()); + QtLocalPeer *peer; + QWidget *actWin; +}; diff --git a/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.cpp b/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.cpp new file mode 100644 index 000000000..cf607710e --- /dev/null +++ b/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + + +#include "qtsinglecoreapplication.h" +#include "qtlocalpeer.h" + +/*! + \class QtSingleCoreApplication qtsinglecoreapplication.h + \brief A variant of the QtSingleApplication class for non-GUI applications. + + This class is a variant of QtSingleApplication suited for use in + console (non-GUI) applications. It is an extension of + QCoreApplication (instead of QApplication). It does not require + the QtGui library. + + The API and usage is identical to QtSingleApplication, except that + functions relating to the "activation window" are not present, for + obvious reasons. Please refer to the QtSingleApplication + documentation for explanation of the usage. + + A QtSingleCoreApplication instance can communicate to a + QtSingleApplication instance if they share the same application + id. Hence, this class can be used to create a light-weight + command-line tool that sends commands to a GUI application. + + \sa QtSingleApplication +*/ + +/*! + Creates a QtSingleCoreApplication object. The application identifier + will be QCoreApplication::applicationFilePath(). \a argc and \a + argv are passed on to the QCoreAppliation constructor. +*/ + +QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv) + : QCoreApplication(argc, argv) +{ + peer = new QtLocalPeer(this); + connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); +} + + +/*! + Creates a QtSingleCoreApplication object with the application + identifier \a appId. \a argc and \a argv are passed on to the + QCoreAppliation constructor. +*/ +QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv) + : QCoreApplication(argc, argv) +{ + peer = new QtLocalPeer(this, appId); + connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); +} + + +/*! + Returns true if another instance of this application is running; + otherwise false. + + This function does not find instances of this application that are + being run by a different user (on Windows: that are running in + another session). + + \sa sendMessage() +*/ + +bool QtSingleCoreApplication::isRunning() +{ + return peer->isClient(); +} + + +/*! + Tries to send the text \a message to the currently running + instance. The QtSingleCoreApplication object in the running instance + will emit the messageReceived() signal when it receives the + message. + + This function returns true if the message has been sent to, and + processed by, the current instance. If there is no instance + currently running, or if the running instance fails to process the + message within \a timeout milliseconds, this function return false. + + \sa isRunning(), messageReceived() +*/ + +bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout) +{ + return peer->sendMessage(message, timeout); +} + + +/*! + Returns the application identifier. Two processes with the same + identifier will be regarded as instances of the same application. +*/ + +QString QtSingleCoreApplication::id() const +{ + return peer->applicationId(); +} + + +/*! + \fn void QtSingleCoreApplication::messageReceived(const QString& message) + + This signal is emitted when the current instance receives a \a + message from another instance of this application. + + \sa sendMessage() +*/ diff --git a/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.h b/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.h new file mode 100644 index 000000000..ef529a8f6 --- /dev/null +++ b/src/libtomahawk/qtsingleapp/qtsinglecoreapplication.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + + +#include + +class QtLocalPeer; + +class QtSingleCoreApplication : public QCoreApplication +{ + Q_OBJECT + +public: + QtSingleCoreApplication(int &argc, char **argv); + QtSingleCoreApplication(const QString &id, int &argc, char **argv); + + bool isRunning(); + QString id() const; + +public Q_SLOTS: + bool sendMessage(const QString &message, int timeout = 5000); + + +Q_SIGNALS: + void messageReceived(const QString &message); + + +private: + QtLocalPeer* peer; +}; diff --git a/src/libtomahawk/query.cpp b/src/libtomahawk/query.cpp new file mode 100644 index 000000000..b8c48e83c --- /dev/null +++ b/src/libtomahawk/query.cpp @@ -0,0 +1,226 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "query.h" + +#include "collection.h" +#include + +#include "database/database.h" +#include "database/databasecommand_logplayback.h" +#include "database/databasecommand_playbackhistory.h" +#include "database/databasecommand_loadplaylistentries.h" +#include "pipeline.h" +#include "sourcelist.h" + +using namespace Tomahawk; + + +query_ptr +Query::get( const QString& artist, const QString& track, const QString& album, const QID& qid ) +{ + query_ptr q = query_ptr( new Query( artist, track, album, qid ) ); + + if ( !qid.isEmpty() ) + Pipeline::instance()->resolve( q ); + return q; +} + + +Query::Query( const QString& artist, const QString& track, const QString& album, const QID& qid ) + : m_solved( false ) + , m_playable( false ) + , m_qid( qid ) + , m_artist( artist ) + , m_album( album ) + , m_track( track ) + , m_duration( -1 ) +{ + if ( !qid.isEmpty() ) + { + connect( Database::instance(), SIGNAL( indexReady() ), SLOT( refreshResults() ), Qt::QueuedConnection ); + } +} + + +void +Query::addResults( const QList< Tomahawk::result_ptr >& newresults ) +{ + bool becameSolved = false; + { +// QMutexLocker lock( &m_mut ); + m_results.append( newresults ); + qStableSort( m_results.begin(), m_results.end(), Query::resultSorter ); + + // hook up signals, and check solved status + foreach( const result_ptr& rp, newresults ) + { + connect( rp.data(), SIGNAL( statusChanged() ), SLOT( onResultStatusChanged() ) ); + } + } + + emit resultsAdded( newresults ); + checkResults(); +} + + +void +Query::refreshResults() +{ + Pipeline::instance()->resolve( id() ); +} + + +void +Query::onResultStatusChanged() +{ + if ( m_results.count() ) + qStableSort( m_results.begin(), m_results.end(), Query::resultSorter ); + checkResults(); + + emit resultsChanged(); +} + + +void +Query::removeResult( const Tomahawk::result_ptr& result ) +{ + { +// QMutexLocker lock( &m_mut ); + m_results.removeAll( result ); + } + + emit resultsRemoved( result ); + checkResults(); +} + + +void +Query::onResolvingFinished() +{ +// qDebug() << Q_FUNC_INFO << "Finished resolving." << toString(); + emit resolvingFinished( m_solved ); +} + + +QList< result_ptr > +Query::results() const +{ +// QMutexLocker lock( &m_mut ); + return m_results; +} + + +unsigned int +Query::numResults() const +{ +// QMutexLocker lock( &m_mut ); + return m_results.length(); +} + + +QID +Query::id() const +{ + if ( m_qid.isEmpty() ) + { + m_qid = uuid(); + } + + return m_qid; +} + + +bool +Query::resultSorter( const result_ptr& left, const result_ptr& right ) +{ + return left->score() > right->score(); +} + + +void +Query::clearResults() +{ + foreach( const result_ptr& rp, m_results ) + { + removeResult( rp ); + } +} + + +void +Query::checkResults() +{ + bool becameSolved = false; + bool becameUnsolved = true; + m_playable = false; + + // hook up signals, and check solved status + foreach( const result_ptr& rp, m_results ) + { + if ( rp->score() > 0.0 && rp->collection().isNull() ) + { + m_playable = true; + } + if ( !rp->collection().isNull() && rp->collection()->source()->isOnline() ) + { + m_playable = true; + + if ( rp->score() > 0.99 ) + { + becameUnsolved = false; + + if ( !m_solved ) + { + m_solved = true; + becameSolved = true; + } + } + } + } + + if ( m_solved && becameUnsolved ) + { + m_solved = false; + emit solvedStateChanged( false ); + } + + if( becameSolved ) + emit solvedStateChanged( true ); +} + + +QVariant +Query::toVariant() const +{ + QVariantMap m; + m.insert( "artist", artist() ); + m.insert( "album", album() ); + m.insert( "track", track() ); + m.insert( "duration", duration() ); + m.insert( "qid", id() ); + + return m; +} + + +QString +Query::toString() const +{ + return QString( "Query(%1, %2 - %3)" ).arg( id() ).arg( artist() ).arg( track() ); +} diff --git a/src/libtomahawk/query.h b/src/libtomahawk/query.h new file mode 100644 index 000000000..bfa2b3d1d --- /dev/null +++ b/src/libtomahawk/query.h @@ -0,0 +1,123 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef QUERY_H +#define QUERY_H + +#include +#include +#include +#include + +#include "result.h" +#include "typedefs.h" + +#include "dllmacro.h" + +class DatabaseCommand_LogPlayback; +class DatabaseCommand_PlaybackHistory; +class DatabaseCommand_LoadPlaylistEntries; + +namespace Tomahawk +{ + +class DLLEXPORT Query : public QObject +{ +Q_OBJECT + +friend class ::DatabaseCommand_LogPlayback; +friend class ::DatabaseCommand_PlaybackHistory; +friend class ::DatabaseCommand_LoadPlaylistEntries; + +public: + static query_ptr get( const QString& artist, const QString& track, const QString& album, const QID& qid = QString() ); + explicit Query( const QString& artist, const QString& track, const QString& album, const QID& qid ); + + /// returns list of all results so far + QList< result_ptr > results() const; + + /// how many results found so far? + unsigned int numResults() const; + + QID id() const; + + /// sorter for list of results + static bool resultSorter( const result_ptr &left, const result_ptr& right ); + + /// true when a perfect result has been found (score of 1.0) + bool solved() const { return m_solved; } + /// true when any result has been found (score may be less than 1.0) + bool playable() const { return m_playable; } + + unsigned int lastPipelineWeight() const { return m_lastpipelineweight; } + void setLastPipelineWeight( unsigned int w ) { m_lastpipelineweight = w; } + + void setArtist( const QString& artist ) { m_artist = artist; } + void setAlbum( const QString& album ) { m_album = album; } + void setTrack( const QString& track ) { m_track = track; } + void setResultHint( const QString& resultHint ) { m_resultHint = resultHint; } + void setDuration( int duration ) { m_duration = duration; } + + QVariant toVariant() const; + QString toString() const; + + QString resultHint() const { return m_resultHint; } + QString artist() const { return m_artist; } + QString album() const { return m_album; } + QString track() const { return m_track; } + int duration() const { return m_duration; } + +signals: + void resultsAdded( const QList& ); + void resultsRemoved( const Tomahawk::result_ptr& ); + + void resultsChanged(); + void solvedStateChanged( bool state ); + void resolvingFinished( bool hasResults ); + +public slots: + /// (indirectly) called by resolver plugins when results are found + void addResults( const QList< Tomahawk::result_ptr >& ); + void removeResult( const Tomahawk::result_ptr& ); + + void onResolvingFinished(); + +private slots: + void onResultStatusChanged(); + void refreshResults(); + +private: + void clearResults(); + void checkResults(); + + QList< Tomahawk::result_ptr > m_results; + bool m_solved; + bool m_playable; + mutable QID m_qid; + unsigned int m_lastpipelineweight; + + QString m_artist; + QString m_album; + QString m_track; + int m_duration; + QString m_resultHint; +}; + +}; //ns + +#endif // QUERY_H diff --git a/src/libtomahawk/resolver.h b/src/libtomahawk/resolver.h new file mode 100644 index 000000000..d1b526ffe --- /dev/null +++ b/src/libtomahawk/resolver.h @@ -0,0 +1,76 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef RESOLVER_H +#define RESOLVER_H + +#include + +#include "query.h" + +#include "dllmacro.h" + +// implement this if you can resolve queries to content + +/* + Weight: 1-100, 100 being the best + Timeout: some millisecond value, after which we try the next highest + weighted resolver + +*/ +namespace Tomahawk +{ + +class DLLEXPORT Resolver : public QObject +{ +Q_OBJECT + +public: + Resolver() {} + + virtual QString name() const = 0; + virtual unsigned int weight() const = 0; + virtual unsigned int preference() const { return 100; }; + virtual unsigned int timeout() const = 0; + + //virtual QWidget * configUI() { return 0; }; + //etc + +public slots: + virtual void resolve( const Tomahawk::query_ptr& query ) = 0; +}; + +class DLLEXPORT ExternalResolver : public Resolver +{ +Q_OBJECT + +public: + ExternalResolver( const QString& filePath ) { m_filePath = filePath; } + + virtual QString filePath() const { return m_filePath; } + +public slots: + virtual void stop() = 0; + +private: + QString m_filePath; +}; + +}; //ns + +#endif // RESOLVER_H diff --git a/src/libtomahawk/result.cpp b/src/libtomahawk/result.cpp new file mode 100644 index 000000000..9f655f149 --- /dev/null +++ b/src/libtomahawk/result.cpp @@ -0,0 +1,187 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "result.h" + +#include "album.h" +#include "collection.h" +#include "database/databasecommand_resolve.h" +#include "database/databasecommand_alltracks.h" +#include "database/databasecommand_addfiles.h" +#include "database/databasecommand_loadfile.h" + +using namespace Tomahawk; + + +Result::Result() + : QObject() + , m_year( 0 ) +{ +} + + +Result::~Result() +{ +} + + +artist_ptr +Result::artist() const +{ + return m_artist; +} + + +album_ptr +Result::album() const +{ + return m_album; +} + + +collection_ptr +Result::collection() const +{ + return m_collection; +} + + +float +Result::score() const +{ + if ( !collection().isNull() && collection()->source()->isOnline() ) + { + return m_score; + } + else + { + // check if this a valid collection-less result (e.g. from youtube, but ignore offline sources still) + if ( collection().isNull() ) + return m_score; + else + return 0.0; + } +} + + +RID +Result::id() const +{ + Q_ASSERT( !m_rid.isEmpty() ); + return m_rid; +} + + +QVariant +Result::toVariant() const +{ + QVariantMap m; + m.insert( "artist", artist()->name() ); + m.insert( "album", album()->name() ); + m.insert( "track", track() ); + + if ( !collection().isNull() ) + m.insert( "source", collection()->source()->friendlyName() ); + else + m.insert( "source", friendlySource() ); + + m.insert( "mimetype", mimetype() ); + m.insert( "size", size() ); + m.insert( "bitrate", bitrate() ); + m.insert( "duration", duration() ); + m.insert( "score", score() ); + m.insert( "sid", id() ); + + return m; +} + + +QString +Result::toString() const +{ + return QString( "Result(%1 %2\t%3 - %4 %5" ).arg( id() ).arg( score() ).arg( artist()->name() ).arg( track() ).arg( url() ); +} + + +Tomahawk::query_ptr +Result::toQuery() const +{ + Tomahawk::query_ptr query = Tomahawk::Query::get( artist()->name(), track(), album()->name() ); + return query; +} + + +void +Result::updateAttributes() +{ + if ( m_attributes.contains( "releaseyear" ) ) + { + m_year = m_attributes.value( "releaseyear" ).toInt(); + } +} + + +void +Result::onOnline() +{ +// qDebug() << Q_FUNC_INFO << toString(); + emit statusChanged(); +} + + +void +Result::onOffline() +{ +// qDebug() << Q_FUNC_INFO << toString(); + emit statusChanged(); +} + + +void +Result::setArtist( const Tomahawk::artist_ptr& artist ) +{ + m_artist = artist; +} + + +void +Result::setAlbum( const Tomahawk::album_ptr& album ) +{ + m_album = album; +} + + +void +Result::setCollection( const Tomahawk::collection_ptr& collection ) +{ + m_collection = collection; + connect( m_collection->source().data(), SIGNAL( online() ), SLOT( onOnline() ), Qt::QueuedConnection ); + connect( m_collection->source().data(), SIGNAL( offline() ), SLOT( onOffline() ), Qt::QueuedConnection ); +} + + +QString +Result::friendlySource() const +{ + if ( collection().isNull() ) + { + return m_friendlySource; + } + else + return collection()->source()->friendlyName(); +} diff --git a/src/libtomahawk/result.h b/src/libtomahawk/result.h new file mode 100644 index 000000000..aff9604ba --- /dev/null +++ b/src/libtomahawk/result.h @@ -0,0 +1,130 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef RESULT_H +#define RESULT_H + +#include + +#include +#include + +#include "typedefs.h" + +#include "dllmacro.h" + +class DatabaseCommand_Resolve; +class DatabaseCommand_AllTracks; +class DatabaseCommand_AddFiles; +class DatabaseCommand_LoadFile; + +namespace Tomahawk +{ + +class DLLEXPORT Result : public QObject +{ +Q_OBJECT + +friend class ::DatabaseCommand_Resolve; +friend class ::DatabaseCommand_AllTracks; +friend class ::DatabaseCommand_AddFiles; +friend class ::DatabaseCommand_LoadFile; + +public: + explicit Result(); + virtual ~Result(); + + QVariant toVariant() const; + QString toString() const; + Tomahawk::query_ptr toQuery() const; + + float score() const; + RID id() const; + collection_ptr collection() const; + Tomahawk::artist_ptr artist() const; + Tomahawk::album_ptr album() const; + QString track() const { return m_track; } + QString url() const { return m_url; } + QString mimetype() const { return m_mimetype; } + QString friendlySource() const; + + unsigned int duration() const { return m_duration; } + unsigned int bitrate() const { return m_bitrate; } + unsigned int size() const { return m_size; } + unsigned int albumpos() const { return m_albumpos; } + unsigned int modificationTime() const { return m_modtime; } + int year() const { return m_year; } + + void setScore( float score ) { m_score = score; } + void setId( unsigned int id ) { m_id = id; } + void setRID( RID id ) { m_rid = id; } + void setCollection( const Tomahawk::collection_ptr& collection ); + void setFriendlySource( const QString& s ) { m_friendlySource = s; } + void setArtist( const Tomahawk::artist_ptr& artist ); + void setAlbum( const Tomahawk::album_ptr& album ); + void setTrack( const QString& track ) { m_track = track; } + void setUrl( const QString& url ) { m_url = url; } + void setMimetype( const QString& mimetype ) { m_mimetype = mimetype; } + void setDuration( unsigned int duration ) { m_duration = duration; } + void setBitrate( unsigned int bitrate ) { m_bitrate = bitrate; } + void setSize( unsigned int size ) { m_size = size; } + void setAlbumPos( unsigned int albumpos ) { m_albumpos = albumpos; } + void setModificationTime( unsigned int modtime ) { m_modtime = modtime; } + + QVariantMap attributes() const { return m_attributes; } + void setAttributes( const QVariantMap& map ) { m_attributes = map; updateAttributes(); } + + unsigned int dbid() const { return m_id; } + +signals: + // emitted when the collection this result comes from is going offline/online: + void statusChanged(); + +private slots: + void onOffline(); + void onOnline(); + +private: + void updateAttributes(); + + mutable RID m_rid; + collection_ptr m_collection; + + Tomahawk::artist_ptr m_artist; + Tomahawk::album_ptr m_album; + QString m_track; + QString m_url; + QString m_mimetype; + QString m_friendlySource; + + unsigned int m_duration; + unsigned int m_bitrate; + unsigned int m_size; + unsigned int m_albumpos; + unsigned int m_modtime; + int m_year; + float m_score; + + QVariantMap m_attributes; + + unsigned int m_id; +}; + +}; //ns + +#endif // RESULT_H diff --git a/src/libtomahawk/sip/SipPlugin.cpp b/src/libtomahawk/sip/SipPlugin.cpp new file mode 100644 index 000000000..4af179694 --- /dev/null +++ b/src/libtomahawk/sip/SipPlugin.cpp @@ -0,0 +1,33 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include + + +QMenu* +SipPlugin::menu() +{ + return 0; +} + + +QWidget* +SipPlugin::configWidget() +{ + return 0; +} diff --git a/src/libtomahawk/sip/SipPlugin.h b/src/libtomahawk/sip/SipPlugin.h new file mode 100644 index 000000000..47af2fcf3 --- /dev/null +++ b/src/libtomahawk/sip/SipPlugin.h @@ -0,0 +1,67 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef SIPPLUGIN_H +#define SIPPLUGIN_H + +#include +#include +#include + +#include "dllmacro.h" + +class DLLEXPORT SipPlugin : public QObject +{ + Q_OBJECT + +public: + enum SipErrorCode { AuthError, ConnectionError }; // Placeholder for errors, to be defined + + virtual ~SipPlugin() {} + + virtual bool isValid() = 0; + virtual const QString name() = 0; + virtual const QString friendlyName() = 0; + virtual const QString accountName() = 0; + virtual QMenu* menu(); + virtual QWidget* configWidget(); + +public slots: + virtual bool connectPlugin( bool startup = false ) = 0; + virtual void disconnectPlugin() = 0; + virtual void checkSettings() = 0; + + virtual void addContact( const QString &jid, const QString& msg = QString() ) = 0; + virtual void sendMsg( const QString& to, const QString& msg ) = 0; + +signals: + void error( int, const QString& ); + void connected(); + void disconnected(); + + void peerOnline( const QString& ); + void peerOffline( const QString& ); + void msgReceived( const QString& from, const QString& msg ); + + void addMenu( QMenu* menu ); + void removeMenu( QMenu* menu ); +}; + +Q_DECLARE_INTERFACE( SipPlugin, "tomahawk.Sip/1.0" ) + +#endif diff --git a/src/libtomahawk/source.cpp b/src/libtomahawk/source.cpp new file mode 100644 index 000000000..59ad94688 --- /dev/null +++ b/src/libtomahawk/source.cpp @@ -0,0 +1,237 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "source.h" + +#include "collection.h" +#include "sourcelist.h" + +#include "network/controlconnection.h" +#include "database/databasecommand_addsource.h" +#include "database/databasecommand_sourceoffline.h" +#include "database/databasecommand_logplayback.h" +#include "database/database.h" + +using namespace Tomahawk; + + +Source::Source( int id, const QString &username ) + : QObject() + , m_isLocal( false ) + , m_online( false ) + , m_username( username ) + , m_id( id ) + , m_cc( 0 ) +{ + qDebug() << Q_FUNC_INFO; + + if ( id == 0 ) + { + m_isLocal = true; + m_online = true; + } +} + + +Source::~Source() +{ + qDebug() << Q_FUNC_INFO << friendlyName(); +} + + +void +Source::setControlConnection( ControlConnection* cc ) +{ + m_cc = cc; + if ( cc ) + connect( cc, SIGNAL( finished() ), SLOT( remove() ), Qt::QueuedConnection ); +} + + +collection_ptr +Source::collection() const +{ + if( m_collections.length() ) + return m_collections.first(); + + collection_ptr tmp; + return tmp; +} + + +void +Source::setStats( const QVariantMap& m ) +{ + m_stats = m; + emit stats( m_stats ); +} + + +void +Source::remove() +{ + qDebug() << Q_FUNC_INFO; + + setOffline(); +} + + +QString +Source::friendlyName() const +{ + if ( m_friendlyname.isEmpty() ) + return m_username; + + //TODO: this is a terrible assumption, help me clean this up, mighty muesli! + if ( m_friendlyname.contains( "@conference.") ) + return QString(m_friendlyname).remove( 0, m_friendlyname.lastIndexOf( "/" )+1 ).append(" via MUC"); + + if ( m_friendlyname.contains( "/tomahawk" ) ) + return m_friendlyname.left( m_friendlyname.indexOf( "/tomahawk" ) ); + + return m_friendlyname; +} + + +void +Source::addCollection( const collection_ptr& c ) +{ + Q_ASSERT( m_collections.length() == 0 ); // only 1 source supported atm + m_collections.append( c ); + emit collectionAdded( c ); +} + + +void +Source::removeCollection( const collection_ptr& c ) +{ + Q_ASSERT( m_collections.length() == 1 && m_collections.first() == c ); // only 1 source supported atm + m_collections.removeAll( c ); + emit collectionRemoved( c ); +} + + +void +Source::setOffline() +{ + if ( !m_online ) + return; + + m_online = false; + emit offline(); + + m_cc = 0; + DatabaseCommand_SourceOffline* cmd = new DatabaseCommand_SourceOffline( id() ); + Database::instance()->enqueue( QSharedPointer(cmd) ); +} + + +void +Source::setOnline() +{ + if ( m_online ) + return; + + // ensure username is in the database + DatabaseCommand_addSource* cmd = new DatabaseCommand_addSource( m_username, m_friendlyname ); + connect( cmd, SIGNAL( done( unsigned int, QString ) ), + SLOT( dbLoaded( unsigned int, const QString& ) ) ); + Database::instance()->enqueue( QSharedPointer(cmd) ); + + m_online = true; + emit online(); +} + + +void +Source::dbLoaded( unsigned int id, const QString& fname ) +{ + qDebug() << Q_FUNC_INFO << id << fname; + + m_id = id; + m_friendlyname = fname; + + emit syncedWithDatabase(); +} + + +void +Source::scanningProgress( unsigned int files ) +{ + m_textStatus = tr( "Scanning (%L1 tracks)" ).arg( files ); + emit stateChanged(); +} + + +void +Source::scanningFinished( unsigned int files ) +{ + m_textStatus = QString(); + emit stateChanged(); +} + + +void +Source::onStateChanged( DBSyncConnection::State newstate, DBSyncConnection::State oldstate, const QString& info ) +{ + QString msg; + switch( newstate ) + { + case DBSyncConnection::CHECKING: + msg = tr( "Checking" ); + break; + case DBSyncConnection::FETCHING: + msg = tr( "Fetching" ); + break; + case DBSyncConnection::PARSING: + msg = tr( "Parsing" ); + break; + case DBSyncConnection::SAVING: + msg = tr( "Saving" ); + break; + case DBSyncConnection::SYNCED: + msg = QString(); + break; + case DBSyncConnection::SCANNING: + msg = tr( "Scanning (%L1 tracks)" ).arg( info ); + break; + + default: + msg = QString(); + } + + m_textStatus = msg; + emit stateChanged(); +} + + +void +Source::onPlaybackStarted( const Tomahawk::query_ptr& query ) +{ + qDebug() << Q_FUNC_INFO << query->toString(); + m_currentTrack = query; + emit playbackStarted( query ); +} + + +void +Source::onPlaybackFinished( const Tomahawk::query_ptr& query ) +{ + qDebug() << Q_FUNC_INFO << query->toString(); + emit playbackFinished( query ); +} diff --git a/src/libtomahawk/source.h b/src/libtomahawk/source.h new file mode 100644 index 000000000..998804b70 --- /dev/null +++ b/src/libtomahawk/source.h @@ -0,0 +1,125 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef SOURCE_H +#define SOURCE_H + +#include +#include +#include + +#include "network/dbsyncconnection.h" +#include "collection.h" +#include "typedefs.h" + +#include "dllmacro.h" + +class DatabaseCommand_LogPlayback; +class ControlConnection; +class FileTransferConnection; + +namespace Tomahawk +{ + +class DLLEXPORT Source : public QObject +{ +Q_OBJECT + +friend class ::DatabaseCommand_LogPlayback; +friend class ::DBSyncConnection; + +public: + explicit Source( int id, const QString& username = QString() ); + virtual ~Source(); + + bool isLocal() const { return m_isLocal; } + bool isOnline() const { return m_online; } + + QString lastOpGuid() const { return m_lastOpGuid; } + + QString userName() const { return m_username; } + QString friendlyName() const; + void setFriendlyName( const QString& fname ) { m_friendlyname = fname; } + + collection_ptr collection() const; + void addCollection( const Tomahawk::collection_ptr& c ); + void removeCollection( const Tomahawk::collection_ptr& c ); + + int id() const { return m_id; } + ControlConnection* controlConnection() const { return m_cc; } + void setControlConnection( ControlConnection* cc ); + + void scanningProgress( unsigned int files ); + void scanningFinished( unsigned int files ); + + void setOffline(); + void setOnline(); + + unsigned int trackCount() const { return m_stats.value( "numfiles" ).toUInt(); } + + Tomahawk::query_ptr currentTrack() const { return m_currentTrack; } + QString textStatus() const { return m_textStatus; } + +signals: + void syncedWithDatabase(); + void online(); + void offline(); + + void collectionAdded( QSharedPointer ); + void collectionRemoved( QSharedPointer ); + + void stats( const QVariantMap& ); + void usernameChanged( const QString& ); + + void playbackStarted( const Tomahawk::query_ptr& query ); + void playbackFinished( const Tomahawk::query_ptr& query ); + + void stateChanged(); + +public slots: + void setStats( const QVariantMap& m ); + +private slots: + void setLastOpGuid( const QString& guid ) { m_lastOpGuid = guid; } + + void dbLoaded( unsigned int id, const QString& fname ); + void remove(); + + void onStateChanged( DBSyncConnection::State newstate, DBSyncConnection::State oldstate, const QString& info ); + void onPlaybackStarted( const Tomahawk::query_ptr& query ); + void onPlaybackFinished( const Tomahawk::query_ptr& query ); + +private: + bool m_isLocal; + bool m_online; + QString m_username, m_friendlyname; + int m_id; + QList< QSharedPointer > m_collections; + QVariantMap m_stats; + QString m_lastOpGuid; + + Tomahawk::query_ptr m_currentTrack; + QString m_textStatus; + + ControlConnection* m_cc; + FileTransferConnection* m_ftc; +}; + +}; + +#endif // SOURCE_H diff --git a/src/libtomahawk/sourcelist.cpp b/src/libtomahawk/sourcelist.cpp new file mode 100644 index 000000000..917a96c00 --- /dev/null +++ b/src/libtomahawk/sourcelist.cpp @@ -0,0 +1,192 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "sourcelist.h" + +#include + +#include "database/database.h" +#include "database/databasecommand_loadallsources.h" +#include "network/remotecollection.h" +#include "network/controlconnection.h" + +using namespace Tomahawk; + +SourceList* SourceList::s_instance = 0; + + +SourceList* +SourceList::instance() +{ + if ( !s_instance ) + { + s_instance = new SourceList(); + } + + return s_instance; +} + + +SourceList::SourceList( QObject* parent ) + : QObject( parent ) +{ + loadSources(); +} + + +const source_ptr& +SourceList::getLocal() +{ + return m_local; +} + + +void +SourceList::loadSources() +{ + qDebug() << Q_FUNC_INFO; + DatabaseCommand_LoadAllSources* cmd = new DatabaseCommand_LoadAllSources(); + + connect( cmd, SIGNAL( done( QList ) ), + SLOT( setSources( QList ) ) ); + + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + + +void +SourceList::setSources( const QList& sources ) +{ + QMutexLocker lock( &m_mut ); + + foreach( const source_ptr& src, sources ) + { + add( src ); + } + + qDebug() << Q_FUNC_INFO << "- Total sources now:" << m_sources.size(); +} + + +void +SourceList::setLocal( const Tomahawk::source_ptr& localSrc ) +{ + Q_ASSERT( localSrc->isLocal() ); + Q_ASSERT( m_local.isNull() ); + + { + QMutexLocker lock( &m_mut ); + m_sources.insert( localSrc->userName(), localSrc ); + m_local = localSrc; + + qDebug() << Q_FUNC_INFO << localSrc->userName(); + } + + emit sourceAdded( localSrc ); +} + + +void +SourceList::add( const source_ptr& source ) +{ + qDebug() << "Adding to sources:" << source->userName() << source->id(); + m_sources.insert( source->userName(), source ); + + if ( source->id() > 0 ) + m_sources_id2name.insert( source->id(), source->userName() ); + connect( source.data(), SIGNAL( syncedWithDatabase() ), SLOT( sourceSynced() ) ); + + collection_ptr coll( new RemoteCollection( source ) ); + source->addCollection( coll ); +// source->collection()->tracks(); + + emit sourceAdded( source ); +} + + +void +SourceList::removeAllRemote() +{ + foreach( const source_ptr& s, m_sources ) + { + if ( !s->isLocal() && s->controlConnection() ) + { + s->controlConnection()->shutdown( true ); + } + } +} + + +QList +SourceList::sources( bool onlyOnline ) const +{ + QMutexLocker lock( &m_mut ); + + QList< source_ptr > sources; + foreach( const source_ptr& src, m_sources ) + { + if ( !onlyOnline || src->controlConnection() ) + sources << src; + } + + return sources; +} + + +source_ptr +SourceList::get( int id ) const +{ + QMutexLocker lock( &m_mut ); + return m_sources.value( m_sources_id2name.value( id ) ); +} + + +source_ptr +SourceList::get( const QString& username, const QString& friendlyName ) +{ + QMutexLocker lock( &m_mut ); + + source_ptr source; + if ( !m_sources.contains( username ) ) + { + source = source_ptr( new Source( -1, username ) ); + source->setFriendlyName( friendlyName ); + add( source ); + } + else + source = m_sources.value( username ); + + return source; +} + + +void +SourceList::sourceSynced() +{ + Source* src = qobject_cast< Source* >( sender() ); + + m_sources_id2name.insert( src->id(), src->userName() ); +} + + +unsigned int +SourceList::count() const +{ + QMutexLocker lock( &m_mut ); + return m_sources.size(); +} diff --git a/src/libtomahawk/sourcelist.h b/src/libtomahawk/sourcelist.h new file mode 100644 index 000000000..3ec7304ef --- /dev/null +++ b/src/libtomahawk/sourcelist.h @@ -0,0 +1,74 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef SOURCELIST_H +#define SOURCELIST_H + +#include +#include +#include + +#include "source.h" +#include "typedefs.h" + +#include "dllmacro.h" + +class DLLEXPORT SourceList : public QObject +{ +Q_OBJECT + +public: + static SourceList* instance(); + + explicit SourceList( QObject* parent = 0 ); + + const Tomahawk::source_ptr& getLocal(); + void setLocal( const Tomahawk::source_ptr& localSrc ); + + void removeAllRemote(); + + QList sources( bool onlyOnline = false ) const; + unsigned int count() const; + + Tomahawk::source_ptr get( const QString& username, const QString& friendlyName = QString() ); + Tomahawk::source_ptr get( int id ) const; + +signals: + void ready(); + + void sourceAdded( const Tomahawk::source_ptr& ); + void sourceRemoved( const Tomahawk::source_ptr& ); + +private slots: + void setSources( const QList& sources ); + void sourceSynced(); + +private: + void loadSources(); + void add( const Tomahawk::source_ptr& source ); + + QMap< QString, Tomahawk::source_ptr > m_sources; + QMap< int, QString > m_sources_id2name; + + Tomahawk::source_ptr m_local; + mutable QMutex m_mut; // mutable so const methods can use a lock + + static SourceList* s_instance; +}; + +#endif // SOURCELIST_H diff --git a/src/libtomahawk/tomahawksettings.cpp b/src/libtomahawk/tomahawksettings.cpp new file mode 100644 index 000000000..a4008388b --- /dev/null +++ b/src/libtomahawk/tomahawksettings.cpp @@ -0,0 +1,606 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "tomahawksettings.h" + +#ifndef TOMAHAWK_HEADLESS + #include + #include "settingsdialog.h" +#endif + +#include +#include + +#define VERSION 1 + +TomahawkSettings* TomahawkSettings::s_instance = 0; + + +TomahawkSettings* +TomahawkSettings::instance() +{ + return s_instance; +} + + +TomahawkSettings::TomahawkSettings( QObject* parent ) + : QSettings( parent ) +{ + s_instance = this; + + if( !contains( "configversion") ) + { + setValue( "configversion", VERSION ); + } + else if( value( "configversion" ).toUInt() != VERSION ) + { + qDebug() << "Config version outdated, old:" << value( "configversion" ).toUInt() + << "new:" << VERSION + << "Doing upgrade, if any..."; + + // insert upgrade code here as required + setValue( "configversion", VERSION ); + } +} + + +TomahawkSettings::~TomahawkSettings() +{ + s_instance = 0; +} + + +QString +TomahawkSettings::scannerPath() const +{ + #ifndef TOMAHAWK_HEADLESS + return value( "scannerpath", QDesktopServices::storageLocation( QDesktopServices::MusicLocation ) ).toString(); + #else + return value( "scannerpath", "" ).toString(); + #endif +} + + +void +TomahawkSettings::setScannerPath( const QString& path ) +{ + setValue( "scannerpath", path ); +} + + +bool +TomahawkSettings::hasScannerPath() const +{ + return contains( "scannerpath" ); +} + +void +TomahawkSettings::setAcceptedLegalWarning( bool accept ) +{ + setValue( "acceptedLegalWarning", accept ); +} + +bool +TomahawkSettings::acceptedLegalWarning() const +{ + return value( "acceptedLegalWarning", false ).toBool(); +} + +bool +TomahawkSettings::httpEnabled() const +{ + return value( "network/http", true ).toBool(); +} + + +void +TomahawkSettings::setHttpEnabled( bool enable ) +{ + setValue( "network/http", enable ); +} + + +QString +TomahawkSettings::proxyHost() const +{ + return value( "network/proxy/host", QString() ).toString(); +} + + +void +TomahawkSettings::setProxyHost( const QString& host ) +{ + setValue( "network/proxy/host", host ); +} + + +qulonglong +TomahawkSettings::proxyPort() const +{ + return value( "network/proxy/port", 1080 ).toULongLong(); +} + + +void +TomahawkSettings::setProxyPort( const qulonglong port ) +{ + setValue( "network/proxy/port", port ); +} + + +QString +TomahawkSettings::proxyUsername() const +{ + return value( "network/proxy/username", QString() ).toString(); +} + + +void +TomahawkSettings::setProxyUsername( const QString& username ) +{ + setValue( "network/proxy/username", username ); +} + + +QString +TomahawkSettings::proxyPassword() const +{ + return value( "network/proxy/password", QString() ).toString(); +} + + +void +TomahawkSettings::setProxyPassword( const QString& password ) +{ + setValue( "network/proxy/password", password ); +} + + +int +TomahawkSettings::proxyType() const +{ + return value( "network/proxy/type", 0 ).toInt(); +} + + +void +TomahawkSettings::setProxyType( const int type ) +{ + setValue( "network/proxy/type", type ); +} + + +QByteArray +TomahawkSettings::mainWindowGeometry() const +{ + return value( "ui/mainwindow/geometry" ).toByteArray(); +} + + +void +TomahawkSettings::setMainWindowGeometry( const QByteArray& geom ) +{ + setValue( "ui/mainwindow/geometry", geom ); +} + + +QByteArray +TomahawkSettings::mainWindowState() const +{ + return value( "ui/mainwindow/state" ).toByteArray(); +} + + +void +TomahawkSettings::setMainWindowState( const QByteArray& state ) +{ + setValue( "ui/mainwindow/state", state ); +} + + +QByteArray +TomahawkSettings::mainWindowSplitterState() const +{ + return value( "ui/mainwindow/splitterState" ).toByteArray(); +} + + +void +TomahawkSettings::setMainWindowSplitterState( const QByteArray& state ) +{ + setValue( "ui/mainwindow/splitterState", state ); +} + + +QByteArray +TomahawkSettings::playlistColumnSizes( const QString& playlistid ) const +{ + return value( QString( "ui/playlist/%1/columnSizes" ).arg( playlistid ) ).toByteArray(); +} + + +void +TomahawkSettings::setPlaylistColumnSizes( const QString& playlistid, const QByteArray& state ) +{ + setValue( QString( "ui/playlist/%1/columnSizes" ).arg( playlistid ), state ); +} + + +QList +TomahawkSettings::recentlyPlayedPlaylists() const +{ + QStringList playlist_guids = value( "playlists/recentlyPlayed" ).toStringList(); + + QList playlists; + foreach( const QString& guid, playlist_guids ) + { + Tomahawk::playlist_ptr pl = Tomahawk::Playlist::load( guid ); + if ( !pl.isNull() ) + playlists << pl; + } + + return playlists; +} + + +void +TomahawkSettings::appendRecentlyPlayedPlaylist( const Tomahawk::playlist_ptr& playlist ) +{ + QStringList playlist_guids = value( "playlists/recentlyPlayed" ).toStringList(); + + playlist_guids.removeAll( playlist->guid() ); + playlist_guids.append( playlist->guid() ); + + setValue( "playlists/recentlyPlayed", playlist_guids ); +} + + +bool +TomahawkSettings::jabberAutoConnect() const +{ + return value( "jabber/autoconnect", true ).toBool(); +} + + +void +TomahawkSettings::setJabberAutoConnect( bool autoconnect ) +{ + setValue( "jabber/autoconnect", autoconnect ); +} + + +unsigned int +TomahawkSettings::jabberPort() const +{ + return value( "jabber/port", 5222 ).toUInt(); +} + + +void +TomahawkSettings::setJabberPort( int port ) +{ + if ( port < 0 ) + return; + setValue( "jabber/port", port ); +} + + +QString +TomahawkSettings::jabberServer() const +{ + return value( "jabber/server" ).toString(); +} + + +void +TomahawkSettings::setJabberServer( const QString& server ) +{ + setValue( "jabber/server", server ); +} + + +QString +TomahawkSettings::jabberUsername() const +{ + return value( "jabber/username" ).toString(); +} + + +void +TomahawkSettings::setJabberUsername( const QString& username ) +{ + setValue( "jabber/username", username ); +} + + +QString +TomahawkSettings::jabberPassword() const +{ + return value( "jabber/password" ).toString(); +} + + +void +TomahawkSettings::setJabberPassword( const QString& pw ) +{ + setValue( "jabber/password", pw ); +} + + +TomahawkSettings::ExternalAddressMode +TomahawkSettings::externalAddressMode() const +{ + return (TomahawkSettings::ExternalAddressMode) value( "network/external-address-mode", TomahawkSettings::Upnp ).toInt(); +} + + +void +TomahawkSettings::setExternalAddressMode( ExternalAddressMode externalAddressMode ) +{ + setValue( "network/external-address-mode", externalAddressMode ); +} + +bool TomahawkSettings::preferStaticHostPort() const +{ + return value( "network/prefer-static-host-and-port" ).toBool(); +} + +void TomahawkSettings::setPreferStaticHostPort( bool prefer ) +{ + setValue( "network/prefer-static-host-and-port", prefer ); +} + +QString +TomahawkSettings::externalHostname() const +{ + return value( "network/external-hostname" ).toString(); +} + +void +TomahawkSettings::setExternalHostname(const QString& externalHostname) +{ + setValue( "network/external-hostname", externalHostname ); +} + +int +TomahawkSettings::externalPort() const +{ + return value( "network/external-port", 50210 ).toInt(); +} + +void +TomahawkSettings::setExternalPort(int externalPort) +{ + if ( externalPort == 0 ) + setValue( "network/external-port", 50210); + else + setValue( "network/external-port", externalPort); +} + + +QString +TomahawkSettings::lastFmPassword() const +{ + return value( "lastfm/password" ).toString(); +} + + +void +TomahawkSettings::setLastFmPassword( const QString& password ) +{ + setValue( "lastfm/password", password ); +} + + +QByteArray +TomahawkSettings::lastFmSessionKey() const +{ + return value( "lastfm/session" ).toByteArray(); +} + + +void +TomahawkSettings::setLastFmSessionKey( const QByteArray& key ) +{ + setValue( "lastfm/session", key ); +} + + +QString +TomahawkSettings::lastFmUsername() const +{ + return value( "lastfm/username" ).toString(); +} + + +void +TomahawkSettings::setLastFmUsername( const QString& username ) +{ + setValue( "lastfm/username", username ); +} + +QString +TomahawkSettings::twitterScreenName() const +{ + return value( "twitter/ScreenName" ).toString(); +} + +void +TomahawkSettings::setTwitterScreenName( const QString& screenName ) +{ + setValue( "twitter/ScreenName", screenName ); +} + +QString +TomahawkSettings::twitterOAuthToken() const +{ + return value( "twitter/OAuthToken" ).toString(); +} + +void +TomahawkSettings::setTwitterOAuthToken( const QString& oauthtoken ) +{ + setValue( "twitter/OAuthToken", oauthtoken ); +} + +QString +TomahawkSettings::twitterOAuthTokenSecret() const +{ + return value( "twitter/OAuthTokenSecret" ).toString(); +} + +void +TomahawkSettings::setTwitterOAuthTokenSecret( const QString& oauthtokensecret ) +{ + setValue( "twitter/OAuthTokenSecret", oauthtokensecret ); +} + +qint64 +TomahawkSettings::twitterCachedFriendsSinceId() const +{ + return value( "twitter/CachedFriendsSinceID", 0 ).toLongLong(); +} + +void +TomahawkSettings::setTwitterCachedFriendsSinceId( qint64 cachedId ) +{ + setValue( "twitter/CachedFriendsSinceID", cachedId ); +} + +qint64 +TomahawkSettings::twitterCachedMentionsSinceId() const +{ + return value( "twitter/CachedMentionsSinceID", 0 ).toLongLong(); +} + +void +TomahawkSettings::setTwitterCachedMentionsSinceId( qint64 cachedId ) +{ + setValue( "twitter/CachedMentionsSinceID", cachedId ); +} + +qint64 +TomahawkSettings::twitterCachedDirectMessagesSinceId() const +{ + return value( "twitter/CachedDirectMessagesSinceID", 0 ).toLongLong(); +} + +void +TomahawkSettings::setTwitterCachedDirectMessagesSinceId( qint64 cachedId ) +{ + setValue( "twitter/CachedDirectMessagesSinceID", cachedId ); +} + +QHash +TomahawkSettings::twitterCachedPeers() const +{ + return value( "twitter/CachedPeers", QHash() ).toHash(); +} + +void +TomahawkSettings::setTwitterCachedPeers( const QHash &cachedPeers ) +{ + setValue( "twitter/CachedPeers", cachedPeers ); +} + +bool +TomahawkSettings::scrobblingEnabled() const +{ + return value( "lastfm/enablescrobbling", false ).toBool(); +} + + +void +TomahawkSettings::setScrobblingEnabled( bool enable ) +{ + setValue( "lastfm/enablescrobbling", enable ); +} + + +QString +TomahawkSettings::xmppBotServer() const +{ + return value( "xmppBot/server", QString() ).toString(); +} + + +void +TomahawkSettings::setXmppBotServer( const QString& server ) +{ + setValue( "xmppBot/server", server ); +} + + +QString +TomahawkSettings::xmppBotJid() const +{ + return value( "xmppBot/jid", QString() ).toString(); +} + + +void +TomahawkSettings::setXmppBotJid( const QString& component ) +{ + setValue( "xmppBot/jid", component ); +} + + +QString +TomahawkSettings::xmppBotPassword() const +{ + return value( "xmppBot/password", QString() ).toString(); +} + + +void +TomahawkSettings::setXmppBotPassword( const QString& password ) +{ + setValue( "xmppBot/password", password ); +} + + +int +TomahawkSettings::xmppBotPort() const +{ + return value( "xmppBot/port", -1 ).toInt(); +} + + +void +TomahawkSettings::setXmppBotPort( const int port ) +{ + setValue( "xmppBot/port", -1 ); +} + +void +TomahawkSettings::addScriptResolver(const QString& resolver) +{ + setValue( "script/resolvers", scriptResolvers() << resolver ); +} + +QStringList +TomahawkSettings::scriptResolvers() const +{ + return value( "script/resolvers" ).toStringList(); +} + +void +TomahawkSettings::setScriptResolvers( const QStringList& resolver ) +{ + setValue( "script/resolvers", resolver ); +} diff --git a/src/libtomahawk/tomahawksettings.h b/src/libtomahawk/tomahawksettings.h new file mode 100644 index 000000000..f7f5e2922 --- /dev/null +++ b/src/libtomahawk/tomahawksettings.h @@ -0,0 +1,176 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TOMAHAWK_SETTINGS_H +#define TOMAHAWK_SETTINGS_h + +#include + +#include "dllmacro.h" + +#include "playlist.h" + +/** + * Convenience wrapper around QSettings for tomahawk-specific config + */ +class DLLEXPORT TomahawkSettings : public QSettings +{ +Q_OBJECT + +public: + static TomahawkSettings* instance(); + + explicit TomahawkSettings( QObject* parent = 0 ); + virtual ~TomahawkSettings(); + + void applyChanges() { emit changed(); } + + /// General settings + QString scannerPath() const; /// QDesktopServices::MusicLocation by default + void setScannerPath( const QString& path ); + bool hasScannerPath() const; + + bool acceptedLegalWarning() const; + void setAcceptedLegalWarning( bool accept ); + + /// UI settings + QByteArray mainWindowGeometry() const; + void setMainWindowGeometry( const QByteArray& geom ); + + QByteArray mainWindowState() const; + void setMainWindowState( const QByteArray& state ); + + QByteArray mainWindowSplitterState() const; + void setMainWindowSplitterState( const QByteArray& state ); + + /// Playlist stuff + QByteArray playlistColumnSizes( const QString& playlistid ) const; + void setPlaylistColumnSizes( const QString& playlistid, const QByteArray& state ); + + QList recentlyPlayedPlaylists() const; + void appendRecentlyPlayedPlaylist( const Tomahawk::playlist_ptr& playlist ); + + /// Jabber settings + bool jabberAutoConnect() const; /// true by default + void setJabberAutoConnect( bool autoconnect = false ); + + QString jabberUsername() const; + void setJabberUsername( const QString& username ); + + QString jabberPassword() const; + void setJabberPassword( const QString& pw ); + + QString jabberServer() const; + void setJabberServer( const QString& server ); + + unsigned int jabberPort() const; // default is 5222 + void setJabberPort( int port ); + + /// Network settings + enum ExternalAddressMode { Lan, Upnp }; + ExternalAddressMode externalAddressMode() const; + void setExternalAddressMode( ExternalAddressMode externalAddressMode ); + + bool preferStaticHostPort() const; + void setPreferStaticHostPort( bool prefer ); + + bool httpEnabled() const; /// true by default + void setHttpEnabled( bool enable ); + + QString externalHostname() const; + void setExternalHostname( const QString& externalHostname ); + + int externalPort() const; + void setExternalPort( int externalPort ); + + QString proxyHost() const; + void setProxyHost( const QString &host ); + + qulonglong proxyPort() const; + void setProxyPort( const qulonglong port ); + + QString proxyUsername() const; + void setProxyUsername( const QString &username ); + + QString proxyPassword() const; + void setProxyPassword( const QString &password ); + + int proxyType() const; + void setProxyType( const int type ); + + /// Last.fm settings + bool scrobblingEnabled() const; /// false by default + void setScrobblingEnabled( bool enable ); + + QString lastFmUsername() const; + void setLastFmUsername( const QString& username ); + + QString lastFmPassword() const; + void setLastFmPassword( const QString& password ); + + QByteArray lastFmSessionKey() const; + void setLastFmSessionKey( const QByteArray& key ); + + /// Twitter settings + QString twitterScreenName() const; + void setTwitterScreenName( const QString& screenName ); + + QString twitterOAuthToken() const; + void setTwitterOAuthToken( const QString& oauthtoken ); + + QString twitterOAuthTokenSecret() const; + void setTwitterOAuthTokenSecret( const QString& oauthtokensecret ); + + qint64 twitterCachedFriendsSinceId() const; + void setTwitterCachedFriendsSinceId( qint64 sinceid ); + + qint64 twitterCachedMentionsSinceId() const; + void setTwitterCachedMentionsSinceId( qint64 sinceid ); + + qint64 twitterCachedDirectMessagesSinceId() const; + void setTwitterCachedDirectMessagesSinceId( qint64 sinceid ); + + QHash twitterCachedPeers() const; + void setTwitterCachedPeers( const QHash &cachedPeers ); + + /// XMPP Component Settings + QString xmppBotServer() const; + void setXmppBotServer( const QString &server ); + + QString xmppBotJid() const; + void setXmppBotJid( const QString &component ); + + QString xmppBotPassword() const; + void setXmppBotPassword( const QString &password ); + + int xmppBotPort() const; + void setXmppBotPort( const int port ); + + /// Script resolver settings + QStringList scriptResolvers() const; + void setScriptResolvers( const QStringList& resolver ); + void addScriptResolver( const QString& resolver ); + +signals: + void changed(); + +private: + static TomahawkSettings* s_instance; +}; + +#endif diff --git a/src/libtomahawk/track.h b/src/libtomahawk/track.h new file mode 100644 index 000000000..c379c620a --- /dev/null +++ b/src/libtomahawk/track.h @@ -0,0 +1,53 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TOMAHAWKTRACK_H +#define TOMAHAWKTRACK_H + +#include +#include + +#include "artist.h" +#include "typedefs.h" + +#include "dllmacro.h" + +namespace Tomahawk +{ + +class DLLEXPORT Track : public QObject +{ +Q_OBJECT + +public: + Track( Tomahawk::artist_ptr artist, const QString& name ) + : m_name( name ) + , m_artist( artist ) + {} + + const QString& name() const { return m_name; } + const artist_ptr artist() const { return m_artist; } + +private: + QString m_name; + artist_ptr m_artist; +}; + +}; // ns + +#endif diff --git a/src/libtomahawk/typedefs.h b/src/libtomahawk/typedefs.h new file mode 100644 index 000000000..8ae84aed8 --- /dev/null +++ b/src/libtomahawk/typedefs.h @@ -0,0 +1,76 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TYPEDEFS_H +#define TYPEDEFS_H + +#include +#include + +namespace Tomahawk +{ + class Artist; + class Album; + class Collection; + class Playlist; + class PlaylistEntry; + class DynamicPlaylist; + class Query; + class Result; + class Source; + class DynamicControl; + class GeneratorInterface; + + typedef QSharedPointer collection_ptr; + typedef QSharedPointer playlist_ptr; + typedef QSharedPointer plentry_ptr; + typedef QSharedPointer dynplaylist_ptr; + typedef QSharedPointer query_ptr; + typedef QSharedPointer result_ptr; + typedef QSharedPointer source_ptr; + typedef QSharedPointer artist_ptr; + typedef QSharedPointer album_ptr; + + typedef QSharedPointer dyncontrol_ptr; + typedef QSharedPointer geninterface_ptr; + + // let's keep these typesafe, they are different kinds of GUID: + typedef QString QID; //query id + typedef QString RID; //result id + + + enum GeneratorMode { + OnDemand = 0, + Static + }; + +}; // ns + +typedef int AudioErrorCode; + +// creates 36char ascii guid without {} around it +inline static QString uuid() +{ + // kinda lame, but + QString q = QUuid::createUuid(); + q.remove( 0, 1 ); + q.chop( 1 ); + return q; +} + +#endif // TYPEDEFS_H diff --git a/src/utils/animatedcounterlabel.h b/src/libtomahawk/utils/animatedcounterlabel.h similarity index 56% rename from src/utils/animatedcounterlabel.h rename to src/libtomahawk/utils/animatedcounterlabel.h index 040f3e9ee..ab430397e 100644 --- a/src/utils/animatedcounterlabel.h +++ b/src/libtomahawk/utils/animatedcounterlabel.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef ANIMATEDCOUNTERLABEL_H #define ANIMATEDCOUNTERLABEL_H @@ -8,7 +26,9 @@ #include #include -class AnimatedCounterLabel : public QLabel +#include "dllmacro.h" + +class DLLEXPORT AnimatedCounterLabel : public QLabel { Q_OBJECT @@ -31,6 +51,13 @@ public: } public slots: + void setVisible( bool b ) + { + QLabel::setVisible( b ); + if ( !m_diff.isNull() ) + m_diff.data()->setVisible( b ); + } + void frame( int f ) { m_displayed = f; @@ -65,24 +92,25 @@ public slots: void showDiff() { int differ = m_val - m_oldval; - QLabel* diff = new QLabel( QString("%1 %L2" ).arg( differ > 0 ? "+" : "" ) + m_diff = new QLabel( QString("%1 %L2" ).arg( differ > 0 ? "+" : "" ) .arg( (int)m_val - (int)m_oldval ), this->parentWidget() ); - diff->setStyleSheet( "font-size:9px; color:grey;" ); - diff->move( QPoint( this->pos().x(), this->pos().y() ) ); - QPropertyAnimation* a = new QPropertyAnimation( diff, "pos" ); + m_diff.data()->setStyleSheet( "font-size:9px; color:grey;" ); + m_diff.data()->move( QPoint( this->pos().x(), this->pos().y() ) ); + QPropertyAnimation* a = new QPropertyAnimation( m_diff.data(), "pos" ); a->setEasingCurve( QEasingCurve( QEasingCurve::InQuad ) ); - a->setStartValue( diff->pos() + QPoint( 0, -10 ) ); - a->setEndValue( QPoint( diff->pos().x(), diff->pos().y() - 25 ) ); + a->setStartValue( m_diff.data()->pos() + QPoint( 0, -10 ) ); + a->setEndValue( QPoint( m_diff.data()->pos().x(), m_diff.data()->pos().y() - 25 ) ); a->setDuration( 1000 ); // qDebug() << "ANIMATING DIFF:" << a->startValue() << a->endValue(); - connect( a, SIGNAL( finished() ), diff, SLOT( hide() ) ); - connect( a, SIGNAL( finished() ), diff, SLOT( deleteLater() ) ); + connect( a, SIGNAL( finished() ), m_diff.data(), SLOT( hide() ) ); + connect( a, SIGNAL( finished() ), m_diff.data(), SLOT( deleteLater() ) ); connect( a, SIGNAL( finished() ), a, SLOT( deleteLater() ) ); - diff->show(); + m_diff.data()->show(); + m_diff.data()->setVisible( this->isVisible() ); a->start(); } @@ -96,7 +124,7 @@ private: unsigned int m_val, m_oldval; QString m_format; - + QWeakPointer m_diff; }; #endif // ANIMATEDCOUNTERLABEL_H diff --git a/src/libtomahawk/utils/animatedsplitter.cpp b/src/libtomahawk/utils/animatedsplitter.cpp new file mode 100644 index 000000000..3d9607ee3 --- /dev/null +++ b/src/libtomahawk/utils/animatedsplitter.cpp @@ -0,0 +1,269 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "animatedsplitter.h" + +#define ANIMATION_TIME 400 + + +AnimatedSplitter::AnimatedSplitter( QWidget* parent ) + : QSplitter( parent ) + , m_animateIndex( -1 ) + , m_greedyIndex( 0 ) +{ + setHandleWidth( 1 ); + + m_timeLine = new QTimeLine( ANIMATION_TIME, this ); + m_timeLine->setUpdateInterval( 5 ); + m_timeLine->setEasingCurve( QEasingCurve::OutBack ); + + connect( m_timeLine, SIGNAL( frameChanged( int ) ), SLOT( onAnimationStep( int ) ) ); + connect( m_timeLine, SIGNAL( finished() ), SLOT( onAnimationFinished() ) ); +} + + +void +AnimatedSplitter::show( int index, bool animate ) +{ + m_animateIndex = index; + + QWidget* w = widget( index ); + QSize size = w->sizeHint(); + + if ( w->height() == size.height() ) + return; + + emit shown( w ); + w->setMaximumHeight( QWIDGETSIZE_MAX ); + qDebug() << "animating to:" << size.height() << "from" << w->height(); + + m_animateForward = true; + if ( animate ) + { + if ( m_timeLine->state() == QTimeLine::Running ) + m_timeLine->stop(); + + m_timeLine->setFrameRange( w->height(), size.height() ); + m_timeLine->setDirection( QTimeLine::Forward ); + m_timeLine->start(); + } + else + { + onAnimationStep( size.height() ); + onAnimationFinished(); + } +} + + +void +AnimatedSplitter::hide( int index, bool animate ) +{ + m_animateIndex = index; + + QWidget* w = widget( index ); + int minHeight = m_sizes.at( index ).height(); + + if ( w->height() == minHeight ) + return; + + emit hidden( w ); + w->setMinimumHeight( minHeight ); + qDebug() << "animating to:" << w->height() << "from" << minHeight; + + m_animateForward = false; + if ( animate ) + { + if ( m_timeLine->state() == QTimeLine::Running ) + m_timeLine->stop(); + + m_timeLine->setFrameRange( minHeight, w->height() ); + m_timeLine->setDirection( QTimeLine::Backward ); + m_timeLine->start(); + } + else + { + onAnimationStep( minHeight ); + onAnimationFinished(); + } +} + + +void +AnimatedSplitter::addWidget( QWidget* widget ) +{ + QSplitter::addWidget( widget ); + m_sizes << widget->minimumSize(); +} + + +void +AnimatedSplitter::addWidget( AnimatedWidget* widget ) +{ + qDebug() << Q_FUNC_INFO << widget; + QSplitter::addWidget( widget ); + m_sizes << widget->hiddenSize(); + + connect( widget, SIGNAL( showWidget() ), SLOT( onShowRequest() ) ); + connect( widget, SIGNAL( hideWidget() ), SLOT( onHideRequest() ) ); + connect( widget, SIGNAL( hiddenSizeChanged() ), SLOT( onHiddenSizeChanged() ) ); + connect( this, SIGNAL( shown( QWidget* ) ), widget, SLOT( onShown( QWidget* ) ) ); + connect( this, SIGNAL( hidden( QWidget* ) ), widget, SLOT( onHidden( QWidget* ) ) ); +} + + +void +AnimatedSplitter::onShowRequest() +{ + qDebug() << Q_FUNC_INFO << sender(); + + int j = -1; + for ( int i = 0; i < count(); i ++ ) + { + if ( widget( i ) == sender() ) + { + j = i; + break; + } + } + + if ( j > 0 ) + show( j ); + else + qDebug() << "Could not find widget:" << sender(); +} + + +void +AnimatedSplitter::onHideRequest() +{ + int j = -1; + for ( int i = 0; i < count(); i ++ ) + { + if ( widget( i ) == sender() ) + { + j = i; + break; + } + } + + if ( j > 0 ) + hide( j ); + else + qDebug() << "Could not find widget:" << sender(); +} + + +void +AnimatedSplitter::onAnimationStep( int frame ) +{ + QList< int > sizes; + + for ( int i = 0; i < count(); i ++ ) + { + int j = 0; + + if ( i == m_greedyIndex ) + { + j = height() - frame; // FIXME + } + else if ( i == m_animateIndex ) + { + j = frame; + } + else + { + j = widget( i )->height(); + } + + sizes << j; + } + + setSizes( sizes ); +} + + +void +AnimatedSplitter::onAnimationFinished() +{ + qDebug() << Q_FUNC_INFO; + + QWidget* w = widget( m_animateIndex ); + if ( m_animateForward ) + { + w->setMinimumHeight( w->minimumHeight() ); + } + else + { + w->setMaximumHeight( m_sizes.at( m_animateIndex ).height() ); + } + + m_animateIndex = -1; +} + +void +AnimatedSplitter::setGreedyWidget(int index) +{ + m_greedyIndex = index; + if( !widget( index ) ) + return; + QSizePolicy policy = widget( m_greedyIndex )->sizePolicy(); + if( orientation() == Qt::Horizontal ) + policy.setHorizontalStretch( 1 ); + else + policy.setVerticalStretch( 1 ); + widget( m_greedyIndex )->setSizePolicy( policy ); + +} + + +void +AnimatedSplitter::onHiddenSizeChanged() +{ + AnimatedWidget* w = (AnimatedWidget*)(sender()); + int i = indexOf( w ); + + m_sizes.replace( i, w->hiddenSize() ); +} + + +AnimatedWidget::AnimatedWidget( AnimatedSplitter* parent ) + : m_parent( parent ) + , m_isHidden( false ) +{ + qDebug() << Q_FUNC_INFO; +} + +AnimatedWidget::~AnimatedWidget() +{ + +} + +void +AnimatedWidget::onShown( QWidget* ) +{ + qDebug() << Q_FUNC_INFO << this; + m_isHidden = false; +} + + +void +AnimatedWidget::onHidden( QWidget* ) +{ + qDebug() << Q_FUNC_INFO << this; + m_isHidden = true; +} diff --git a/src/libtomahawk/utils/animatedsplitter.h b/src/libtomahawk/utils/animatedsplitter.h new file mode 100644 index 000000000..ba1671f4e --- /dev/null +++ b/src/libtomahawk/utils/animatedsplitter.h @@ -0,0 +1,98 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef ANIMATEDSPLITTER_H +#define ANIMATEDSPLITTER_H + +#include +#include +#include + +#include "dllmacro.h" + +class AnimatedWidget; + +class DLLEXPORT AnimatedSplitter : public QSplitter +{ +Q_OBJECT + +public: + explicit AnimatedSplitter( QWidget* parent = 0 ); + + void show( int index, bool animate = true ); + void hide( int index, bool animate = true ); + + void setGreedyWidget( int index ); + + void addWidget( QWidget* widget ); + void addWidget( AnimatedWidget* widget ); + +signals: + void shown( QWidget* ); + void hidden( QWidget* ); + +private slots: + void onShowRequest(); + void onHideRequest(); + + void onAnimationStep( int frame ); + void onAnimationFinished(); + + void onHiddenSizeChanged(); + +private: + int m_animateIndex; + bool m_animateForward; + + int m_greedyIndex; + QList m_sizes; + QTimeLine* m_timeLine; +}; + +class DLLEXPORT AnimatedWidget : public QWidget +{ +Q_OBJECT +public: + explicit AnimatedWidget( AnimatedSplitter* parent = 0 ); + virtual ~AnimatedWidget(); + + QSize hiddenSize() const { return m_hiddenSize; } + void setHiddenSize( const QSize& size ) { m_hiddenSize = size; emit hiddenSizeChanged(); } + + bool isHidden() const { return m_isHidden; } + +public slots: + virtual void onShown( QWidget* ); + virtual void onHidden( QWidget* ); + +signals: + void showWidget(); + void hideWidget(); + + void hiddenSizeChanged(); +protected: + + AnimatedSplitter* splitter() { return m_parent; } + +private: + AnimatedSplitter* m_parent; + QSize m_hiddenSize; + bool m_isHidden; +}; + +#endif //ANIMATEDSPLITTER_H diff --git a/src/utils/elidedlabel.cpp b/src/libtomahawk/utils/elidedlabel.cpp similarity index 62% rename from src/utils/elidedlabel.cpp rename to src/libtomahawk/utils/elidedlabel.cpp index 8c581997c..9b4343383 100644 --- a/src/utils/elidedlabel.cpp +++ b/src/libtomahawk/utils/elidedlabel.cpp @@ -1,10 +1,28 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "elidedlabel.h" -#include #include #include #include #include +#include ElidedLabel::ElidedLabel( QWidget* parent, Qt::WindowFlags flags ) @@ -48,16 +66,16 @@ ElidedLabel::setText( const QString& text ) Qt::Alignment ElidedLabel::alignment() const { - return align; + return m_align; } void ElidedLabel::setAlignment( Qt::Alignment alignment ) { - if ( this->align != alignment ) + if ( m_align != alignment ) { - this->align = alignment; + m_align = alignment; update(); // no geometry change, repaint is sufficient } } @@ -66,27 +84,40 @@ ElidedLabel::setAlignment( Qt::Alignment alignment ) Qt::TextElideMode ElidedLabel::elideMode() const { - return mode; + return m_mode; } void ElidedLabel::setElideMode( Qt::TextElideMode mode ) { - if ( this->mode != mode ) + if ( m_mode != mode ) { - this->mode = mode; + m_mode = mode; updateLabel(); } } +void +ElidedLabel::setMargin( int margin ) +{ + m_margin = margin; +} + +int +ElidedLabel::margin() const +{ + return m_margin; +} + void ElidedLabel::init( const QString& txt ) { m_text = txt; - align = Qt::AlignLeft; - mode = Qt::ElideMiddle; + m_align = Qt::AlignLeft; + m_mode = Qt::ElideMiddle; + m_margin = 0; setContentsMargins( 0, 0, 0, 0 ); } @@ -111,7 +142,7 @@ ElidedLabel::sizeHint() const QSize ElidedLabel::minimumSizeHint() const { - switch ( mode ) + switch ( m_mode ) { case Qt::ElideNone: return sizeHint(); @@ -132,8 +163,10 @@ ElidedLabel::paintEvent( QPaintEvent* event ) QFrame::paintEvent( event ); QPainter p( this ); QRect r = contentsRect(); - const QString elidedText = fontMetrics().elidedText( m_text, mode, r.width() ); - p.drawText( r, align, elidedText ); + r.adjust( m_margin, m_margin, -m_margin, -m_margin ); + + const QString elidedText = fontMetrics().elidedText( m_text, m_mode, r.width() ); + p.drawText( r, m_align, elidedText ); } @@ -159,7 +192,7 @@ void ElidedLabel::mousePressEvent( QMouseEvent* event ) { QFrame::mousePressEvent( event ); - time.start(); + m_time.start(); } @@ -167,6 +200,6 @@ void ElidedLabel::mouseReleaseEvent( QMouseEvent* event ) { QFrame::mouseReleaseEvent( event ); - if ( time.elapsed() < qApp->doubleClickInterval() ) + if ( m_time.elapsed() < qApp->doubleClickInterval() ) emit clicked(); } diff --git a/src/utils/elidedlabel.h b/src/libtomahawk/utils/elidedlabel.h similarity index 54% rename from src/utils/elidedlabel.h rename to src/libtomahawk/utils/elidedlabel.h index a3e4f5d4f..2f45e1c97 100644 --- a/src/utils/elidedlabel.h +++ b/src/libtomahawk/utils/elidedlabel.h @@ -1,10 +1,30 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef ELIDEDLABEL_H #define ELIDEDLABEL_H #include #include -class ElidedLabel : public QFrame +#include "dllmacro.h" + +class DLLEXPORT ElidedLabel : public QFrame { Q_OBJECT Q_PROPERTY( QString text READ text WRITE setText NOTIFY textChanged ) @@ -24,16 +44,19 @@ public: Qt::TextElideMode elideMode() const; void setElideMode( Qt::TextElideMode mode ); + void setMargin( int margin ); + int margin() const; + virtual QSize sizeHint() const; virtual QSize minimumSizeHint() const; void init( const QString& txt = QString() ); void updateLabel(); -public Q_SLOTS: +public slots: void setText( const QString& text ); -Q_SIGNALS: +signals: void clicked(); void textChanged( const QString& text ); @@ -44,10 +67,11 @@ protected: virtual void paintEvent( QPaintEvent* event ); private: - QTime time; + QTime m_time; QString m_text; - Qt::Alignment align; - Qt::TextElideMode mode; + Qt::Alignment m_align; + Qt::TextElideMode m_mode; + int m_margin; }; #endif // ELIDEDLABEL_H diff --git a/src/utils/imagebutton.cpp b/src/libtomahawk/utils/imagebutton.cpp similarity index 65% rename from src/utils/imagebutton.cpp rename to src/libtomahawk/utils/imagebutton.cpp index de2a8a580..90eb1c521 100644 --- a/src/utils/imagebutton.cpp +++ b/src/libtomahawk/utils/imagebutton.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "imagebutton.h" #include diff --git a/src/utils/imagebutton.h b/src/libtomahawk/utils/imagebutton.h similarity index 50% rename from src/utils/imagebutton.h rename to src/libtomahawk/utils/imagebutton.h index 66e9082d6..d3b060ab4 100644 --- a/src/utils/imagebutton.h +++ b/src/libtomahawk/utils/imagebutton.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef IMAGE_BUTTON_H #define IMAGE_BUTTON_H @@ -5,8 +23,9 @@ #include #include +#include "dllmacro.h" -class ImageButton : public QAbstractButton +class DLLEXPORT ImageButton : public QAbstractButton { Q_OBJECT diff --git a/src/libtomahawk/utils/progresstreeview.cpp b/src/libtomahawk/utils/progresstreeview.cpp new file mode 100644 index 000000000..2458de911 --- /dev/null +++ b/src/libtomahawk/utils/progresstreeview.cpp @@ -0,0 +1,25 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "progresstreeview.h" + +ProgressTreeView::ProgressTreeView( QWidget* parent ) + : QTreeView( parent ) + , m_progressBar( 0 ) +{ +} diff --git a/src/libtomahawk/utils/progresstreeview.h b/src/libtomahawk/utils/progresstreeview.h new file mode 100644 index 000000000..e77971eaf --- /dev/null +++ b/src/libtomahawk/utils/progresstreeview.h @@ -0,0 +1,44 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef PROGRESSTREEVIEW_H +#define PROGRESSTREEVIEW_H + +#include +#include + +#include "dllmacro.h" + +class DLLEXPORT ProgressTreeView : public QTreeView +{ +Q_OBJECT + +public: + ProgressTreeView( QWidget* parent ); + + void connectProgressBar( QProgressBar* progressBar ) { m_progressBar = progressBar; progressBar->setVisible( false ); } + + void setProgressStarted( int length ) { if ( m_progressBar ) { m_progressBar->setRange( 0, length ); m_progressBar->setValue( 0 ); m_progressBar->setVisible( true ); } } + void setProgressEnded() { if ( m_progressBar ) m_progressBar->setVisible( false ); } + void setProgressCompletion( int completion ) { if ( m_progressBar ) m_progressBar->setValue( completion ); } + +private: + QProgressBar* m_progressBar; +}; + +#endif // PROGRESSTREEVIEW_H diff --git a/src/libtomahawk/utils/proxystyle.cpp b/src/libtomahawk/utils/proxystyle.cpp new file mode 100644 index 000000000..038208a85 --- /dev/null +++ b/src/libtomahawk/utils/proxystyle.cpp @@ -0,0 +1,85 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "proxystyle.h" + +#include +#include +#include +#include +#include + +#define ARROW_WIDTH 8 +#define ARROW_HEIGHT 8 + + +void +ProxyStyle::drawPrimitive( PrimitiveElement pe, const QStyleOption* opt, QPainter* p, const QWidget* w ) const +{ + if ( pe == PE_IndicatorBranch ) + { + if ( opt->state & QStyle::State_Children ) + { + QRect r = opt->rect; + + int hd = ( opt->rect.height() - ARROW_HEIGHT ) / 2; + int wd = ( opt->rect.width() - ARROW_WIDTH ) / 2; + r.adjust( wd, hd, 0, 0 ); + + QPointF pointsOpened[3] = { QPointF( r.x(), r.y() ), QPointF( r.x() + ARROW_WIDTH, r.y() ), QPointF( r.x() + ARROW_WIDTH / 2, r.y() + ARROW_HEIGHT ) }; + QPointF pointsClosed[3] = { QPointF( r.x(), r.y() ), QPointF( r.x() + ARROW_WIDTH, r.y() + ARROW_HEIGHT / 2 ), QPointF( r.x(), r.y() + ARROW_HEIGHT ) }; + + p->save(); + p->setRenderHint( QPainter::Antialiasing ); + + p->setPen( opt->palette.dark().color() ); + p->setBrush( opt->palette.dark().color() ); + if ( !( opt->state & QStyle::State_Open ) ) + { + p->drawPolygon( pointsClosed, 3 ); + } + else + { + p->drawPolygon( pointsOpened, 3 ); + } + + p->restore(); + } + return; + } + + if ( pe != PE_FrameStatusBar ) + QProxyStyle::drawPrimitive( pe, opt, p, w ); +} + + +void +ProxyStyle::drawControl( ControlElement ce, const QStyleOption* opt, QPainter* p, const QWidget* w ) const +{ + if ( ce == CE_Splitter ) + { + const QSplitter* splitter = qobject_cast< const QSplitter* >( w ); + if ( !splitter->sizes().contains( 0 ) ) + { + p->setPen( QColor( 0x8c, 0x8c, 0x8c ) ); + p->drawLine( opt->rect.topLeft(), opt->rect.bottomRight() ); + } + } + else + QProxyStyle::drawControl( ce, opt, p, w ); +} diff --git a/src/libtomahawk/utils/proxystyle.h b/src/libtomahawk/utils/proxystyle.h new file mode 100644 index 000000000..488c87908 --- /dev/null +++ b/src/libtomahawk/utils/proxystyle.h @@ -0,0 +1,36 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef PROXYSTYLE_H +#define PROXYSTYLE_H + +#include +#include + +#include "dllmacro.h" + +class DLLEXPORT ProxyStyle : public QProxyStyle +{ +public: + ProxyStyle() {} + + virtual void drawPrimitive( PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w = 0 ) const; + virtual void drawControl( ControlElement ce, const QStyleOption *opt, QPainter *p, const QWidget *w = 0 ) const; +}; + +#endif // PROXYSTYLE_H diff --git a/src/libtomahawk/utils/querylabel.cpp b/src/libtomahawk/utils/querylabel.cpp new file mode 100644 index 000000000..8a5d77ce0 --- /dev/null +++ b/src/libtomahawk/utils/querylabel.cpp @@ -0,0 +1,587 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "querylabel.h" + +#include +#include +#include +#include +#include + +#include "tomahawkutils.h" +#include "artist.h" +#include "album.h" +#include "query.h" + + +#define BOXMARGIN 2 +#define DASH " - " + +QueryLabel::QueryLabel( QWidget* parent, Qt::WindowFlags flags ) + : QFrame( parent, flags ) + , m_type( Complete ) +{ + init(); +} + + +QueryLabel::QueryLabel( DisplayType type, QWidget* parent, Qt::WindowFlags flags ) + : QFrame( parent, flags ) + , m_type( type ) +{ + init(); +} + + +QueryLabel::QueryLabel( const Tomahawk::result_ptr& result, DisplayType type, QWidget* parent, Qt::WindowFlags flags ) + : QFrame( parent, flags ) + , m_type( type ) + , m_result( result ) +{ + init(); +} + + +QueryLabel::QueryLabel( const Tomahawk::query_ptr& query, DisplayType type, QWidget* parent, Qt::WindowFlags flags ) + : QFrame( parent, flags ) + , m_type( type ) + , m_query( query ) +{ + init(); +} + + +QueryLabel::~QueryLabel() +{ +} + + +void +QueryLabel::init() +{ + m_hoverType = None; + setContentsMargins( 0, 0, 0, 0 ); + setMouseTracking( true ); + + align = Qt::AlignLeft; + mode = Qt::ElideMiddle; +} + + +QString +QueryLabel::text() const +{ + QString text; + + if ( m_result.isNull() && m_query.isNull() ) + return m_text; + + if ( !m_result.isNull() ) + { + if ( m_type & Artist ) + { + text += m_result->artist()->name(); + } + if ( m_type & Album ) + { + smartAppend( text, m_result->album()->name() ); + } + if ( m_type & Track ) + { + smartAppend( text, m_result->track() ); + } + } + else + { + if ( m_type & Artist ) + { + text += m_query->artist(); + } + if ( m_type & Album ) + { + smartAppend( text, m_query->album() ); + } + if ( m_type & Track ) + { + smartAppend( text, m_query->track() ); + } + } + + return text; +} + + +QString +QueryLabel::artist() const +{ + if ( m_result.isNull() && m_query.isNull() ) + return QString(); + + if ( !m_result.isNull() ) + return m_result->artist()->name(); + else + return m_query->artist(); +} + + +QString +QueryLabel::album() const +{ + if ( m_result.isNull() && m_query.isNull() ) + return QString(); + + if ( !m_result.isNull() ) + return m_result->album()->name(); + else + return m_query->album(); +} + + +QString +QueryLabel::track() const +{ + if ( m_result.isNull() && m_query.isNull() ) + return QString(); + + if ( !m_result.isNull() ) + return m_result->track(); + else + return m_query->track(); +} + + +void +QueryLabel::setText( const QString& text ) +{ + setContentsMargins( m_textMargins ); + + m_result.clear(); + m_query.clear(); + m_text = text; + + updateLabel(); + + emit textChanged( m_text ); + emit resultChanged( m_result ); +} + + +void +QueryLabel::setResult( const Tomahawk::result_ptr& result ) +{ + if ( result.isNull() ) + return; + + if ( !m_text.isEmpty() && contentsMargins().left() != 0 ) // FIXME: hacky + m_textMargins = contentsMargins(); + + setContentsMargins( BOXMARGIN * 2, BOXMARGIN / 2, BOXMARGIN * 2, BOXMARGIN / 2); + + if ( m_result.isNull() || m_result.data() != result.data() ) + { + m_result = result; + + m_query = m_result->toQuery(); + QList rl; + rl << m_result; + m_query->addResults( rl ); + + updateLabel(); + + emit textChanged( text() ); + emit resultChanged( m_result ); + } +} + + +void +QueryLabel::setQuery( const Tomahawk::query_ptr& query ) +{ + if ( query.isNull() ) + return; + + setContentsMargins( BOXMARGIN * 2, BOXMARGIN / 2, BOXMARGIN * 2, BOXMARGIN / 2 ); + + if ( m_query.isNull() || m_query.data() != query.data() ) + { + m_query = query; + m_result.clear(); + updateLabel(); + + emit textChanged( text() ); + emit queryChanged( m_query ); + } +} + + +Qt::Alignment +QueryLabel::alignment() const +{ + return align; +} + + +void +QueryLabel::setAlignment( Qt::Alignment alignment ) +{ + if ( this->align != alignment ) + { + this->align = alignment; + update(); // no geometry change, repaint is sufficient + } +} + + +Qt::TextElideMode +QueryLabel::elideMode() const +{ + return mode; +} + + +void +QueryLabel::setElideMode( Qt::TextElideMode mode ) +{ + if ( this->mode != mode ) + { + this->mode = mode; + updateLabel(); + } +} + + +void +QueryLabel::updateLabel() +{ + m_hoverArea = QRect(); + m_hoverType = None; + + updateGeometry(); + update(); +} + + +QSize +QueryLabel::sizeHint() const +{ + const QFontMetrics& fm = fontMetrics(); + QSize size( fm.width( text() ) + contentsMargins().left() * 2, fm.height() + contentsMargins().top() * 2 ); + return size; +} + + +QSize +QueryLabel::minimumSizeHint() const +{ + switch ( mode ) + { + case Qt::ElideNone: + return sizeHint(); + + default: + { + const QFontMetrics& fm = fontMetrics(); + QSize size( fm.width( "..." ), fm.height() + contentsMargins().top() * 2 ); + return size; + } + } +} + + +void +QueryLabel::paintEvent( QPaintEvent* event ) +{ + QFrame::paintEvent( event ); + QPainter p( this ); + QRect r = contentsRect(); + QString s = text(); + const QString elidedText = fontMetrics().elidedText( s, mode, r.width() ); + + p.save(); + p.setRenderHint( QPainter::Antialiasing ); + + if ( m_hoverArea.width() ) + { + if ( elidedText != s ) + { + m_hoverArea.setLeft( 0 ); + m_hoverArea.setRight( fontMetrics().width( elidedText ) + contentsMargins().left() * 2 ); + m_hoverType = Track; + } + + p.setPen( palette().mid().color() ); + p.setBrush( palette().highlight() ); + p.drawRoundedRect( m_hoverArea, 4.0, 4.0 ); + } + + if ( elidedText != s || ( m_result.isNull() && m_query.isNull() ) ) + { + if ( m_hoverArea.width() ) + { + p.setPen( palette().highlightedText().color() ); + p.setBrush( palette().highlight() ); + } + else + { + p.setBrush( palette().window() ); + p.setPen( palette().color( foregroundRole() ) ); + } + p.drawText( r, align, elidedText ); + } + else + { + const QFontMetrics& fm = fontMetrics(); + int dashX = fm.width( DASH ); + int artistX = m_type & Artist ? fm.width( artist() ) : 0; + int albumX = m_type & Album ? fm.width( album() ) : 0; + int trackX = m_type & Track ? fm.width( track() ) : 0; + + if ( m_type & Artist ) + { + p.setBrush( palette().window() ); + p.setPen( palette().color( foregroundRole() ) ); + + if ( m_hoverType == Artist ) + { + p.setPen( palette().highlightedText().color() ); + p.setBrush( palette().highlight() ); + } + + p.drawText( r, align, artist() ); + r.adjust( artistX, 0, 0, 0 ); + } + if ( m_type & Album ) + { + p.setBrush( palette().window() ); + p.setPen( palette().color( foregroundRole() ) ); + + if ( m_type & Artist ) + { + p.drawText( r, align, DASH ); + r.adjust( dashX, 0, 0, 0 ); + } + if ( m_hoverType == Album ) + { + p.setPen( palette().highlightedText().color() ); + p.setBrush( palette().highlight() ); + } + + p.drawText( r, align, album() ); + r.adjust( albumX, 0, 0, 0 ); + } + if ( m_type & Track ) + { + p.setBrush( palette().window() ); + p.setPen( palette().color( foregroundRole() ) ); + + if ( m_type & Artist || m_type & Album ) + { + p.drawText( r, align, DASH ); + r.adjust( dashX, 0, 0, 0 ); + } + if ( m_hoverType == Track ) + { + p.setPen( palette().highlightedText().color() ); + p.setBrush( palette().highlight() ); + } + + p.drawText( r, align, track() ); + r.adjust( trackX, 0, 0, 0 ); + } + } + + p.restore(); +} + + +void +QueryLabel::changeEvent( QEvent* event ) +{ + QFrame::changeEvent( event ); + switch ( event->type() ) + { + case QEvent::FontChange: + case QEvent::ApplicationFontChange: + updateLabel(); + break; + + default: + break; + } +} + + +void +QueryLabel::mousePressEvent( QMouseEvent* event ) +{ + QFrame::mousePressEvent( event ); + time.start(); +} + + +void +QueryLabel::mouseReleaseEvent( QMouseEvent* event ) +{ + QFrame::mouseReleaseEvent( event ); + + m_dragPos = QPoint(); + if ( time.elapsed() < qApp->doubleClickInterval() ) + { + switch( m_hoverType ) + { + case Artist: + emit clickedArtist(); + break; + case Album: + emit clickedAlbum(); + break; + case Track: + emit clickedTrack(); + break; + + default: + emit clicked(); + } + } +} + + +void +QueryLabel::mouseMoveEvent( QMouseEvent* event ) +{ + QFrame::mouseMoveEvent( event ); + int x = event->x(); + + if ( event->buttons() & Qt::LeftButton && + ( m_dragPos - event->pos() ).manhattanLength() >= QApplication::startDragDistance() ) + { + startDrag(); + leaveEvent( 0 ); + return; + } + + if ( m_query.isNull() && m_result.isNull() ) + { + m_hoverArea = QRect(); + m_hoverType = None; + return; + } + + const QFontMetrics& fm = fontMetrics(); + int dashX = fm.width( DASH ); + int artistX = m_type & Artist ? fm.width( artist() ) : 0; + int albumX = m_type & Album ? fm.width( album() ) : 0; + int trackX = m_type & Track ? fm.width( track() ) : 0; + + if ( m_type & Track ) + { + trackX += contentsMargins().left(); + } + if ( m_type & Album ) + { + trackX += albumX + dashX; + albumX += contentsMargins().left(); + } + if ( m_type & Artist ) + { + albumX += artistX + dashX; + trackX += artistX + dashX; + artistX += contentsMargins().left(); + } + + QRect hoverArea; + m_hoverType = None; + if ( m_type & Artist && x < artistX ) + { + m_hoverType = Artist; + hoverArea.setLeft( 0 ); + hoverArea.setRight( artistX + contentsMargins().left() - 1 ); + } + else if ( m_type & Album && x < albumX && x > artistX ) + { + m_hoverType = Album; + int spacing = ( m_type & Artist ) ? dashX : 0; + hoverArea.setLeft( artistX + spacing ); + hoverArea.setRight( albumX + spacing + contentsMargins().left() - 1 ); + } + else if ( m_type & Track && x < trackX && x > albumX ) + { + m_hoverType = Track; + int spacing = ( m_type & Album ) ? dashX : 0; + hoverArea.setLeft( albumX + spacing ); + hoverArea.setRight( trackX + contentsMargins().left() - 1 ); + } + + if ( hoverArea.width() ) + { + hoverArea.setY( 1 ); + hoverArea.setHeight( height() - 2 ); + } + if ( hoverArea != m_hoverArea ) + { + m_hoverArea = hoverArea; + repaint(); + } +} + + +void +QueryLabel::leaveEvent( QEvent* event ) +{ + m_hoverArea = QRect(); + m_hoverType = None; + repaint(); +} + + +void +QueryLabel::startDrag() +{ + if ( m_query.isNull() ) + return; + + QByteArray queryData; + QDataStream queryStream( &queryData, QIODevice::WriteOnly ); + QMimeData* mimeData = new QMimeData(); + mimeData->setText( text() ); + + queryStream << qlonglong( &m_query ); + + mimeData->setData( "application/tomahawk.query.list", queryData ); + QDrag *drag = new QDrag( this ); + drag->setMimeData( mimeData ); + drag->setPixmap( TomahawkUtils::createDragPixmap() ); + +// QPoint hotSpot = event->pos() - child->pos(); +// drag->setHotSpot( hotSpot ); + + drag->exec( Qt::CopyAction ); +} + + +QString +QueryLabel::smartAppend( QString& text, const QString& appendage ) const +{ + QString s; + if ( !text.isEmpty() ) + s = DASH; + + text += s + appendage; + return text; +} diff --git a/src/libtomahawk/utils/querylabel.h b/src/libtomahawk/utils/querylabel.h new file mode 100644 index 000000000..1fed3d9c2 --- /dev/null +++ b/src/libtomahawk/utils/querylabel.h @@ -0,0 +1,120 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef QUERYLABEL_H +#define QUERYLABEL_H + +#include +#include + +#include "result.h" +#include "query.h" +#include "typedefs.h" +#include "dllmacro.h" + +class DLLEXPORT QueryLabel : public QFrame +{ +Q_OBJECT + +public: + enum DisplayType + { + None = 0, + Artist = 1, + Album = 2, + Track = 4, + ArtistAndAlbum = 3, + ArtistAndTrack = 5, + AlbumAndTrack = 6, + Complete = 7 + }; + + explicit QueryLabel( QWidget* parent = 0, Qt::WindowFlags flags = 0 ); + explicit QueryLabel( DisplayType type = Complete, QWidget* parent = 0, Qt::WindowFlags flags = 0 ); + explicit QueryLabel( const Tomahawk::result_ptr& result, DisplayType type = Complete, QWidget* parent = 0, Qt::WindowFlags flags = 0 ); + explicit QueryLabel( const Tomahawk::query_ptr& query, DisplayType type = Complete, QWidget* parent = 0, Qt::WindowFlags flags = 0 ); + virtual ~QueryLabel(); + + QString text() const; + QString artist() const; + QString album() const; + QString track() const; + + Tomahawk::result_ptr result() const { return m_result; } + Tomahawk::query_ptr query() const { return m_query; } + + DisplayType type() const { return m_type; } + void setType( DisplayType type ) { m_type = type; } + + Qt::Alignment alignment() const; + void setAlignment( Qt::Alignment alignment ); + + Qt::TextElideMode elideMode() const; + void setElideMode( Qt::TextElideMode mode ); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + void init(); + void updateLabel(); + +public slots: + void setText( const QString& text ); + void setResult( const Tomahawk::result_ptr& result ); + void setQuery( const Tomahawk::query_ptr& query ); + +signals: + void clicked(); + void clickedArtist(); + void clickedAlbum(); + void clickedTrack(); + + void textChanged( const QString& text ); + void resultChanged( const Tomahawk::result_ptr& result ); + void queryChanged( const Tomahawk::query_ptr& query ); + +protected: + virtual void mousePressEvent( QMouseEvent* event ); + virtual void mouseReleaseEvent( QMouseEvent* event ); + virtual void mouseMoveEvent( QMouseEvent* event ); + virtual void leaveEvent( QEvent* event ); + + virtual void changeEvent( QEvent* event ); + virtual void paintEvent( QPaintEvent* event ); + + virtual void startDrag(); + +private: + QString smartAppend( QString& text, const QString& appendage ) const; + QTime time; + + DisplayType m_type; + QString m_text; + Tomahawk::result_ptr m_result; + Tomahawk::query_ptr m_query; + + Qt::Alignment align; + Qt::TextElideMode mode; + + DisplayType m_hoverType; + QRect m_hoverArea; + QPoint m_dragPos; + QMargins m_textMargins; +}; + +#endif // QUERYLABEL_H diff --git a/src/libtomahawk/utils/tomahawkutils.cpp b/src/libtomahawk/utils/tomahawkutils.cpp new file mode 100644 index 000000000..a551e39c6 --- /dev/null +++ b/src/libtomahawk/utils/tomahawkutils.cpp @@ -0,0 +1,424 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "tomahawkutils.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 + #include + #include +#endif + +#ifdef Q_WS_MAC + #include + #include +#endif + +#include +#include + +namespace TomahawkUtils +{ + +#ifdef Q_WS_MAC + +QString +appSupportFolderPath() +{ + // honestly, it is *always* this --mxcl + return QDir::home().filePath( "Library/Application Support" ); +} + +#endif // Q_WS_MAC + + +QDir +appConfigDir() +{ + QDir ret; + +#ifdef Q_WS_MAC + if( getenv( "HOME" ) ) + { + return QDir( QString( "%1" ).arg( getenv( "HOME" ) ) ); + } + else + { + qDebug() << "Error, $HOME not set."; + throw "$HOME not set"; + return QDir( "/tmp" ); + } + +#elif defined(Q_WS_WIN) + throw "TODO"; + return QDir( "c:\\" ); //TODO refer to Qt documentation to get code to do this + +#else + if( getenv( "XDG_CONFIG_HOME" ) ) + { + ret = QDir( QString( "%1/Tomahawk" ).arg( getenv( "XDG_CONFIG_HOME" ) ) ); + } + else if( getenv( "HOME" ) ) + { + ret = QDir( QString( "%1/.config/Tomahawk" ).arg( getenv( "HOME" ) ) ); + } + else + { + qDebug() << "Error, $HOME or $XDG_CONFIG_HOME not set."; + throw "Error, $HOME or $XDG_CONFIG_HOME not set."; + ret = QDir( "/tmp" ); + } + + if( !ret.exists() ) + { + ret.mkpath( ret.canonicalPath() ); + } + + return ret; +#endif +} + + +QDir +appDataDir() +{ + QString path; + + #ifdef WIN32 + if ( ( QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based ) == 0 ) + { + // Use this for non-DOS-based Windowses + char acPath[MAX_PATH]; + HRESULT h = SHGetFolderPathA( NULL, CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE, + NULL, 0, acPath ); + if ( h == S_OK ) + { + path = QString::fromLocal8Bit( acPath ); + } + } + #elif defined(Q_WS_MAC) + path = appSupportFolderPath(); + #elif defined(Q_WS_X11) + path = QDir::home().filePath( ".local/share" ); + #else + path = QCoreApplication::applicationDirPath(); + #endif + + path += "/" + QCoreApplication::organizationName(); + QDir d( path ); + d.mkpath( path ); + + return d; +} + + +QDir +appLogDir() +{ +#ifndef Q_WS_MAC + return appDataDir(); +#else + return QDir( QDir::homePath() + "/Library/Logs" ); +#endif +} + + +QString +timeToString( int seconds ) +{ + int hrs = seconds / 60 / 60; + int mins = seconds / 60 % 60; + int secs = seconds % 60; + + if ( seconds < 0 ) + { + hrs = mins = secs = 0; + } + + return QString( "%1%2:%3" ).arg( hrs > 0 ? hrs < 10 ? "0" + QString::number( hrs ) + ":" : QString::number( hrs ) + ":" : "" ) + .arg( mins < 10 ? "0" + QString::number( mins ) : QString::number( mins ) ) + .arg( secs < 10 ? "0" + QString::number( secs ) : QString::number( secs ) ); +} + +QString +ageToString( const QDateTime& time ) +{ + QDateTime now = QDateTime::currentDateTime(); + + int mins = time.secsTo( now ) / 60; + int hours = mins / 60; + int days = time.daysTo( now ); + int weeks = days / 7; + int months = days / 30.42; + int years = months / 12; + + if ( years ) + { + if ( years > 1 ) + return QString( "%1 years" ).arg( years ); + else + return QString( "%1 year" ).arg( years ); + } + + if ( months ) + { + if ( months > 1 ) + return QString( "%1 months" ).arg( months ); + else + return QString( "%1 month" ).arg( months ); + } + + if ( weeks ) + { + if ( weeks > 1 ) + return QString( "%1 weeks" ).arg( weeks ); + else + return QString( "%1 week" ).arg( weeks ); + } + + if ( days ) + { + if ( days > 1 ) + return QString( "%1 days" ).arg( days ); + else + return QString( "%1 day" ).arg( days ); + } + + if ( hours ) + { + if ( hours > 1 ) + return QString( "%1 hours" ).arg( hours ); + else + return QString( "%1 hour" ).arg( hours ); + } + + if ( mins ) + { + if ( mins > 1 ) + return QString( "%1 minutes" ).arg( mins ); + else + return QString( "%1 minute" ).arg( mins ); + } + + return QString(); +} + + +QString +filesizeToString( unsigned int size ) +{ + int kb = size / 1024; + int mb = kb / 1024; + + if ( mb ) + { + return QString( "%1.%2 Mb" ).arg( mb ).arg( int( ( kb % 1024 ) / 102.4 ) ); + } + else if ( kb ) + { + return QString( "%1 Kb" ).arg( kb ); + } + else + return QString::number( size ); +} + + +QString +extensionToMimetype( const QString& extension ) +{ + static QMap s_ext2mime; + if ( s_ext2mime.isEmpty() ) + { + s_ext2mime.insert( "mp3", "audio/mpeg" ); + s_ext2mime.insert( "ogg", "application/ogg" ); + s_ext2mime.insert( "flac", "audio/flac" ); + s_ext2mime.insert( "mpc", "audio/x-musepack" ); + s_ext2mime.insert( "wma", "audio/x-ms-wma" ); + s_ext2mime.insert( "aac", "audio/mp4" ); + s_ext2mime.insert( "m4a", "audio/mp4" ); + s_ext2mime.insert( "mp4", "audio/mp4" ); + } + + return s_ext2mime.value( extension, "unknown" ); +} + + +QPixmap +createDragPixmap( int itemCount ) +{ + // If more than one item is dragged, align the items inside a + // rectangular grid. The maximum grid size is limited to 5 x 5 items. + int xCount = 3; + int size = 32; + + if ( itemCount > 16 ) + { + xCount = 5; + size = 16; + } else if( itemCount > 9 ) + { + xCount = 4; + size = 22; + } + + if( itemCount < xCount ) + { + xCount = itemCount; + } + + int yCount = itemCount / xCount; + if( itemCount % xCount != 0 ) + { + ++yCount; + } + if( yCount > xCount ) + { + yCount = xCount; + } + // Draw the selected items into the grid cells + QPixmap dragPixmap( xCount * size + xCount - 1, yCount * size + yCount - 1 ); + dragPixmap.fill( Qt::transparent ); + + QPainter painter( &dragPixmap ); + painter.setRenderHint( QPainter::Antialiasing ); + int x = 0; + int y = 0; + for( int i = 0; i < itemCount; ++i ) + { + const QPixmap pixmap = QPixmap( QString( ":/data/icons/audio-x-generic-%2x%2.png" ).arg( size ) ); + painter.drawPixmap( x, y, pixmap ); + + x += size + 1; + if ( x >= dragPixmap.width() ) + { + x = 0; + y += size + 1; + } + if ( y >= dragPixmap.height() ) + { + break; + } + } + + return dragPixmap; +} + + +QNetworkAccessManager* s_nam = 0; +QNetworkProxy* s_proxy = 0; + +QNetworkAccessManager* +nam() +{ + return s_nam; +} + + +QNetworkProxy* +proxy() +{ + return s_proxy; +} + + +void +setNam( QNetworkAccessManager* nam ) +{ + s_nam = nam; +} + + +void +setProxy( QNetworkProxy* proxy ) +{ + s_proxy = proxy; +} + + +///////////////// DNSResolver ///////////////// + +static DNSResolver* s_dnsResolver = 0; + +DNSResolver* +dnsResolver() +{ + if( !s_dnsResolver ) + s_dnsResolver = new DNSResolver(); + + return s_dnsResolver; +} + +DNSResolver::DNSResolver() +{ + m_dnsShared = new JDnsShared(JDnsShared::UnicastInternet); + m_dnsShared->addInterface(QHostAddress::Any); + m_dnsShared->addInterface(QHostAddress::AnyIPv6); + m_dnsSharedRequest = new JDnsSharedRequest(m_dnsShared); + + connect(m_dnsSharedRequest, SIGNAL(resultsReady()), SLOT(resultsReady())); +} + +void +DNSResolver::resolve( QString &host, QString type ) +{ + if( type == "SRV" ) + { + // For the moment, assume we are looking for XMPP... + QString fullHost( "_xmpp-client._tcp." + host ); + + qDebug() << "Looking up SRV record for " << fullHost.toUtf8(); + + m_dnsSharedRequest->query( fullHost.toUtf8(), QJDns::Srv ); + } + else + { + QString badResult( "NONE" ); + emit result( badResult ); + } +} + +void +DNSResolver::resultsReady() +{ + if( m_dnsSharedRequest->success() ) + { + QList results = m_dnsSharedRequest->results(); + foreach( QJDns::Record r, results ) + { + qDebug() << "Found result (of some type): " << QString( r.name ); + if( r.type == QJDns::Srv ) + { + QString foundResult( r.name ); + emit result( foundResult ); + return; + } + } + } + qDebug() << "DNS resolve request was NOT successful! Error: " << (int)(m_dnsSharedRequest->error()); + QString badResult( "NONE" ); + emit result( badResult ); +} + +} // ns diff --git a/src/libtomahawk/utils/tomahawkutils.h b/src/libtomahawk/utils/tomahawkutils.h new file mode 100644 index 000000000..103e379ce --- /dev/null +++ b/src/libtomahawk/utils/tomahawkutils.h @@ -0,0 +1,103 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TOMAHAWKUTILS_H +#define TOMAHAWKUTILS_H + +#include "dllmacro.h" +#include +#include + +#define RESPATH ":/data/" + +class QDir; +class QDateTime; +class QString; +class QPixmap; +class QNetworkAccessManager; +class QNetworkProxy; + +class JDnsShared; +class JDnsSharedRequest; + +namespace TomahawkUtils +{ + //NOTE: The JDnsShared system is supposed to allow you to make multiple requests + //at once, but either I'm a dumbass, or it's a broken paradigm, or both, + //because there's no way that I can see to tell what result is for what query. + //Be aware of this if ever we want to do parallel connections/lookups; turn it into + //a non-static non-singleton normal utility class then. + class DLLEXPORT DNSResolver : public QObject + { + Q_OBJECT + + public: + explicit DNSResolver(); + ~DNSResolver() {} + + void resolve( QString& host, QString type ); + + signals: + void result( QString& result ); + + public slots: + void resultsReady(); + + private: + JDnsShared* m_dnsShared; + JDnsSharedRequest* m_dnsSharedRequest; + }; + + class DLLEXPORT Sleep : public QThread + { + public: + static void sleep( unsigned long secs ) + { + QThread::sleep( secs ); + } + static void msleep( unsigned long msecs ) + { + QThread::msleep( msecs ); + } + static void usleep( unsigned long usecs ) + { + QThread::usleep( usecs ); + } + }; + + DLLEXPORT QDir appConfigDir(); + DLLEXPORT QDir appDataDir(); + DLLEXPORT QDir appLogDir(); + + DLLEXPORT QString timeToString( int seconds ); + DLLEXPORT QString ageToString( const QDateTime& time ); + DLLEXPORT QString filesizeToString( unsigned int size ); + DLLEXPORT QString extensionToMimetype( const QString& extension ); + + DLLEXPORT QPixmap createDragPixmap( int itemCount = 1 ); + + DLLEXPORT QNetworkAccessManager* nam(); + DLLEXPORT QNetworkProxy* proxy(); + + DLLEXPORT void setNam( QNetworkAccessManager* nam ); + DLLEXPORT void setProxy( QNetworkProxy* proxy ); + + DLLEXPORT DNSResolver* dnsResolver(); +} + +#endif // TOMAHAWKUTILS_H diff --git a/src/utils/widgetdragfilter.cpp b/src/libtomahawk/utils/widgetdragfilter.cpp similarity index 77% rename from src/utils/widgetdragfilter.cpp rename to src/libtomahawk/utils/widgetdragfilter.cpp index 40aec2aa8..fe5216669 100644 --- a/src/utils/widgetdragfilter.cpp +++ b/src/libtomahawk/utils/widgetdragfilter.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "widgetdragfilter.h" #include #include diff --git a/src/libtomahawk/utils/widgetdragfilter.h b/src/libtomahawk/utils/widgetdragfilter.h new file mode 100644 index 000000000..ab6c5c5ea --- /dev/null +++ b/src/libtomahawk/utils/widgetdragfilter.h @@ -0,0 +1,50 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef WIDGET_DRAG_FILTER_H +#define WIDGET_DRAG_FILTER_H + +#include +#include +#include + +#include "dllmacro.h" + +class QMouseEvent; +class QEvent; + +/** + * This class encapsulates an event filter on a widget that lets any drag events over the widget + * translate into move events for the whole application. + */ +class DLLEXPORT WidgetDragFilter : public QObject +{ + Q_OBJECT +public: + explicit WidgetDragFilter(QObject* parent = 0); + + virtual bool eventFilter(QObject* , QEvent* ); +private: + bool canDrag( QObject* obj, QMouseEvent* ev ) const; + + QWeakPointer m_target; // in case it's deleted under us + QPoint m_dragPoint; + bool m_dragStarted; +}; + +#endif diff --git a/src/libtomahawk/utils/xspfloader.cpp b/src/libtomahawk/utils/xspfloader.cpp new file mode 100644 index 000000000..5b1899300 --- /dev/null +++ b/src/libtomahawk/utils/xspfloader.cpp @@ -0,0 +1,189 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "xspfloader.h" + +#include +#include +#include + +#include "utils/tomahawkutils.h" + +#include "sourcelist.h" +#include "playlist.h" + +using namespace Tomahawk; + + +void +XSPFLoader::load( const QUrl& url ) +{ + QNetworkRequest request( url ); + QNetworkReply* reply = TomahawkUtils::nam()->get( request ); + + // isn't there a race condition here? something could happen before we connect() + connect( reply, SIGNAL( finished() ), + SLOT( networkLoadFinished() ) ); + + connect( reply, SIGNAL( error( QNetworkReply::NetworkError ) ), + SLOT( networkError( QNetworkReply::NetworkError ) ) ); +} + + +void +XSPFLoader::load( QFile& file ) +{ + if( file.open( QFile::ReadOnly ) ) + { + m_body = file.readAll(); + gotBody(); + } + else + { + qDebug() << "Failed to open xspf file"; + reportError(); + } +} + + +void +XSPFLoader::reportError() +{ + qDebug() << Q_FUNC_INFO; + emit failed(); + deleteLater(); +} + + +void +XSPFLoader::networkLoadFinished() +{ + qDebug() << Q_FUNC_INFO; + QNetworkReply* reply = qobject_cast(sender()); + m_body = reply->readAll(); + gotBody(); +} + + +void +XSPFLoader::networkError( QNetworkReply::NetworkError e ) +{ + qDebug() << Q_FUNC_INFO << e; + reportError(); +} + + +void +XSPFLoader::gotBody() +{ + qDebug() << Q_FUNC_INFO; + + QDomDocument xmldoc; + bool namespaceProcessing = true; + xmldoc.setContent( m_body, namespaceProcessing ); + QDomElement docElement( xmldoc.documentElement() ); + + QString origTitle; + QDomNodeList tracklist; + QDomElement n = docElement.firstChildElement(); + for ( ; !n.isNull(); n = n.nextSiblingElement() ) { + if (n.namespaceURI() == m_NS && n.localName() == "title") { + origTitle = n.text(); + } else if (n.namespaceURI() == m_NS && n.localName() == "creator") { + m_creator = n.text(); + } else if (n.namespaceURI() == m_NS && n.localName() == "info") { + m_info = n.text(); + } else if (n.namespaceURI() == m_NS && n.localName() == "trackList") { + tracklist = n.childNodes(); + } + } + + m_title = origTitle; + if ( m_title.isEmpty() ) + m_title = tr( "New Playlist" ); + + bool shownError = false; + for ( unsigned int i = 0; i < tracklist.length(); i++ ) + { + QDomNode e = tracklist.at( i ); + + QString artist, album, track, duration, annotation; + QDomElement n = e.firstChildElement(); + for ( ; !n.isNull(); n = n.nextSiblingElement() ) { + if (n.namespaceURI() == m_NS && n.localName() == "duration") { + duration = n.text(); + } else if (n.namespaceURI() == m_NS && n.localName() == "annotation") { + annotation = n.text(); + } else if (n.namespaceURI() == m_NS && n.localName() == "creator") { + artist = n.text(); + } else if (n.namespaceURI() == m_NS && n.localName() == "album") { + album = n.text(); + } else if (n.namespaceURI() == m_NS && n.localName() == "title") { + track = n.text(); + } + } + + if( artist.isEmpty() || track.isEmpty() ) { + if( !shownError ) { + QMessageBox::warning( 0, tr( "Failed to save tracks" ), tr( "Some tracks in the playlist do not contain an artist and a title. They will be ignored." ), QMessageBox::Ok ); + shownError = true; + } + continue; + } + + plentry_ptr p( new PlaylistEntry ); + p->setGuid( uuid() ); + p->setDuration( duration.toInt() / 1000 ); + p->setLastmodified( 0 ); + p->setAnnotation( annotation ); + + p->setQuery( Tomahawk::Query::get( artist, track, album, uuid() ) ); + p->query()->setDuration( duration.toInt() / 1000 ); + m_entries << p; + } + + if ( origTitle.isEmpty() && m_entries.isEmpty() ) + { + if ( m_autoCreate ) + { + QMessageBox::critical( 0, tr( "XSPF Error" ), tr( "This is not a valid XSPF playlist." ) ); + deleteLater(); + return; + } + else + { + emit failed(); + return; + } + } + + if ( m_autoCreate ) + { + m_playlist = Playlist::create( SourceList::instance()->getLocal(), + uuid(), + m_title, + m_info, + m_creator, + false ); + + m_playlist->createNewRevision( uuid(), m_playlist->currentrevision(), m_entries ); + deleteLater(); + } + + emit ok( m_playlist ); +} diff --git a/src/libtomahawk/utils/xspfloader.h b/src/libtomahawk/utils/xspfloader.h new file mode 100644 index 000000000..d541293cf --- /dev/null +++ b/src/libtomahawk/utils/xspfloader.h @@ -0,0 +1,81 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +/* + Fetches and parses an XSPF document from a QFile or QUrl. + */ + +#ifndef XSPFLOADER_H +#define XSPFLOADER_H + +#include +#include +#include +#include +#include +#include + +#include "playlist.h" +#include "typedefs.h" + +#include "dllmacro.h" + +class DLLEXPORT XSPFLoader : public QObject +{ +Q_OBJECT + +public: + explicit XSPFLoader( bool autoCreate = true, QObject* parent = 0 ) + : QObject( parent ) + , m_autoCreate( autoCreate ) + , m_NS("http://xspf.org/ns/0/") + {} + + virtual ~XSPFLoader() + { + qDebug() << Q_FUNC_INFO; + } + + QList< Tomahawk::plentry_ptr > entries() const { return m_entries; } + +signals: + void failed(); + void ok( const Tomahawk::playlist_ptr& ); + +public slots: + void load( const QUrl& url ); + void load( QFile& file ); + +private slots: + void networkLoadFinished(); + void networkError( QNetworkReply::NetworkError e ); + +private: + void reportError(); + void gotBody(); + + bool m_autoCreate; + QString m_NS; + QList< Tomahawk::plentry_ptr > m_entries; + QString m_title, m_info, m_creator; + + QByteArray m_body; + Tomahawk::playlist_ptr m_playlist; +}; + +#endif // XSPFLOADER_H diff --git a/src/libtomahawk/viewpage.cpp b/src/libtomahawk/viewpage.cpp new file mode 100644 index 000000000..4cd52c941 --- /dev/null +++ b/src/libtomahawk/viewpage.cpp @@ -0,0 +1,24 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "viewpage.h" + +#include + +using namespace Tomahawk; + diff --git a/src/libtomahawk/viewpage.h b/src/libtomahawk/viewpage.h new file mode 100644 index 000000000..b59695e7a --- /dev/null +++ b/src/libtomahawk/viewpage.h @@ -0,0 +1,56 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef VIEWPAGE_H +#define VIEWPAGE_H + +#include + +#include "typedefs.h" +#include "playlistinterface.h" +#include "utils/tomahawkutils.h" + +#include "dllmacro.h" + +namespace Tomahawk +{ + +class DLLEXPORT ViewPage +{ +public: + ViewPage() {} + + virtual QWidget* widget() = 0; + virtual PlaylistInterface* playlistInterface() const = 0; + + virtual QString title() const = 0; + virtual QString description() const = 0; + virtual QPixmap pixmap() const { return QPixmap( RESPATH "icons/tomahawk-icon-128x128.png" ); } + + virtual bool showStatsBar() const { return true; } + virtual bool showModes() const { return false; } + virtual bool queueVisible() const { return true; } + + virtual bool jumpToCurrentTrack() = 0; + +private: +}; + +}; // ns + +#endif //VIEWPAGE_H diff --git a/src/libtomahawk/widgets/infowidgets/sourceinfowidget.cpp b/src/libtomahawk/widgets/infowidgets/sourceinfowidget.cpp new file mode 100644 index 000000000..c78783538 --- /dev/null +++ b/src/libtomahawk/widgets/infowidgets/sourceinfowidget.cpp @@ -0,0 +1,95 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "sourceinfowidget.h" +#include "ui_sourceinfowidget.h" + +#include "utils/tomahawkutils.h" + +#include "playlist/playlistmanager.h" +#include "playlist/albummodel.h" +#include "playlist/collectionflatmodel.h" +#include "playlist/playlistmodel.h" + +#include "database/databasecommand_alltracks.h" +#include "database/databasecommand_allalbums.h" + +#include "widgets/overlaywidget.h" + + +SourceInfoWidget::SourceInfoWidget( const Tomahawk::source_ptr& source, QWidget* parent ) + : QWidget( parent ) + , ui( new Ui::SourceInfoWidget ) +{ + ui->setupUi( this ); + + ui->historyView->overlay()->setEnabled( false ); + + m_recentCollectionModel = new CollectionFlatModel( ui->recentCollectionView ); + ui->recentCollectionView->setModel( m_recentCollectionModel ); + m_recentCollectionModel->addFilteredCollection( source->collection(), 250, DatabaseCommand_AllTracks::ModificationTime ); + + m_historyModel = new PlaylistModel( ui->historyView ); + ui->historyView->setModel( m_historyModel ); + m_historyModel->loadHistory( source ); + + connect( source.data(), SIGNAL( playbackFinished( Tomahawk::query_ptr ) ), SLOT( onPlaybackFinished( Tomahawk::query_ptr ) ) ); + + ui->recentCollectionView->setColumnHidden( TrackModel::Bitrate, true ); + ui->recentCollectionView->setColumnHidden( TrackModel::Origin, true ); + ui->recentCollectionView->setColumnHidden( TrackModel::Filesize, true ); + + ui->historyView->setColumnHidden( TrackModel::Bitrate, true ); + ui->historyView->setColumnHidden( TrackModel::Origin, true ); + ui->historyView->setColumnHidden( TrackModel::Filesize, true ); + + m_recentAlbumModel = new AlbumModel( ui->recentAlbumView ); + ui->recentAlbumView->setModel( m_recentAlbumModel ); + m_recentAlbumModel->addFilteredCollection( source->collection(), 20, DatabaseCommand_AllAlbums::ModificationTime ); + + m_title = tr( "Info about %1" ).arg( source->isLocal() ? tr( "Your Collection" ) : source->friendlyName() ); +} + + +SourceInfoWidget::~SourceInfoWidget() +{ + delete ui; +} + + +void +SourceInfoWidget::onPlaybackFinished( const Tomahawk::query_ptr& query ) +{ + m_historyModel->insert( 0, query ); +} + + +void +SourceInfoWidget::changeEvent( QEvent* e ) +{ + QWidget::changeEvent( e ); + switch ( e->type() ) + { + case QEvent::LanguageChange: + ui->retranslateUi( this ); + break; + + default: + break; + } +} diff --git a/src/libtomahawk/widgets/infowidgets/sourceinfowidget.h b/src/libtomahawk/widgets/infowidgets/sourceinfowidget.h new file mode 100644 index 000000000..12528129e --- /dev/null +++ b/src/libtomahawk/widgets/infowidgets/sourceinfowidget.h @@ -0,0 +1,75 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef SOURCEINFOWIDGET_H +#define SOURCEINFOWIDGET_H + +#include + +#include "album.h" +#include "result.h" +#include "playlistinterface.h" +#include "viewpage.h" + +#include "dllmacro.h" + +class AlbumModel; +class CollectionFlatModel; +class PlaylistModel; + +namespace Ui +{ + class SourceInfoWidget; +} + +class DLLEXPORT SourceInfoWidget : public QWidget, public Tomahawk::ViewPage +{ +Q_OBJECT + +public: + SourceInfoWidget( const Tomahawk::source_ptr& source, QWidget* parent = 0 ); + ~SourceInfoWidget(); + + virtual QWidget* widget() { return this; } + virtual PlaylistInterface* playlistInterface() const { return 0; } + + virtual QString title() const { return m_title; } + virtual QString description() const { return m_description; } + + virtual bool showStatsBar() const { return false; } + + virtual bool jumpToCurrentTrack() { return false; } + +protected: + void changeEvent( QEvent* e ); + +private slots: + void onPlaybackFinished( const Tomahawk::query_ptr& query ); + +private: + Ui::SourceInfoWidget *ui; + + CollectionFlatModel* m_recentCollectionModel; + PlaylistModel* m_historyModel; + AlbumModel* m_recentAlbumModel; + + QString m_title; + QString m_description; +}; + +#endif // SOURCEINFOWIDGET_H diff --git a/src/infowidgets/sourceinfowidget.ui b/src/libtomahawk/widgets/infowidgets/sourceinfowidget.ui similarity index 51% rename from src/infowidgets/sourceinfowidget.ui rename to src/libtomahawk/widgets/infowidgets/sourceinfowidget.ui index d0beceddb..cfb416fcb 100644 --- a/src/infowidgets/sourceinfowidget.ui +++ b/src/libtomahawk/widgets/infowidgets/sourceinfowidget.ui @@ -10,24 +10,7 @@ 460
- - - - - - 22 - 75 - true - - - - Source Name - - - false - - - + @@ -55,13 +38,13 @@ 0 - 210 + 192 16777215 - 210 + 192 @@ -69,38 +52,68 @@ - + - - - - 13 - 75 - true - - - - Latest Additions to their Collection - - + + + + + + 13 + 75 + true + + + + Latest Additions to their Collection + + + + + + + - + + + + + + 13 + 75 + true + + + + Recently played Tracks + + + + + + + + + PlaylistView + QTreeView +
playlist/playlistview.h
+
CollectionView QTreeView -
collectionview.h
+
playlist/collectionview.h
AlbumView QListView -
albumview.h
+
playlist/albumview.h
diff --git a/src/libtomahawk/widgets/newplaylistwidget.cpp b/src/libtomahawk/widgets/newplaylistwidget.cpp new file mode 100644 index 000000000..869bfdb3e --- /dev/null +++ b/src/libtomahawk/widgets/newplaylistwidget.cpp @@ -0,0 +1,157 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "newplaylistwidget.h" +#include "ui_newplaylistwidget.h" + +#include +#include + +#include "utils/tomahawkutils.h" + +#include "playlist/playlistmanager.h" +#include "playlist/playlistmodel.h" + +#include "widgets/overlaywidget.h" + +#include "utils/xspfloader.h" + +#include "sourcelist.h" + +#define FILTER_TIMEOUT 280 + + +NewPlaylistWidget::NewPlaylistWidget( QWidget* parent ) + : QWidget( parent ) + , ui( new Ui::NewPlaylistWidget ) +{ + ui->setupUi( this ); + + m_saveButton = new QPushButton( tr( "&Create Playlist" ) ); + m_saveButton->setDefault( true ); + m_saveButton->setEnabled( false ); + + ui->buttonBox->addButton( m_saveButton, QDialogButtonBox::AcceptRole ); + + connect( ui->titleEdit, SIGNAL( textChanged( QString ) ), SLOT( onTitleChanged( QString ) ) ); + connect( ui->tagEdit, SIGNAL( textChanged( QString ) ), SLOT( onTagChanged() ) ); + connect( ui->buttonBox, SIGNAL( accepted() ), SLOT( savePlaylist() ) ); + connect( ui->buttonBox, SIGNAL( rejected() ), SLOT( cancel() ) ); + + m_suggestionsModel = new PlaylistModel( ui->suggestionsView ); + ui->suggestionsView->setModel( m_suggestionsModel ); + ui->suggestionsView->overlay()->setEnabled( false ); + + connect( &m_filterTimer, SIGNAL( timeout() ), SLOT( updateSuggestions() ) ); +} + + +NewPlaylistWidget::~NewPlaylistWidget() +{ + delete ui; +} + + +void +NewPlaylistWidget::changeEvent( QEvent* e ) +{ + QWidget::changeEvent( e ); + switch ( e->type() ) + { + case QEvent::LanguageChange: + ui->retranslateUi( this ); + break; + + default: + break; + } +} + + +void +NewPlaylistWidget::onTitleChanged( const QString& title ) +{ + m_saveButton->setEnabled( !title.isEmpty() ); +} + + +void +NewPlaylistWidget::onTagChanged() +{ + m_tag = ui->tagEdit->text(); + + m_filterTimer.stop(); + m_filterTimer.setInterval( FILTER_TIMEOUT ); + m_filterTimer.setSingleShot( true ); + m_filterTimer.start(); +} + + +void +NewPlaylistWidget::updateSuggestions() +{ + QUrl url( QString( "http://ws.audioscrobbler.com/1.0/tag/%1/toptracks.xspf" ).arg( m_tag ) ); + + XSPFLoader* loader = new XSPFLoader( false ); + connect( loader, SIGNAL( ok( Tomahawk::playlist_ptr ) ), SLOT( suggestionsFound() ) ); + + loader->load( url ); +} + + +void +NewPlaylistWidget::suggestionsFound() +{ + XSPFLoader* loader = qobject_cast( sender() ); + + m_entries = loader->entries(); + + delete m_suggestionsModel; + m_suggestionsModel = new PlaylistModel( ui->suggestionsView ); + ui->suggestionsView->setModel( m_suggestionsModel ); + + QList ql; + foreach( const Tomahawk::plentry_ptr& entry, m_entries ) + { + m_suggestionsModel->append( entry->query() ); + ql.append( entry->query() ); + } + + loader->deleteLater(); +} + + +void +NewPlaylistWidget::savePlaylist() +{ + Tomahawk::playlist_ptr playlist; + + playlist = Tomahawk::Playlist::create( SourceList::instance()->getLocal(), uuid(), ui->titleEdit->text(), "", "", false ); + playlist->createNewRevision( uuid(), playlist->currentrevision(), m_entries ); + + PlaylistManager::instance()->show( playlist ); + cancel(); +} + + +void +NewPlaylistWidget::cancel() +{ + emit destroyed( this ); + deleteLater(); +} diff --git a/src/libtomahawk/widgets/newplaylistwidget.h b/src/libtomahawk/widgets/newplaylistwidget.h new file mode 100644 index 000000000..bb6c27629 --- /dev/null +++ b/src/libtomahawk/widgets/newplaylistwidget.h @@ -0,0 +1,85 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef NEWPLAYLISTWIDGET_H +#define NEWPLAYLISTWIDGET_H + +#include +#include + +#include "album.h" +#include "result.h" +#include "playlistinterface.h" +#include "viewpage.h" + +#include "dllmacro.h" + +class QPushButton; +class PlaylistModel; + +namespace Ui +{ + class NewPlaylistWidget; +} + +class DLLEXPORT NewPlaylistWidget : public QWidget, public Tomahawk::ViewPage +{ +Q_OBJECT + +public: + NewPlaylistWidget( QWidget* parent = 0 ); + ~NewPlaylistWidget(); + + virtual QWidget* widget() { return this; } + virtual PlaylistInterface* playlistInterface() const { return 0; } + + virtual QString title() const { return tr( "Create a new playlist" ); } + virtual QString description() const { return QString(); } + + virtual bool showStatsBar() const { return false; } + + virtual bool jumpToCurrentTrack() { return false; } + +protected: + void changeEvent( QEvent* e ); + +signals: + void destroyed( QWidget* widget ); + +private slots: + void onTitleChanged( const QString& title ); + void onTagChanged(); + + void updateSuggestions(); + void suggestionsFound(); + + void savePlaylist(); + void cancel(); + +private: + Ui::NewPlaylistWidget *ui; + + PlaylistModel* m_suggestionsModel; + QList< Tomahawk::plentry_ptr > m_entries; + + QTimer m_filterTimer; + QString m_tag; + QPushButton* m_saveButton; +}; + +#endif // NEWPLAYLISTWIDGET_H diff --git a/src/libtomahawk/widgets/newplaylistwidget.ui b/src/libtomahawk/widgets/newplaylistwidget.ui new file mode 100644 index 000000000..6a2701ee8 --- /dev/null +++ b/src/libtomahawk/widgets/newplaylistwidget.ui @@ -0,0 +1,94 @@ + + + NewPlaylistWidget + + + + 0 + 0 + 985 + 460 + + + + + + + + 13 + + + + Enter a title for the new playlist: + + + + + + + + 0 + 26 + + + + + + + + + 13 + + + + Tomahawk offers a variety of ways to help you create playlists and find music you enjoy! + + + true + + + + + + + + 13 + + + + Just enter a genre or tag name and Tomahawk will suggest a few songs to get you started with your new playlist: + + + + + + + + 0 + 26 + + + + + + + + + + + QDialogButtonBox::Cancel + + + + + + + + PlaylistView + QTreeView +
playlist/playlistview.h
+
+
+ + +
diff --git a/src/libtomahawk/widgets/overlaywidget.cpp b/src/libtomahawk/widgets/overlaywidget.cpp new file mode 100644 index 000000000..3f46f5e11 --- /dev/null +++ b/src/libtomahawk/widgets/overlaywidget.cpp @@ -0,0 +1,165 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "overlaywidget.h" + +#include +#include +#include + +#define CORNER_ROUNDNESS 16.0 +#define FADING_DURATION 500 +#define FONT_SIZE 16 +#define OPACITY 0.86 + + +OverlayWidget::OverlayWidget( QWidget* parent ) + : QWidget( parent ) // this is on purpose! + , m_opacity( 0.00 ) + , m_parent( parent ) +{ + resize( 380, 128 ); + setAttribute( Qt::WA_TranslucentBackground, true ); + + setOpacity( m_opacity ); + + m_timer.setSingleShot( true ); + connect( &m_timer, SIGNAL( timeout() ), this, SLOT( hide() ) ); + +#ifdef Q_WS_MAC + QFont f( font() ); + f.setPointSize( f.pointSize() - 2 ); + setFont( f ); +#endif +} + + +OverlayWidget::~OverlayWidget() +{ +} + + +void +OverlayWidget::setOpacity( qreal opacity ) +{ + m_opacity = opacity; + + if ( m_opacity == 0.00 && !isHidden() ) + { + QWidget::hide(); + } + else if ( m_opacity > 0.00 && isHidden() ) + { + QWidget::show(); + } + + repaint(); +} + + +void +OverlayWidget::setText( const QString& text ) +{ + m_text = text; +} + + +void +OverlayWidget::show( int timeoutSecs ) +{ + if ( !isEnabled() ) + return; + + QPropertyAnimation* animation = new QPropertyAnimation( this, "opacity" ); + animation->setDuration( FADING_DURATION ); + animation->setEndValue( OPACITY ); + animation->start(); + + if( timeoutSecs > 0 ) + m_timer.start( timeoutSecs * 1000 ); +} + + +void +OverlayWidget::hide() +{ + if ( !isEnabled() ) + return; + + QPropertyAnimation* animation = new QPropertyAnimation( this, "opacity" ); + animation->setDuration( FADING_DURATION ); + animation->setEndValue( 0.00 ); + animation->start(); +} + + +bool +OverlayWidget::shown() const +{ + if ( !isEnabled() ) + return false; + + return m_opacity == OPACITY; +} + + +void +OverlayWidget::paintEvent( QPaintEvent* event ) +{ + QPoint center( ( m_parent->width() - width() ) / 2, ( m_parent->height() - height() ) / 2 ); + move( center ); + + QPainter p( this ); + QRect r = contentsRect(); + + p.setBackgroundMode( Qt::TransparentMode ); + p.setRenderHint( QPainter::Antialiasing ); + p.setOpacity( m_opacity ); + + QPen pen( palette().dark().color(), .5 ); + p.setPen( pen ); + p.setBrush( palette().highlight() ); + + p.drawRoundedRect( r, CORNER_ROUNDNESS, CORNER_ROUNDNESS ); + + QTextOption to( Qt::AlignCenter ); + to.setWrapMode( QTextOption::WrapAtWordBoundaryOrAnywhere ); + + // shrink to fit if needed + QFont f( font() ); + f.setPointSize( FONT_SIZE ); + f.setBold( true ); + + QRectF textRect = r.adjusted( 8, 8, -8, -8 ); + qreal availHeight = textRect.height(); + + QFontMetricsF fm( f ); + qreal textHeight = fm.boundingRect( textRect, Qt::AlignCenter | Qt::TextWordWrap, text() ).height(); + while( textHeight > availHeight ) + { + if( f.pointSize() <= 4 ) // don't try harder + break; + + f.setPointSize( f.pointSize() - 1 ); + fm = QFontMetricsF( f ); + textHeight = fm.boundingRect( textRect, Qt::AlignCenter | Qt::TextWordWrap, text() ).height(); + } + p.setFont( f ); + p.setPen( palette().highlightedText().color() ); + p.drawText( r.adjusted( 8, 8, -8, -8 ), text(), to ); +} diff --git a/src/libtomahawk/widgets/overlaywidget.h b/src/libtomahawk/widgets/overlaywidget.h new file mode 100644 index 000000000..5775b991d --- /dev/null +++ b/src/libtomahawk/widgets/overlaywidget.h @@ -0,0 +1,61 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef OVERLAYWIDGET_H +#define OVERLAYWIDGET_H + +#include +#include + +#include "dllmacro.h" +#include + +class DLLEXPORT OverlayWidget : public QWidget +{ +Q_OBJECT +Q_PROPERTY( qreal opacity READ opacity WRITE setOpacity ) + +public: + OverlayWidget( QWidget* parent ); + ~OverlayWidget(); + + qreal opacity() const { return m_opacity; } + void setOpacity( qreal opacity ); + + QString text() const { return m_text; } + void setText( const QString& text ); + + bool shown() const; + +public slots: + void show( int timeoutSecs = 0 ); + void hide(); + +protected: +// void changeEvent( QEvent* e ); + void paintEvent( QPaintEvent* event ); + +private: + QString m_text; + qreal m_opacity; + + QWidget* m_parent; + QTimer m_timer; +}; + +#endif // OVERLAYWIDGET_H diff --git a/src/libtomahawk/widgets/welcomewidget.cpp b/src/libtomahawk/widgets/welcomewidget.cpp new file mode 100644 index 000000000..cc4460895 --- /dev/null +++ b/src/libtomahawk/widgets/welcomewidget.cpp @@ -0,0 +1,217 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "welcomewidget.h" +#include "ui_welcomewidget.h" + +#include "utils/tomahawkutils.h" + +#include "playlist/playlistmanager.h" +#include "playlist/playlistmodel.h" + +#include "widgets/overlaywidget.h" + +#include "sourcelist.h" +#include "tomahawksettings.h" + +#include + +#define FILTER_TIMEOUT 280 + + +WelcomeWidget::WelcomeWidget( QWidget* parent ) + : QWidget( parent ) + , ui( new Ui::WelcomeWidget ) +{ + ui->setupUi( this ); + + ui->playlistWidget->setItemDelegate( new PlaylistDelegate() ); + ui->playlistWidget->overlay()->resize( 380, 86 ); + ui->tracksView->overlay()->setEnabled( false ); + + m_tracksModel = new PlaylistModel( ui->tracksView ); + ui->tracksView->setModel( m_tracksModel ); + m_tracksModel->loadHistory( Tomahawk::source_ptr() ); + + connect( SourceList::instance(), SIGNAL( sourceAdded( Tomahawk::source_ptr ) ), SLOT( onSourceAdded( Tomahawk::source_ptr ) ) ); + + connect( ui->playlistWidget, SIGNAL( itemActivated( QListWidgetItem* ) ), SLOT( onPlaylistActivated( QListWidgetItem* ) ) ); +} + + +WelcomeWidget::~WelcomeWidget() +{ + delete ui; +} + + +void +WelcomeWidget::updatePlaylists() +{ + ui->playlistWidget->clear(); + + QList playlists = TomahawkSettings::instance()->recentlyPlayedPlaylists(); + + foreach( const Tomahawk::playlist_ptr& playlist, playlists ) + { + connect( playlist.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), SLOT( updatePlaylists() ) ); + + PlaylistWidgetItem* item = new PlaylistWidgetItem( playlist ); + ui->playlistWidget->addItem( item ); + item->setData( Qt::DisplayRole, playlist->title() ); + } + + if ( !playlists.count() ) + { + ui->playlistWidget->overlay()->setText( tr( "You have not played any playlists yet." ) ); + ui->playlistWidget->overlay()->show(); + } + else + ui->playlistWidget->overlay()->hide(); +} + + +void +WelcomeWidget::onSourceAdded( const Tomahawk::source_ptr& source ) +{ + connect( source->collection().data(), SIGNAL( playlistsAdded( QList ) ), SLOT( updatePlaylists() ) ); + connect( source->collection().data(), SIGNAL( playlistsDeleted( QList ) ), SLOT( updatePlaylists() ) ); + + connect( source.data(), SIGNAL( playbackFinished( Tomahawk::query_ptr ) ), SLOT( onPlaybackFinished( Tomahawk::query_ptr ) ) ); +} + + +void +WelcomeWidget::onPlaybackFinished( const Tomahawk::query_ptr& query ) +{ + m_tracksModel->insert( 0, query ); +} + + +void +WelcomeWidget::onPlaylistActivated( QListWidgetItem* item ) +{ + qDebug() << Q_FUNC_INFO; + + PlaylistWidgetItem* pwi = dynamic_cast(item); + if( Tomahawk::dynplaylist_ptr dynplaylist = pwi->playlist().dynamicCast< Tomahawk::DynamicPlaylist >() ) + PlaylistManager::instance()->show( dynplaylist ); + else + PlaylistManager::instance()->show( pwi->playlist() ); +} + + +void +WelcomeWidget::changeEvent( QEvent* e ) +{ + QWidget::changeEvent( e ); + switch ( e->type() ) + { + case QEvent::LanguageChange: + ui->retranslateUi( this ); + break; + + default: + break; + } +} + + +QVariant +PlaylistWidgetItem::data( int role ) const +{ + if ( role == ArtistRole ) + { + if ( m_artists.isEmpty() ) + { + QStringList artists; + + foreach( const Tomahawk::plentry_ptr& entry, m_playlist->entries() ) + { + if ( !artists.contains( entry->query()->artist() ) ) + artists << entry->query()->artist(); + } + + m_artists = artists.join( ", " ); + } + + return m_artists; + } + + if ( role == TrackCountRole ) + { + return m_playlist->entries().count(); + } + + if ( role == Qt::DisplayRole ) + { + return m_playlist->title(); + } + + return QListWidgetItem::data( role ); +} + + +QSize +PlaylistDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + return QSize( 0, 64 ); +} + + +void +PlaylistDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + QStyleOptionViewItemV4 opt = option; + initStyleOption( &opt, QModelIndex() ); + qApp->style()->drawControl( QStyle::CE_ItemViewItem, &opt, painter ); + + if ( option.state & QStyle::State_Selected ) + { + opt.palette.setColor( QPalette::Text, opt.palette.color( QPalette::HighlightedText ) ); + } + + painter->save(); + painter->setRenderHint( QPainter::Antialiasing ); + painter->setPen( opt.palette.color( QPalette::Text ) ); + + QTextOption to; + to.setAlignment( Qt::AlignCenter ); + QFont font = opt.font; + QFont boldFont = opt.font; + boldFont.setBold( true ); + + painter->drawPixmap( option.rect.adjusted( 10, 13, -option.rect.width() + 48, -13 ), m_playlistIcon ); + + painter->drawText( option.rect.adjusted( 56, 26, -100, -8 ), index.data( PlaylistWidgetItem::ArtistRole ).toString() ); + + QString trackCount = tr( "%1 tracks" ).arg( index.data( PlaylistWidgetItem::TrackCountRole ).toString() ); + painter->drawText( option.rect.adjusted( option.rect.width() - 96, 2, 0, -2 ), trackCount, to ); + + painter->setFont( boldFont ); + painter->drawText( option.rect.adjusted( 56, 6, -100, -option.rect.height() + 20 ), index.data().toString() ); + + painter->restore(); +} + + +PlaylistWidget::PlaylistWidget( QWidget* parent ) + : QListWidget( parent ) +{ + m_overlay = new OverlayWidget( this ); +} diff --git a/src/libtomahawk/widgets/welcomewidget.h b/src/libtomahawk/widgets/welcomewidget.h new file mode 100644 index 000000000..9ec4e249c --- /dev/null +++ b/src/libtomahawk/widgets/welcomewidget.h @@ -0,0 +1,133 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef WELCOMEWIDGET_H +#define WELCOMEWIDGET_H + +#include +#include +#include + +#include "playlistinterface.h" + +#include "playlist.h" +#include "result.h" +#include "viewpage.h" + +#include "utils/tomahawkutils.h" + +#include "dllmacro.h" + +class PlaylistModel; +class OverlayWidget; + +namespace Ui +{ + class WelcomeWidget; +} + +class DLLEXPORT PlaylistDelegate : public QStyledItemDelegate +{ +Q_OBJECT + +public: + PlaylistDelegate() + { + m_playlistIcon = QPixmap( RESPATH "images/playlist-icon.png" ); + } + +protected: + void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; + QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const; + +private: + QPixmap m_playlistIcon; +}; + + +class DLLEXPORT PlaylistWidgetItem : public QListWidgetItem +{ +public: + enum ItemRoles + { ArtistRole = Qt::UserRole, TrackCountRole }; + + PlaylistWidgetItem( const Tomahawk::playlist_ptr& playlist ) : QListWidgetItem() { m_playlist = playlist; } + ~PlaylistWidgetItem() {} + + virtual QVariant data( int role ) const; + + Tomahawk::playlist_ptr playlist() const { return m_playlist; } + +private: + Tomahawk::playlist_ptr m_playlist; + + mutable QString m_artists; +}; + + +class DLLEXPORT PlaylistWidget : public QListWidget +{ +public: + PlaylistWidget( QWidget* parent = 0 ); + + OverlayWidget* overlay() const { return m_overlay; } + +private: + OverlayWidget* m_overlay; +}; + + +class DLLEXPORT WelcomeWidget : public QWidget, public Tomahawk::ViewPage +{ +Q_OBJECT + +public: + WelcomeWidget( QWidget* parent = 0 ); + ~WelcomeWidget(); + + virtual QWidget* widget() { return this; } + virtual PlaylistInterface* playlistInterface() const { return 0; } + + virtual QString title() const { return tr( "Welcome to Tomahawk" ); } + virtual QString description() const { return QString(); } + + virtual bool showStatsBar() const { return false; } + + virtual bool jumpToCurrentTrack() { return false; } + +protected: + void changeEvent( QEvent* e ); + +signals: + void destroyed( QWidget* widget ); + +public slots: + void updatePlaylists(); + +private slots: + void onSourceAdded( const Tomahawk::source_ptr& source ); + void onPlaylistActivated( QListWidgetItem* item ); + void onPlaybackFinished( const Tomahawk::query_ptr& query ); + +private: + Ui::WelcomeWidget *ui; + + PlaylistModel* m_tracksModel; +}; + +#endif // WELCOMEWIDGET_H diff --git a/src/libtomahawk/widgets/welcomewidget.ui b/src/libtomahawk/widgets/welcomewidget.ui new file mode 100644 index 000000000..fd734da7c --- /dev/null +++ b/src/libtomahawk/widgets/welcomewidget.ui @@ -0,0 +1,60 @@ + + + WelcomeWidget + + + + 0 + 0 + 985 + 460 + + + + + + + + 14 + + + + Recently played playlists: + + + + + + + + + + + 14 + + + + Recently played tracks: + + + + + + + + + + + PlaylistView + QTreeView +
playlist/playlistview.h
+
+ + PlaylistWidget + QListWidget +
widgets/welcomewidget.h
+
+
+ + +
diff --git a/src/mac/macshortcuthandler.cpp b/src/mac/macshortcuthandler.cpp new file mode 100644 index 000000000..34701f7f1 --- /dev/null +++ b/src/mac/macshortcuthandler.cpp @@ -0,0 +1,49 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "macshortcuthandler.h" + +#include +#include + +using namespace Tomahawk; + +MacShortcutHandler::MacShortcutHandler(QObject *parent) : + Tomahawk::ShortcutHandler(parent) +{ + +} + +void +MacShortcutHandler::macMediaKeyPressed( int key ) +{ + switch (key) { + case NX_KEYTYPE_PLAY: + qDebug() << "emitting PlayPause pressed"; + emit playPause(); + break; + case NX_KEYTYPE_FAST: + qDebug() << "emitting next pressed"; + emit next(); + break; + case NX_KEYTYPE_REWIND: + qDebug() << "emitting prev pressed"; + emit previous(); + break; + } +} diff --git a/src/mac/macshortcuthandler.h b/src/mac/macshortcuthandler.h new file mode 100644 index 000000000..b7739c20d --- /dev/null +++ b/src/mac/macshortcuthandler.h @@ -0,0 +1,40 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef MACSHORTCUTHANDLER_H +#define MACSHORTCUTHANDLER_H + +#include "shortcuthandler.h" + +#include + +namespace Tomahawk { + + +class MacShortcutHandler : public ShortcutHandler +{ + Q_OBJECT +public: + explicit MacShortcutHandler(QObject *parent = 0); + + void macMediaKeyPressed( int key ); +}; + +} + +#endif // MACSHORTCUTHANDLER_H diff --git a/src/mac/tomahawkapp_mac.h b/src/mac/tomahawkapp_mac.h new file mode 100644 index 000000000..86ae6702c --- /dev/null +++ b/src/mac/tomahawkapp_mac.h @@ -0,0 +1,49 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TOMAHAWKAPP_MAC_H +#define TOMAHAWKAPP_MAC_H + +// this file and tomahawk_app.mm copied and inspired by mac_startup.* in clementine player, +// copyright David Sansome 2010 + +class QString; + +namespace Tomahawk { + +class MacShortcutHandler; + +/// Interface between cocoa and tomahawk +class PlatformInterface { + public: + // Called when the application should show itself. + virtual void activate() = 0; + virtual bool loadUrl( const QString& url ) = 0; + + virtual ~PlatformInterface() {} +}; + +void macMain(); +void setShortcutHandler(Tomahawk::MacShortcutHandler* engine); +// used for opening files with tomahawk +void setApplicationHandler(PlatformInterface* handler); +void checkForUpdates(); + +}; + +#endif diff --git a/src/mac/tomahawkapp_mac.mm b/src/mac/tomahawkapp_mac.mm new file mode 100644 index 000000000..44660e742 --- /dev/null +++ b/src/mac/tomahawkapp_mac.mm @@ -0,0 +1,190 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "tomahawkapp_mac.h" +#include "tomahawkapp_macdelegate.h" +#include "macshortcuthandler.h" +#include + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#ifdef HAVE_SPARKLE +#import +#endif + +// Capture global media keys on Mac (Cocoa only!) +// See: http://www.rogueamoeba.com/utm/2007/09/29/apple-keyboard-media-key-event-handling/ + +@interface MacApplication :NSApplication { + Tomahawk::MacShortcutHandler* shortcut_handler_; + Tomahawk::PlatformInterface* application_handler_; +} + +- (Tomahawk::MacShortcutHandler*) shortcutHandler; +- (void) setShortcutHandler: (Tomahawk::MacShortcutHandler*)handler; + +- (Tomahawk::PlatformInterface*) application_handler; +- (void) setApplicationHandler: (Tomahawk::PlatformInterface*)handler; +- (void) mediaKeyEvent: (int)key state: (BOOL)state repeat: (BOOL)repeat; +@end + + +@implementation AppDelegate + +- (id) init { + if ((self = [super init])) { + application_handler_ = nil; +// dock_menu_ = nil; + } + return self; +} + +- (id) initWithHandler: (Tomahawk::PlatformInterface*)handler { + application_handler_ = handler; + return self; +} + +- (BOOL) applicationShouldHandleReopen: (NSApplication*)app hasVisibleWindows:(BOOL)flag { + if (application_handler_) { + application_handler_->activate(); + } + return YES; +} +/* +- (void) setDockMenu: (NSMenu*)menu { + dock_menu_ = menu; +} + +- (NSMenu*) applicationDockMenu: (NSApplication*)sender { + return dock_menu_; +} +*/ +- (BOOL) application: (NSApplication*)app openFile:(NSString*)filename { + qDebug() << "Wants to open:" << [filename UTF8String]; + + if (application_handler_->loadUrl(QString::fromUtf8([filename UTF8String]))) { + return YES; + } + + return NO; +} +@end + +@implementation MacApplication + +- (id) init { + if ((self = [super init])) { + [self setShortcutHandler:nil]; + [self setApplicationHandler:nil]; + + NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager]; + [em + setEventHandler:self + andSelector:@selector(getUrl:withReplyEvent:) + forEventClass:kInternetEventClass + andEventID:kAEGetURL]; + [em + setEventHandler:self + andSelector:@selector(getUrl:withReplyEvent:) + forEventClass:'WWW!' + andEventID:'OURL']; + NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; + OSStatus httpResult = LSSetDefaultHandlerForURLScheme((CFStringRef)@"tomahawk", (CFStringRef)bundleID); + + //TODO: Check httpResult and httpsResult for errors + } + return self; +} + +- (Tomahawk::MacShortcutHandler*) shortcutHandler { + return shortcut_handler_; +} + +- (void) setShortcutHandler: (Tomahawk::MacShortcutHandler*)handler { + qDebug() << "Setting shortcut handler of MacAPp"; + shortcut_handler_ = handler; +} + +- (Tomahawk::PlatformInterface*) application_handler { + return application_handler_; +} + +- (void) setApplicationHandler: (Tomahawk::PlatformInterface*)handler { + AppDelegate* delegate = [[AppDelegate alloc] initWithHandler:handler]; + [self setDelegate:delegate]; +} + +-(void) sendEvent: (NSEvent*)event { + if ([event type] == NSSystemDefined && [event subtype] == 8) { + int keycode = (([event data1] & 0xFFFF0000) >> 16); + int keyflags = ([event data1] & 0x0000FFFF); + int keystate = (((keyflags & 0xFF00) >> 8)) == 0xA; + int keyrepeat = (keyflags & 0x1); + + [self mediaKeyEvent: keycode state: keystate repeat: keyrepeat]; + } + + [super sendEvent: event]; +} + +-(void) mediaKeyEvent: (int)key state: (BOOL)state repeat: (BOOL)repeat { + if (!shortcut_handler_) { + return; + } + if (state == 0) { + shortcut_handler_->macMediaKeyPressed(key); + } +} + +@end + +void Tomahawk::macMain() { + [[NSAutoreleasePool alloc] init]; + // Creates and sets the magic global variable so QApplication will find it. + [MacApplication sharedApplication]; +#ifdef HAVE_SPARKLE + // Creates and sets the magic global variable for Sparkle. + [[SUUpdater sharedUpdater] setDelegate: NSApp]; +#endif +} + + +void Tomahawk::setShortcutHandler(Tomahawk::MacShortcutHandler* handler) { + [NSApp setShortcutHandler: handler]; +} + +void Tomahawk::setApplicationHandler(Tomahawk::PlatformInterface* handler) { + [NSApp setApplicationHandler: handler]; +} + +void Tomahawk::checkForUpdates() { +#ifdef HAVE_SPARKLE + [[SUUpdater sharedUpdater] checkForUpdates: NSApp]; +#endif +} diff --git a/src/main.cpp b/src/main.cpp index 12d2361b5..916ffbb2e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,79 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "tomahawk/tomahawkapp.h" -int main( int argc, char *argv[] ) +#ifdef Q_WS_MAC + #include "tomahawkapp_mac.h" + #include + static pascal OSErr appleEventHandler( const AppleEvent*, AppleEvent*, long ); +#endif + +#include +int +main( int argc, char *argv[] ) { - TomahawkApp a( argc, argv ); - return a.exec(); +#ifdef Q_WS_MAC + // Do Mac specific startup to get media keys working. + // This must go before QApplication initialisation. + Tomahawk::macMain(); + + // used for url handler + AEEventHandlerUPP h = AEEventHandlerUPP( appleEventHandler ); + AEInstallEventHandler( 'GURL', 'GURL', h, 0, false ); + +#endif + try + { + TomahawkApp a( argc, argv ); + return a.exec(); + } + catch( const std::runtime_error& e ) + { + return 0; + } } + +#ifdef Q_WS_MAC +static pascal OSErr +appleEventHandler( const AppleEvent* e, AppleEvent*, long ) +{ + OSType id = typeWildCard; + AEGetAttributePtr( e, keyEventIDAttr, typeType, 0, &id, sizeof( id ), 0 ); + + switch ( id ) + { + case 'GURL': + { + DescType type; + Size size; + + char buf[1024]; + AEGetParamPtr( e, keyDirectObject, typeChar, &type, &buf, 1023, &size ); + buf[size] = '\0'; + + QString url = QString::fromUtf8( buf ); + static_cast(qApp)->loadUrl( url ); + return noErr; + } + + default: + return unimpErr; + } +} +#endif diff --git a/src/musicscanner.cpp b/src/musicscanner.cpp index 2769968a0..220baa1a7 100644 --- a/src/musicscanner.cpp +++ b/src/musicscanner.cpp @@ -1,39 +1,116 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "musicscanner.h" #include "tomahawk/tomahawkapp.h" -#include "database.h" -#include "databasecommand_dirmtimes.h" -#include "databasecommand_addfiles.h" +#include "sourcelist.h" +#include "database/database.h" +#include "database/databasecommand_dirmtimes.h" +#include "database/databasecommand_addfiles.h" +#include "database/databasecommand_deletefiles.h" using namespace Tomahawk; -MusicScanner::MusicScanner( const QString& dir, quint32 bs ) - : QThread() - , m_dir( dir ) - , m_batchsize( bs ) +void +DirLister::go() { - moveToThread( this ); - - m_ext2mime.insert( "mp3", "audio/mpeg" ); - - // m_ext2mime.insert( "aac", "audio/mp4" ); - // m_ext2mime.insert( "m4a", "audio/mp4" ); - // m_ext2mime.insert( "mp4", "audio/mp4" ); - // m_ext2mime.insert( "flac", "audio/flac" ); - -#ifndef NO_OGG - // not compiled on windows yet - m_ext2mime.insert( "ogg", "application/ogg" ); -#endif + scanDir( m_dir, 0 ); + emit finished( m_newdirmtimes ); } void -MusicScanner::run() +DirLister::scanDir( QDir dir, int depth ) { - QTimer::singleShot( 0, this, SLOT( startScan() ) ); - exec(); + QFileInfoList dirs; + const uint mtime = QFileInfo( dir.absolutePath() ).lastModified().toUTC().toTime_t(); + m_newdirmtimes.insert( dir.absolutePath(), mtime ); + + if ( m_dirmtimes.contains( dir.absolutePath() ) && mtime == m_dirmtimes.value( dir.absolutePath() ) ) + { + // dont scan this dir, unchanged since last time. + } + else + { + if ( m_dirmtimes.contains( dir.absolutePath() ) ) + Database::instance()->enqueue( QSharedPointer( new DatabaseCommand_DeleteFiles( dir, SourceList::instance()->getLocal() ) ) ); + + dir.setFilter( QDir::Files | QDir::Readable | QDir::NoDotAndDotDot ); + dir.setSorting( QDir::Name ); + dirs = dir.entryInfoList(); + foreach( const QFileInfo& di, dirs ) + { + emit fileToScan( di ); + } + } + dir.setFilter( QDir::Dirs | QDir::Readable | QDir::NoDotAndDotDot ); + dirs = dir.entryInfoList(); + + foreach( const QFileInfo& di, dirs ) + { + scanDir( di.absoluteFilePath(), depth + 1 ); + } +} + + +MusicScanner::MusicScanner( const QString& dir, quint32 bs ) + : QObject() + , m_dir( dir ) + , m_batchsize( bs ) + , m_dirLister( 0 ) + , m_dirListerThreadController( 0 ) +{ + m_ext2mime.insert( "mp3", TomahawkUtils::extensionToMimetype( "mp3" ) ); + +#ifndef NO_OGG + m_ext2mime.insert( "ogg", TomahawkUtils::extensionToMimetype( "ogg" ) ); +#endif +#ifndef NO_FLAC + m_ext2mime.insert( "flac", TomahawkUtils::extensionToMimetype( "flac" ) ); +#endif +} + + +MusicScanner::~MusicScanner() +{ + qDebug() << Q_FUNC_INFO; + + if( m_dirListerThreadController ) + { + m_dirListerThreadController->quit(); + + while( !m_dirListerThreadController->isFinished() ) + { + QCoreApplication::processEvents( QEventLoop::AllEvents, 200 ); + TomahawkUtils::Sleep::msleep( 100 ); + } + + if( m_dirLister ) + { + delete m_dirLister; + m_dirLister = 0; + } + + delete m_dirListerThreadController; + m_dirListerThreadController = 0; + } } @@ -46,12 +123,12 @@ MusicScanner::startScan() // trigger the scan once we've loaded old mtimes for dirs below our path DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( m_dir ); - connect( cmd, SIGNAL( done( const QMap& ) ), - SLOT( setMtimes( const QMap& ) ), Qt::DirectConnection ); - connect( cmd, SIGNAL( done( const QMap& ) ), - SLOT( scan() ), Qt::DirectConnection ); + connect( cmd, SIGNAL( done( QMap ) ), + SLOT( setMtimes( QMap ) ) ); + connect( cmd, SIGNAL( done( QMap ) ), + SLOT( scan() ) ); - TomahawkApp::instance()->database()->enqueue( QSharedPointer(cmd) ); + Database::instance()->enqueue( QSharedPointer(cmd) ); } @@ -65,24 +142,24 @@ MusicScanner::setMtimes( const QMap& m ) void MusicScanner::scan() { - TomahawkApp::instance()->sourcelist().getLocal()->scanningProgress( 0 ); qDebug() << "Scanning, num saved mtimes from last scan:" << m_dirmtimes.size(); connect( this, SIGNAL( batchReady( QVariantList ) ), SLOT( commitBatch( QVariantList ) ), Qt::DirectConnection ); - DirLister* lister = new DirLister( QDir( m_dir, 0 ), m_dirmtimes ); + m_dirListerThreadController = new QThread( this ); + m_dirLister = new DirLister( QDir( m_dir, 0 ), m_dirmtimes ); + m_dirLister->moveToThread( m_dirListerThreadController ); - connect( lister, SIGNAL( fileToScan( QFileInfo ) ), - SLOT( scanFile( QFileInfo ) ), Qt::QueuedConnection ); + connect( m_dirLister, SIGNAL( fileToScan( QFileInfo ) ), + SLOT( scanFile( QFileInfo ) ), Qt::QueuedConnection ); // queued, so will only fire after all dirs have been scanned: - connect( lister, SIGNAL( finished( const QMap& ) ), - SLOT( listerFinished( const QMap& ) ), Qt::QueuedConnection ); - - connect( lister, SIGNAL( finished() ), lister, SLOT( deleteLater() ) ); - - lister->start(); + connect( m_dirLister, SIGNAL( finished( QMap ) ), + SLOT( listerFinished( QMap ) ), Qt::QueuedConnection ); + + m_dirListerThreadController->start(); + QMetaObject::invokeMethod( m_dirLister, "go" ); } @@ -90,20 +167,31 @@ void MusicScanner::listerFinished( const QMap& newmtimes ) { qDebug() << Q_FUNC_INFO; + // any remaining stuff that wasnt emitted as a batch: if( m_scannedfiles.length() ) { - TomahawkApp::instance()->sourcelist().getLocal()->scanningProgress( m_scanned ); + SourceList::instance()->getLocal()->scanningFinished( m_scanned ); commitBatch( m_scannedfiles ); } + // remove obsolete / stale files + foreach ( const QString& path, m_dirmtimes.keys() ) + { + if ( !newmtimes.keys().contains( path ) ) + { + qDebug() << "Removing stale dir:" << path; + Database::instance()->enqueue( QSharedPointer( new DatabaseCommand_DeleteFiles( path, SourceList::instance()->getLocal() ) ) ); + } + } + // save mtimes, then quit thread DatabaseCommand_DirMtimes* cmd = new DatabaseCommand_DirMtimes( newmtimes ); - connect( cmd, SIGNAL( finished() ), SLOT( quit() ) ); - TomahawkApp::instance()->database()->enqueue( QSharedPointer(cmd) ); + connect( cmd, SIGNAL( finished() ), SLOT( deleteLister() ) ); + Database::instance()->enqueue( QSharedPointer(cmd) ); qDebug() << "Scanning complete, saving to database. " - "(scanned" << m_scanned << "skipped" << m_skipped << ")"; + "( scanned" << m_scanned << "skipped" << m_skipped << ")"; qDebug() << "Skipped the following files (no tags / no valid audio):"; foreach( const QString& s, m_skippedFiles ) @@ -111,14 +199,43 @@ MusicScanner::listerFinished( const QMap& newmtimes ) } +void +MusicScanner::deleteLister() +{ + qDebug() << Q_FUNC_INFO; + connect( m_dirListerThreadController, SIGNAL( finished() ), SLOT( listerQuit() ) ); + m_dirListerThreadController->quit(); +} + + +void +MusicScanner::listerQuit() +{ + qDebug() << Q_FUNC_INFO; + connect( m_dirLister, SIGNAL( destroyed( QObject* ) ), SLOT( listerDestroyed( QObject* ) ) ); + delete m_dirLister; + m_dirLister = 0; +} + + +void +MusicScanner::listerDestroyed( QObject* dirLister ) +{ + qDebug() << Q_FUNC_INFO; + m_dirListerThreadController->deleteLater(); + m_dirListerThreadController = 0; + emit finished(); +} + + void MusicScanner::commitBatch( const QVariantList& tracks ) { if ( tracks.length() ) { qDebug() << Q_FUNC_INFO << tracks.length(); - source_ptr localsrc = TomahawkApp::instance()->sourcelist().getLocal(); - TomahawkApp::instance()->database()->enqueue( + source_ptr localsrc = SourceList::instance()->getLocal(); + Database::instance()->enqueue( QSharedPointer( new DatabaseCommand_AddFiles( tracks, localsrc ) ) ); } @@ -129,12 +246,11 @@ void MusicScanner::scanFile( const QFileInfo& fi ) { QVariant m = readFile( fi ); - if( m.toMap().isEmpty() ) + if ( m.toMap().isEmpty() ) return; m_scannedfiles << m; - if( m_batchsize != 0 && - (quint32)m_scannedfiles.length() >= m_batchsize ) + if ( m_batchsize != 0 && (quint32)m_scannedfiles.length() >= m_batchsize ) { qDebug() << "batchReady, size:" << m_scannedfiles.length(); emit batchReady( m_scannedfiles ); @@ -146,18 +262,28 @@ MusicScanner::scanFile( const QFileInfo& fi ) QVariant MusicScanner::readFile( const QFileInfo& fi ) { - if ( ! m_ext2mime.contains( fi.suffix().toLower() ) ) + const QString suffix = fi.suffix().toLower(); + + if ( ! m_ext2mime.contains( suffix ) ) { m_skipped++; return QVariantMap(); // invalid extension } - if( m_scanned % 3 == 0 ) - TomahawkApp::instance()->sourcelist().getLocal()->scanningProgress( m_scanned ); + if ( m_scanned ) + if( m_scanned % 3 == 0 ) + SourceList::instance()->getLocal()->scanningProgress( m_scanned ); if( m_scanned % 100 == 0 ) qDebug() << "SCAN" << m_scanned << fi.absoluteFilePath(); - TagLib::FileRef f( fi.absoluteFilePath().toUtf8().constData() ); + #ifdef COMPLEX_TAGLIB_FILENAME + const wchar_t *encodedName = reinterpret_cast< const wchar_t * >( fi.absoluteFilePath().utf16() ); + #else + QByteArray fileName = QFile::encodeName( fi.absoluteFilePath() ); + const char *encodedName = fileName.constData(); + #endif + + TagLib::FileRef f( encodedName ); if ( f.isNull() || !f.tag() ) { // qDebug() << "Doesn't seem to be a valid audiofile:" << fi.absoluteFilePath(); @@ -169,17 +295,16 @@ MusicScanner::readFile( const QFileInfo& fi ) int bitrate = 0; int duration = 0; TagLib::Tag *tag = f.tag(); - if ( f.audioProperties() ) { TagLib::AudioProperties *properties = f.audioProperties(); duration = properties->length(); bitrate = properties->bitrate(); } + QString artist = TStringToQString( tag->artist() ).trimmed(); QString album = TStringToQString( tag->album() ).trimmed(); QString track = TStringToQString( tag->title() ).trimmed(); - if ( artist.isEmpty() || track.isEmpty() ) { // FIXME: do some clever filename guessing @@ -189,14 +314,13 @@ MusicScanner::readFile( const QFileInfo& fi ) return QVariantMap(); } - QString mimetype = m_ext2mime.value( fi.suffix().toLower() ); + QString mimetype = m_ext2mime.value( suffix ); QString url( "file://%1" ); QVariantMap m; m["url"] = url.arg( fi.absoluteFilePath() ); - m["lastmodified"] = fi.lastModified().toUTC().toTime_t(); + m["mtime"] = fi.lastModified().toUTC().toTime_t(); m["size"] = (unsigned int)fi.size(); - m["hash"] = ""; // TODO m["mimetype"] = mimetype; m["duration"] = duration; m["bitrate"] = bitrate; @@ -204,7 +328,9 @@ MusicScanner::readFile( const QFileInfo& fi ) m["album"] = album; m["track"] = track; m["albumpos"] = tag->track(); - + m["year"] = tag->year(); + m["hash"] = ""; // TODO + m_scanned++; return m; } diff --git a/src/musicscanner.h b/src/musicscanner.h index 366c1fb5f..8fbca2739 100644 --- a/src/musicscanner.h +++ b/src/musicscanner.h @@ -1,30 +1,80 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef MUSICSCANNER_H #define MUSICSCANNER_H -#include -#include +/* taglib */ +#include +#include #include -#include #include #include #include #include #include +#include -class MusicScanner : public QThread +// descend dir tree comparing dir mtimes to last known mtime +// emit signal for any dir with new content, so we can scan it. +// finally, emit the list of new mtimes we observed. +class DirLister : public QObject +{ +Q_OBJECT + +public: + DirLister( QDir d, QMap& mtimes ) + : QObject(), m_dir( d ), m_dirmtimes( mtimes ) + { + qDebug() << Q_FUNC_INFO; + } + + ~DirLister() + { + qDebug() << Q_FUNC_INFO; + } + +signals: + void fileToScan( QFileInfo ); + void finished( const QMap& ); + +private slots: + void go(); + void scanDir( QDir dir, int depth ); + +private: + QDir m_dir; + QMap m_dirmtimes; + QMap m_newdirmtimes; +}; + +class MusicScanner : public QObject { Q_OBJECT public: MusicScanner( const QString& dir, quint32 bs = 0 ); - -protected: - void run(); + ~MusicScanner(); signals: //void fileScanned( QVariantMap ); - void finished( int, int ); + void finished(); void batchReady( const QVariantList& ); private: @@ -32,6 +82,9 @@ private: private slots: void listerFinished( const QMap& newmtimes ); + void deleteLister(); + void listerQuit(); + void listerDestroyed( QObject* dirLister ); void scanFile( const QFileInfo& fi ); void startScan(); void scan(); @@ -40,7 +93,7 @@ private slots: private: QString m_dir; - QMap m_ext2mime; // eg: mp3 -> audio/mpeg + QMap m_ext2mime; // eg: mp3 -> audio/mpeg unsigned int m_scanned; unsigned int m_skipped; @@ -51,83 +104,9 @@ private: QList m_scannedfiles; quint32 m_batchsize; -}; - -#include - -// descend dir tree comparing dir mtimes to last known mtime -// emit signal for any dir with new content, so we can scan it. -// finally, emit the list of new mtimes we observed. -class DirLister : public QThread -{ - Q_OBJECT -public: - DirLister( QDir d, QMap& mtimes ) - : QThread(), m_dir( d ), m_dirmtimes( mtimes ) - { - qDebug() << Q_FUNC_INFO; - moveToThread(this); - } - - ~DirLister() - { - qDebug() << Q_FUNC_INFO; - } - -protected: - void run() - { - QTimer::singleShot(0,this,SLOT(go())); - exec(); - } - -signals: - void fileToScan( QFileInfo ); - void finished( const QMap& ); - -private slots: - void go() - { - scanDir( m_dir, 0 ); - emit finished( m_newdirmtimes ); - } - - void scanDir( QDir dir, int depth ) - { - QFileInfoList dirs; - const uint mtime = QFileInfo( dir.absolutePath() ).lastModified().toUTC().toTime_t(); - m_newdirmtimes.insert( dir.absolutePath(), mtime ); - - if ( m_dirmtimes.contains( dir.absolutePath() ) && - mtime == m_dirmtimes.value( dir.absolutePath() ) - ) - { - // dont scan this dir, unchanged since last time. - } - else - { - dir.setFilter( QDir::Files | QDir::Readable | QDir::NoDotAndDotDot ); - dir.setSorting( QDir::Name ); - dirs = dir.entryInfoList(); - foreach( QFileInfo di, dirs ) - { - emit fileToScan( di ); - } - } - dir.setFilter( QDir::Dirs | QDir::Readable | QDir::NoDotAndDotDot ); - dirs = dir.entryInfoList(); - - foreach( QFileInfo di, dirs ) - { - scanDir( di.absoluteFilePath(), depth + 1 ); - } - } - -private: - QDir m_dir; - QMap m_dirmtimes; - QMap m_newdirmtimes; + DirLister* m_dirLister; + QThread* m_dirListerThreadController; }; #endif diff --git a/src/network/controlconnection.h b/src/network/controlconnection.h deleted file mode 100644 index 2280cbce2..000000000 --- a/src/network/controlconnection.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - One ControlConnection always remains open to each peer. - - They arrange connections/reverse connections, inform us - when the peer goes offline, and own+setup DBSyncConnections. - -*/ -#ifndef CONTROLCONNECTION_H -#define CONTROLCONNECTION_H - -#include "connection.h" -#include "servent.h" -#include "tomahawk/source.h" -#include "tomahawk/typedefs.h" - -class FileTransferSession; - -class ControlConnection : public Connection -{ -Q_OBJECT - -public: - explicit ControlConnection( Servent* parent = 0 ); - ~ControlConnection(); - Connection* clone(); - - DBSyncConnection* dbSyncConnection(); - -protected: - virtual void setup(); - -protected slots: - virtual void handleMsg( msg_ptr msg ); - -signals: - -private slots: - void dbSyncConnFinished( QObject* c ); - void registerSource(); - -private: - void setupDbSyncConnection( bool ondemand = false ); - - Tomahawk::source_ptr m_source; - DBSyncConnection* m_dbsyncconn; - - QString m_dbconnkey; - bool m_registered; -}; - -#endif // CONTROLCONNECTION_H diff --git a/src/network/dbsyncconnection.cpp b/src/network/dbsyncconnection.cpp deleted file mode 100644 index 0cf155c44..000000000 --- a/src/network/dbsyncconnection.cpp +++ /dev/null @@ -1,305 +0,0 @@ -/* - Database syncing using the oplog table. - ======================================= - Load the last GUID we applied for the peer, tell them it. - In return, they send us all new ops since that guid. - - We then apply those new ops to our cache of their data - - Synced. - -*/ - -#include "dbsyncconnection.h" - -#include - -#include "tomahawk/tomahawkapp.h" -#include "tomahawk/source.h" - -#include "database.h" -#include "databasecommand.h" -#include "databasecommand_collectionstats.h" -#include "databasecommand_loadops.h" -#include "remotecollection.h" - -// close the dbsync connection after this much inactivity. -// it's automatically reestablished as needed. -#define IDLE_TIMEOUT 60000 - -using namespace Tomahawk; - - -DBSyncConnection::DBSyncConnection( Servent* s, source_ptr src ) - : Connection( s ) - , m_source( src ) - , m_state( UNKNOWN ) -{ - qDebug() << Q_FUNC_INFO << thread(); - connect( this, SIGNAL( stateChanged( DBSyncConnection::State, DBSyncConnection::State, QString ) ), - m_source.data(), SIGNAL( loadingStateChanged(DBSyncConnection::State, DBSyncConnection::State, QString ) ) - ); - - m_timer.setInterval( IDLE_TIMEOUT ); - connect( &m_timer, SIGNAL( timeout() ), SLOT( idleTimeout() ) ); - - this->setMsgProcessorModeIn( MsgProcessor::PARSE_JSON | MsgProcessor::UNCOMPRESS_ALL ); - - // msgs are stored compressed in the db, so not typically needed here, but doesnt hurt: - this->setMsgProcessorModeOut( MsgProcessor::COMPRESS_IF_LARGE ); -} - - -DBSyncConnection::~DBSyncConnection() -{ - qDebug() << "DTOR" << Q_FUNC_INFO; -} - - -void -DBSyncConnection::idleTimeout() -{ - qDebug() << Q_FUNC_INFO << "*************"; - shutdown(true); -} - - -void -DBSyncConnection::changeState( State newstate ) -{ - State s = m_state; - m_state = newstate; - qDebug() << "DBSYNC State changed from" << s << "to" << newstate; - emit stateChanged( newstate, s, "" ); - - if( newstate == SYNCED ) - { - qDebug() << "Synced :)"; - } -} - - -void -DBSyncConnection::setup() -{ - qDebug() << Q_FUNC_INFO; - setId( QString("DBSyncConnection/%1").arg(socket()->peerAddress().toString()) ); - check(); -} - - -void -DBSyncConnection::trigger() -{ - qDebug() << Q_FUNC_INFO; - - // if we're still setting up the connection, do nothing - we sync on first connect anyway: - if( !this->isRunning() ) return; - - QMetaObject::invokeMethod( this, "sendMsg", Qt::QueuedConnection, - Q_ARG( msg_ptr, - Msg::factory( "{\"method\":\"trigger\"}", Msg::JSON ) ) - ); -} - - -void -DBSyncConnection::check() -{ - qDebug() << Q_FUNC_INFO; - if( m_state != UNKNOWN && m_state != SYNCED ) - { - qDebug() << "Syncing in progress already."; - return; - } - m_uscache.clear(); - m_themcache.clear(); - m_us.clear(); - - changeState(CHECKING); - - // load last-modified etc data for our collection and theirs from our DB: - DatabaseCommand_CollectionStats * cmd_us = - new DatabaseCommand_CollectionStats( APP->sourcelist().getLocal() ); - - DatabaseCommand_CollectionStats * cmd_them = - new DatabaseCommand_CollectionStats( m_source ); - - connect( cmd_us, SIGNAL( done(const QVariantMap&) ), - this, SLOT( gotUs(const QVariantMap&) ) ); - - connect( cmd_them, SIGNAL( done(const QVariantMap&) ), - this, SLOT( gotThemCache(const QVariantMap&) ) ); - - - APP->database()->enqueue( QSharedPointer(cmd_us) ); - - APP->database()->enqueue( QSharedPointer(cmd_them) ); - - // restarts idle countdown - m_timer.start(); -} - - -/// Called once we've loaded our mtimes etc from the DB for our local -/// collection - send them to the remote peer to compare. -void -DBSyncConnection::gotUs( const QVariantMap& m ) -{ - m_us = m; - if( !m_uscache.empty() ) sendOps(); -} - - -/// Called once we've loaded our cached data about their collection -void -DBSyncConnection::gotThemCache( const QVariantMap& m ) -{ - m_themcache = m; - qDebug() << "Sending a FETCHOPS cmd since:" << m.value("lastop").toString(); - changeState(FETCHING); - QVariantMap msg; - msg.insert( "method", "fetchops" ); - msg.insert( "lastop", m_themcache.value("lastop").toString() ); - sendMsg( msg ); -} - - -void -DBSyncConnection::handleMsg( msg_ptr msg ) -{ - Q_ASSERT( !msg->is( Msg::COMPRESSED ) ); - - if( m_state == FETCHING ) changeState(PARSING); - - // "everything is synced" indicated by non-json msg containing "ok": - if( !msg->is( Msg::JSON ) && - msg->is( Msg::DBOP ) && - msg->payload() == "ok" ) - { - qDebug() << "No ops to apply, we are synced."; - changeState(SYNCED); - // calc the collection stats, to updates the "X tracks" in the sidebar etc - // this is done automatically if you run a dbcmd to add files. - DatabaseCommand_CollectionStats * cmd = new DatabaseCommand_CollectionStats( m_source ); - connect( cmd, SIGNAL( done( const QVariantMap & ) ), - m_source.data(), SLOT( setStats( const QVariantMap& ) ), Qt::QueuedConnection ); - APP->database()->enqueue( QSharedPointer(cmd) ); - return; - } - - Q_ASSERT( msg->is( Msg::JSON ) ); - - QVariantMap m = msg->json().toMap(); - - //qDebug() << ">>>>" << m; - - if( m.empty() ) - { - qDebug() << "Failed to parse msg in dbsync"; - Q_ASSERT( false ); - return; - } - - // a db sync op msg - if( msg->is( Msg::DBOP ) ) - { - DatabaseCommand *cmd = DatabaseCommand::factory( m, m_source ); - if ( !cmd ) - { - qDebug() << "UNKNOWN DBOP CMD!"; - return; - } - - qDebug() << "APPLYING CMD" << cmd->commandname() << cmd->guid(); - - if( !msg->is( Msg::FRAGMENT ) ) // last msg in this batch - { - changeState( SAVING ); // just DB work left to complete - connect( cmd, SIGNAL( finished() ), this, SLOT( lastOpApplied() ) ); - } - APP->database()->enqueue( QSharedPointer( cmd ) ); - return; - } - - if( m.value( "method" ).toString() == "fetchops" ) - { - m_uscache = m; - if( !m_us.empty() ) sendOps(); - return; - } - - if( m.value( "method" ).toString() == "trigger" ) - { - qDebug() << "Got trigger msg on dbsyncconnection, checking for new stuff."; - check(); - return; - } - - qDebug() << Q_FUNC_INFO << "Unhandled msg: " << msg->payload(); - Q_ASSERT( false ); -} - - -void -DBSyncConnection::lastOpApplied() -{ - qDebug() << Q_FUNC_INFO; - changeState(SYNCED); - // check again, until peer reponds we have no new ops to process - check(); -} - - -/// request new copies of anything we've cached that is stale -void -DBSyncConnection::sendOps() -{ - qDebug() << Q_FUNC_INFO; - - const QString sinceguid = m_uscache.value( "lastop" ).toString(); - - qDebug() << "Will send peer all ops since" << sinceguid; - - source_ptr src = APP->sourcelist().getLocal(); - - DatabaseCommand_loadOps * cmd = new DatabaseCommand_loadOps( src, sinceguid ); - connect( cmd, SIGNAL( done( QString, QList< dbop_ptr > ) ), - this, SLOT( sendOpsData( QString, QList< dbop_ptr > ) ) ); - - APP->database()->enqueue( QSharedPointer( cmd ) ); -} - - -void -DBSyncConnection::sendOpsData( QString sinceguid, QList< dbop_ptr > ops ) -{ - qDebug() << Q_FUNC_INFO << sinceguid << "Num ops to send: " << ops.length(); - - if( ops.length() == 0 ) - { - sendMsg( Msg::factory( "ok", Msg::DBOP ) ); - return; - } - - int i; - for( i = 0; i < ops.length(); ++i ) - { - quint8 flags = Msg::JSON | Msg::DBOP; - - if( ops.at(i)->compressed ) - flags |= Msg::COMPRESSED; - if( i != ops.length()-1 ) - flags |= Msg::FRAGMENT; - - sendMsg( Msg::factory( ops.at(i)->payload, flags ) ); - } -} - - -Connection* -DBSyncConnection::clone() -{ - Q_ASSERT( false ); - return 0; -} diff --git a/src/network/filetransferconnection.cpp b/src/network/filetransferconnection.cpp deleted file mode 100644 index 3bb5cddbc..000000000 --- a/src/network/filetransferconnection.cpp +++ /dev/null @@ -1,194 +0,0 @@ -#include "filetransferconnection.h" - -#include - -#include "tomahawk/tomahawkapp.h" -#include "tomahawk/result.h" - -#include "bufferiodevice.h" -#include "controlconnection.h" -#include "databasecommand_loadfile.h" -#include "database.h" - -// Msgs are framed, this is the size each msg we send containing audio data: -#define BLOCKSIZE 4096 - -using namespace Tomahawk; - - -FileTransferConnection::FileTransferConnection( Servent* s, ControlConnection* cc, QString fid, unsigned int size ) - : Connection( s ) - , m_cc( cc ) - , m_fid( fid ) - , m_type( RECEIVING ) - , m_badded( 0 ) - , m_bsent( 0 ) - , m_allok( false ) -{ - qDebug() << Q_FUNC_INFO; - - BufferIODevice* bio = new BufferIODevice( size ); - m_iodev = QSharedPointer( bio ); // device audio data gets written to - m_iodev->open( QIODevice::ReadWrite ); - - APP->servent().registerFileTransferConnection( this ); - - // if the audioengine closes the iodev (skip/stop/etc) then kill the connection - // immediately to avoid unnecessary network transfer - connect( m_iodev.data(), SIGNAL( aboutToClose() ), this, SLOT( shutdown() ), Qt::QueuedConnection ); - - // auto delete when connection closes: - connect( this, SIGNAL( finished() ), SLOT( deleteLater() ), Qt::QueuedConnection ); - - // don't fuck with our messages at all. No compression, no parsing, nothing: - this->setMsgProcessorModeIn ( MsgProcessor::NOTHING ); - this->setMsgProcessorModeOut( MsgProcessor::NOTHING ); -} - - -FileTransferConnection::FileTransferConnection( Servent* s, QString fid ) - : Connection( s ) - , m_fid( fid ) - , m_type( SENDING ) - , m_badded( 0 ) - , m_bsent( 0 ) - , m_allok( false ) -{ - APP->servent().registerFileTransferConnection( this ); - // auto delete when connection closes: - connect( this, SIGNAL( finished() ), SLOT( deleteLater() ), Qt::QueuedConnection ); -} - - -FileTransferConnection::~FileTransferConnection() -{ - qDebug() << Q_FUNC_INFO << "TX/RX:" << bytesSent() << bytesReceived(); - if( m_type == RECEIVING && !m_allok ) - { - qDebug() << "FTConnection closing before last data msg received, shame."; - //TODO log the fact that our peer was bad-mannered enough to not finish the upload - - // protected, we could expose it: - //m_iodev->setErrorString("FTConnection providing data went away mid-transfer"); - - ((BufferIODevice*)m_iodev.data())->inputComplete(); - } - - APP->servent().fileTransferFinished( this ); -} - - -QString -FileTransferConnection::id() const -{ - return QString( "FTC[%1 %2]" ) - .arg( m_type == SENDING ? "TX" : "RX" ) - .arg( m_fid ); -} - - -void -FileTransferConnection::showStats( qint64 tx, qint64 rx ) -{ - if( tx > 0 || rx > 0 ) - { - qDebug() << id() - << QString( "Down: %L1 bytes/sec," ).arg( rx ) - << QString( "Up: %L1 bytes/sec" ).arg( tx ); - } -} - - -void -FileTransferConnection::setup() -{ - connect( this, SIGNAL( statsTick( qint64, qint64 ) ), SLOT( showStats( qint64, qint64 ) ) ); - if( m_type == RECEIVING ) - { - qDebug() << "in RX mode"; - return; - } - - qDebug() << "in TX mode, fid:" << m_fid; - - DatabaseCommand_LoadFile* cmd = new DatabaseCommand_LoadFile( m_fid ); - connect( cmd, SIGNAL( result( QVariantMap ) ), SLOT( startSending( QVariantMap ) ) ); - TomahawkApp::instance()->database()->enqueue( QSharedPointer( cmd ) ); -} - - -void -FileTransferConnection::startSending( QVariantMap f ) -{ - Tomahawk::result_ptr result( new Tomahawk::Result( f, collection_ptr() ) ); - qDebug() << "Starting to transmit" << result->url(); - QSharedPointer io = TomahawkApp::instance()->getIODeviceForUrl( result ); - if( !io ) - { - qDebug() << "Couldn't read from source:" << result->url(); - shutdown(); - return; - } - m_readdev = QSharedPointer( io ); - sendSome(); -} - - -void -FileTransferConnection::handleMsg( msg_ptr msg ) -{ - Q_ASSERT( m_type == FileTransferConnection::RECEIVING ); - Q_ASSERT( msg->is( Msg::RAW ) ); - - m_badded += msg->payload().length(); - ((BufferIODevice*)m_iodev.data())->addData( msg->payload() ); - - //qDebug() << Q_FUNC_INFO << "flags" << (int) msg->flags() - // << "payload len" << msg->payload().length() - // << "written to device so far: " << m_badded; - - if( !msg->is( Msg::FRAGMENT ) ) - { - qDebug() << "*** Got last msg in filetransfer. added" << m_badded - << "io size" << m_iodev->size(); - - m_allok = true; - // tell our iodev there is no more data to read, no args meaning a success: - ((BufferIODevice*)m_iodev.data())->inputComplete(); - shutdown(); - } -} - - -Connection* FileTransferConnection::clone() -{ - Q_ASSERT( false ); - return 0; -} - - -void FileTransferConnection::sendSome() -{ - Q_ASSERT( m_type == FileTransferConnection::SENDING ); - - QByteArray ba = m_readdev->read( BLOCKSIZE ); - m_bsent += ba.length(); - //qDebug() << "Sending" << ba.length() << "bytes of audiofile"; - - if( m_readdev->atEnd() ) - { - sendMsg( Msg::factory( ba, Msg::RAW ) ); - qDebug() << "Sent all. DONE." << m_bsent; - shutdown( true ); - return; - } - else - { - // more to come -> FRAGMENT - sendMsg( Msg::factory( ba, Msg::RAW | Msg::FRAGMENT ) ); - } - - // HINT: change the 0 to 50 to transmit at 640Kbps, for example - // (this is where upload throttling could be implemented) - QTimer::singleShot( 0, this, SLOT( sendSome() ) ); -} diff --git a/src/network/filetransferconnection.h b/src/network/filetransferconnection.h deleted file mode 100644 index 6dd0464bd..000000000 --- a/src/network/filetransferconnection.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef FILETRANSFERCONNECTION_H -#define FILETRANSFERCONNECTION_H - -#include -#include -#include - -#include "connection.h" - -class ControlConnection; -class BufferIODevice; - -class FileTransferConnection : public Connection -{ -Q_OBJECT - -public: - enum Type - { - SENDING = 0, - RECEIVING = 1 - }; - - // RX: - explicit FileTransferConnection( Servent* s, ControlConnection* parent, QString fid, unsigned int size ); - // TX: - explicit FileTransferConnection( Servent* s, QString fid ); - - virtual ~FileTransferConnection(); - - QString id() const; - void setup(); - Connection* clone(); - - const QSharedPointer& iodevice() { return m_iodev; } - - Type type() const { return m_type; } - QString fid() const { return m_fid; } - -protected slots: - virtual void handleMsg( msg_ptr msg ); - -private slots: - void startSending( QVariantMap ); - void sendSome(); - void showStats(qint64 tx, qint64 rx); - -private: - QSharedPointer m_iodev; - ControlConnection* m_cc; - QString m_fid; - Type m_type; - QSharedPointer m_readdev; - - int m_badded, m_bsent; - bool m_allok; // got last msg ok, transfer complete? -}; - -#endif // FILETRANSFERCONNECTION_H diff --git a/src/network/remotecollection.cpp b/src/network/remotecollection.cpp deleted file mode 100644 index c4d52538a..000000000 --- a/src/network/remotecollection.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "remotecollection.h" - -using namespace Tomahawk; - -RemoteCollection::RemoteCollection( source_ptr source, QObject* parent ) - : DatabaseCollection( source, parent ) -{ - qDebug() << Q_FUNC_INFO; -} - - -// adding/removing is done by dbsyncconnection, and the dbcmd objects that modify -// the database will make us emit the appropriate signals (tracksAdded etc.) -void RemoteCollection::addTracks( const QList &newitems ) -{ - qDebug() << Q_FUNC_INFO; - Q_ASSERT( false ); -} - - -void RemoteCollection::removeTracks( const QList &olditems ) -{ - qDebug() << Q_FUNC_INFO; - Q_ASSERT( false ); -} diff --git a/src/network/remotecollection.h b/src/network/remotecollection.h deleted file mode 100644 index bde90af93..000000000 --- a/src/network/remotecollection.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef REMOTECOLLECTION_H -#define REMOTECOLLECTION_H - -#include "tomahawk/typedefs.h" - -#include "controlconnection.h" -#include "databasecollection.h" - -class RemoteCollection : public DatabaseCollection -{ -Q_OBJECT - -friend class ControlConnection; // for receiveTracks() - -public: - explicit RemoteCollection( Tomahawk::source_ptr source, QObject* parent = 0 ); - ~RemoteCollection() - { - qDebug() << Q_FUNC_INFO; - } - -public slots: - virtual void addTracks( const QList &newitems ); - virtual void removeTracks( const QList &olditems ); -}; - -#endif // REMOTECOLLECTION_H diff --git a/src/pipeline.cpp b/src/pipeline.cpp deleted file mode 100644 index b5a294221..000000000 --- a/src/pipeline.cpp +++ /dev/null @@ -1,202 +0,0 @@ -#include "tomahawk/pipeline.h" - -#include -#include - -#include "tomahawk/functimeout.h" -#include "tomahawk/tomahawkapp.h" -#include "database/database.h" - -using namespace Tomahawk; - - -Pipeline::Pipeline( QObject* parent ) - : QObject( parent ) - , m_index_ready( false ) -{ - -} - - -void -Pipeline::databaseReady() -{ - connect( APP->database(), SIGNAL(indexReady()), this, SLOT(indexReady()), Qt::QueuedConnection ); - APP->database()->loadIndex(); -} - - -void Pipeline::indexReady() -{ - qDebug() << Q_FUNC_INFO << "shuting this many pending queries:" << m_queries_pending.size(); - m_index_ready = true; - foreach( const query_ptr& q, m_queries_pending ) - { - q->setLastPipelineWeight( 101 ); - shunt( q ); - } - m_queries_pending.clear(); -} - - -void -Pipeline::removeResolver( Resolver* r ) -{ - m_resolvers.removeAll( r ); -} - - -void -Pipeline::addResolver( Resolver* r, bool sort ) -{ - m_resolvers.append( r ); - if( sort ) - { - qSort( m_resolvers.begin(), - m_resolvers.end(), - Pipeline::resolverSorter ); - } - qDebug() << "Adding resolver" << r->name(); - -/* qDebug() << "Current pipeline:"; - foreach( Resolver * r, m_resolvers ) - { - qDebug() << "* score:" << r->weight() - << "pref:" << r->preference() - << "name:" << r->name(); - }*/ -} - - -void -Pipeline::add( const QList& qlist ) -{ - { - QMutexLocker lock( &m_mut ); - foreach( const query_ptr& q, qlist ) - { - qDebug() << Q_FUNC_INFO << (qlonglong)q.data() << q->toString(); - if( !m_qids.contains( q->id() ) ) - { - m_qids.insert( q->id(), q ); - } - } - } - - /* - Since resolvers are async, we now dispatch to the highest weighted ones - and after timeout, dispatch to next highest etc, aborting when solved - - If index not yet loaded, leave in the pending list instead. - (they are shunted when index is ready) - */ - if( m_index_ready ) - { - foreach( const query_ptr& q, qlist ) - { - q->setLastPipelineWeight( 101 ); - shunt( q ); // bump into next stage of pipeline (highest weights are 100) - } - } - else - { - qDebug() << "Placing query in pending queue - index not ready yet"; - m_queries_pending.append( qlist ); - } -} - - -void -Pipeline::add( const query_ptr& q ) -{ - //qDebug() << Q_FUNC_INFO << (qlonglong)q.data() << q->toString(); - QList< query_ptr > qlist; - qlist << q; - add( qlist ); -} - - -void -Pipeline::reportResults( QID qid, const QList< result_ptr >& results ) -{ - QMutexLocker lock( &m_mut ); - - if( !m_qids.contains( qid ) ) - { - qDebug() << "reportResults called for unknown QID"; - return; - } - - const query_ptr& q = m_qids.value( qid ); - //qDebug() << Q_FUNC_INFO << qid; - //qDebug() << "solved query:" << (qlonglong)q.data() << q->toString(); - q->addResults( results ); - - //qDebug() << "Results for " << q->toString() << ", just added" << results.length(); - foreach( const result_ptr& r, q->results() ) - { - m_rids.insert( r->id(), r ); - //qDebug() << "* " << (results.contains(r) ? "NEW" : "") << r->toString(); - } - -} - - -void -Pipeline::shunt( const query_ptr& q ) -{ - if( q->solved() ) - { - qDebug() << "Query solved, pipeline aborted:" << q->toString() - << "numresults:" << q->results().length(); - return; - } - unsigned int lastweight = 0; - unsigned int lasttimeout = 0; - foreach( Resolver* r, m_resolvers ) - { - if ( r->weight() >= q->lastPipelineWeight() ) - continue; - - if ( lastweight == 0 ) - { - lastweight = r->weight(); - lasttimeout = r->timeout(); - //qDebug() << "Shunting into weight" << lastweight << "q:" << q->toString(); - } - if ( lastweight == r->weight() ) - { - // snag the lowest timeout at this weight - if ( r->timeout() < lasttimeout ) - lasttimeout = r->timeout(); - - // resolvers aren't allowed to block in this call: - //qDebug() << "Dispaching to resolver" << r->name(); - r->resolve( q->toVariant() ); - } - else - break; - } - - if ( lastweight > 0 ) - { - q->setLastPipelineWeight( lastweight ); - //qDebug() << "Shunting in" << lasttimeout << "ms, q:" << q->toString(); - new FuncTimeout( lasttimeout, boost::bind( &Pipeline::shunt, this, q ) ); - } - else - { - //qDebug() << "Reached end of pipeline for:" << q->toString(); - // reached end of pipeline - } -} - - -bool -Pipeline::resolverSorter( const Resolver* left, const Resolver* right ) -{ - if( left->weight() == right->weight() ) - return left->preference() > right->preference(); - else - return left->weight() > right->weight(); -} diff --git a/src/playlist/albumitem.h b/src/playlist/albumitem.h deleted file mode 100644 index f5be3cbc4..000000000 --- a/src/playlist/albumitem.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef ALBUMITEM_H -#define ALBUMITEM_H - -#include -#include -#include -#include - -#include "tomahawk/album.h" - -class AlbumItem : public QObject -{ -Q_OBJECT - -public: - ~AlbumItem(); - - explicit AlbumItem( AlbumItem* parent = 0, QAbstractItemModel* model = 0 ); - explicit AlbumItem( const Tomahawk::album_ptr& album, AlbumItem* parent = 0, int row = -1 ); - - const Tomahawk::album_ptr& album() const { return m_album; }; - - AlbumItem* parent; - QList children; - QHash hash; - int childCount; - QPersistentModelIndex index; - QAbstractItemModel* model; - QPixmap cover; - bool toberemoved; - -signals: - void dataChanged(); - -private: - Tomahawk::album_ptr m_album; -}; - -#endif // ALBUMITEM_H diff --git a/src/playlist/albumproxymodel.cpp b/src/playlist/albumproxymodel.cpp deleted file mode 100644 index 9c88ed6b6..000000000 --- a/src/playlist/albumproxymodel.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "albumproxymodel.h" - -#include -#include - -#include "tomahawk/query.h" -#include "collectionmodel.h" - - -AlbumProxyModel::AlbumProxyModel( QObject* parent ) - : QSortFilterProxyModel( parent ) - , m_model( 0 ) -{ - qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); - - setFilterCaseSensitivity( Qt::CaseInsensitive ); - setSortCaseSensitivity( Qt::CaseInsensitive ); - setDynamicSortFilter( true ); - - setSourceModel( 0 ); -} - - -void -AlbumProxyModel::setSourceModel( AlbumModel* sourceModel ) -{ - m_model = sourceModel; - - QSortFilterProxyModel::setSourceModel( sourceModel ); -} - - -void -AlbumProxyModel::setFilterRegExp( const QString& pattern ) -{ - qDebug() << Q_FUNC_INFO; - QSortFilterProxyModel::setFilterRegExp( pattern ); - - emit filterChanged( pattern ); -} - - -bool -AlbumProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const -{ - if ( filterRegExp().isEmpty() ) - return true; - - AlbumItem* pi = sourceModel()->itemFromIndex( sourceModel()->index( sourceRow, 0, sourceParent ) ); - if ( !pi ) - return false; - - const Tomahawk::album_ptr& q = pi->album(); - - QStringList sl = filterRegExp().pattern().split( " ", QString::SkipEmptyParts ); - bool found = true; - - foreach( const QString& s, sl ) - { - if ( !q->name().contains( s, Qt::CaseInsensitive ) ) - { - found = false; - } - } - - return found; -} - - -void -AlbumProxyModel::removeIndex( const QModelIndex& index ) -{ - qDebug() << Q_FUNC_INFO; - - if ( !sourceModel() ) - return; - if ( index.column() > 0 ) - return; - - sourceModel()->removeIndex( mapToSource( index ) ); -} - - -void -AlbumProxyModel::removeIndexes( const QList& indexes ) -{ - if ( !sourceModel() ) - return; - - foreach( const QModelIndex& idx, indexes ) - { - removeIndex( idx ); - } -} - - -Tomahawk::result_ptr -AlbumProxyModel::siblingItem( int itemsAway ) -{ - qDebug() << Q_FUNC_INFO; - return Tomahawk::result_ptr(); -} diff --git a/src/playlist/albumproxymodel.h b/src/playlist/albumproxymodel.h deleted file mode 100644 index bce853224..000000000 --- a/src/playlist/albumproxymodel.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef ALBUMPROXYMODEL_H -#define ALBUMPROXYMODEL_H - -#include - -#include "tomahawk/playlistinterface.h" -#include "playlist/albummodel.h" - -class AlbumProxyModel : public QSortFilterProxyModel, public PlaylistInterface -{ -Q_OBJECT - -public: - explicit AlbumProxyModel( QObject* parent = 0 ); - - virtual AlbumModel* sourceModel() const { return m_model; } - virtual void setSourceModel( AlbumModel* sourceModel ); - - virtual int trackCount() const { return rowCount( QModelIndex() ); } - virtual int albumCount() const { return rowCount( QModelIndex() ); } - - virtual void removeIndex( const QModelIndex& index ); - virtual void removeIndexes( const QList& indexes ); - - virtual Tomahawk::result_ptr siblingItem( int direction ); - - void setFilterRegExp( const QString& pattern ); - - virtual PlaylistInterface::RepeatMode repeatMode() const { return m_repeatMode; } - virtual bool shuffled() const { return m_shuffled; } - -signals: - void repeatModeChanged( PlaylistInterface::RepeatMode mode ); - void shuffleModeChanged( bool enabled ); - - void trackCountChanged( unsigned int tracks ); - - void filterChanged( const QString& filter ); - -public slots: - virtual void setRepeatMode( RepeatMode mode ) { m_repeatMode = mode; emit repeatModeChanged( mode ); } - virtual void setShuffled( bool enabled ) { m_shuffled = enabled; emit shuffleModeChanged( enabled ); } - -protected: - bool filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const; - -private: - AlbumModel* m_model; - RepeatMode m_repeatMode; - bool m_shuffled; -}; - -#endif // ALBUMPROXYMODEL_H diff --git a/src/playlist/albumview.h b/src/playlist/albumview.h deleted file mode 100644 index fb30c89a4..000000000 --- a/src/playlist/albumview.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef ALBUMVIEW_H -#define ALBUMVIEW_H - -#include -#include - -class AlbumModel; -class AlbumProxyModel; - -class AlbumView : public QListView -{ -Q_OBJECT - -public: - explicit AlbumView( QWidget* parent = 0 ); - ~AlbumView(); - - void setProxyModel( AlbumProxyModel* model ); - - AlbumModel* model() { return m_model; } - AlbumProxyModel* proxyModel() { return (AlbumProxyModel*)m_proxyModel; } -// PlaylistItemDelegate* delegate() { return m_delegate; } - - void setModel( AlbumModel* model ); - -public slots: - void onItemActivated( const QModelIndex& index ); - -protected: - virtual void startDrag( Qt::DropActions supportedActions ); - virtual void dragEnterEvent( QDragEnterEvent* event ); - virtual void dragMoveEvent( QDragMoveEvent* event ); - virtual void dropEvent( QDropEvent* event ); - - void paintEvent( QPaintEvent* event ); - -private slots: - void onFilterChanged( const QString& filter ); - -private: - QPixmap createDragPixmap( int itemCount ) const; - - AlbumModel* m_model; - AlbumProxyModel* m_proxyModel; -// PlaylistItemDelegate* m_delegate; -}; - -#endif // ALBUMVIEW_H diff --git a/src/playlist/collectionflatmodel.cpp b/src/playlist/collectionflatmodel.cpp deleted file mode 100644 index 3c11b216f..000000000 --- a/src/playlist/collectionflatmodel.cpp +++ /dev/null @@ -1,210 +0,0 @@ -#include "collectionflatmodel.h" - -#include -#include -#include - -#include "database.h" - -using namespace Tomahawk; - - -CollectionFlatModel::CollectionFlatModel( QObject* parent ) - : TrackModel( parent ) -{ - qDebug() << Q_FUNC_INFO; - - connect( &APP->sourcelist(), SIGNAL( sourceRemoved( Tomahawk::source_ptr ) ), SLOT( onSourceOffline( Tomahawk::source_ptr ) ) ); -} - - -CollectionFlatModel::~CollectionFlatModel() -{ -} - - -int -CollectionFlatModel::columnCount( const QModelIndex& parent ) const -{ - return TrackModel::columnCount( parent ); -} - - -QVariant -CollectionFlatModel::data( const QModelIndex& index, int role ) const -{ - return TrackModel::data( index, role ); -} - - -QVariant -CollectionFlatModel::headerData( int section, Qt::Orientation orientation, int role ) const -{ - return TrackModel::headerData( section, orientation, role ); -} - - -void -CollectionFlatModel::addCollection( const collection_ptr& collection ) -{ - qDebug() << Q_FUNC_INFO << collection->name() - << collection->source()->id() - << collection->source()->userName(); - - emit loadingStarts(); - - onTracksAdded( collection->tracks(), collection ); - - connect( collection.data(), SIGNAL( tracksAdded( QList, Tomahawk::collection_ptr ) ), - SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ) ); - connect( collection.data(), SIGNAL( tracksFinished( Tomahawk::collection_ptr ) ), - SLOT( onTracksAddingFinished( Tomahawk::collection_ptr ) ) ); -} - - -void -CollectionFlatModel::addFilteredCollection( const collection_ptr& collection, unsigned int amount, DatabaseCommand_AllTracks::SortOrder order ) -{ - qDebug() << Q_FUNC_INFO << collection->name() - << collection->source()->id() - << collection->source()->userName() - << amount << order; - - emit loadingStarts(); - - DatabaseCommand_AllTracks* cmd = new DatabaseCommand_AllTracks( collection ); - cmd->setLimit( amount ); - cmd->setSortOrder( order ); - cmd->setSortDescending( true ); - - connect( cmd, SIGNAL( tracks( QList, Tomahawk::collection_ptr ) ), - SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ) ); - - TomahawkApp::instance()->database()->enqueue( QSharedPointer( cmd ) ); -} - - -void -CollectionFlatModel::removeCollection( const collection_ptr& collection ) -{ - disconnect( collection.data(), SIGNAL( tracksAdded( QList, Tomahawk::collection_ptr ) ), - this, SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ) ); - disconnect( collection.data(), SIGNAL( tracksFinished( Tomahawk::collection_ptr ) ), - this, SLOT( onTracksAddingFinished( Tomahawk::collection_ptr ) ) ); - -// QList plitems = m_collectionIndex.values( collection ); - QList< QPair< int, int > > rows; - QList< QPair< int, int > > sortrows; - QPair< int, int > row; - QPair< int, int > rowf; - rows = m_collectionRows.values( collection ); - - while ( rows.count() ) - { - int x = -1; - int j = 0; - foreach( row, rows ) - { - if ( x < 0 || row.first > rows.at( x ).first ) - x = j; - - j++; - } - - sortrows.append( rows.at( x ) ); - rows.removeAt( x ); - } - - foreach( row, sortrows ) - { - QMap< Tomahawk::collection_ptr, QPair< int, int > > newrows; - foreach ( const collection_ptr& col, m_collectionRows.uniqueKeys() ) - { - if ( col.data() == collection.data() ) - continue; - - foreach ( rowf, m_collectionRows.values( col ) ) - { - if ( rowf.first > row.first ) - { - rowf.first -= ( row.second - row.first ) + 1; - rowf.second -= ( row.second - row.first ) + 1; - } - newrows.insertMulti( col, rowf ); - } - } - - m_collectionRows = newrows; - - qDebug() << "Removing rows:" << row.first << row.second; - emit beginRemoveRows( QModelIndex(), row.first, row.second ); - for ( int i = row.second; i >= row.first; i-- ) - { - PlItem* item = itemFromIndex( index( i, 0, QModelIndex() ) ); - delete item; - } - emit endRemoveRows(); - } - -// m_collectionIndex.remove( collection ); -} - - -void -CollectionFlatModel::onTracksAdded( const QList& tracks, const Tomahawk::collection_ptr& collection ) -{ - if ( !tracks.count() ) - return; - - int c = rowCount( QModelIndex() ); - QPair< int, int > crows; - crows.first = c; - crows.second = c + tracks.count() - 1; - - emit beginInsertRows( QModelIndex(), crows.first, crows.second ); - - PlItem* plitem; - foreach( const query_ptr& query, tracks ) - { - plitem = new PlItem( query, m_rootItem ); - plitem->index = createIndex( m_rootItem->children.count() - 1, 0, plitem ); - - connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); - } - - m_collectionRows.insertMulti( collection, crows ); - emit endInsertRows(); - - emit trackCountChanged( rowCount( QModelIndex() ) ); - qDebug() << rowCount( QModelIndex() ); -} - - -void -CollectionFlatModel::onTracksAddingFinished( const Tomahawk::collection_ptr& collection ) -{ - qDebug() << "Finished loading tracks" << collection->source()->friendlyName(); - - emit loadingFinished(); -} - - -void -CollectionFlatModel::onDataChanged() -{ - PlItem* p = (PlItem*)sender(); -// emit itemSizeChanged( p->index ); - emit dataChanged( p->index, p->index.sibling( p->index.row(), columnCount() - 1 ) ); -} - - -void -CollectionFlatModel::onSourceOffline( const Tomahawk::source_ptr& src ) -{ - qDebug() << Q_FUNC_INFO; - - if ( m_collectionRows.contains( src->collection() ) ) - { - removeCollection( src->collection() ); - } -} diff --git a/src/playlist/collectionflatmodel.h b/src/playlist/collectionflatmodel.h deleted file mode 100644 index 4e886ed23..000000000 --- a/src/playlist/collectionflatmodel.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef COLLECTIONFLATMODEL_H -#define COLLECTIONFLATMODEL_H - -#include -#include -#include - -#include "plitem.h" -#include "trackmodel.h" -#include "tomahawk/tomahawkapp.h" -#include "tomahawk/collection.h" -#include "tomahawk/query.h" -#include "tomahawk/typedefs.h" -#include "tomahawk/playlist.h" -#include "tomahawk/playlistinterface.h" - -#include "databasecommand_alltracks.h" - -class QMetaData; - -class CollectionFlatModel : public TrackModel -{ -Q_OBJECT - -public: - explicit CollectionFlatModel( QObject* parent = 0 ); - ~CollectionFlatModel(); - - int columnCount( const QModelIndex& parent = QModelIndex() ) const; - - QVariant data( const QModelIndex& index, int role ) const; - QVariant headerData( int section, Qt::Orientation orientation, int role ) const; - - void addCollection( const Tomahawk::collection_ptr& collection ); - void removeCollection( const Tomahawk::collection_ptr& collection ); - - void addFilteredCollection( const Tomahawk::collection_ptr& collection, unsigned int amount, DatabaseCommand_AllTracks::SortOrder order ); - -signals: - void repeatModeChanged( PlaylistInterface::RepeatMode mode ); - void shuffleModeChanged( bool enabled ); - - void itemSizeChanged( const QModelIndex& index ); - - void loadingStarts(); - void loadingFinished(); - -private slots: - void onDataChanged(); - - void onTracksAdded( const QList& tracks, const Tomahawk::collection_ptr& collection ); - void onTracksAddingFinished( const Tomahawk::collection_ptr& collection ); - - void onSourceOffline( const Tomahawk::source_ptr& src ); - -private: - QMap< Tomahawk::collection_ptr, QPair< int, int > > m_collectionRows; -}; - -#endif // COLLECTIONFLATMODEL_H diff --git a/src/playlist/collectionproxymodel.h b/src/playlist/collectionproxymodel.h deleted file mode 100644 index 0a08aeb41..000000000 --- a/src/playlist/collectionproxymodel.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef COLLECTIONPROXYMODEL_H -#define COLLECTIONPROXYMODEL_H - -#include "trackproxymodel.h" - -class CollectionProxyModel : public TrackProxyModel -{ -Q_OBJECT - -public: - explicit CollectionProxyModel( QObject* parent = 0 ); - -protected: - bool lessThan( const QModelIndex& left, const QModelIndex& right ) const; -}; - -#endif // COLLECTIONPROXYMODEL_H diff --git a/src/playlist/collectionview.cpp b/src/playlist/collectionview.cpp deleted file mode 100644 index e364485ff..000000000 --- a/src/playlist/collectionview.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "collectionview.h" - -#include -#include - -#include "playlist/collectionproxymodel.h" - -using namespace Tomahawk; - - -CollectionView::CollectionView( QWidget* parent ) - : TrackView( parent ) -{ - setProxyModel( new CollectionProxyModel( this ) ); - - setSortingEnabled( true ); - sortByColumn( 0, Qt::AscendingOrder ); - - setDragDropMode( QAbstractItemView::DragOnly ); - setAcceptDrops( false ); -} - - -CollectionView::~CollectionView() -{ - qDebug() << Q_FUNC_INFO; -} - - -void -CollectionView::dragEnterEvent( QDragEnterEvent* event ) -{ - qDebug() << Q_FUNC_INFO; - event->ignore(); -} - diff --git a/src/playlist/collectionview.h b/src/playlist/collectionview.h deleted file mode 100644 index 880970627..000000000 --- a/src/playlist/collectionview.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef COLLECTIONVIEW_H -#define COLLECTIONVIEW_H - -#include "tomahawk/tomahawkapp.h" -#include "trackview.h" - -class CollectionView : public TrackView -{ -Q_OBJECT - -public: - explicit CollectionView( QWidget* parent = 0 ); - ~CollectionView(); - -protected: - virtual void dragEnterEvent( QDragEnterEvent* event ); -}; - -#endif // COLLECTIONVIEW_H diff --git a/src/playlist/playlistitemdelegate.cpp b/src/playlist/playlistitemdelegate.cpp deleted file mode 100644 index f5a713418..000000000 --- a/src/playlist/playlistitemdelegate.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include "playlistitemdelegate.h" - -#include -#include -#include - -#include "tomahawk/query.h" -#include "tomahawk/result.h" - -#include "playlist/plitem.h" -#include "playlist/trackproxymodel.h" - - -PlaylistItemDelegate::PlaylistItemDelegate( QAbstractItemView* parent, TrackProxyModel* proxy ) - : QStyledItemDelegate( (QObject*)parent ) - , m_view( parent ) - , m_model( proxy ) -{ -} - - -void -PlaylistItemDelegate::updateRowSize( const QModelIndex& index ) -{ - emit sizeHintChanged( index ); -} - - -QSize -PlaylistItemDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const -{ - QSize size = QStyledItemDelegate::sizeHint( option, index ); - return size; -} - - -QWidget* -PlaylistItemDelegate::createEditor ( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const -{ - return 0; -} - - -void -PlaylistItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const -{ - PlItem* item = m_model->itemFromIndex( m_model->mapToSource( index ) ); - if ( !item ) - return; - - if ( item->query()->results().count() ) - painter->setOpacity( item->query()->results().at( 0 )->score() ); - else - painter->setOpacity( 0.3 ); - - if ( item->isPlaying() ) - { - painter->save(); - painter->setRenderHint( QPainter::Antialiasing ); - - { - QRect r = option.rect.adjusted( 3, 0, 0, -3 ); - if ( index.column() == 0 ) - { - painter->drawPixmap( r.adjusted( 3, 3, 18 - r.width(), 0 ), QPixmap( index.data( Qt::DecorationRole ).toString() ) ); - r = r.adjusted( 22, 0, 0, 0 ); - } - - painter->setPen( option.palette.text().color() ); - painter->drawText( r.adjusted( 0, 2, 0, 0 ), index.data().toString() ); - } - - if ( index.column() == index.model()->columnCount() - 1 ) - { - QRect r = QRect( 3, option.rect.y() + 1, m_view->contentsRect().width() - 6, option.rect.height() - 2 ); - painter->setPen( option.palette.highlight().color() ); - QPen pen = painter->pen(); - pen.setWidth( 1.0 ); - painter->setPen( pen ); - painter->drawRoundedRect( r, 3.0, 3.0 ); - } - - painter->restore(); - } - else - { - QStyledItemDelegate::paint( painter, option, index ); - } -} diff --git a/src/playlist/playlistitemdelegate.h b/src/playlist/playlistitemdelegate.h deleted file mode 100644 index f47826f63..000000000 --- a/src/playlist/playlistitemdelegate.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef PLAYLISTITEMDELEGATE_H -#define PLAYLISTITEMDELEGATE_H - -#include - -class TrackProxyModel; - -class PlaylistItemDelegate : public QStyledItemDelegate -{ -Q_OBJECT - -public: - PlaylistItemDelegate( QAbstractItemView* parent = 0, TrackProxyModel* proxy = 0 ); - - void updateRowSize( const QModelIndex& index ); - -public slots: - void setRemovalProgress( unsigned int progress ) { m_removalProgress = progress; } - -protected: - void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; - QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const; - - QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const; - -private: - unsigned int m_removalProgress; - QAbstractItemView* m_view; - TrackProxyModel* m_model; -}; - -#endif // PLAYLISTITEMDELEGATE_H diff --git a/src/playlist/playlistmanager.cpp b/src/playlist/playlistmanager.cpp deleted file mode 100644 index 8438db332..000000000 --- a/src/playlist/playlistmanager.cpp +++ /dev/null @@ -1,352 +0,0 @@ -#include "playlistmanager.h" - -#include "audioengine.h" -#include "collectionmodel.h" -#include "collectionflatmodel.h" -#include "collectionview.h" -#include "playlistmodel.h" -#include "playlistview.h" -#include "trackproxymodel.h" -#include "trackmodel.h" - -#include "infowidgets/sourceinfowidget.h" - -#define FILTER_TIMEOUT 280 - -PlaylistManager::PlaylistManager( QObject* parent ) - : QObject( parent ) - , m_widget( new QStackedWidget() ) - , m_currentProxyModel( 0 ) - , m_currentModel( 0 ) - , m_currentView( 0 ) - , m_currentMode( 0 ) - , m_superCollectionVisible( true ) -{ - m_widget->setMinimumWidth( 690 ); - - m_superCollectionView = new CollectionView(); - m_superCollectionFlatModel = new CollectionFlatModel( m_superCollectionView ); - m_superCollectionView->setModel( m_superCollectionFlatModel ); - - m_widget->addWidget( m_superCollectionView ); - m_currentView = m_superCollectionView; - - connect( &m_filterTimer, SIGNAL( timeout() ), SLOT( applyFilter() ) ); -} - - -PlaylistManager::~PlaylistManager() -{ - delete m_widget; -} - - -bool -PlaylistManager::show( const Tomahawk::playlist_ptr& playlist ) -{ - unlinkPlaylist(); - - if ( !m_playlistViews.contains( playlist ) ) - { - PlaylistView* view = new PlaylistView(); - PlaylistModel* model = new PlaylistModel(); - view->setModel( model ); - - m_currentProxyModel = view->proxyModel(); - m_currentModel = view->model(); - - model->loadPlaylist( playlist ); - playlist->resolve(); - - m_playlistViews.insert( playlist, view ); - m_views.insert( (PlaylistInterface*)m_currentProxyModel, view ); - - m_widget->addWidget( view ); - m_widget->setCurrentWidget( view ); - m_currentView = view; - } - else - { - PlaylistView* view = m_playlistViews.value( playlist ); - m_widget->setCurrentWidget( view ); - m_currentProxyModel = view->proxyModel(); - m_currentModel = view->model(); - m_currentView = view; - } - - m_superCollectionVisible = false; - linkPlaylist(); - - emit numSourcesChanged( APP->sourcelist().count() ); - return true; -} - - -bool -PlaylistManager::show( const Tomahawk::album_ptr& album ) -{ - qDebug() << Q_FUNC_INFO << &album << album.data(); - unlinkPlaylist(); - - if ( !m_albumViews.contains( album ) ) - { - PlaylistView* view = new PlaylistView(); - PlaylistModel* model = new PlaylistModel(); - view->setModel( model ); - - m_currentProxyModel = view->proxyModel(); - m_currentModel = view->model(); - - model->loadAlbum( album ); - - m_albumViews.insert( album, view ); - m_views.insert( (PlaylistInterface*)m_currentProxyModel, view ); - - m_widget->addWidget( view ); - m_widget->setCurrentWidget( view ); - m_currentView = view; - } - else - { - PlaylistView* view = m_albumViews.value( album ); - m_widget->setCurrentWidget( view ); - m_currentProxyModel = view->proxyModel(); - m_currentModel = view->model(); - m_currentView = view; - } - - m_superCollectionVisible = false; - linkPlaylist(); - - emit numSourcesChanged( 1 ); - return true; -} - - -bool -PlaylistManager::show( const Tomahawk::collection_ptr& collection ) -{ - unlinkPlaylist(); - - if ( !m_collectionViews.contains( collection ) ) - { - CollectionView* view = new CollectionView(); - CollectionFlatModel* model = new CollectionFlatModel(); - view->setModel( model ); - - m_currentProxyModel = view->proxyModel(); - m_currentModel = view->model(); - - model->addCollection( collection ); - - m_collectionViews.insert( collection, view ); - m_views.insert( (PlaylistInterface*)m_currentProxyModel, view ); - - m_widget->addWidget( view ); - m_widget->setCurrentWidget( view ); - m_currentView = view; - } - else - { - CollectionView* view = m_collectionViews.value( collection ); - m_widget->setCurrentWidget( view ); - m_currentProxyModel = view->proxyModel(); - m_currentModel = view->model(); - m_currentView = view; - } - - m_superCollectionVisible = false; - linkPlaylist(); - - emit numSourcesChanged( 1 ); - return true; -} - - -bool -PlaylistManager::show( const Tomahawk::source_ptr& source ) -{ - unlinkPlaylist(); - - m_currentProxyModel = 0; - m_currentModel = 0; - m_currentView = 0; - - if ( !m_sourceViews.contains( source ) ) - { - SourceInfoWidget* swidget = new SourceInfoWidget( source ); - m_currentInfoWidget = swidget; - m_widget->addWidget( m_currentInfoWidget ); - m_sourceViews.insert( source, swidget ); - } - else - { - m_currentInfoWidget = m_sourceViews.value( source ); - } - - m_widget->setCurrentWidget( m_currentInfoWidget ); - m_superCollectionVisible = false; - - emit numSourcesChanged( 1 ); - return true; -} - - -bool -PlaylistManager::showSuperCollection() -{ - foreach( const Tomahawk::source_ptr& source, APP->sourcelist().sources() ) - { - if ( !m_superCollections.contains( source->collection() ) ) - { - m_superCollections.append( source->collection() ); - m_superCollectionFlatModel->addCollection( source->collection() ); - } - } - - m_widget->setCurrentWidget( m_superCollectionView ); - m_currentProxyModel = m_superCollectionView->proxyModel(); - m_currentModel = m_superCollectionView->model(); - m_currentView = m_superCollectionView; - m_views.insert( (PlaylistInterface*)m_currentProxyModel, m_superCollectionView ); - - m_superCollectionVisible = true; - linkPlaylist(); - - emit numSourcesChanged( m_superCollections.count() ); - return true; -} - - -void -PlaylistManager::setTreeMode() -{ - return; - - qDebug() << Q_FUNC_INFO; - - m_currentMode = 1; - m_widget->setCurrentWidget( m_superCollectionView ); -} - - -void -PlaylistManager::setTableMode() -{ - qDebug() << Q_FUNC_INFO; - - m_currentMode = 0; - m_widget->setCurrentWidget( m_superCollectionView ); -} - - -void -PlaylistManager::setFilter( const QString& filter ) -{ - m_filter = filter; - - m_filterTimer.stop(); - m_filterTimer.setInterval( FILTER_TIMEOUT ); - m_filterTimer.setSingleShot( true ); - m_filterTimer.start(); -} - - -void -PlaylistManager::applyFilter() -{ - qDebug() << Q_FUNC_INFO; - - if ( m_currentProxyModel ) - m_currentProxyModel->setFilterRegExp( m_filter ); -} - - -void -PlaylistManager::unlinkPlaylist() -{ - if ( m_currentModel ) - { - disconnect( m_currentModel, SIGNAL( trackCountChanged( unsigned int ) ), - this, SLOT( onTrackCountChanged( unsigned int ) ) ); - } - - if ( m_currentProxyModel ) - { - disconnect( m_currentProxyModel, SIGNAL( trackCountChanged( unsigned int ) ), - this, SLOT( onTrackCountChanged( unsigned int ) ) ); - - disconnect( m_currentProxyModel, SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ), - this, SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ) ); - - disconnect( m_currentProxyModel, SIGNAL( shuffleModeChanged( bool ) ), - this, SIGNAL( shuffleModeChanged( bool ) ) ); - } -} - - -void -PlaylistManager::linkPlaylist() -{ - connect( m_currentModel, SIGNAL( trackCountChanged( unsigned int ) ), - this, SLOT( onTrackCountChanged( unsigned int ) ) ); - - connect( m_currentProxyModel, SIGNAL( trackCountChanged( unsigned int ) ), - this, SLOT( onTrackCountChanged( unsigned int ) ) ); - - connect( m_currentProxyModel, SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ), - this, SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ) ); - - connect( m_currentProxyModel, SIGNAL( shuffleModeChanged( bool ) ), - this, SIGNAL( shuffleModeChanged( bool ) ) ); - - applyFilter(); - APP->audioEngine()->setPlaylist( (PlaylistInterface*)m_currentProxyModel ); - - emit numTracksChanged( m_currentModel->trackCount() ); - emit repeatModeChanged( m_currentProxyModel->repeatMode() ); - emit shuffleModeChanged( m_currentProxyModel->shuffled() ); -} - - -void -PlaylistManager::onTrackCountChanged( unsigned int ) -{ - emit numTracksChanged( m_currentModel->trackCount() ); - emit numShownChanged( m_currentProxyModel->trackCount() ); -} - - -void -PlaylistManager::setRepeatMode( PlaylistInterface::RepeatMode mode ) -{ - if ( m_currentProxyModel ) - m_currentProxyModel->setRepeatMode( mode ); -} - - -void -PlaylistManager::setShuffled( bool enabled ) -{ - if ( m_currentProxyModel ) - m_currentProxyModel->setShuffled( enabled ); -} - - -void -PlaylistManager::showCurrentTrack() -{ - PlaylistInterface* playlist = APP->audioEngine()->currentPlaylist(); - - if ( m_views.contains( playlist ) ) - { - m_currentView = m_views.value( playlist ); - m_currentProxyModel = m_currentView->proxyModel(); - m_currentModel = m_currentView->model(); - - m_widget->setCurrentWidget( m_currentView ); - } - - if ( m_currentView && m_currentProxyModel ) - m_currentView->scrollTo( m_currentProxyModel->currentItem(), QAbstractItemView::PositionAtCenter ); -} diff --git a/src/playlist/playlistmanager.h b/src/playlist/playlistmanager.h deleted file mode 100644 index ac70eee39..000000000 --- a/src/playlist/playlistmanager.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef PLAYLISTMANAGER_H -#define PLAYLISTMANAGER_H - -#include -#include -#include - -#include "tomahawk/collection.h" -#include "tomahawk/playlistinterface.h" - -class CollectionModel; -class CollectionFlatModel; -class CollectionView; -class PlaylistView; -class TrackProxyModel; -class TrackModel; -class TrackView; -class SourceInfoWidget; - -class PlaylistManager : public QObject -{ -Q_OBJECT - -public: - explicit PlaylistManager( QObject* parent = 0 ); - ~PlaylistManager(); - - QWidget* widget() const { return m_widget; } - - bool isSuperCollectionVisible() const { return true; } - -// QList views( const Tomahawk::playlist_ptr& playlist ) { return m_views.value( playlist ); } - - bool show( const Tomahawk::playlist_ptr& playlist ); - bool show( const Tomahawk::album_ptr& album ); - bool show( const Tomahawk::collection_ptr& collection ); - bool show( const Tomahawk::source_ptr& source ); - bool showSuperCollection(); - - void showCurrentTrack(); - -signals: - void numSourcesChanged( unsigned int sources ); - void numTracksChanged( unsigned int tracks ); - void numArtistsChanged( unsigned int artists ); - void numShownChanged( unsigned int shown ); - - void repeatModeChanged( PlaylistInterface::RepeatMode mode ); - void shuffleModeChanged( bool enabled ); - -public slots: - void setTreeMode(); - void setTableMode(); - - void setFilter( const QString& filter ); - - void setRepeatMode( PlaylistInterface::RepeatMode mode ); - void setShuffled( bool enabled ); - -private slots: - void applyFilter(); - void onTrackCountChanged( unsigned int ); - -private: - void unlinkPlaylist(); - void linkPlaylist(); - - QStackedWidget* m_widget; - - CollectionFlatModel* m_superCollectionFlatModel; - CollectionView* m_superCollectionView; - - QList< Tomahawk::collection_ptr > m_superCollections; - - QHash< PlaylistInterface*, TrackView* > m_views; - QHash< Tomahawk::collection_ptr, CollectionView* > m_collectionViews; - QHash< Tomahawk::playlist_ptr, PlaylistView* > m_playlistViews; - QHash< Tomahawk::album_ptr, PlaylistView* > m_albumViews; - QHash< Tomahawk::source_ptr, SourceInfoWidget* > m_sourceViews; - - TrackProxyModel* m_currentProxyModel; - TrackModel* m_currentModel; - TrackView* m_currentView; - - QWidget* m_currentInfoWidget; - - int m_currentMode; - bool m_superCollectionVisible; - - QTimer m_filterTimer; - QString m_filter; -}; - -#endif // PLAYLISTMANAGER_H diff --git a/src/playlist/playlistmodel.h b/src/playlist/playlistmodel.h deleted file mode 100644 index adc908649..000000000 --- a/src/playlist/playlistmodel.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef PLAYLISTMODEL_H -#define PLAYLISTMODEL_H - -#include -#include - -#include "plitem.h" -#include "trackmodel.h" -#include "tomahawk/tomahawkapp.h" -#include "tomahawk/collection.h" -#include "tomahawk/query.h" -#include "tomahawk/typedefs.h" -#include "tomahawk/playlist.h" -#include "tomahawk/playlistinterface.h" - -class QMetaData; - -class PlaylistModel : public TrackModel -{ -Q_OBJECT - -public: - explicit PlaylistModel( QObject* parent = 0 ); - ~PlaylistModel(); - - int columnCount( const QModelIndex& parent = QModelIndex() ) const; - - QVariant data( const QModelIndex& index, int role ) const; - QVariant headerData( int section, Qt::Orientation orientation, int role ) const; - - virtual bool dropMimeData( const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent ); - - void loadPlaylist( const Tomahawk::playlist_ptr& playlist ); - void loadAlbum( const Tomahawk::album_ptr& album ); - - virtual void removeIndex( const QModelIndex& index ); - -signals: - void repeatModeChanged( PlaylistInterface::RepeatMode mode ); - void shuffleModeChanged( bool enabled ); - - void itemSizeChanged( const QModelIndex& index ); - - void loadingStarts(); - void loadingFinished(); - -private slots: - void onDataChanged(); - - void onRevisionLoaded( Tomahawk::PlaylistRevision revision ); - void onPlaylistChanged(); - - void onTracksAdded( const QList& tracks, const Tomahawk::collection_ptr& collection ); - -private: - QList playlistEntries() const; - - Tomahawk::playlist_ptr m_playlist; - bool m_waitForUpdate; -}; - -#endif // PLAYLISTMODEL_H diff --git a/src/playlist/playlistproxymodel.cpp b/src/playlist/playlistproxymodel.cpp deleted file mode 100644 index d536d9fce..000000000 --- a/src/playlist/playlistproxymodel.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "playlistproxymodel.h" - - -PlaylistProxyModel::PlaylistProxyModel( QObject* parent ) - : TrackProxyModel( parent ) -{ -} diff --git a/src/playlist/playlistproxymodel.h b/src/playlist/playlistproxymodel.h deleted file mode 100644 index ad3dc6ecd..000000000 --- a/src/playlist/playlistproxymodel.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef PLAYLISTPROXYMODEL_H -#define PLAYLISTPROXYMODEL_H - -#include "trackproxymodel.h" - -class PlaylistProxyModel : public TrackProxyModel -{ -Q_OBJECT - -public: - explicit PlaylistProxyModel( QObject* parent = 0 ); -}; - -#endif // PLAYLISTPROXYMODEL_H diff --git a/src/playlist/playlistview.cpp b/src/playlist/playlistview.cpp deleted file mode 100644 index 5027e8bcc..000000000 --- a/src/playlist/playlistview.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include "playlistview.h" - -#include -#include - -#include "playlist/playlistproxymodel.h" - -using namespace Tomahawk; - - -PlaylistView::PlaylistView( QWidget* parent ) - : TrackView( parent ) -{ - setProxyModel( new PlaylistProxyModel( this ) ); - - setContextMenuPolicy( Qt::CustomContextMenu ); - connect( this, SIGNAL( customContextMenuRequested( const QPoint& ) ), SLOT( onCustomContextMenu( const QPoint& ) ) ); -} - - -PlaylistView::~PlaylistView() -{ - qDebug() << Q_FUNC_INFO; -} - - -void -PlaylistView::setModel( TrackModel* model ) -{ - TrackView::setModel( model ); - - // setColumnHidden( 5, true ); // Hide age column per default -} - - -void -PlaylistView::setupMenus() -{ - m_itemMenu.clear(); - - m_playItemAction = m_itemMenu.addAction( tr( "&Play" ) ); - m_itemMenu.addSeparator(); - m_addItemsToPlaylistAction = m_itemMenu.addAction( tr( "&Add to Playlist" ) ); - m_itemMenu.addSeparator(); - m_deleteItemAction = m_itemMenu.addAction( tr( "&Delete Item" ) ); - - if ( model() ) - m_deleteItemAction->setEnabled( !model()->isReadOnly() ); - - connect( m_playItemAction, SIGNAL( triggered() ), SLOT( playItem() ) ); - connect( m_addItemsToPlaylistAction, SIGNAL( triggered() ), SLOT( addItemsToPlaylist() ) ); - connect( m_deleteItemAction, SIGNAL( triggered() ), SLOT( deleteItem() ) ); -} - - -void -PlaylistView::onCustomContextMenu( const QPoint& pos ) -{ - qDebug() << Q_FUNC_INFO; - setupMenus(); - - QModelIndex idx = indexAt( pos ); - idx = idx.sibling( idx.row(), 0 ); - m_contextMenuIndex = idx; - - if ( !idx.isValid() ) - return; - - m_itemMenu.exec( mapToGlobal( pos ) ); -} - - -void -PlaylistView::keyPressEvent( QKeyEvent* event ) -{ - qDebug() << Q_FUNC_INFO; - QTreeView::keyPressEvent( event ); - - if ( !model() ) - return; - - if ( event->key() == Qt::Key_Delete ) - { - qDebug() << "Removing selected items"; - proxyModel()->removeIndexes( selectedIndexes() ); - } -} - - -void -PlaylistView::playItem() -{ - onItemActivated( m_contextMenuIndex ); -} - - -void -PlaylistView::addItemsToPlaylist() -{ -} - - -void -PlaylistView::deleteItem() -{ - proxyModel()->removeIndex( m_contextMenuIndex ); -} diff --git a/src/playlist/playlistview.h b/src/playlist/playlistview.h deleted file mode 100644 index 0addecccf..000000000 --- a/src/playlist/playlistview.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef PLAYLISTVIEW_H -#define PLAYLISTVIEW_H - -#include - -#include "tomahawk/tomahawkapp.h" -#include "trackview.h" - -class PlaylistView : public TrackView -{ -Q_OBJECT - -public: - explicit PlaylistView( QWidget* parent = 0 ); - ~PlaylistView(); - - void setModel( TrackModel* model ); - -protected: - virtual void keyPressEvent( QKeyEvent* event ); - -private slots: - void onCustomContextMenu( const QPoint& pos ); - - void playItem(); - void addItemsToPlaylist(); - void deleteItem(); - -private: - void setupMenus(); - - QModelIndex m_contextMenuIndex; - - QMenu m_itemMenu; - QAction* m_playItemAction; - QAction* m_addItemsToPlaylistAction; - QAction* m_deleteItemAction; -}; - -#endif // PLAYLISTVIEW_H diff --git a/src/playlist/plitem.h b/src/playlist/plitem.h deleted file mode 100644 index 5cd41e471..000000000 --- a/src/playlist/plitem.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef PLITEM_H -#define PLITEM_H - -#include -#include -#include - -#include "tomahawk/query.h" - -#include "tomahawk/typedefs.h" - -class PlItem : public QObject -{ -Q_OBJECT - -public: - ~PlItem(); - - explicit PlItem( PlItem* parent = 0, QAbstractItemModel* model = 0 ); - explicit PlItem( const QString& caption, PlItem* parent = 0 ); - explicit PlItem( const Tomahawk::query_ptr& query, PlItem* parent = 0, int row = -1 ); - explicit PlItem( const Tomahawk::plentry_ptr& entry, PlItem* parent = 0, int row = -1 ); - - const Tomahawk::plentry_ptr& entry() const { return m_entry; }; - const Tomahawk::query_ptr& query() const { return m_query; }; - - bool isPlaying() { return m_isPlaying; } - void setIsPlaying( bool b ) { m_isPlaying = b; emit dataChanged(); } - - PlItem* parent; - QList children; - QHash hash; - QString caption; - int childCount; - QPersistentModelIndex index; - QAbstractItemModel* model; - bool toberemoved; - -signals: - void dataChanged(); - -private slots: - void onResultsAdded( const QList& result ); - void onResultsRemoved( const Tomahawk::result_ptr& result ); - -private: - void setupItem( const Tomahawk::query_ptr& query, PlItem* parent, int row = -1 ); - - Tomahawk::plentry_ptr m_entry; - Tomahawk::query_ptr m_query; - bool m_isPlaying; -}; - -#endif // PLITEM_H diff --git a/src/playlist/trackview.h b/src/playlist/trackview.h deleted file mode 100644 index e376ff6f6..000000000 --- a/src/playlist/trackview.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef TRACKVIEW_H -#define TRACKVIEW_H - -#include -#include - -#include "playlistitemdelegate.h" - -class PlaylistInterface; -class TrackModel; -class TrackProxyModel; - -class TrackView : public QTreeView -{ -Q_OBJECT - -public: - explicit TrackView( QWidget* parent = 0 ); - ~TrackView(); - - void setProxyModel( TrackProxyModel* model ); - - TrackModel* model() { return m_model; } - TrackProxyModel* proxyModel() { return (TrackProxyModel*)m_proxyModel; } - PlaylistItemDelegate* delegate() { return m_delegate; } - - void setModel( TrackModel* model ); - -public slots: - void onItemActivated( const QModelIndex& index ); - -protected: - virtual void resizeEvent( QResizeEvent* event ); - - virtual void startDrag( Qt::DropActions supportedActions ); - virtual void dragEnterEvent( QDragEnterEvent* event ); - virtual void dragLeaveEvent( QDragLeaveEvent* event ) { m_dragging = false; setDirtyRegion( m_dropRect ); } - virtual void dragMoveEvent( QDragMoveEvent* event ); - virtual void dropEvent( QDropEvent* event ); - - void paintEvent( QPaintEvent* event ); - -private slots: - void onItemResized( const QModelIndex& index ); - - void resizeColumns(); - void onSectionResized( int logicalIndex, int oldSize, int newSize ); - - void onFilterChanged( const QString& filter ); - -private: - void restoreColumnsState(); - void saveColumnsState(); - - QPixmap createDragPixmap( int itemCount ) const; - - TrackModel* m_model; - TrackProxyModel* m_proxyModel; - PlaylistInterface* m_modelInterface; - PlaylistItemDelegate* m_delegate; - - QList m_columnWeights; - QList m_columnWidths; - - bool m_resizing; - bool m_dragging; - QRect m_dropRect; -}; - -#endif // TRACKVIEW_H diff --git a/src/pluginapi.cpp b/src/pluginapi.cpp deleted file mode 100644 index 0649c6fb4..000000000 --- a/src/pluginapi.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "tomahawk/pluginapi.h" -#include "tomahawk/tomahawkapp.h" - -using namespace Tomahawk; - -PluginAPI::PluginAPI( Pipeline* p ) - : m_pipeline( p ) -{ -} - - -/*void -PluginAPI::reportResults( const QString& qid, const QList& vresults ) -{ - QList< result_ptr > results; - foreach( QVariantMap m, vresults ) - { - result_ptr rp( new Result( m ) ); - results.append( rp ); - } - m_pipeline->reportResults( QID( qid ), results ); -}*/ - - -void -PluginAPI::addSource( source_ptr s ) -{ - TomahawkApp::instance()->sourcelist().add( s ); -} - - -void -PluginAPI::removeSource( source_ptr s ) -{ - TomahawkApp::instance()->sourcelist().remove( s ); -} - - -void -PluginAPI::addResolver( Resolver* r ) -{ - TomahawkApp::instance()->pipeline()->addResolver( r ); -} diff --git a/src/plugins/fake/CMakeLists.txt b/src/plugins/fake/CMakeLists.txt deleted file mode 100644 index 8fe1f24bf..000000000 --- a/src/plugins/fake/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ -project( tomahawk ) -cmake_minimum_required(VERSION 2.6) -find_package( Qt4 REQUIRED ) - -include( ${QT_USE_FILE} ) - - -SET(TOMAHAWK_INC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../include/") - -SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${TOMAHAWK_INC_DIR}/..") - -set(cpps - fakeplugin.cpp - fakecollection.cpp -) - -set(hs -fakeplugin.h -fakecollection.h -) - -include_directories( - . - .. - ${TOMAHAWK_INC_DIR} - ${CMAKE_CURRENT_BINARY_DIR} - ${QT_INCLUDE_DIR} -) - -qt4_wrap_cpp( mocs ${hs} ) - -ADD_DEFINITIONS(${QT_DEFINITIONS}) -ADD_DEFINITIONS(-DQT_PLUGIN) -#ADD_DEFINITIONS(-DQT_NO_DEBUG) -ADD_DEFINITIONS(-DQT_SHARED) - -add_library(fake SHARED - ${cpps} - ${mocs} -) - -target_link_libraries(fake - ${QT_LIBRARIES} - ${QT_QTSQL_LIBRARIES} -) diff --git a/src/plugins/fake/fakecollection.cpp b/src/plugins/fake/fakecollection.cpp deleted file mode 100644 index fc01399f8..000000000 --- a/src/plugins/fake/fakecollection.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "fakecollection.h" -#include "tomahawk/functimeout.h" - -FakeCollection::FakeCollection(QObject *parent) : - Collection("FakeCollection", parent) -{ -} - -void FakeCollection::load() -{ - QList tracks; - QVariantMap t1, t2, t3; - t1["artist"] = "0AAAAAArtist 1"; - t1["track"] = "0TTTTTTrack 1"; - t1["album"] = "0AAAAAAlbum 1"; - t1["url"] = "fake://1"; - t1["filesize"] = 5000000; - t1["duration"] = 300; - t1["bitrate"] = 192; - tracks << t1; - - new Tomahawk::FuncTimeout(5000, boost::bind(&FakeCollection::removeTracks, - this, tracks)); - - addTracks(tracks); - reportFinishedLoading(); - } diff --git a/src/plugins/fake/fakecollection.h b/src/plugins/fake/fakecollection.h deleted file mode 100644 index 72cb9bc70..000000000 --- a/src/plugins/fake/fakecollection.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef FAKECOLLECTION_H -#define FAKECOLLECTION_H -#include "tomahawk/collection.h" - -class FakeCollection : public Collection -{ -Q_OBJECT -public: - explicit FakeCollection(QObject *parent = 0); - ~FakeCollection() - { - qDebug() << Q_FUNC_INFO; - } - - virtual void load(); - -signals: - -public slots: - -}; - -#endif // FAKECOLLECTION_H diff --git a/src/plugins/fake/fakeplugin.cpp b/src/plugins/fake/fakeplugin.cpp deleted file mode 100644 index 90f50c74e..000000000 --- a/src/plugins/fake/fakeplugin.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "fakeplugin.h" -#include "fakecollection.h" - -Q_EXPORT_PLUGIN2(fake, FakePlugin) - -FakePlugin::FakePlugin(Tomahawk::PluginAPI* api) - : TomahawkPlugin(api), m_api(api) -{ - init(); -} - -TomahawkPlugin * -FakePlugin::factory(Tomahawk::PluginAPI* api) -{ - return new FakePlugin(api); -} - -void FakePlugin::init() -{ - source_ptr src(new Source("Mr. Fake")); - collection_ptr coll(new FakeCollection); - src->addCollection(coll); - m_api->addSource(src); - coll->load(); -}; - diff --git a/src/plugins/fake/fakeplugin.h b/src/plugins/fake/fakeplugin.h deleted file mode 100644 index 4244aa64c..000000000 --- a/src/plugins/fake/fakeplugin.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef TOMAHAWK_LIB_PLUGIN_H -#define TOMAHAWK_LIB_PLUGIN_H -#include - -#include "tomahawk/plugin_includes.h" - -class FakePlugin : public QObject, public TomahawkPlugin -{ - Q_OBJECT - Q_INTERFACES(TomahawkPlugin) - -public: - - FakePlugin(){}; - - FakePlugin(Tomahawk::PluginAPI* api); - TomahawkPlugin * factory(Tomahawk::PluginAPI* api); - QString name() const { return "FakePlugin"; }; - QString description() const { return "Fake stuff, hardcoded"; }; - -private: - - void init(); - - Tomahawk::PluginAPI* m_api; -}; - - - -#endif - diff --git a/src/query.cpp b/src/query.cpp deleted file mode 100644 index b5de0088e..000000000 --- a/src/query.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "tomahawk/query.h" - -#include - -using namespace Tomahawk; - -Query::Query( const QVariant& v ) - : m_v( v ) - , m_solved( false ) -{ - // ensure a QID is present: - QVariantMap m = m_v.toMap(); - - m_artist = m.value( "artist" ).toString(); - m_album = m.value( "album" ).toString(); - m_track = m.value( "track" ).toString(); - - m_qid = m.value( "qid" ).toString(); -} - - -void -Query::addResults( const QList< Tomahawk::result_ptr >& newresults ) -{ - bool becameSolved = false; - { - QMutexLocker lock( &m_mut ); - m_results.append( newresults ); - qStableSort( m_results.begin(), m_results.end(), Query::resultSorter ); - - // hook up signals, and check solved status - foreach( const result_ptr& rp, newresults ) - { - connect( rp.data(), SIGNAL( becomingUnavailable() ), SLOT( resultUnavailable() ) ); - if( !m_solved && rp->score() > 0.99 ) - { - m_solved = true; - becameSolved = true; - } - } - } - emit resultsAdded( newresults ); - if( becameSolved ) emit solvedStateChanged( true ); -} - - -void -Query::resultUnavailable() -{ - Result* result = (Result*) sender(); - Q_ASSERT( result ); - - for ( int i = 0; i < m_results.length(); ++i ) - { - if ( m_results.value( i ).data() == result ) - { - result_ptr r = m_results.value( i ); - m_results.removeAt( i ); - - emit resultsRemoved( r ); - break; - } - } - - if ( m_results.isEmpty() ) // FIXME proper score checking - emit solvedStateChanged( false ); -} - - -void -Query::removeResult( const Tomahawk::result_ptr& result ) -{ - { - QMutexLocker lock( &m_mut ); - m_results.removeAll( result ); - } - emit resultsRemoved( result ); - - if ( m_results.isEmpty() ) // FIXME proper score checking - emit solvedStateChanged( false ); -} - - -QList< result_ptr > -Query::results() const -{ - QMutexLocker lock( &m_mut ); - return m_results; -} - - -unsigned int -Query::numResults() const -{ - QMutexLocker lock( &m_mut ); - return m_results.length(); -} - - -QID Query::id() const -{ - if ( m_qid.isEmpty() ) - { - m_qid = uuid(); - - QVariantMap m = m_v.toMap(); - m.insert( "qid", m_qid ); - - m_v = m; - } - return m_qid; -} - - -bool Query::resultSorter( const result_ptr& left, const result_ptr& right ) -{ - return left->score() > right->score(); -} - diff --git a/src/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp new file mode 100644 index 000000000..d4c0cc5f5 --- /dev/null +++ b/src/resolvers/qtscriptresolver.cpp @@ -0,0 +1,153 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "qtscriptresolver.h" + +#include "artist.h" +#include "album.h" +#include "pipeline.h" +#include "sourcelist.h" +#include "utils/tomahawkutils.h" + + +QtScriptResolver::QtScriptResolver( const QString& scriptPath ) + : Tomahawk::ExternalResolver( scriptPath ) + , m_engine( new ScriptEngine( this ) ) + , m_thread( new QThread( this ) ) + , m_ready( false ) + , m_stopped( false ) +{ + qDebug() << Q_FUNC_INFO << scriptPath; + + m_thread->start(); + + QFile scriptFile( scriptPath ); + if ( !scriptFile.open( QIODevice::ReadOnly ) ) + { + qDebug() << Q_FUNC_INFO << "Failed loading JavaScript resolver:" << scriptPath; + deleteLater(); + return; + } + + m_engine->mainFrame()->setHtml( "" ); + m_engine->mainFrame()->evaluateJavaScript( scriptFile.readAll() ); + scriptFile.close(); + + QVariantMap m = m_engine->mainFrame()->evaluateJavaScript( "getSettings();" ).toMap(); + m_name = m.value( "name" ).toString(); + m_weight = m.value( "weight", 0 ).toUInt(); + m_timeout = m.value( "timeout", 25 ).toUInt() * 1000; + m_preference = m.value( "preference", 0 ).toUInt(); + + qDebug() << "QTSCRIPT" << filePath() << "READY," << endl + << "name" << m_name << endl + << "weight" << m_weight << endl + << "timeout" << m_timeout << endl + << "preference" << m_preference; + + m_engine->moveToThread( m_thread ); + m_ready = true; + Tomahawk::Pipeline::instance()->addResolver( this ); + + connect( this, SIGNAL( destroyed( QObject* ) ), m_thread, SLOT( deleteLater() ) ); +} + + +QtScriptResolver::~QtScriptResolver() +{ + Tomahawk::Pipeline::instance()->removeResolver( this ); + delete m_engine; +} + + +void +QtScriptResolver::resolve( const Tomahawk::query_ptr& query ) +{ + QMetaObject::invokeMethod( m_engine, "resolve", Qt::QueuedConnection, Q_ARG( Tomahawk::query_ptr, query ) ); +} + + +void +ScriptEngine::resolve( const Tomahawk::query_ptr& query ) +{ + qDebug() << Q_FUNC_INFO << query->toString(); + QString eval = QString( "resolve( '%1', '%2', '%3', '%4' );" ) + .arg( query->id().replace( "'", "\\'" ) ) + .arg( query->artist().replace( "'", "\\'" ) ) + .arg( query->album().replace( "'", "\\'" ) ) + .arg( query->track().replace( "'", "\\'" ) ); + + QList< Tomahawk::result_ptr > results; + + QVariantMap m = mainFrame()->evaluateJavaScript( eval ).toMap(); + qDebug() << "JavaScript Result:" << m; + + const QString qid = query->id(); + const QVariantList reslist = m.value( "results" ).toList(); + + foreach( const QVariant& rv, reslist ) + { + QVariantMap m = rv.toMap(); + qDebug() << "RES" << m; + + Tomahawk::result_ptr rp( new Tomahawk::Result() ); + Tomahawk::artist_ptr ap = Tomahawk::Artist::get( 0, m.value( "artist" ).toString() ); + rp->setArtist( ap ); + rp->setAlbum( Tomahawk::Album::get( 0, m.value( "album" ).toString(), ap ) ); + rp->setTrack( m.value( "track" ).toString() ); + rp->setBitrate( m.value( "bitrate" ).toUInt() ); + rp->setUrl( m.value( "url" ).toString() ); + rp->setSize( m.value( "size" ).toUInt() ); + rp->setScore( m.value( "score" ).toFloat() * ( (float)m_parent->weight() / 100.0 ) ); + rp->setRID( uuid() ); + rp->setFriendlySource( m_parent->name() ); + + if ( m.contains( "year" ) ) + { + QVariantMap attr; + attr[ "releaseyear" ] = m.value( "year" ); + rp->setAttributes( attr ); + } + + rp->setDuration( m.value( "duration", 0 ).toUInt() ); + if ( rp->duration() <= 0 && m.contains( "durationString" ) ) + { + QTime time = QTime::fromString( m.value( "durationString" ).toString(), "hh:mm:ss" ); + rp->setDuration( time.secsTo( QTime( 0, 0 ) ) * -1 ); + } + + rp->setMimetype( m.value( "mimetype" ).toString() ); + if ( rp->mimetype().isEmpty() ) + { + rp->setMimetype( TomahawkUtils::extensionToMimetype( m.value( "extension" ).toString() ) ); + Q_ASSERT( !rp->mimetype().isEmpty() ); + } + + results << rp; + } + + Tomahawk::Pipeline::instance()->reportResults( qid, results ); +} + + +void +QtScriptResolver::stop() +{ + m_stopped = true; + emit finished(); +} diff --git a/src/resolvers/qtscriptresolver.h b/src/resolvers/qtscriptresolver.h new file mode 100644 index 000000000..05b55cc71 --- /dev/null +++ b/src/resolvers/qtscriptresolver.h @@ -0,0 +1,92 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef QTSCRIPTRESOLVER_H +#define QTSCRIPTRESOLVER_H + +#include "resolver.h" +#include "query.h" +#include "result.h" + +#include +#include +#include +#include +#include + +class QtScriptResolver; + +class ScriptEngine : public QWebPage +{ +Q_OBJECT + +public: + explicit ScriptEngine( QtScriptResolver* parent ) + : QWebPage( (QObject*)parent ) + , m_parent( parent ) + {} + +public slots: + void resolve( const Tomahawk::query_ptr& query ); + + bool shouldInterruptJavaScript() + { + return false; + } + +protected: + virtual void javaScriptConsoleMessage( const QString & message, int lineNumber, const QString & sourceID ) + { qDebug() << "JAVASCRIPT ERROR:" << message << lineNumber << sourceID; } + +private: + QtScriptResolver* m_parent; +}; + +class QtScriptResolver : public Tomahawk::ExternalResolver +{ +Q_OBJECT + +public: + explicit QtScriptResolver( const QString& scriptPath ); + virtual ~QtScriptResolver(); + + virtual QString name() const { return m_name; } + virtual unsigned int weight() const { return m_weight; } + virtual unsigned int preference() const { return m_preference; } + virtual unsigned int timeout() const { return m_timeout; } + +public slots: + virtual void resolve( const Tomahawk::query_ptr& query ); + virtual void stop(); + +signals: + void finished(); + +private slots: + +private: + ScriptEngine* m_engine; + QThread* m_thread; + + QString m_name; + unsigned int m_weight, m_preference, m_timeout; + + bool m_ready, m_stopped; +}; + +#endif // QTSCRIPTRESOLVER_H diff --git a/src/resolvers/scriptresolver.cpp b/src/resolvers/scriptresolver.cpp new file mode 100644 index 000000000..913588663 --- /dev/null +++ b/src/resolvers/scriptresolver.cpp @@ -0,0 +1,255 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "scriptresolver.h" + +#include + +#include "artist.h" +#include "album.h" +#include "pipeline.h" +#include "sourcelist.h" +#include "functimeout.h" +#include "utils/tomahawkutils.h" + + +ScriptResolver::ScriptResolver( const QString& exe ) + : Tomahawk::ExternalResolver( exe ) + , m_num_restarts( 0 ) + , m_msgsize( 0 ) + , m_ready( false ) + , m_stopped( false ) +{ + qDebug() << Q_FUNC_INFO << exe; + connect( &m_proc, SIGNAL( readyReadStandardError() ), SLOT( readStderr() ) ); + connect( &m_proc, SIGNAL( readyReadStandardOutput() ), SLOT( readStdout() ) ); + connect( &m_proc, SIGNAL( finished( int, QProcess::ExitStatus ) ), SLOT( cmdExited( int, QProcess::ExitStatus ) ) ); + + m_proc.start( filePath() ); +} + + +ScriptResolver::~ScriptResolver() +{ + Tomahawk::Pipeline::instance()->removeResolver( this ); +} + + +void +ScriptResolver::readStderr() +{ + qDebug() << "SCRIPT_STDERR" << filePath() << m_proc.readAllStandardError(); +} + + +void +ScriptResolver::readStdout() +{ + qDebug() << Q_FUNC_INFO << m_proc.bytesAvailable(); + if( m_msgsize == 0 ) + { + if( m_proc.bytesAvailable() < 4 ) return; + quint32 len_nbo; + m_proc.read( (char*) &len_nbo, 4 ); + m_msgsize = qFromBigEndian( len_nbo ); + qDebug() << Q_FUNC_INFO << "msgsize" << m_msgsize; + } + + if( m_msgsize > 0 ) + { + m_msg.append( m_proc.read( m_msgsize - m_msg.length() ) ); + } + + if( m_msgsize == (quint32) m_msg.length() ) + { + handleMsg( m_msg ); + m_msgsize = 0; + m_msg.clear(); + + if( m_proc.bytesAvailable() ) + QTimer::singleShot( 0, this, SLOT( readStdout() ) ); + } +} + + +void +ScriptResolver::sendMsg( const QByteArray& msg ) +{ + qDebug() << Q_FUNC_INFO << m_ready << msg << msg.length(); + + if( !m_ready ) return; + + quint32 len; + qToBigEndian( msg.length(), (uchar*) &len ); + m_proc.write( (const char*) &len, 4 ); + m_proc.write( msg ); +} + + +void +ScriptResolver::handleMsg( const QByteArray& msg ) +{ + qDebug() << Q_FUNC_INFO << msg.size() << QString::fromAscii( msg ); + bool ok; + QVariant v = m_parser.parse( msg, &ok ); + if( !ok || v.type() != QVariant::Map ) + { + Q_ASSERT(false); + return; + } + QVariantMap m = v.toMap(); + QString msgtype = m.value( "_msgtype" ).toString(); + + if( msgtype == "settings" ) + { + doSetup( m ); + return; + } + + if( msgtype == "results" ) + { + const QString qid = m.value( "qid" ).toString(); + if ( !m_queryState.contains( qid ) ) + { + //FIXME: We should always accept results, even if they arrive too late. Needs some work in Pipeline. + qDebug() << "Ignoring results for" << qid << "- arrived after timeout."; + return; + } + m_queryState.remove( qid ); + + QList< Tomahawk::result_ptr > results; + const QVariantList reslist = m.value( "results" ).toList(); + + foreach( const QVariant& rv, reslist ) + { + QVariantMap m = rv.toMap(); + qDebug() << "RES" << m; + + Tomahawk::result_ptr rp( new Tomahawk::Result() ); + Tomahawk::artist_ptr ap = Tomahawk::Artist::get( 0, m.value( "artist" ).toString() ); + rp->setArtist( ap ); + rp->setAlbum( Tomahawk::Album::get( 0, m.value( "album" ).toString(), ap ) ); + rp->setTrack( m.value( "track" ).toString() ); + rp->setDuration( m.value( "duration" ).toUInt() ); + rp->setBitrate( m.value( "bitrate" ).toUInt() ); + rp->setUrl( m.value( "url" ).toString() ); + rp->setSize( m.value( "size" ).toUInt() ); + rp->setScore( m.value( "score" ).toFloat() * ( (float)weight() / 100.0 ) ); + rp->setRID( uuid() ); + rp->setFriendlySource( m_name ); + + rp->setMimetype( m.value( "mimetype" ).toString() ); + if ( rp->mimetype().isEmpty() ) + { + rp->setMimetype( TomahawkUtils::extensionToMimetype( m.value( "extension" ).toString() ) ); + Q_ASSERT( !rp->mimetype().isEmpty() ); + } + + results << rp; + } + + Tomahawk::Pipeline::instance()->reportResults( qid, results ); + } +} + + +void +ScriptResolver::cmdExited( int code, QProcess::ExitStatus status ) +{ + m_ready = false; + qDebug() << Q_FUNC_INFO << "SCRIPT EXITED, code" << code << "status" << status << filePath(); + Tomahawk::Pipeline::instance()->removeResolver( this ); + + if( m_stopped ) + { + qDebug() << "*** Script resolver stopped "; + emit finished(); + + return; + } + + if( m_num_restarts < 10 ) + { + m_num_restarts++; + qDebug() << "*** Restart num" << m_num_restarts; + m_proc.start( filePath() ); + } + else + { + qDebug() << "*** Reached max restarts, not restarting."; + } +} + + +void +ScriptResolver::resolve( const Tomahawk::query_ptr& query ) +{ + QVariantMap m; + m.insert( "_msgtype", "rq" ); + m.insert( "artist", query->artist() ); + m.insert( "track", query->track() ); + m.insert( "qid", query->id() ); + + const QByteArray msg = m_serializer.serialize( QVariant( m ) ); + qDebug() << "ASKING SCRIPT RESOLVER TO RESOLVE:" << msg; + sendMsg( msg ); + + m_queryState.insert( query->id(), 1 ); + new Tomahawk::FuncTimeout( m_timeout, boost::bind( &ScriptResolver::onTimeout, this, query ) ); +} + + +void +ScriptResolver::doSetup( const QVariantMap& m ) +{ + qDebug() << Q_FUNC_INFO << m; + m_name = m.value( "name" ).toString(); + m_weight = m.value( "weight", 0 ).toUInt(); + m_timeout = m.value( "timeout", 25 ).toUInt() * 1000; + m_preference = m.value( "preference", 0 ).toUInt(); + qDebug() << "SCRIPT" << filePath() << "READY," << endl + << "name" << m_name << endl + << "weight" << m_weight << endl + << "timeout" << m_timeout << endl + << "preference" << m_preference; + + m_ready = true; + Tomahawk::Pipeline::instance()->addResolver( this ); +} + + +void +ScriptResolver::stop() +{ + m_stopped = true; + m_proc.kill(); +} + + +void +ScriptResolver::onTimeout( const Tomahawk::query_ptr& query ) +{ + // check if this query has already been processed + if ( !m_queryState.contains( query->id() ) ) + return; + + // if not, it's time to emit an empty result list + m_queryState.remove( query->id() ); + QList< Tomahawk::result_ptr > results; + Tomahawk::Pipeline::instance()->reportResults( query->id(), results ); +} diff --git a/src/resolvers/scriptresolver.h b/src/resolvers/scriptresolver.h new file mode 100644 index 000000000..dcd5eeed6 --- /dev/null +++ b/src/resolvers/scriptresolver.h @@ -0,0 +1,79 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef SCRIPTRESOLVER_H +#define SCRIPTRESOLVER_H + +#include + +#include +#include +#include + +#include "resolver.h" +#include "query.h" +#include "result.h" + +class ScriptResolver : public Tomahawk::ExternalResolver +{ +Q_OBJECT + +public: + explicit ScriptResolver( const QString& exe ); + virtual ~ScriptResolver(); + + virtual QString name() const { return m_name; } + virtual unsigned int weight() const { return m_weight; } + virtual unsigned int preference() const { return m_preference; } + virtual unsigned int timeout() const { return m_timeout; } + +signals: + void finished(); + +public slots: + virtual void stop(); + virtual void resolve( const Tomahawk::query_ptr& query ); + +private slots: + void readStderr(); + void readStdout(); + void cmdExited( int code, QProcess::ExitStatus status ); + + void onTimeout( const Tomahawk::query_ptr& query ); + +private: + void handleMsg( const QByteArray& msg ); + void sendMsg( const QByteArray& msg ); + void doSetup( const QVariantMap& m ); + + QProcess m_proc; + QString m_name; + unsigned int m_weight, m_preference, m_timeout, m_num_restarts; + + quint32 m_msgsize; + QByteArray m_msg; + + bool m_ready, m_stopped; + + QHash< QString /* QID */, unsigned int /* state */ > m_queryState; + + QJson::Parser m_parser; + QJson::Serializer m_serializer; +}; + +#endif // SCRIPTRESOLVER_H diff --git a/src/result.cpp b/src/result.cpp deleted file mode 100644 index b84c7ec9e..000000000 --- a/src/result.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "tomahawk/result.h" - -#include "tomahawk/album.h" - -using namespace Tomahawk; - - -Result::Result( const QVariant& v, const collection_ptr& collection ) - : m_v( v ) - , m_collection( collection ) -{ - QVariantMap m = m_v.toMap(); - - m_artist = artist_ptr( new Artist( m.value( "artist" ).toString() ) ); - m_album = Album::get( m.value( "albumid" ).toUInt(), m.value( "album" ).toString(), m_artist, collection ); - m_track = m.value( "track" ).toString(); - m_url = m.value( "url" ).toString(); - m_mimetype = m.value( "mimetype" ).toString(); - - m_duration = m.value( "duration" ).toUInt(); - m_bitrate = m.value( "bitrate" ).toUInt(); - m_size = m.value( "size" ).toUInt(); - m_albumpos = m.value( "albumpos" ).toUInt(); - m_modtime = m.value( "mtime" ).toUInt(); - - m_id = m.value( "id" ).toUInt(); - - if ( !m_collection.isNull() ) - connect( m_collection->source().data(), SIGNAL( offline() ), SIGNAL( becomingUnavailable() ), Qt::QueuedConnection ); -} - - -float -Result::score() const -{ - return m_v.toMap().value( "score", 0.0 ).toFloat(); -} - - -RID -Result::id() const -{ - if ( m_rid.isEmpty() ) - { - m_rid = m_v.toMap().value( "sid" ).toString(); - } - return m_rid; -}; - - -QString -Result::toString() const -{ - return QString( "Result(%1 %2\t%3 - %4 %5" ).arg( id() ).arg( score() ).arg( artist()->name() ).arg( track() ).arg( url() ); -} diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp new file mode 100644 index 000000000..ebbe0eabb --- /dev/null +++ b/src/scanmanager.cpp @@ -0,0 +1,136 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "scanmanager.h" + +#include +#include +#include + +#include "musicscanner.h" +#include "tomahawksettings.h" +#include "tomahawkutils.h" + +ScanManager* ScanManager::s_instance = 0; + + +ScanManager* +ScanManager::instance() +{ + return s_instance; +} + + +ScanManager::ScanManager( QObject* parent ) + : QObject( parent ) + , m_scanner( 0 ) + , m_musicScannerThreadController( 0 ) +{ + s_instance = this; + + connect( TomahawkSettings::instance(), SIGNAL( changed() ), SLOT( onSettingsChanged() ) ); + + if ( TomahawkSettings::instance()->hasScannerPath() ) + m_currScannerPath = TomahawkSettings::instance()->scannerPath(); +} + + +ScanManager::~ScanManager() +{ + qDebug() << Q_FUNC_INFO; + + if( m_musicScannerThreadController ) + { + m_musicScannerThreadController->quit(); + + while( !m_musicScannerThreadController->isFinished() ) + { + QCoreApplication::processEvents( QEventLoop::AllEvents, 200 ); + TomahawkUtils::Sleep::msleep( 100 ); + } + + if( m_scanner ) + { + delete m_scanner; + m_scanner = 0; + } + + delete m_musicScannerThreadController; + m_musicScannerThreadController = 0; + } +} + + +void +ScanManager::onSettingsChanged() +{ + if ( TomahawkSettings::instance()->hasScannerPath() && + m_currScannerPath != TomahawkSettings::instance()->scannerPath() ) + { + m_currScannerPath = TomahawkSettings::instance()->scannerPath(); + runManualScan( m_currScannerPath ); + } +} + + +void +ScanManager::runManualScan( const QString& path ) +{ + qDebug() << Q_FUNC_INFO; + if ( !m_musicScannerThreadController && !m_scanner ) //still running if these are not zero + { + m_musicScannerThreadController = new QThread( this ); + m_scanner = new MusicScanner( path ); + m_scanner->moveToThread( m_musicScannerThreadController ); + connect( m_scanner, SIGNAL( finished() ), SLOT( scannerFinished() ) ); + m_musicScannerThreadController->start( QThread::IdlePriority ); + QMetaObject::invokeMethod( m_scanner, "startScan" ); + } + else + qDebug() << "Could not run manual scan, old scan still running"; +} + + +void +ScanManager::scannerFinished() +{ + qDebug() << Q_FUNC_INFO; + connect( m_musicScannerThreadController, SIGNAL( finished() ), SLOT( scannerQuit() ) ); + m_musicScannerThreadController->quit(); +} + + +void +ScanManager::scannerQuit() +{ + qDebug() << Q_FUNC_INFO; + connect( m_scanner, SIGNAL( destroyed( QObject* ) ), SLOT( scannerDestroyed( QObject* ) ) ); + delete m_scanner; + m_scanner = 0; +} + + +void +ScanManager::scannerDestroyed( QObject* scanner ) +{ + qDebug() << Q_FUNC_INFO; + m_musicScannerThreadController->deleteLater(); + m_musicScannerThreadController = 0; + emit finished(); +} + diff --git a/src/scanmanager.h b/src/scanmanager.h new file mode 100644 index 000000000..0a115b444 --- /dev/null +++ b/src/scanmanager.h @@ -0,0 +1,59 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef SCANMANAGER_H +#define SCANMANAGER_H + +#include + +#include "dllmacro.h" + +class MusicScanner; +class QThread; + +class ScanManager : public QObject +{ +Q_OBJECT + +public: + static ScanManager* instance(); + + explicit ScanManager( QObject* parent = 0 ); + virtual ~ScanManager(); + + void runManualScan( const QString& path ); + +signals: + void finished(); + +private slots: + void scannerQuit(); + void scannerFinished(); + void scannerDestroyed( QObject* scanner ); + + void onSettingsChanged(); + +private: + static ScanManager* s_instance; + + MusicScanner* m_scanner; + QThread* m_musicScannerThreadController; + QString m_currScannerPath; +}; + +#endif diff --git a/src/scriptresolver.cpp b/src/scriptresolver.cpp deleted file mode 100644 index c240f03ab..000000000 --- a/src/scriptresolver.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include -#include "tomahawk/tomahawkapp.h" -#include "scriptresolver.h" - -ScriptResolver::ScriptResolver(const QString& exe) : - Tomahawk::Resolver() - , m_cmd( exe ) - , m_num_restarts( 0 ) - , m_msgsize( 0 ) - , m_ready( false ) -{ - qDebug() << Q_FUNC_INFO << exe; - connect( &m_proc, SIGNAL(readyReadStandardError()), SLOT(readStderr()) ); - connect( &m_proc, SIGNAL(readyReadStandardOutput()), SLOT(readStdout()) ); - connect( &m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(cmdExited(int,QProcess::ExitStatus)) ); - - m_proc.start( m_cmd ); -} - - -void ScriptResolver::readStderr() -{ - qDebug() << "SCRIPT_STDERR" << m_cmd << m_proc.readAllStandardError(); -} - - -void ScriptResolver::readStdout() -{ - qDebug() << Q_FUNC_INFO << m_proc.bytesAvailable(); - if( m_msgsize == 0 ) - { - if( m_proc.bytesAvailable() < 4 ) return; - quint32 len_nbo; - m_proc.read( (char*) &len_nbo, 4 ); - m_msgsize = qFromBigEndian( len_nbo ); - qDebug() << Q_FUNC_INFO << "msgsize" << m_msgsize; - } - - if( m_msgsize > 0 ) - { - m_msg.append( m_proc.read( m_msgsize - m_msg.length() ) ); - } - - if( m_msgsize == (quint32) m_msg.length() ) - { - handleMsg( m_msg ); - m_msgsize = 0; - m_msg.clear(); - if( m_proc.bytesAvailable() ) QTimer::singleShot( 0, this, SLOT(readStdout()) ); - } -} - - -void ScriptResolver::sendMsg( const QByteArray& msg ) -{ - qDebug() << Q_FUNC_INFO << m_ready << msg; - - if( !m_ready ) return; - - quint32 len; - qToBigEndian( msg.length(), (uchar*) &len ); - m_proc.write( (const char*) &len, 4 ); - m_proc.write( msg ); -} - - -void ScriptResolver::handleMsg( const QByteArray& msg ) -{ - qDebug() << Q_FUNC_INFO << msg.size() << QString::fromAscii(msg); - bool ok; - QVariant v = m_parser.parse( msg, &ok ); - if( !ok || v.type() != QVariant::Map ) - { - Q_ASSERT(false); - return; - } - QVariantMap m = v.toMap(); - const QString& msgtype = m.value( "_msgtype" ).toString(); - - if( msgtype == "settings" ) - { - doSetup( m ); - return; - } - - if( msgtype == "results" ) - { - QList< Tomahawk::result_ptr > results; - const QString& qid = m.value( "qid" ).toString(); - const QVariantList& reslist = m.value( "results" ).toList(); - Tomahawk::collection_ptr coll = APP->sourcelist().getLocal()->collection(); - foreach( const QVariant& rv, reslist ) - { - qDebug() << "RES" << rv; - Tomahawk::result_ptr rp( new Tomahawk::Result( rv, coll ) ); - results << rp; - } - APP->pipeline()->reportResults( qid, results ); - } -} - - -void ScriptResolver::cmdExited(int code, QProcess::ExitStatus status) -{ - m_ready = false; - qDebug() << Q_FUNC_INFO << "SCRIPT EXITED, code" << code << "status" << status << m_cmd; - APP->pipeline()->removeResolver( this ); - - if( m_num_restarts < 10 ) - { - m_num_restarts++; - qDebug() << "*** Restart num" << m_num_restarts; - m_proc.start( m_cmd ); - } - else - { - qDebug() << "*** Reached max restarts, not restarting."; - } -} - - -void ScriptResolver::resolve( const QVariant& v ) -{ - QVariantMap m = v.toMap(); - m.insert( "_msgtype", "rq" ); - const QByteArray msg = m_serializer.serialize( m ); - sendMsg( msg ); -} - - -void ScriptResolver::doSetup( const QVariantMap& m ) -{ - qDebug() << Q_FUNC_INFO << m; - m_name = m.value( "name" ).toString(); - m_weight = m.value( "weight", 0 ).toUInt(); - m_timeout = m.value( "timeout", 5000 ).toUInt(); - m_preference = m.value( "preference", 0 ).toUInt(); - qDebug() << "SCRIPT" << m_cmd << "READY, " << endl - << " name" << m_name << endl - << " weight" << m_weight << endl - << " timeout" << m_timeout << endl - << " preference" << m_preference - ; - m_ready = true; - APP->pipeline()->addResolver( this ); -} diff --git a/src/scriptresolver.h b/src/scriptresolver.h deleted file mode 100644 index 6b37ae482..000000000 --- a/src/scriptresolver.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef SCRIPTRESOLVER_H -#define SCRIPTRESOLVER_H - -#include - -#include -#include -#include - -#include "tomahawk/resolver.h" -#include "tomahawk/result.h" - -class ScriptResolver : public Tomahawk::Resolver -{ -Q_OBJECT -public: - explicit ScriptResolver(const QString& exe); - - virtual QString name() const { return m_name; } - virtual unsigned int weight() const { return m_weight; } - virtual unsigned int preference() const { return m_preference; } - virtual unsigned int timeout() const { return m_timeout; } - - virtual void resolve( const QVariant& v ); - -signals: - -public slots: - -private slots: - void readStderr(); - void readStdout(); - void cmdExited(int code, QProcess::ExitStatus status); - -private: - void handleMsg( const QByteArray& msg ); - void sendMsg( const QByteArray& msg ); - void doSetup( const QVariantMap& m ); - - QProcess m_proc; - QString m_name, m_cmd; - unsigned int m_weight, m_preference, m_timeout, m_num_restarts; - - quint32 m_msgsize; - QByteArray m_msg; - - bool m_ready; - - QJson::Parser m_parser; - QJson::Serializer m_serializer; - -}; - -#endif // SCRIPTRESOLVER_H diff --git a/src/scrobbler.cpp b/src/scrobbler.cpp index 7cbb94659..06ebe33f5 100644 --- a/src/scrobbler.cpp +++ b/src/scrobbler.cpp @@ -1,65 +1,53 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "scrobbler.h" #include #include #include -#include -#include "tomahawk/album.h" -#include "tomahawk/typedefs.h" +#include "album.h" +#include "typedefs.h" #include "audio/audioengine.h" #include "tomahawksettings.h" +#include "tomahawk/tomahawkapp.h" +#include "tomahawk/infosystem.h" -#include -#include - - -static QString -md5( const QByteArray& src ) -{ - QByteArray const digest = QCryptographicHash::hash( src, QCryptographicHash::Md5 ); - return QString::fromLatin1( digest.toHex() ).rightJustified( 32, '0' ); -} - +static QString s_infoIdentifier = QString("SCROBBLER"); Scrobbler::Scrobbler( QObject* parent ) : QObject( parent ) - , m_scrobbler( 0 ) , m_reachedScrobblePoint( false ) - , m_authJob( 0 ) { - lastfm::ws::ApiKey = "2aa1089093868876bba20b0482b9cef9"; - lastfm::ws::SharedSecret = "a7085ef81d7b46fe6ffe11c15b85902f"; - lastfm::ws::Username = TomahawkApp::instance()->settings()->lastFmUsername(); + connect( AudioEngine::instance(), SIGNAL( timerSeconds( unsigned int ) ), + SLOT( engineTick( unsigned int ) ), Qt::QueuedConnection ); - m_pw = TomahawkApp::instance()->settings()->lastFmPassword(); + connect( TomahawkApp::instance()->infoSystem(), + SIGNAL( info( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ), + SLOT( infoSystemInfo( QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, Tomahawk::InfoSystem::InfoCustomDataHash ) ) ); - if( TomahawkApp::instance()->settings()->scrobblingEnabled() && !lastfm::ws::Username.isEmpty() ) - { - createScrobbler(); - } - - //HACK work around a bug in liblastfm---it doesn't create its config dir, so when it - // tries to write the track cache, it fails silently. until we have a fixed version, do this - // code taken from Amarok (src/services/lastfm/ScrobblerAdapter.cpp) - QString lpath = QDir::home().filePath( ".local/share/Last.fm" ); - QDir ldir = QDir( lpath ); - if( !ldir.exists() ) - { - ldir.mkpath( lpath ); - } - - connect( TomahawkApp::instance(), SIGNAL( settingsChanged() ), - SLOT( settingsChanged() ), Qt::QueuedConnection ); - - connect( TomahawkApp::instance()->audioEngine(), SIGNAL( timerSeconds( unsigned int ) ), - SLOT( engineTick( unsigned int ) ), Qt::QueuedConnection ); + connect( TomahawkApp::instance()->infoSystem(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) ); } Scrobbler::~Scrobbler() { - delete m_scrobbler; } @@ -69,26 +57,23 @@ Scrobbler::trackStarted( const Tomahawk::result_ptr& track ) Q_ASSERT( QThread::currentThread() == thread() ); // qDebug() << Q_FUNC_INFO; - if( !m_scrobbler ) - return; - if( m_reachedScrobblePoint ) { m_reachedScrobblePoint = false; scrobble(); } - m_track = lastfm::MutableTrack(); - m_track.stamp(); - - m_track.setTitle( track->track() ); - m_track.setArtist( track->artist()->name() ); - m_track.setAlbum( track->album()->name() ); - m_track.setDuration( track->duration() ); - m_track.setSource( lastfm::Track::Player ); - - m_scrobbler->nowPlaying( m_track ); - m_scrobblePoint = ScrobblePoint( m_track.duration() / 2 ); + Tomahawk::InfoSystem::InfoCustomDataHash trackInfo; + + trackInfo["title"] = QVariant::fromValue< QString >( track->track() ); + trackInfo["artist"] = QVariant::fromValue< QString >( track->artist()->name() ); + trackInfo["album"] = QVariant::fromValue< QString >( track->album()->name() ); + trackInfo["duration"] = QVariant::fromValue< uint >( track->duration() ); + TomahawkApp::instance()->infoSystem()->getInfo( + s_infoIdentifier, Tomahawk::InfoSystem::InfoMiscSubmitNowPlaying, + QVariant::fromValue< Tomahawk::InfoSystem::InfoCustomDataHash >( trackInfo ), Tomahawk::InfoSystem::InfoCustomDataHash() ); + + m_scrobblePoint = ScrobblePoint( track->duration() / 2 ); } @@ -131,99 +116,31 @@ void Scrobbler::scrobble() { Q_ASSERT( QThread::currentThread() == thread() ); - - qDebug() << Q_FUNC_INFO << m_track.toString(); - m_scrobbler->cache( m_track ); - m_scrobbler->submit(); -} - - -void -Scrobbler::settingsChanged() -{ - if( !m_scrobbler && TomahawkApp::instance()->settings()->scrobblingEnabled() ) - { // can simply create the scrobbler - lastfm::ws::Username = TomahawkApp::instance()->settings()->lastFmUsername(); - m_pw = TomahawkApp::instance()->settings()->lastFmPassword(); - - createScrobbler(); - } - else if( m_scrobbler && !TomahawkApp::instance()->settings()->scrobblingEnabled() ) - { - delete m_scrobbler; - m_scrobbler = 0; - } - else if( TomahawkApp::instance()->settings()->lastFmUsername() != lastfm::ws::Username || - TomahawkApp::instance()->settings()->lastFmPassword() != m_pw ) - { - lastfm::ws::Username = TomahawkApp::instance()->settings()->lastFmUsername(); - // credentials have changed, have to re-create scrobbler for them to take effect - if( m_scrobbler ) - delete m_scrobbler; - - createScrobbler(); - } -} - - -void -Scrobbler::onAuthenticated() -{ - if( !m_authJob ) - { - qDebug() << Q_FUNC_INFO << "Help! No longer got a last.fm auth job!"; - return; - } - if( m_authJob->error() == QNetworkReply::NoError ) - { - lastfm::XmlQuery lfm = lastfm::XmlQuery( m_authJob->readAll() ); - - if( lfm.children( "error" ).size() > 0 ) - { - qDebug() << "Error from authenticating with Last.fm service:" << lfm.text(); - TomahawkApp::instance()->settings()->setLastFmSessionKey( QByteArray() ); - - } - else - { - lastfm::ws::SessionKey = lfm[ "session" ][ "key" ].text(); - - TomahawkApp::instance()->settings()->setLastFmSessionKey( lastfm::ws::SessionKey.toLatin1() ); - - if( TomahawkApp::instance()->settings()->scrobblingEnabled() ) - m_scrobbler = new lastfm::Audioscrobbler( "tst" ); - } - } - else - { - qDebug() << "Got error in Last.fm authentication job:" << m_authJob->errorString(); - } - - m_authJob->deleteLater(); + TomahawkApp::instance()->infoSystem()->getInfo( + s_infoIdentifier, Tomahawk::InfoSystem::InfoMiscSubmitScrobble, + QVariant(), Tomahawk::InfoSystem::InfoCustomDataHash() ); } - void -Scrobbler::createScrobbler() +Scrobbler::infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ) { - if( TomahawkApp::instance()->settings()->lastFmSessionKey().isEmpty() ) // no session key, so get one + if ( caller == s_infoIdentifier ) { - QString authToken = md5( ( lastfm::ws::Username.toLower() + md5( m_pw.toUtf8() ) ).toUtf8() ); - - QMap query; - query[ "method" ] = "auth.getMobileSession"; - query[ "username" ] = lastfm::ws::Username; - query[ "authToken" ] = authToken; - m_authJob = lastfm::ws::post( query ); - - connect( m_authJob, SIGNAL( finished() ), SLOT( onAuthenticated() ) ); - } - else - { - lastfm::ws::SessionKey = TomahawkApp::instance()->settings()->lastFmSessionKey(); - - m_scrobbler = new lastfm::Audioscrobbler( "tst" ); - m_scrobbler->moveToThread( thread() ); + qDebug() << Q_FUNC_INFO; + if ( type == Tomahawk::InfoSystem::InfoMiscSubmitNowPlaying ) + qDebug() << "Scrobbler received now playing response from InfoSystem"; + else if ( type == Tomahawk::InfoSystem::InfoMiscSubmitScrobble ) + qDebug() << "Scrobbler received scrobble response from InfoSystem"; } } + +void +Scrobbler::infoSystemFinished( QString target ) +{ + if ( target == s_infoIdentifier ) + { + qDebug() << Q_FUNC_INFO; + qDebug() << "Scrobbler received done signal from InfoSystem"; + } +} \ No newline at end of file diff --git a/src/scrobbler.h b/src/scrobbler.h index 72c5a3a55..4ed020fba 100644 --- a/src/scrobbler.h +++ b/src/scrobbler.h @@ -1,16 +1,32 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ #ifndef TOMAHAWK_SCROBBLER_H #define TOMAHAWK_SCROBBLER_H -#include "tomahawk/result.h" +#include "result.h" -#include -#include -#include +#include "lastfm/ScrobblePoint" + +#include "tomahawk/infosystem.h" #include -class QNetworkReply; /** * Simple class that listens to signals from AudioEngine and scrobbles * what it is playing. @@ -29,20 +45,14 @@ public slots: void trackStopped(); void engineTick( unsigned int secondsElapsed ); - void settingsChanged(); - void onAuthenticated(); + void infoSystemInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, Tomahawk::InfoSystem::InfoCustomDataHash customData ); + void infoSystemFinished( QString target ); private: - void scrobble( ); - void createScrobbler(); + void scrobble(); - lastfm::MutableTrack m_track; - lastfm::Audioscrobbler* m_scrobbler; - QString m_pw; bool m_reachedScrobblePoint; ScrobblePoint m_scrobblePoint; - - QNetworkReply* m_authJob; }; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 09c3a9008..8baadd6e9 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -1,23 +1,44 @@ -#include "settingsdialog.h" -#include "ui_settingsdialog.h" -#include "ui_proxydialog.h" +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "config.h" #include #include +#include #include #include #include -#ifndef NO_LIBLASTFM +#ifdef LIBLASTFM_FOUND #include #include #endif +#include "settingsdialog.h" +#include "ui_settingsdialog.h" +#include "ui_proxydialog.h" #include "tomahawk/tomahawkapp.h" #include "musicscanner.h" #include "tomahawksettings.h" -#include - +#include "sip/SipHandler.h" +#include +#include "scanmanager.h" static QString md5( const QByteArray& src ) @@ -35,10 +56,12 @@ SettingsDialog::SettingsDialog( QWidget *parent ) , m_testLastFmQuery( 0 ) { ui->setupUi( this ); - TomahawkSettings* s = TomahawkApp::instance()->settings(); + TomahawkSettings* s = TomahawkSettings::instance(); ui->checkBoxHttp->setChecked( s->httpEnabled() ); - ui->checkBoxUpnp->setChecked( s->upnpEnabled() ); + ui->checkBoxStaticPreferred->setChecked( s->preferStaticHostPort() ); + ui->checkBoxUpnp->setChecked( s->externalAddressMode() == TomahawkSettings::Upnp ); + ui->checkBoxUpnp->setEnabled( !s->preferStaticHostPort() ); // JABBER ui->checkBoxJabberAutoConnect->setChecked( s->jabberAutoConnect() ); @@ -46,20 +69,20 @@ SettingsDialog::SettingsDialog( QWidget *parent ) ui->jabberPassword->setText( s->jabberPassword() ); ui->jabberServer->setText( s->jabberServer() ); ui->jabberPort->setValue( s->jabberPort() ); + + ui->staticHostName->setText( s->externalHostname() ); + ui->staticPort->setValue( s->externalPort() ); + ui->proxyButton->setVisible( false ); - if ( ui->jabberPort->text().toInt() != 5222 || !ui->jabberServer->text().isEmpty() ) + // SIP PLUGINS + foreach(SipPlugin *plugin, APP->sipHandler()->plugins()) { - ui->checkBoxJabberAdvanced->setChecked( true ); - } - else - { - // hide advanved settings - ui->checkBoxJabberAdvanced->setChecked( false ); - ui->jabberServer->setVisible( false ); - ui->jabberPort->setVisible( false ); - ui->labelJabberServer->setVisible( false ); - ui->labelJabberPort->setVisible( false ); + if(plugin->configWidget()) + { + qDebug() << "Adding configWidget for " << plugin->name(); + ui->tabWidget->addTab(plugin->configWidget(), plugin->friendlyName()); + } } // MUSIC SCANNER @@ -71,8 +94,20 @@ SettingsDialog::SettingsDialog( QWidget *parent ) ui->lineEditLastfmPassword->setText(s->lastFmPassword() ); connect( ui->pushButtonTestLastfmLogin, SIGNAL( clicked( bool) ), this, SLOT( testLastFmLogin() ) ); + // SCRIPT RESOLVER + ui->removeScript->setEnabled( false ); + foreach( const QString& resolver, s->scriptResolvers() ) { + QFileInfo info( resolver ); + ui->scriptList->addTopLevelItem( new QTreeWidgetItem( QStringList() << info.baseName() << resolver ) ); + + } + connect( ui->scriptList, SIGNAL( itemClicked( QTreeWidgetItem*, int ) ), this, SLOT( scriptSelectionChanged() ) ); + connect( ui->addScript, SIGNAL( clicked( bool ) ), this, SLOT( addScriptResolver() ) ); + connect( ui->removeScript, SIGNAL( clicked( bool ) ), this, SLOT( removeScriptResolver() ) ); + connect( ui->buttonBrowse, SIGNAL( clicked() ), SLOT( showPathSelector() ) ); connect( ui->proxyButton, SIGNAL( clicked() ), SLOT( showProxySettings() ) ); + connect( ui->checkBoxStaticPreferred, SIGNAL( toggled(bool) ), SLOT( toggleUpnp(bool) ) ); connect( this, SIGNAL( rejected() ), SLOT( onRejected() ) ); } @@ -83,46 +118,35 @@ SettingsDialog::~SettingsDialog() if ( !m_rejected ) { - TomahawkSettings* s = TomahawkApp::instance()->settings(); + TomahawkSettings* s = TomahawkSettings::instance(); - // if jabber or scan dir changed, reconnect/rescan - bool rescan = ui->lineEditMusicPath->text() != s->scannerPath(); - bool rejabber = false; - if ( ui->jabberUsername->text() != s->jabberUsername() || - ui->jabberPassword->text() != s->jabberPassword() || - ui->jabberServer->text() != s->jabberServer() || - ui->jabberPort->value() != s->jabberPort() - ) - { - rejabber = true; - } + s->setHttpEnabled( ui->checkBoxHttp->checkState() == Qt::Checked ); + s->setPreferStaticHostPort( ui->checkBoxStaticPreferred->checkState() == Qt::Checked ); + s->setExternalAddressMode( ui->checkBoxUpnp->checkState() == Qt::Checked ? TomahawkSettings::Upnp : TomahawkSettings::Lan ); - s->setHttpEnabled( ui->checkBoxHttp->checkState() == Qt::Checked ); - s->setUPnPEnabled( ui->checkBoxUpnp->checkState() == Qt::Checked ); - - s->setJabberAutoConnect( ui->checkBoxJabberAutoConnect->checkState() == Qt::Checked ); - s->setJabberUsername( ui->jabberUsername->text() ); - s->setJabberPassword( ui->jabberPassword->text() ); - s->setJabberServer( ui->jabberServer->text() ); - s->setJabberPort( ui->jabberPort->value() ); - - s->setScannerPath( ui->lineEditMusicPath->text() ); + s->setJabberAutoConnect( ui->checkBoxJabberAutoConnect->checkState() == Qt::Checked ); + s->setJabberUsername( ui->jabberUsername->text() ); + s->setJabberPassword( ui->jabberPassword->text() ); + s->setJabberServer( ui->jabberServer->text() ); + s->setJabberPort( ui->jabberPort->value() ); - s->setScrobblingEnabled( ui->checkBoxEnableLastfm->isChecked() ); - s->setLastFmUsername( ui->lineEditLastfmUsername->text() ); - s->setLastFmPassword( ui->lineEditLastfmPassword->text() ); + s->setExternalHostname( ui->staticHostName->text() ); + s->setExternalPort( ui->staticPort->value() ); - if( rescan ) - { - MusicScanner* scanner = new MusicScanner(s->scannerPath() ); - connect( scanner, SIGNAL( finished() ), scanner, SLOT( deleteLater() ) ); - scanner->start(); - } + s->setScannerPath( ui->lineEditMusicPath->text() ); + + s->setScrobblingEnabled( ui->checkBoxEnableLastfm->isChecked() ); + s->setLastFmUsername( ui->lineEditLastfmUsername->text() ); + s->setLastFmPassword( ui->lineEditLastfmPassword->text() ); - if( rejabber ) + QStringList resolvers; + for( int i = 0; i < ui->scriptList->topLevelItemCount(); i++ ) { - APP->reconnectJabber(); + resolvers << ui->scriptList->topLevelItem( i )->data( 1, Qt::DisplayRole ).toString(); } + s->setScriptResolvers( resolvers ); + + s->applyChanges(); } else qDebug() << "Settings dialog cancelled, NOT saving prefs."; @@ -147,21 +171,6 @@ SettingsDialog::showPathSelector() } -void -SettingsDialog::doScan() -{ - // TODO this doesnt really belong here.. - QString path = ui->lineEditMusicPath->text(); - MusicScanner* scanner = new MusicScanner( path ); - connect( scanner, SIGNAL( finished() ), scanner, SLOT( deleteLater() ) ); - scanner->start(); - - QMessageBox::information( this, tr( "Scanning Started" ), - tr( "Scanning now, check console output. TODO." ), - QMessageBox::Ok ); -} - - void SettingsDialog::onRejected() { @@ -194,14 +203,24 @@ SettingsDialog::showProxySettings() } +void +SettingsDialog::toggleUpnp( bool preferStaticEnabled ) +{ + if ( preferStaticEnabled ) + ui->checkBoxUpnp->setEnabled( false ); + else + ui->checkBoxUpnp->setEnabled( true ); +} + + void SettingsDialog::testLastFmLogin() { -#ifndef NO_LIBLASTFM +#ifdef LIBLASTFM_FOUND ui->pushButtonTestLastfmLogin->setEnabled( false ); - ui->pushButtonTestLastfmLogin->setText( "Testing..." ); + ui->pushButtonTestLastfmLogin->setText( "Testing..." ); - QString authToken = md5( ( ui->lineEditLastfmUsername->text() + md5( ui->lineEditLastfmPassword->text().toUtf8() ) ).toUtf8() ); + QString authToken = md5( ( ui->lineEditLastfmUsername->text() + md5( ui->lineEditLastfmPassword->text().toUtf8() ) ).toUtf8() ); // now authenticate w/ last.fm and get our session key QMap query; @@ -218,7 +237,7 @@ SettingsDialog::testLastFmLogin() void SettingsDialog::onLastFmFinished() { -#ifndef NO_LIBLASTFM +#ifdef LIBLASTFM_FOUND lastfm::XmlQuery lfm = lastfm::XmlQuery( m_testLastFmQuery->readAll() ); switch( m_testLastFmQuery->error() ) @@ -230,8 +249,8 @@ SettingsDialog::onLastFmFinished() qDebug() << "ERROR from last.fm:" << lfm.text(); ui->pushButtonTestLastfmLogin->setText( tr( "Failed" ) ); ui->pushButtonTestLastfmLogin->setEnabled( true ); - - } else + } + else { ui->pushButtonTestLastfmLogin->setText( tr( "Success" ) ); ui->pushButtonTestLastfmLogin->setEnabled( false ); @@ -268,7 +287,7 @@ ProxyDialog::ProxyDialog( QWidget *parent ) ui->typeBox->insertItem( i, "SOCKS 5", QNetworkProxy::Socks5Proxy ); enumMap[QNetworkProxy::Socks5Proxy] = i++; - TomahawkSettings* s = TomahawkApp::instance()->settings(); + TomahawkSettings* s = TomahawkSettings::instance(); ui->typeBox->setCurrentIndex( enumMap[s->proxyType()] ); ui->hostLineEdit->setText( s->proxyHost() ); @@ -284,7 +303,7 @@ ProxyDialog::saveSettings() qDebug() << Q_FUNC_INFO; //First set settings - TomahawkSettings* s = TomahawkApp::instance()->settings(); + TomahawkSettings* s = TomahawkSettings::instance(); s->setProxyHost( ui->hostLineEdit->text() ); bool ok; @@ -301,9 +320,9 @@ ProxyDialog::saveSettings() return; QNetworkProxy proxy( static_cast(s->proxyType()), s->proxyHost(), s->proxyPort(), s->proxyUsername(), s->proxyPassword() ); - QNetworkAccessManager* nam = TomahawkApp::instance()->nam(); + QNetworkAccessManager* nam = TomahawkUtils::nam(); nam->setProxy( proxy ); - QNetworkProxy* globalProxy = TomahawkApp::instance()->proxy(); + QNetworkProxy* globalProxy = TomahawkUtils::proxy(); QNetworkProxy* oldProxy = globalProxy; globalProxy = new QNetworkProxy( proxy ); if( oldProxy ) @@ -311,3 +330,40 @@ ProxyDialog::saveSettings() QNetworkProxy::setApplicationProxy( proxy ); } + + +void +SettingsDialog::addScriptResolver() +{ + QString resolver = QFileDialog::getOpenFileName( this, tr( "Load script resolver file" ), qApp->applicationDirPath() ); + if( !resolver.isEmpty() ) { + QFileInfo info( resolver ); + ui->scriptList->addTopLevelItem( new QTreeWidgetItem( QStringList() << info.baseName() << resolver ) ); + + TomahawkApp::instance()->addScriptResolver( resolver ); + } +} + + +void +SettingsDialog::removeScriptResolver() +{ + // only one selection + if( !ui->scriptList->selectedItems().isEmpty() ) { + QString resolver = ui->scriptList->selectedItems().first()->data( 1, Qt::DisplayRole ).toString(); + delete ui->scriptList->takeTopLevelItem( ui->scriptList->indexOfTopLevelItem( ui->scriptList->selectedItems().first() ) ); + + TomahawkApp::instance()->removeScriptResolver( resolver ); + } +} + + +void +SettingsDialog::scriptSelectionChanged() +{ + if( !ui->scriptList->selectedItems().isEmpty() ) { + ui->removeScript->setEnabled( true ); + } else { + ui->removeScript->setEnabled( false ); + } +} diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 3d71d82c5..02a3c608f 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef SETTINGSDIALOG_H #define SETTINGSDIALOG_H @@ -16,13 +34,13 @@ class ProxyDialog : public QDialog Q_OBJECT public: - explicit ProxyDialog( QWidget *parent = 0 ); + explicit ProxyDialog( QWidget* parent = 0 ); ~ProxyDialog() {}; void saveSettings(); private: - Ui::ProxyDialog *ui; + Ui::ProxyDialog* ui; }; class SettingsDialog : public QDialog @@ -30,29 +48,32 @@ class SettingsDialog : public QDialog Q_OBJECT public: - static const unsigned int VERSION = 1; // config version - - explicit SettingsDialog( QWidget *parent = 0 ); + explicit SettingsDialog( QWidget* parent = 0 ); ~SettingsDialog(); -Q_SIGNALS: +signals: void settingsChanged(); protected: - void changeEvent( QEvent *e ); + void changeEvent( QEvent* e ); private slots: void onRejected(); + void showPathSelector(); - void doScan(); - + + void toggleUpnp( bool preferStaticEnabled ); void showProxySettings(); void testLastFmLogin(); void onLastFmFinished(); + void addScriptResolver(); + void scriptSelectionChanged(); + void removeScriptResolver(); + private: - Ui::SettingsDialog *ui; + Ui::SettingsDialog* ui; ProxyDialog m_proxySettings; bool m_rejected; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 820c61ba8..3a07d4254 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -7,7 +7,7 @@ 0 0 621 - 353 + 434 @@ -34,62 +34,59 @@ 16 - - - 16 - - + + - + 0 0 - 100 + 0 0 Jabber ID: - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - jabberUsername - + - + 0 0 - + + + + 0 + 0 + + Password: - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - jabberPassword - + - + 0 0 @@ -102,166 +99,124 @@ - - - - - 0 - 0 - - - - Qt::RightToLeft - - - Show Advanced Settings - - - - - - 16 + + + Qt::Vertical - - - - - 0 - 0 - - - - - 100 - 0 - - - - Jabber Server: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - jabberServer - - - - - - - - 0 - 0 - - - - - - - - Port: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - Qt::Horizontal - - - QSizePolicy::MinimumExpanding - - - - 40 - 8 - - - - - - - - - 0 - 0 - - - - - 90 - 0 - - - - - 90 - 16777215 - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 1 - - - 65535 - - - 5222 - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Proxy Settings... - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - + + QSizePolicy::Fixed + + + + 20 + 8 + + + + + + + + true + + + + 0 + 0 + + + + Advanced Jabber Settings + + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + Server: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + jabberServer + + + + + + + + 0 + 0 + + + + + + + + Port: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 90 + 0 + + + + + 90 + 16777215 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1 + + + 65535 + + + 5222 + + + + + + + @@ -271,55 +226,203 @@ 20 - 73 + 40 + + + + + Network + + - - - Qt::RightToLeft + + + + 0 + 0 + - - Playdar HTTP API - NO AUTH - - - false - - - - - - - Qt::RightToLeft - - - Use UPnP to establish port forward - - - true - - - - - - - Qt::RightToLeft - - - Connect automatically when Tomahawk starts - - - true + + Advanced Network Settings + + + + + + + + + + + 0 + 0 + + + + If you're having difficulty connecting to peers, try setting this to your external IP address/host name and a port number (default 50210). Make sure to forward that port to this machine! + + + true + + + + + + + + + + 0 + 0 + + + + Static Host Name: + + + + + + + + + + + 0 + 0 + + + + Static Port: + + + + + + + 65535 + + + 50210 + + + + + + + + + Qt::RightToLeft + + + Always use static host name/port? (Overrides UPnP discovery/port forwarding) + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Proxy Settings... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::RightToLeft + + + Playdar HTTP API + + + true + + + + + + + Qt::RightToLeft + + + Connect automatically when Tomahawk starts + + + true + + + + + + + Qt::RightToLeft + + + Use UPnP to establish port forward + + + true + + + + - Music + Local Music @@ -456,6 +559,110 @@
+ + + Script Resolvers + + + + + + Loaded script resolvers: + + + + + + + + + true + + + QAbstractItemView::SingleSelection + + + false + + + false + + + true + + + 2 + + + false + + + true + + + 150 + + + true + + + true + + + + 1 + + + + + 2 + + + + + + + + + + + + + + :/data/images/list-add.png:/data/images/list-add.png + + + + + + + + + + + :/data/images/list-remove.png:/data/images/list-remove.png + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + @@ -470,7 +677,9 @@ - + + + buttonBox @@ -479,8 +688,8 @@ accept() - 398 - 374 + 402 + 348 157 @@ -495,8 +704,8 @@ reject() - 466 - 380 + 470 + 348 286 @@ -504,85 +713,5 @@ - - checkBoxJabberAdvanced - toggled(bool) - jabberServer - setVisible(bool) - - - 480 - 124 - - - 496 - 154 - - - - - checkBoxJabberAdvanced - toggled(bool) - labelJabberServer - setVisible(bool) - - - 483 - 124 - - - 114 - 154 - - - - - checkBoxJabberAdvanced - toggled(bool) - labelJabberPort - setVisible(bool) - - - 461 - 124 - - - 114 - 182 - - - - - checkBoxJabberAdvanced - toggled(bool) - jabberPort - setVisible(bool) - - - 441 - 112 - - - 439 - 163 - - - - - checkBoxJabberAdvanced - toggled(bool) - proxyButton - setVisible(bool) - - - 310 - 110 - - - 368 - 194 - - - diff --git a/src/shortcuthandler.cpp b/src/shortcuthandler.cpp new file mode 100644 index 000000000..766b971be --- /dev/null +++ b/src/shortcuthandler.cpp @@ -0,0 +1,31 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "shortcuthandler.h" + +using namespace Tomahawk; + +ShortcutHandler::ShortcutHandler( QObject *parent ) + : QObject( parent ) +{ + +} + + ShortcutHandler::~ShortcutHandler() + { + } diff --git a/src/shortcuthandler.h b/src/shortcuthandler.h new file mode 100644 index 000000000..a74e7660d --- /dev/null +++ b/src/shortcuthandler.h @@ -0,0 +1,52 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef SHORTCUTHANDLER_H +#define SHORTCUTHANDLER_H + +#include + +namespace Tomahawk { +/** + Base class for various shortcut plugins on different platforms + */ +class ShortcutHandler : public QObject +{ + Q_OBJECT +public: + virtual ~ShortcutHandler(); + +signals: + // add more as needed + void playPause(); + void pause(); + void stop(); + void previous(); + void next(); + + void volumeUp(); + void volumeDown(); + void mute(); +protected: + explicit ShortcutHandler( QObject *parent = 0 ); + +}; + +} + +#endif // SHORTCUTHANDLER_H diff --git a/src/sip/CMakeLists.txt b/src/sip/CMakeLists.txt new file mode 100644 index 000000000..82aa3ae97 --- /dev/null +++ b/src/sip/CMakeLists.txt @@ -0,0 +1,10 @@ +# only build one of them, if ENABLE_JREEN is true, GLOOX_FOUND is automatically set to "false" +IF( GLOOX_FOUND ) + ADD_SUBDIRECTORY( jabber ) +ENDIF( GLOOX_FOUND ) +IF( ENABLE_JREEN ) + ADD_SUBDIRECTORY( jreen ) +ENDIF( ENABLE_JREEN) + +ADD_SUBDIRECTORY( twitter ) +ADD_SUBDIRECTORY( zeroconf ) diff --git a/src/sip/SipHandler.cpp b/src/sip/SipHandler.cpp new file mode 100644 index 000000000..a8284a93e --- /dev/null +++ b/src/sip/SipHandler.cpp @@ -0,0 +1,348 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "SipHandler.h" +#include "sip/SipPlugin.h" + +#include +#include +#include +#include + +#include "database/database.h" +#include "network/controlconnection.h" +#include "sourcelist.h" +#include "tomahawksettings.h" +#include "tomahawk/tomahawkapp.h" + +#include "config.h" + + +SipHandler::SipHandler( QObject* parent ) + : QObject( parent ) + , m_connected( false ) +{ + loadPlugins( findPlugins() ); + + connect( TomahawkSettings::instance(), SIGNAL( changed() ), SLOT( onSettingsChanged() ) ); +} + + +SipHandler::~SipHandler() +{ + disconnectPlugins(); +} + + +QList< SipPlugin* > +SipHandler::plugins() const +{ + return m_plugins; +} + + +void +SipHandler::onSettingsChanged() +{ + checkSettings(); +} + + +QStringList +SipHandler::findPlugins() +{ + QStringList paths; + QList< QDir > pluginDirs; + + QDir appDir( qApp->applicationDirPath() ); + #ifdef Q_OS_MAC + if ( appDir.dirName() == "MacOS" ) + { + // Development convenience-hack + appDir.cdUp(); + appDir.cdUp(); + appDir.cdUp(); + } + #endif + + QDir libDir( CMAKE_INSTALL_PREFIX "/lib" ); + + QDir lib64Dir( appDir ); + lib64Dir.cdUp(); + lib64Dir.cd( "lib64" ); + + pluginDirs << appDir << libDir << lib64Dir << QDir( qApp->applicationDirPath() ); + foreach ( const QDir& pluginDir, pluginDirs ) + { + qDebug() << "Checking directory for plugins:" << pluginDir; + foreach ( QString fileName, pluginDir.entryList( QStringList() << "*tomahawk_sip*.so" << "*tomahawk_sip*.dylib" << "*tomahawk_sip*.dll", QDir::Files ) ) + { + if ( fileName.startsWith( "libtomahawk_sip" ) ) + { + const QString path = pluginDir.absoluteFilePath( fileName ); + if ( !paths.contains( path ) ) + paths << path; + } + } + } + + return paths; +} + + +void +SipHandler::loadPlugins( const QStringList& paths ) +{ + foreach ( QString fileName, paths ) + { + if ( !QLibrary::isLibrary( fileName ) ) + continue; + + qDebug() << "Trying to load plugin:" << fileName; + loadPlugin( fileName ); + } +} + + +void +SipHandler::loadPlugin( const QString& path ) +{ + QPluginLoader loader( path ); + QObject* plugin = loader.instance(); + if ( !plugin ) + { + qDebug() << "Error loading plugin:" << loader.errorString(); + } + + SipPlugin* sip = qobject_cast(plugin); + if ( sip ) + { + if ( pluginLoaded( sip->name() ) ) + { + qDebug() << "Plugin" << sip->name() << "already loaded! Not loading:" << loader.fileName(); + return; + } + qDebug() << "Loaded plugin:" << loader.fileName(); + + QObject::connect( sip, SIGNAL( peerOnline( QString ) ), SLOT( onPeerOnline( QString ) ) ); + QObject::connect( sip, SIGNAL( peerOffline( QString ) ), SLOT( onPeerOffline( QString ) ) ); + QObject::connect( sip, SIGNAL( msgReceived( QString, QString ) ), SLOT( onMessage( QString, QString ) ) ); + + QObject::connect( sip, SIGNAL( connected() ), SIGNAL( connected() ) ); + QObject::connect( sip, SIGNAL( disconnected() ), SIGNAL( disconnected() ) ); + QObject::connect( sip, SIGNAL( error( int, QString ) ), SLOT( onError( int, QString ) ) ); + + m_plugins << sip; + } +} + + +bool +SipHandler::pluginLoaded( const QString& name ) const +{ + foreach( SipPlugin* plugin, m_plugins ) + { + if ( plugin->name() == name ) + return true; + } + + return false; +} + + +void +SipHandler::checkSettings() +{ + foreach( SipPlugin* sip, m_plugins ) + { + sip->checkSettings(); + } +} + + +void +SipHandler::connectPlugins( bool startup, const QString &pluginName ) +{ +#ifndef TOMAHAWK_HEADLESS + if ( !TomahawkSettings::instance()->acceptedLegalWarning() ) + { + int result = QMessageBox::question( + TomahawkApp::instance()->mainWindow(), "Legal Warning", + "By pressing OK below, you agree that your use of Tomahawk will be in accordance with any applicable laws, including copyright and intellectual property laws, in effect in your country of residence, and indemify the Tomahawk developers and project from liability should you choose to break those laws.\n\nFor more information, please see http://gettomahawk.com/legal", + "I Do Not Agree", "I Agree" + ); + if ( result != 1 ) + return; + else + TomahawkSettings::instance()->setAcceptedLegalWarning( true ); + } +#endif + foreach( SipPlugin* sip, m_plugins ) + { + if ( pluginName.isEmpty() || ( !pluginName.isEmpty() && sip->name() == pluginName ) ) + sip->connectPlugin( startup ); + } + + if ( pluginName.isEmpty() ) + { + m_connected = true; + } +} + + +void +SipHandler::disconnectPlugins( const QString &pluginName ) +{ + foreach( SipPlugin* sip, m_plugins ) + { + if ( pluginName.isEmpty() || ( !pluginName.isEmpty() && sip->name() == pluginName ) ) + sip->disconnectPlugin(); + } + + if ( pluginName.isEmpty() ) + { + SourceList::instance()->removeAllRemote(); + m_connected = false; + } +} + + +void +SipHandler::toggleConnect() +{ + if( m_connected ) + disconnectPlugins(); + else + connectPlugins(); +} + + +void +SipHandler::onPeerOnline( const QString& jid ) +{ +// qDebug() << Q_FUNC_INFO; + qDebug() << "SIP online:" << jid; + + SipPlugin* sip = qobject_cast(sender()); + + QVariantMap m; + if( Servent::instance()->visibleExternally() ) + { + QString key = uuid(); + ControlConnection* conn = new ControlConnection( Servent::instance() ); + + const QString& nodeid = Database::instance()->dbid(); + + //TODO: this is a terrible assumption, help me clean this up, mighty muesli! + if ( jid.contains( "@conference.") ) + conn->setName( jid ); + else + conn->setName( jid.left( jid.indexOf( "/" ) ) ); + + conn->setId( nodeid ); + + Servent::instance()->registerOffer( key, conn ); + m["visible"] = true; + m["ip"] = Servent::instance()->externalAddress(); + m["port"] = Servent::instance()->externalPort(); + m["key"] = key; + m["uniqname"] = nodeid; + + qDebug() << "Asking them to connect to us:" << m; + } + else + { + m["visible"] = false; + qDebug() << "We are not visible externally:" << m; + } + + QJson::Serializer ser; + QByteArray ba = ser.serialize( m ); + + sip->sendMsg( jid, QString::fromAscii( ba ) ); +} + + +void +SipHandler::onPeerOffline( const QString& jid ) +{ +// qDebug() << Q_FUNC_INFO; + qDebug() << "SIP offline:" << jid; +} + + +void +SipHandler::onMessage( const QString& from, const QString& msg ) +{ + qDebug() << Q_FUNC_INFO; + qDebug() << "SIP Message:" << from << msg; + + QJson::Parser parser; + bool ok; + QVariant v = parser.parse( msg.toAscii(), &ok ); + if ( !ok || v.type() != QVariant::Map ) + { + qDebug() << "Invalid JSON in XMPP msg"; + return; + } + + QVariantMap m = v.toMap(); + /* + If only one party is externally visible, connection is obvious + If both are, peer with lowest IP address initiates the connection. + This avoids dupe connections. + */ + if ( m.value( "visible" ).toBool() ) + { + if( !Servent::instance()->visibleExternally() || + Servent::instance()->externalAddress() <= m.value( "ip" ).toString() ) + { + qDebug() << "Initiate connection to" << from; + Servent::instance()->connectToPeer( m.value( "ip" ).toString(), + m.value( "port" ).toInt(), + m.value( "key" ).toString(), + from, + m.value( "uniqname" ).toString() ); + } + else + { + qDebug() << Q_FUNC_INFO << "They should be conecting to us..."; + } + } + else + { + qDebug() << Q_FUNC_INFO << "They are not visible, doing nothing atm"; + } +} + + +void +SipHandler::onError( int code, const QString& msg ) +{ + qWarning() << "Failed to connect to SIP:" << code << msg; + + if ( code == SipPlugin::AuthError ) + { + emit authError(); + } + else + { + SipPlugin* sip = qobject_cast(sender()); + QTimer::singleShot( 10000, sip, SLOT( connectPlugin() ) ); + } +} diff --git a/src/sip/SipHandler.h b/src/sip/SipHandler.h new file mode 100644 index 000000000..0d3dcfa63 --- /dev/null +++ b/src/sip/SipHandler.h @@ -0,0 +1,71 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef SIPHANDLER_H +#define SIPHANDLER_H + +#include "sip/SipPlugin.h" + +#include +#include + +class SipHandler : public QObject +{ + Q_OBJECT + +public: +// static SipHandler* instance() { return s_instance ? s_instance : new SipHandler(); } + + SipHandler( QObject* parent ); + ~SipHandler(); + + QList< SipPlugin* > plugins() const; + +public slots: + void addContact( const QString& id ) { qDebug() << Q_FUNC_INFO << id; } + + void checkSettings(); + void connectPlugins( bool startup = false, const QString &pluginName = QString() ); + void disconnectPlugins( const QString &pluginName = QString() ); + void toggleConnect(); + +signals: + void connected(); + void disconnected(); + void authError(); + +private slots: + void onMessage( const QString&, const QString& ); + void onPeerOffline( const QString& ); + void onPeerOnline( const QString& ); + void onError( int code, const QString& msg ); + + void onSettingsChanged(); + +private: + QStringList findPlugins(); + bool pluginLoaded( const QString& name ) const; + + void loadPlugins( const QStringList& paths ); + void loadPlugin( const QString& path ); + + QList< SipPlugin* > m_plugins; + bool m_connected; +}; + +#endif diff --git a/src/sip/jabber/CMakeLists.txt b/src/sip/jabber/CMakeLists.txt new file mode 100644 index 000000000..56ff17970 --- /dev/null +++ b/src/sip/jabber/CMakeLists.txt @@ -0,0 +1,46 @@ +project( tomahawk ) + +include( ${QT_USE_FILE} ) +add_definitions( ${QT_DEFINITIONS} ) +add_definitions( -DQT_PLUGIN ) +add_definitions( -DQT_SHARED ) +add_definitions( -DSIPDLLEXPORT_PRO ) + +set( jabberSources + jabber.cpp + jabber_p.cpp +) + +set( jabberHeaders + jabber.h + jabber_p.h +) + +include_directories( . ${CMAKE_CURRENT_BINARY_DIR} .. + ${QT_INCLUDE_DIR} +) + +qt4_wrap_cpp( jabberMoc ${jabberHeaders} ) +add_library( tomahawk_sipjabber SHARED ${jabberSources} ${jabberMoc} ) + +IF( WIN32 ) +SET( OS_SPECIFIC_LINK_LIBRARIES + ${OS_SPECIFIC_LINK_LIBRARIES} + "secur32.dll" + "crypt32.dll" + ${CMAKE_BINARY_DIR}/src/libtomahawk/libtomahawklib.dll +) +ENDIF( WIN32 ) + +target_link_libraries( tomahawk_sipjabber + ${QT_LIBRARIES} + ${GLOOX_LIBRARIES} + ${OS_SPECIFIC_LINK_LIBRARIES} + tomahawklib +) + +IF( APPLE ) +# SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) +ENDIF( APPLE ) + +install( TARGETS tomahawk_sipjabber DESTINATION lib${LIB_SUFFIX} ) diff --git a/src/sip/jabber/jabber.cpp b/src/sip/jabber/jabber.cpp new file mode 100644 index 000000000..ce62909c4 --- /dev/null +++ b/src/sip/jabber/jabber.cpp @@ -0,0 +1,204 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "jabber.h" + +#include "tomahawksettings.h" + +#include +#include +#include +#include + + +JabberPlugin::JabberPlugin() + : p( 0 ), + m_menu ( 0 ), + m_addFriendAction( 0 ) +{ +} + + +void +JabberPlugin::setProxy( QNetworkProxy* proxy ) +{ + p->setProxy( proxy ); +} + + +const QString +JabberPlugin::name() +{ + return QString( MYNAME ); +} + + +const QString +JabberPlugin::friendlyName() +{ + return QString( "Jabber" ); +} + + +const QString +JabberPlugin::accountName() +{ + return TomahawkSettings::instance()->jabberUsername(); +} + + +QMenu* +JabberPlugin::menu() +{ + return m_menu; +} + + +bool +JabberPlugin::connectPlugin( bool startup ) +{ + qDebug() << "JabberPlugin::connect"; + if ( startup && !TomahawkSettings::instance()->jabberAutoConnect() ) + return false; + + m_currentUsername = TomahawkSettings::instance()->jabberUsername(); + m_currentServer = TomahawkSettings::instance()->jabberServer(); + m_currentPassword = TomahawkSettings::instance()->jabberPassword(); + m_currentPort = TomahawkSettings::instance()->jabberPort(); + QString server = m_currentServer; + + QStringList splitJid = m_currentUsername.split( '@', QString::SkipEmptyParts ); + if ( splitJid.size() < 2 ) + { + qDebug() << "JID did not have an @ in it, could not find a server part"; + return false; + } + + if ( server.isEmpty() ) + server = splitJid[1]; + + if ( m_currentPort < 1 || m_currentPort > 65535 || m_currentUsername.isEmpty() || m_currentPassword.isEmpty() ) + { + qDebug() << "Jabber credentials look wrong, not connecting"; + return false; + } + + delete p; + p = new Jabber_p( m_currentUsername, m_currentPassword, server, m_currentPort ); + + QObject::connect( p, SIGNAL( peerOnline( QString ) ), SIGNAL( peerOnline( QString ) ) ); + QObject::connect( p, SIGNAL( peerOffline( QString ) ), SIGNAL( peerOffline( QString ) ) ); + QObject::connect( p, SIGNAL( msgReceived( QString, QString ) ), SIGNAL( msgReceived( QString, QString ) ) ); + + QObject::connect( p, SIGNAL( connected() ), SLOT( onConnected() ) ); + QObject::connect( p, SIGNAL( disconnected() ), SLOT( onDisconnected() ) ); + QObject::connect( p, SIGNAL( authError( int, QString ) ), SLOT( onAuthError( int, QString ) ) ); + + p->resolveHostSRV(); + + return true; +} + + +void +JabberPlugin::onConnected() +{ + if ( !m_menu ) + { + m_menu = new QMenu( QString( "Jabber (" ).append( accountName() ).append( ")" ) ); + m_addFriendAction = m_menu->addAction( tr( "Add Friend..." ) ); + + connect( m_addFriendAction, SIGNAL( triggered() ), SLOT( showAddFriendDialog() ) ) ; + + emit addMenu( m_menu ); + } + + emit connected(); +} + + +void +JabberPlugin::onDisconnected() +{ + if ( m_menu && m_addFriendAction ) + { + emit removeMenu( m_menu ); + + delete m_menu; + m_menu = 0; + m_addFriendAction = 0; + } + + emit disconnected(); +} + + +void +JabberPlugin::onAuthError( int code, const QString& message ) +{ + if ( code == gloox::ConnAuthenticationFailed ) + { + emit error( SipPlugin::AuthError, message ); + } + else + { + emit error( SipPlugin::ConnectionError, message ); + } +} + + +void +JabberPlugin::showAddFriendDialog() +{ + bool ok; + QString id = QInputDialog::getText( 0, tr( "Add Friend" ), + tr( "Enter Jabber ID:" ), QLineEdit::Normal, "", &ok ); + if ( !ok ) + return; + + qDebug() << "Attempting to add jabber contact to roster:" << id; + addContact( id ); +} + + +void +JabberPlugin::checkSettings() +{ + bool reconnect = false; + if ( m_currentUsername != TomahawkSettings::instance()->jabberUsername() ) + reconnect = true; + if ( m_currentPassword != TomahawkSettings::instance()->jabberPassword() ) + reconnect = true; + if ( m_currentServer != TomahawkSettings::instance()->jabberServer() ) + reconnect = true; + if ( m_currentPort != TomahawkSettings::instance()->jabberPort() ) + reconnect = true; + + m_currentUsername = TomahawkSettings::instance()->jabberUsername(); + m_currentPassword = TomahawkSettings::instance()->jabberPassword(); + m_currentServer = TomahawkSettings::instance()->jabberServer(); + m_currentPort = TomahawkSettings::instance()->jabberPort(); + + if ( reconnect && ( p || TomahawkSettings::instance()->jabberAutoConnect() ) ) + { + disconnectPlugin(); + connectPlugin( false ); + } +} + +Q_EXPORT_PLUGIN2( sip, JabberPlugin ) diff --git a/src/sip/jabber/jabber.h b/src/sip/jabber/jabber.h new file mode 100644 index 000000000..b3d868896 --- /dev/null +++ b/src/sip/jabber/jabber.h @@ -0,0 +1,99 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef JABBER_H +#define JABBER_H + +#include "sip/SipPlugin.h" +#include "jabber_p.h" + +#include "../sipdllmacro.h" + +#define MYNAME "SIPJABBER" + +class SIPDLLEXPORT JabberPlugin : public SipPlugin +{ + Q_OBJECT + Q_INTERFACES( SipPlugin ) + +public: + JabberPlugin(); + + virtual ~JabberPlugin() { delete p; } + + //FIXME: Make this more correct + virtual bool isValid() { return true; } + virtual const QString name(); + virtual const QString friendlyName(); + virtual const QString accountName(); + virtual QMenu* menu(); + + void setProxy( QNetworkProxy* proxy ); + +public slots: + virtual bool connectPlugin( bool startup ); + + void disconnectPlugin() + { + onDisconnected(); + + if ( p ) + p->disconnect(); + + delete p; + p = 0; + } + + void checkSettings(); + + void sendMsg( const QString& to, const QString& msg ) + { + if ( p ) + p->sendMsg( to, msg ); + } + + void broadcastMsg( const QString &msg ) + { + if ( p ) + p->broadcastMsg( msg ); + } + + void addContact( const QString &jid, const QString& msg = QString() ) + { + if ( p ) + p->addContact( jid, msg ); + } + +private slots: + void onAuthError( int, const QString& ); + void showAddFriendDialog(); + void onConnected(); + void onDisconnected(); + +private: + Jabber_p* p; + QMenu* m_menu; + QAction* m_addFriendAction; + + QString m_currentUsername; + QString m_currentPassword; + QString m_currentServer; + unsigned int m_currentPort; +}; + +#endif diff --git a/src/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp similarity index 50% rename from src/jabber/jabber_p.cpp rename to src/sip/jabber/jabber_p.cpp index a13bcedc2..c5836005c 100644 --- a/src/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "jabber_p.h" #include @@ -5,28 +23,33 @@ #include #include #include +#include +#include +#include +#include +#include +#include -using namespace gloox; -using namespace std; - +#define TOMAHAWK_CAP_NODE_NAME QString::fromAscii("http://tomahawk-player.org/") Jabber_p::Jabber_p( const QString& jid, const QString& password, const QString& server, const int port ) : QObject() + , m_server() { qDebug() << Q_FUNC_INFO; qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); - m_presences[Presence::Available] = "available"; - m_presences[Presence::Chat] = "chat"; - m_presences[Presence::Away] = "away"; - m_presences[Presence::DND] = "dnd"; - m_presences[Presence::XA] = "xa"; - m_presences[Presence::Unavailable] = "unavailable"; - m_presences[Presence::Probe] = "probe"; - m_presences[Presence::Error] = "error"; - m_presences[Presence::Invalid] = "invalid"; + m_presences[gloox::Presence::Available] = "available"; + m_presences[gloox::Presence::Chat] = "chat"; + m_presences[gloox::Presence::Away] = "away"; + m_presences[gloox::Presence::DND] = "dnd"; + m_presences[gloox::Presence::XA] = "xa"; + m_presences[gloox::Presence::Unavailable] = "unavailable"; + m_presences[gloox::Presence::Probe] = "probe"; + m_presences[gloox::Presence::Error] = "error"; + m_presences[gloox::Presence::Invalid] = "invalid"; - m_jid = JID( jid.toStdString() ); + m_jid = gloox::JID( jid.toStdString() ); if( m_jid.resource().find( "tomahawk" ) == std::string::npos ) { @@ -36,21 +59,13 @@ Jabber_p::Jabber_p( const QString& jid, const QString& password, const QString& qDebug() << "Our JID set to:" << m_jid.full().c_str(); - // the google hack, because they filter disco features they don't know. - if( m_jid.server().find( "googlemail." ) != string::npos - || m_jid.server().find( "gmail." ) != string::npos - || m_jid.server().find( "gtalk." ) != string::npos ) - { - if( m_jid.resource().find( "tomahawk" ) == string::npos ) - { - qDebug() << "Forcing your /resource to contain 'tomahawk' (the google workaround)"; - m_jid.setResource( "tomahawk-tomahawk" ); - } - } - m_client = QSharedPointer( new gloox::Client( m_jid, password.toStdString(), port ) ); - if( !server.isEmpty() ) - m_client->setServer( server.toStdString() ); + const gloox::Capabilities *caps = m_client->presence().capabilities(); + if( caps ) + { + const_cast(caps)->setNode(TOMAHAWK_CAP_NODE_NAME.toStdString()); + } + m_server = server; } @@ -65,6 +80,19 @@ Jabber_p::~Jabber_p() } } +void +Jabber_p::resolveHostSRV() +{ + if( m_server.isEmpty() ) + { + qDebug() << "No server found!"; + return; + } + TomahawkUtils::DNSResolver *resolver = TomahawkUtils::dnsResolver(); + connect( resolver, SIGNAL(result(QString &)), SLOT(resolveResult(QString &)) ); + qDebug() << "Resolving SRV record of " << m_server; + resolver->resolve( m_server, "SRV" ); +} void Jabber_p::setProxy( QNetworkProxy* proxy ) @@ -98,13 +126,30 @@ Jabber_p::setProxy( QNetworkProxy* proxy ) } } +void +Jabber_p::resolveResult( QString& result ) +{ + if ( result != "NONE" ) + m_server = result; + qDebug() << "Final host name for XMPP server set to " << m_server; + QMetaObject::invokeMethod( this, "go", Qt::QueuedConnection ); +} void Jabber_p::go() { + if( !m_server.isEmpty() ) + m_client->setServer( m_server.toStdString() ); + else + { + qDebug() << "No server found!"; + return; + } + + m_client->registerPresenceHandler( this ); m_client->registerConnectionListener( this ); - m_client->rosterManager()->registerRosterListener( this ); - m_client->logInstance().registerLogHandler( LogLevelWarning, LogAreaAll, this ); + m_client->rosterManager()->registerRosterListener( this, false ); // false means async + m_client->logInstance().registerLogHandler( gloox::LogLevelWarning, gloox::LogAreaAll, this ); m_client->registerMessageHandler( this ); /* @@ -114,18 +159,22 @@ Jabber_p::go() m_client->disco()->addFeature( "tomahawk:player" ); */ - m_client->setPresence( Presence::Available, 1, "Tomahawk available" ); + m_client->setPresence( gloox::Presence::XA, -127, "Tomahawk available" ); // m_client->connect(); // return; // Handle proxy - if ( m_client->connect( false ) ) + qDebug() << "Connecting to the XMPP server..."; + if( m_client->connect( false ) ) { - emit connected(); - QTimer::singleShot( 0, this, SLOT( doJabberRecv() ) ); + int sock = static_cast( m_client->connectionImpl() )->socket(); + m_notifier.reset( new QSocketNotifier( sock, QSocketNotifier::Read ) ); + connect( m_notifier.data(), SIGNAL( activated(int) ), SLOT( doJabberRecv() )); } + else + qDebug() << "Could not connect to the XMPP server!"; } @@ -135,15 +184,11 @@ Jabber_p::doJabberRecv() if ( m_client.isNull() ) return; - ConnectionError ce = m_client->recv( 100 ); - if ( ce != ConnNoError ) + gloox::ConnectionError ce = m_client->recv( 100 ); + if ( ce != gloox::ConnNoError ) { qDebug() << "Jabber_p::Recv failed, disconnected"; } - else - { - QTimer::singleShot( 100, this, SLOT( doJabberRecv() ) ); - } } @@ -177,7 +222,7 @@ Jabber_p::sendMsg( const QString& to, const QString& msg ) return; qDebug() << Q_FUNC_INFO << to << msg; - Message m( Message::Chat, JID(to.toStdString()), msg.toStdString(), "" ); + gloox::Message m( gloox::Message::Chat, gloox::JID(to.toStdString()), msg.toStdString(), "" ); m_client->send( m ); // assuming this is threadsafe } @@ -201,17 +246,35 @@ Jabber_p::broadcastMsg( const QString &msg ) std::string msg_s = msg.toStdString(); foreach( const QString& jidstr, m_peers.keys() ) { - Message m(Message::Chat, JID(jidstr.toStdString()), msg_s, ""); + gloox::Message m(gloox::Message::Chat, gloox::JID(jidstr.toStdString()), msg_s, ""); m_client->send( m ); } } +void +Jabber_p::addContact( const QString& jid, const QString& msg ) +{ + if ( QThread::currentThread() != thread() ) + { + QMetaObject::invokeMethod( this, "addContact", + Qt::QueuedConnection, + Q_ARG(const QString, jid), + Q_ARG(const QString, msg) + ); + return; + } + + handleSubscription(gloox::JID(jid.toStdString()), msg.toStdString()); + return; +} + /// GLOOX IMPL STUFF FOLLOWS void Jabber_p::onConnect() { + qDebug() << "Connected to the XMPP server"; // update jid resource, servers like gtalk use resource binding and may // have changed our requested /resource if ( m_client->resource() != m_jid.resource() ) @@ -222,27 +285,29 @@ Jabber_p::onConnect() } qDebug() << "Connected as:" << m_jid.full().c_str(); + emit connected(); } void -Jabber_p::onDisconnect( ConnectionError e ) +Jabber_p::onDisconnect( gloox::ConnectionError e ) { qDebug() << "Jabber Disconnected"; QString error; + bool triggeredDisconnect = false; switch( e ) { - case AuthErrorUndefined: + case gloox::AuthErrorUndefined: error = " No error occurred, or error condition is unknown"; break; - case SaslAborted: + case gloox::SaslAborted: error = "The receiving entity acknowledges an <abort/> element sent " "by the initiating entity; sent in reply to the <abort/> element."; break; - case SaslIncorrectEncoding: + case gloox::SaslIncorrectEncoding: error = "The data provided by the initiating entity could not be processed " "because the [BASE64] encoding is incorrect (e.g., because the encoding " "does not adhere to the definition in Section 3 of [BASE64]); sent in " @@ -250,7 +315,7 @@ Jabber_p::onDisconnect( ConnectionError e ) "initial response data."; break; - case SaslInvalidAuthzid: + case gloox::SaslInvalidAuthzid: error = "The authzid provided by the initiating entity is invalid, either " "because it is incorrectly formatted or because the initiating entity " "does not have permissions to authorize that ID; sent in reply to a " @@ -258,74 +323,77 @@ Jabber_p::onDisconnect( ConnectionError e ) "response data."; break; - case SaslInvalidMechanism: + case gloox::SaslInvalidMechanism: error = "The initiating entity did not provide a mechanism or requested a " "mechanism that is not supported by the receiving entity; sent in reply " "to an <auth/> element."; break; - case SaslMalformedRequest: + case gloox::SaslMalformedRequest: error = "The request is malformed (e.g., the <auth/> element includes " "an initial response but the mechanism does not allow that); sent in " "reply to an <abort/>, <auth/>, <challenge/>, or " "<response/> element."; break; - case SaslMechanismTooWeak: + case gloox::SaslMechanismTooWeak: error = "The mechanism requested by the initiating entity is weaker than " "server policy permits for that initiating entity; sent in reply to a " "<response/> element or an <auth/> element with initial " "response data."; break; - case SaslNotAuthorized: + case gloox::SaslNotAuthorized: error = "The authentication failed because the initiating entity did not " "provide valid credentials (this includes but is not limited to the " "case of an unknown username); sent in reply to a <response/> " "element or an <auth/> element with initial response data. "; break; - case SaslTemporaryAuthFailure: + case gloox::SaslTemporaryAuthFailure: error = "The authentication failed because of a temporary error condition " "within the receiving entity; sent in reply to an <auth/> element " "or <response/> element."; break; - case NonSaslConflict: + case gloox::NonSaslConflict: error = "XEP-0078: Resource Conflict"; break; - case NonSaslNotAcceptable: + case gloox::NonSaslNotAcceptable: error = "XEP-0078: Required Information Not Provided"; break; - case NonSaslNotAuthorized: + case gloox::NonSaslNotAuthorized: error = "XEP-0078: Incorrect Credentials"; break; - case ConnAuthenticationFailed: + case gloox::ConnAuthenticationFailed: error = "Authentication failed"; break; - case ConnNoSupportedAuth: + case gloox::ConnNoSupportedAuth: error = "No supported auth mechanism"; break; default : error = "UNKNOWN ERROR"; + triggeredDisconnect = true; } qDebug() << "Connection error msg:" << error; - emit authError( e, error ); + // Assume that an unknown error is due to a disconnect triggered by the user + if( !triggeredDisconnect ) + emit authError( e, error ); // trigger reconnect emit disconnected(); - // trigger reconnect + } bool -Jabber_p::onTLSConnect( const CertInfo& info ) +Jabber_p::onTLSConnect( const gloox::CertInfo& info ) { qDebug() << Q_FUNC_INFO << "Status" << info.status @@ -335,8 +403,8 @@ Jabber_p::onTLSConnect( const CertInfo& info ) << "mac" << info.mac.c_str() << "cipher" << info.cipher.c_str() << "compression" << info.compression.c_str() - << "from" << ctime( (const time_t*)&info.date_from ) - << "to" << ctime( (const time_t*)&info.date_to ) + << "from" << std::ctime( (const time_t*)&info.date_from ) + << "to" << std::ctime( (const time_t*)&info.date_to ) ; //onConnect(); @@ -345,7 +413,7 @@ Jabber_p::onTLSConnect( const CertInfo& info ) void -Jabber_p::handleMessage( const Message& m, MessageSession * /*session*/ ) +Jabber_p::handleMessage( const gloox::Message& m, gloox::MessageSession * /*session*/ ) { QString from = QString::fromStdString( m.from().full() ); QString msg = QString::fromStdString( m.body() ); @@ -361,12 +429,21 @@ Jabber_p::handleMessage( const Message& m, MessageSession * /*session*/ ) //sendMsg( from, QString("You said %1").arg(msg) ); + QJson::Parser parser; + bool ok; + QVariant v = parser.parse( msg.toAscii(), &ok ); + if ( !ok || v.type() != QVariant::Map ) + { + sendMsg( from, QString( "I'm sorry -- I'm just an automatic presence used by Tomahawk Player (http://gettomahawk.com). If you are getting this message, the person you are trying to reach is probably not signed on, so please try again later!" ) ); + return; + } + emit msgReceived( from, msg ); } void -Jabber_p::handleLog( LogLevel level, LogArea area, const std::string& message ) +Jabber_p::handleLog( gloox::LogLevel level, gloox::LogArea area, const std::string& message ) { qDebug() << Q_FUNC_INFO << "level:" << level @@ -378,49 +455,49 @@ Jabber_p::handleLog( LogLevel level, LogArea area, const std::string& message ) /// ROSTER STUFF // {{{ void -Jabber_p::onResourceBindError( ResourceBindError error ) +Jabber_p::onResourceBindError( gloox::ResourceBindError error ) { qDebug() << Q_FUNC_INFO; } void -Jabber_p::onSessionCreateError( SessionCreateError error ) +Jabber_p::onSessionCreateError( gloox::SessionCreateError error ) { qDebug() << Q_FUNC_INFO; } void -Jabber_p::handleItemSubscribed( const JID& jid ) +Jabber_p::handleItemSubscribed( const gloox::JID& jid ) { qDebug() << Q_FUNC_INFO << jid.full().c_str(); } void -Jabber_p::handleItemAdded( const JID& jid ) +Jabber_p::handleItemAdded( const gloox::JID& jid ) { qDebug() << Q_FUNC_INFO << jid.full().c_str(); } void -Jabber_p::handleItemUnsubscribed( const JID& jid ) +Jabber_p::handleItemUnsubscribed( const gloox::JID& jid ) { qDebug() << Q_FUNC_INFO << jid.full().c_str(); } void -Jabber_p::handleItemRemoved( const JID& jid ) +Jabber_p::handleItemRemoved( const gloox::JID& jid ) { qDebug() << Q_FUNC_INFO << jid.full().c_str(); } void -Jabber_p::handleItemUpdated( const JID& jid ) +Jabber_p::handleItemUpdated( const gloox::JID& jid ) { qDebug() << Q_FUNC_INFO << jid.full().c_str(); } @@ -428,14 +505,14 @@ Jabber_p::handleItemUpdated( const JID& jid ) void -Jabber_p::handleRoster( const Roster& roster ) +Jabber_p::handleRoster( const gloox::Roster& roster ) { // qDebug() << Q_FUNC_INFO; - Roster::const_iterator it = roster.begin(); +gloox::Roster::const_iterator it = roster.begin(); for ( ; it != roster.end(); ++it ) { - if ( (*it).second->subscription() != S10nBoth ) continue; + if ( (*it).second->subscription() != gloox::S10nBoth ) continue; qDebug() << (*it).second->jid().c_str() << (*it).second->name().c_str(); //printf("JID: %s\n", (*it).second->jid().c_str()); } @@ -447,30 +524,32 @@ Jabber_p::handleRoster( const Roster& roster ) void -Jabber_p::handleRosterError( const IQ& /*iq*/ ) +Jabber_p::handleRosterError( const gloox::IQ& /*iq*/ ) { qDebug() << Q_FUNC_INFO; } void -Jabber_p::handleRosterPresence( const RosterItem& item, const std::string& resource, - Presence::PresenceType presence, const std::string& /*msg*/ ) -{ - JID jid( item.jid() ); - jid.setResource( resource ); +Jabber_p::handlePresence( const gloox::Presence& presence ) +{ + gloox::JID jid = presence.from(); QString fulljid( jid.full().c_str() ); - qDebug() << "* handleRosterPresence" << fulljid << presence; + qDebug() << "* handleRosterPresence" << fulljid << presence.subtype(); if( jid == m_jid ) return; // ignore anyone not running tomahawk: // convert to QString to get proper regex support - QString res( jid.resource().c_str() ); - QRegExp regex( "tomahawk\\d+" ); - if( res != "tomahawk-tomahawk" && !res.contains( regex ) ) + QString node; + const gloox::Capabilities *caps = presence.findExtension( gloox::ExtCaps ); + if( caps ) + QString node = QString::fromAscii( caps->node().c_str() ); + + if( !QString::fromAscii( jid.resource().c_str() ).startsWith( QLatin1String( "tomahawk" ) ) + && !( node == TOMAHAWK_CAP_NODE_NAME ) ) { //qDebug() << "not considering resource of" << res; // Disco them to check if they are tomahawk-capable @@ -487,57 +566,133 @@ Jabber_p::handleRosterPresence( const RosterItem& item, const std::string& resou // << " presencetype" << presence; // "going offline" event - if ( !presenceMeansOnline( presence ) && + if ( !presenceMeansOnline( presence.subtype() ) && ( !m_peers.contains( fulljid ) || - presenceMeansOnline( m_peers.value( fulljid ) ) - ) - ) + presenceMeansOnline( m_peers.value( fulljid ) ) ) ) { - m_peers[ fulljid ] = presence; + m_peers[ fulljid ] = presence.subtype(); qDebug() << "* Peer goes offline:" << fulljid; emit peerOffline( fulljid ); return; } // "coming online" event - if( presenceMeansOnline( presence ) && - ( !m_peers.contains( fulljid ) || - !presenceMeansOnline( m_peers.value( fulljid ) ) - ) - ) + if ( presenceMeansOnline( presence.subtype() ) && + ( !m_peers.contains( fulljid ) || + !presenceMeansOnline( m_peers.value( fulljid ) ) ) ) { - m_peers[ fulljid ] = presence; + m_peers[ fulljid ] = presence.subtype(); qDebug() << "* Peer goes online:" << fulljid; emit peerOnline( fulljid ); return; } //qDebug() << "Updating presence data for" << fulljid; - m_peers[ fulljid ] = presence; -} - - -void -Jabber_p::handleSelfPresence( const RosterItem& item, const std::string& resource, - Presence::PresenceType presence, const std::string& msg ) -{ -// qDebug() << Q_FUNC_INFO; - handleRosterPresence( item, resource, presence, msg ); + m_peers[ fulljid ] = presence.subtype(); } bool -Jabber_p::handleSubscriptionRequest( const JID& jid, const std::string& /*msg*/ ) +Jabber_p::handleSubscription( const gloox::JID& jid, const std::string& /*msg*/ ) { qDebug() << Q_FUNC_INFO << jid.bare().c_str(); - StringList groups; + + gloox::StringList groups; + groups.push_back( "Tomahawk" ); m_client->rosterManager()->subscribe( jid, "", groups, "" ); return true; } bool -Jabber_p::handleUnsubscriptionRequest( const JID& jid, const std::string& /*msg*/ ) +Jabber_p::handleSubscriptionRequest( const gloox::JID& jid, const std::string& /*msg*/ ) +{ + qDebug() << Q_FUNC_INFO << jid.bare().c_str(); + + // check if the requester is already on the roster + gloox::RosterItem *item = m_client->rosterManager()->getRosterItem(jid); + if(item) qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "subscription status:" << static_cast( item->subscription() ); + if(item && + ( + item->subscription() == gloox::S10nNoneOut || // Contact and user are not subscribed to each other, and user has sent contact a subscription request but contact has not replied yet. + item->subscription() == gloox::S10nTo || // User is subscribed to contact (one-way). + item->subscription() == gloox::S10nToIn // User is subscribed to contact, and contact has sent user a subscription request but user has not replied yet. + ) + ) + { + qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "already on the roster so we assume ack'ing subscription request is okay..."; + + // ack the request + m_client->rosterManager()->ackSubscriptionRequest( jid, true ); + + // return anything, the result is ignored + return false; + } + + // we don't have to check for an already open check box because gloox doesnt call this method until a former request was ack'ed + qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "open subscription request box"; + + // preparing the confirm box for the user + QMessageBox *confirmBox = new QMessageBox( + QMessageBox::Question, + tr( "Authorize User" ), + QString( tr( "Do you want to grant %1 access to your Collection?" ) ).arg( QLatin1String( jid.bare().c_str() ) ), + QMessageBox::Yes | QMessageBox::No, + 0 + ); + + // add confirmBox to m_subscriptionConfirmBoxes + m_subscriptionConfirmBoxes.insert( jid, confirmBox ); + + // display the box and wait for the answer + confirmBox->open( this, SLOT( onSubscriptionRequestConfirmed( int ) ) ); + + return false; +} + + +void +Jabber_p::onSubscriptionRequestConfirmed( int result ) +{ + qDebug() << Q_FUNC_INFO << result; + + QList< QMessageBox* > confirmBoxes = m_subscriptionConfirmBoxes.values(); + gloox::JID jid; + + foreach( QMessageBox* currentBox, confirmBoxes ) + { + if( currentBox == sender() ) + { + jid = m_subscriptionConfirmBoxes.key( currentBox ); + } + } + + qDebug() << Q_FUNC_INFO << "box confirmed for" << jid.bare().c_str(); + + // we got an answer, deleting the box + m_subscriptionConfirmBoxes.remove( jid ); + sender()->deleteLater(); + + QMessageBox::StandardButton allowSubscription = static_cast( result ); + + if ( allowSubscription == QMessageBox::Yes ) + { + qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "accepted by user, adding to roster"; + gloox::StringList groups; + groups.push_back( "Tomahawk" ); + m_client->rosterManager()->subscribe( jid, "", groups, "" ); + } + else + { + qDebug() << Q_FUNC_INFO << jid.bare().c_str() << "declined by user"; + } + + m_client->rosterManager()->ackSubscriptionRequest( jid, allowSubscription == QMessageBox::Yes ); +} + + +bool +Jabber_p::handleUnsubscriptionRequest( const gloox::JID& jid, const std::string& /*msg*/ ) { qDebug() << Q_FUNC_INFO << jid.bare().c_str(); return true; @@ -545,7 +700,7 @@ Jabber_p::handleUnsubscriptionRequest( const JID& jid, const std::string& /*msg* void -Jabber_p::handleNonrosterPresence( const Presence& presence ) +Jabber_p::handleNonrosterPresence( const gloox::Presence& presence ) { qDebug() << Q_FUNC_INFO << presence.from().full().c_str(); } @@ -553,14 +708,14 @@ Jabber_p::handleNonrosterPresence( const Presence& presence ) void -Jabber_p::handleVCard( const JID& jid, const VCard* vcard ) +Jabber_p::handleVCard( const gloox::JID& jid, const gloox::VCard* vcard ) { qDebug() << "VCARD RECEIVED!" << jid.bare().c_str(); } void -Jabber_p::handleVCardResult( VCardContext context, const JID& jid, StanzaError se ) +Jabber_p::handleVCardResult( gloox::VCardHandler::VCardContext context, const gloox::JID& jid, gloox::StanzaError se ) { qDebug() << "VCARD RESULT RECEIVED!" << jid.bare().c_str(); } @@ -568,14 +723,14 @@ Jabber_p::handleVCardResult( VCardContext context, const JID& jid, StanzaError s /// DISCO STUFF void -Jabber_p::handleDiscoInfo( const JID& from, const Disco::Info& info, int context) +Jabber_p::handleDiscoInfo( const gloox::JID& from, const gloox::Disco::Info& info, int context ) { QString jidstr( from.full().c_str() ); //qDebug() << "DISCOinfo" << jidstr; if ( info.hasFeature("tomahawk:player") ) { qDebug() << "Peer online and DISCOed ok:" << jidstr; - m_peers.insert( jidstr, Presence::XA ); + m_peers.insert( jidstr, gloox::Presence::XA ); emit peerOnline( jidstr ); } else @@ -586,14 +741,14 @@ Jabber_p::handleDiscoInfo( const JID& from, const Disco::Info& info, int context void -Jabber_p::handleDiscoItems( const JID& /*iq*/, const Disco::Items&, int /*context*/ ) +Jabber_p::handleDiscoItems( const gloox::JID& /*iq*/, const gloox::Disco::Items&, int /*context*/ ) { qDebug() << Q_FUNC_INFO; } void -Jabber_p::handleDiscoError( const JID& j, const Error* e, int /*context*/ ) +Jabber_p::handleDiscoError( const gloox::JID& j, const gloox::Error* e, int /*context*/ ) { qDebug() << Q_FUNC_INFO << j.full().c_str() << e->text().c_str() << e->type(); } @@ -601,13 +756,13 @@ Jabber_p::handleDiscoError( const JID& j, const Error* e, int /*context*/ ) bool -Jabber_p::presenceMeansOnline( Presence::PresenceType p ) +Jabber_p::presenceMeansOnline( gloox::Presence::PresenceType p ) { switch(p) { - case Presence::Invalid: - case Presence::Unavailable: - case Presence::Error: + case gloox::Presence::Invalid: + case gloox::Presence::Unavailable: + case gloox::Presence::Error: return false; break; default: diff --git a/src/jabber/jabber_p.h b/src/sip/jabber/jabber_p.h similarity index 69% rename from src/jabber/jabber_p.h rename to src/sip/jabber/jabber_p.h index acccd7bce..3a120fbac 100644 --- a/src/jabber/jabber_p.h +++ b/src/sip/jabber/jabber_p.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + /* This is the Jabber client that the rest of the app sees Gloox stuff should NOT leak outside this class. @@ -7,10 +25,12 @@ #define JABBER_P_H #include +#include #include +#include #include #include -#include +#include #include @@ -49,12 +69,15 @@ # include #endif -class Jabber_p : +#include "../sipdllmacro.h" + +class SIPDLLEXPORT Jabber_p : public QObject, public gloox::ConnectionListener, - public gloox::RosterListener, public gloox::MessageHandler, public gloox::VCardHandler, + public gloox::PresenceHandler, + public gloox::RosterListener, gloox::LogHandler //public gloox::DiscoHandler, { @@ -66,8 +89,8 @@ public: void setProxy( QNetworkProxy* proxy ); - void disconnect(); - + void resolveHostSRV(); + /// GLOOX IMPLEMENTATION STUFF FOLLOWS virtual void onConnect(); virtual void onDisconnect( gloox::ConnectionError e ); @@ -88,10 +111,13 @@ public: virtual void handleRoster( const gloox::Roster& roster ); virtual void handleRosterError( const gloox::IQ& /*iq*/ ); - virtual void handleRosterPresence( const gloox::RosterItem& item, const std::string& resource, - gloox::Presence::PresenceType presence, const std::string& /*msg*/ ); - virtual void handleSelfPresence( const gloox::RosterItem& item, const std::string& resource, - gloox::Presence::PresenceType presence, const std::string& msg ); + + // not actually used, just needed for the interface to support subscription requests, see handlePresence for presence handling + virtual void handleRosterPresence( const gloox::RosterItem&, const std::string&, gloox::Presence::PresenceType, const std::string& ) {} + virtual void handleSelfPresence( const gloox::RosterItem&, const std::string&, gloox::Presence::PresenceType, const std::string& ) {} + + virtual void handlePresence( const gloox::Presence& presence ); + virtual bool handleSubscription( const gloox::JID& jid, const std::string& /*msg*/ ); virtual bool handleSubscriptionRequest( const gloox::JID& jid, const std::string& /*msg*/ ); virtual bool handleUnsubscriptionRequest( const gloox::JID& jid, const std::string& /*msg*/ ); virtual void handleNonrosterPresence( const gloox::Presence& presence ); @@ -119,12 +145,16 @@ signals: void authError( int, const QString& ); public slots: + void resolveResult( QString & ); void go(); void sendMsg( const QString& to, const QString& msg ); - void broadcastMsg( const QString &msg ); + void broadcastMsg( const QString& msg ); + void addContact( const QString& jid, const QString& msg = QString() ); + void disconnect(); private slots: void doJabberRecv(); + void onSubscriptionRequestConfirmed( int result ); private: bool presenceMeansOnline( gloox::Presence::PresenceType p ); @@ -134,6 +164,9 @@ private: QMap m_presences; QMap m_peers; QSharedPointer m_vcardManager; + QString m_server; + QScopedPointer m_notifier; + QHash m_subscriptionConfirmBoxes; }; #endif // JABBER_H diff --git a/src/sip/jreen/CMakeLists.txt b/src/sip/jreen/CMakeLists.txt new file mode 100644 index 000000000..df2a4f7b6 --- /dev/null +++ b/src/sip/jreen/CMakeLists.txt @@ -0,0 +1,46 @@ +project( tomahawk ) + +include( ${QT_USE_FILE} ) +add_definitions( ${QT_DEFINITIONS} ) +add_definitions( -DQT_PLUGIN ) +add_definitions( -DQT_SHARED ) +add_definitions( -DSIPDLLEXPORT_PRO ) + +set( jabberSources + jabber.cpp + jabber_p.cpp +) + +set( jabberHeaders + jabber.h + jabber_p.h +) + +include_directories( . ${CMAKE_CURRENT_BINARY_DIR} .. + ${QT_INCLUDE_DIR} + ${LIBJREEN_INCLUDE_DIR} +) + +qt4_wrap_cpp( jabberMoc ${jabberHeaders} ) +add_library( tomahawk_sipjreen SHARED ${jabberSources} ${jabberMoc} ) + +IF( WIN32 ) +SET( OS_SPECIFIC_LINK_LIBRARIES + ${OS_SPECIFIC_LINK_LIBRARIES} + "secur32.dll" + "crypt32.dll" + ${TOMAHAWK_LIBRARIES} +) +ENDIF( WIN32 ) + +target_link_libraries( tomahawk_sipjreen + ${QT_LIBRARIES} + ${LIBJREEN_LIBRARY} + ${OS_SPECIFIC_LINK_LIBRARIES} +) + +IF( APPLE ) +# SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) +ENDIF( APPLE ) + +install( TARGETS tomahawk_sipjreen DESTINATION lib${LIB_SUFFIX} ) diff --git a/src/sip/jreen/jabber.cpp b/src/sip/jreen/jabber.cpp new file mode 100644 index 000000000..0d0c9acc6 --- /dev/null +++ b/src/sip/jreen/jabber.cpp @@ -0,0 +1,193 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "jabber.h" + +#include "tomahawksettings.h" + +#include +#include +#include +#include +#include + +JabberPlugin::JabberPlugin() + : p( 0 ) + , m_menu( 0 ) + , m_addFriendAction( 0 ) +{ +} + +JabberPlugin::~JabberPlugin() +{ + delete p; +} + +void +JabberPlugin::setProxy( QNetworkProxy* proxy ) +{ + p->setProxy( proxy ); +} + + +const QString +JabberPlugin::name() +{ + return QString( MYNAME ); +} + +const QString +JabberPlugin::friendlyName() +{ + return QString( "Jabber" ); +} + +const QString +JabberPlugin::accountName() +{ + return TomahawkSettings::instance()->jabberUsername(); +} + +QMenu* +JabberPlugin::menu() +{ + return m_menu; +} + +bool +JabberPlugin::connectPlugin( bool startup ) +{ + qDebug() << Q_FUNC_INFO; + + if ( startup && !TomahawkSettings::instance()->jabberAutoConnect() ) + return false; + + QString jid = TomahawkSettings::instance()->jabberUsername(); + QString server = TomahawkSettings::instance()->jabberServer(); + QString password = TomahawkSettings::instance()->jabberPassword(); + unsigned int port = TomahawkSettings::instance()->jabberPort(); + + QStringList splitJid = jid.split( '@', QString::SkipEmptyParts ); + if ( splitJid.size() < 2 ) + { + qDebug() << "JID did not have an @ in it, could not find a server part"; + return false; + } + + if ( server.isEmpty() ) + server = splitJid[1]; + + if ( port < 1 || port > 65535 || jid.isEmpty() || password.isEmpty() ) + { + qDebug() << "Jabber credentials look wrong, not connecting"; + return false; + } + + delete p; + p = new Jabber_p( jid, password, server, port ); + + QObject::connect( p, SIGNAL( peerOnline( QString ) ), SIGNAL( peerOnline( QString ) ) ); + QObject::connect( p, SIGNAL( peerOffline( QString ) ), SIGNAL( peerOffline( QString ) ) ); + QObject::connect( p, SIGNAL( msgReceived( QString, QString ) ), SIGNAL( msgReceived( QString, QString ) ) ); + + QObject::connect( p, SIGNAL( connected() ), SIGNAL( onConnected() ) ); + QObject::connect( p, SIGNAL( disconnected() ), SIGNAL( onDisconnected() ) ); + + return true; +} + +void +JabberPlugin::disconnectPlugin() +{ + onDisconnected(); + + if ( p ) + p->disconnect(); + + delete p; + p = 0; +} + +void +JabberPlugin::onConnected() +{ + if( !m_menu ) { + m_menu = new QMenu( QString( "JREEN (" ).append( accountName() ).append(")" ) ); + m_addFriendAction = m_menu->addAction( "Add Friend..." ); + QAction *connectAction = m_menu->addAction( "Connect" ); + + connect( m_addFriendAction, SIGNAL(triggered() ), + this, SLOT( showAddFriendDialog() ) ); + connect( connectAction, SIGNAL( triggered() ), SLOT( connectPlugin() ) ); + + emit addMenu( m_menu ); + } + + emit connected(); +} + +void +JabberPlugin::onDisconnected() +{ + if( m_menu && m_addFriendAction ) { + emit removeMenu( m_menu ); + + delete m_menu; + m_menu = 0; + m_addFriendAction = 0; // deleted by menu + } + + emit disconnected(); +} + +void +JabberPlugin::sendMsg(const QString& to, const QString& msg) +{ + if ( p ) + p->sendMsg( to, msg ); +} + +void +JabberPlugin::broadcastMsg(const QString& msg) +{ + if ( p ) + p->broadcastMsg( msg ); +} + +void +JabberPlugin::addContact(const QString& jid, const QString& msg) +{ + if ( p ) + p->addContact( jid, msg ); +} + +void +JabberPlugin::showAddFriendDialog() +{ + bool ok; + QString id = QInputDialog::getText( 0, tr( "Add Friend" ), + tr( "Enter Jabber ID:" ), QLineEdit::Normal, + "", &ok ); + if ( !ok ) + return; + + qDebug() << "Attempting to add jabber contact to roster:" << id; + addContact( id ); +} + +Q_EXPORT_PLUGIN2( sip, JabberPlugin ) diff --git a/src/sip/jreen/jabber.h b/src/sip/jreen/jabber.h new file mode 100644 index 000000000..543a5a218 --- /dev/null +++ b/src/sip/jreen/jabber.h @@ -0,0 +1,65 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef JABBER_H +#define JABBER_H + +#include "sip/SipPlugin.h" +#include "jabber_p.h" + +#include "../sipdllmacro.h" + +#define MYNAME "SIPJABBER" + +class SIPDLLEXPORT JabberPlugin : public SipPlugin +{ + Q_OBJECT + Q_INTERFACES( SipPlugin ) + +public: + JabberPlugin(); + virtual ~JabberPlugin(); + + //FIXME: Make this more correct + virtual bool isValid() { return true; } + virtual const QString name(); + virtual const QString friendlyName(); + virtual const QString accountName(); + virtual QMenu* menu(); + + void setProxy( QNetworkProxy* proxy ); + +public slots: + virtual bool connectPlugin( bool startup ); + void disconnectPlugin(); + void sendMsg( const QString& to, const QString& msg ); + void broadcastMsg( const QString &msg ); + void addContact( const QString &jid, const QString& msg = QString() ); + +private slots: + void showAddFriendDialog(); + void onConnected(); + void onDisconnected(); + +private: + Jabber_p* p; + QMenu* m_menu; + QAction* m_addFriendAction; +}; + +#endif diff --git a/src/sip/jreen/jabber_p.cpp b/src/sip/jreen/jabber_p.cpp new file mode 100644 index 000000000..a902ef4a3 --- /dev/null +++ b/src/sip/jreen/jabber_p.cpp @@ -0,0 +1,371 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "jabber_p.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +//remove +#include +#include + +using namespace std; + + +#define TOMAHAWK_CAP_NODE_NAME QLatin1String("http://tomahawk-player.org/") + +Jabber_p::Jabber_p( const QString& jid, const QString& password, const QString& server, const int port ) + : QObject() + , m_server() +{ + qDebug() << Q_FUNC_INFO; + //qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); + qsrand(QDateTime::currentDateTime().toTime_t()); + + m_presences[jreen::Presence::Available] = "available"; + m_presences[jreen::Presence::Chat] = "chat"; + m_presences[jreen::Presence::Away] = "away"; + m_presences[jreen::Presence::DND] = "dnd"; + m_presences[jreen::Presence::XA] = "xa"; + m_presences[jreen::Presence::Unavailable] = "unavailable"; + m_presences[jreen::Presence::Probe] = "probe"; + m_presences[jreen::Presence::Error] = "error"; + m_presences[jreen::Presence::Invalid] = "invalid"; + + m_jid = jreen::JID( jid ); + + m_client = new jreen::Client( jid, password ); + m_client->setResource( QString( "tomahawk%1" ).arg( "DOMME" ) ); + + jreen::Capabilities::Ptr caps = m_client->presence().findExtension(); + caps->setNode(TOMAHAWK_CAP_NODE_NAME); + + qDebug() << "Our JID set to:" << m_client->jid().full(); + qDebug() << "Our Server set to:" << m_client->server(); + qDebug() << "Our Port set to" << m_client->port(); + + connect(m_client->connection(), SIGNAL(error(SocketError)), SLOT(onError(SocketError))); + connect(m_client, SIGNAL(serverFeaturesReceived(QSet)), SLOT(onConnect())); + connect(m_client, SIGNAL(disconnected(jreen::Client::DisconnectReason)), SLOT(onDisconnect(jreen::Client::DisconnectReason))); + connect(m_client, SIGNAL(destroyed(QObject*)), this, SLOT(onDestroy())); + connect(m_client, SIGNAL(newMessage(jreen::Message)), SLOT(onNewMessage(jreen::Message))); + connect(m_client, SIGNAL(newPresence(jreen::Presence)), SLOT(onNewPresence(jreen::Presence))); + + qDebug() << "Connecting to the XMPP server..."; + m_client->connectToServer(); +} + + +Jabber_p::~Jabber_p() +{ + delete m_client; +} + +void +Jabber_p::setProxy( QNetworkProxy* proxy ) +{ + qDebug() << Q_FUNC_INFO << "NOT IMPLEMENTED"; +} + +void +Jabber_p::disconnect() +{ + if ( m_client ) + { + m_client->disconnect(); + } +} + + +void +Jabber_p::sendMsg( const QString& to, const QString& msg ) +{ + qDebug() << Q_FUNC_INFO; + if ( QThread::currentThread() != thread() ) + { + qDebug() << Q_FUNC_INFO << "invoking in correct thread, not" + << QThread::currentThread(); + + QMetaObject::invokeMethod( this, "sendMsg", + Qt::QueuedConnection, + Q_ARG( const QString, to ), + Q_ARG( const QString, msg ) + ); + return; + } + + if ( !m_client ) { + return; + } + + qDebug() << Q_FUNC_INFO << to << msg; + jreen::Message m( jreen::Message::Chat, jreen::JID(to), msg); + + m_client->send( m ); // assuming this is threadsafe +} + + +void +Jabber_p::broadcastMsg( const QString &msg ) +{ + qDebug() << Q_FUNC_INFO; + if ( QThread::currentThread() != thread() ) + { + QMetaObject::invokeMethod( this, "broadcastMsg", + Qt::QueuedConnection, + Q_ARG(const QString, msg) + ); + return; + } + + if ( !m_client ) + return; + + foreach( const QString& jidstr, m_peers.keys() ) + { + qDebug() << "Broadcasting to" << jidstr <<"..."; + jreen::Message m(jreen::Message::Chat, jreen::JID(jidstr), msg, ""); + m_client->send( m ); + } +} + + +void +Jabber_p::addContact( const QString& jid, const QString& msg ) +{ + if ( QThread::currentThread() != thread() ) + { + QMetaObject::invokeMethod( this, "addContact", + Qt::QueuedConnection, + Q_ARG(const QString, jid), + Q_ARG(const QString, msg) + ); + return; + } + + // Add contact to the Tomahawk group on the roster + m_roster->add( jid, jid, QStringList() << "Tomahawk" ); + + return; +} + +void +Jabber_p::onConnect() +{ + qDebug() << Q_FUNC_INFO; + + // update jid resource, servers like gtalk use resource binding and may + // have changed our requested /resource + if ( m_client->jid().resource() != m_jid.resource() ) + { + m_jid.setResource( m_client->jid().resource() ); + QString jidstr( m_jid.full() ); + emit jidChanged( jidstr ); + } + + emit connected(); + qDebug() << "Connected as:" << m_jid.full(); + + m_client->setPresence(jreen::Presence::Available, "Tomahawk-JREEN available", 1); + m_client->disco()->setSoftwareVersion( "Tomahawk JREEN", "0.0.0.0", "Foobar" ); + m_client->setPingInterval(60000); + m_roster = new jreen::SimpleRoster( m_client ); + m_roster->load(); + + // join MUC with bare jid as nickname + //TODO: make the room a list of rooms and make that configurable + QString bare(m_jid.bare()); + m_room = new jreen::MUCRoom(m_client, jreen::JID(QString("tomahawk@conference.qutim.org/").append(bare.replace("@", "-")))); + m_room->setHistorySeconds(0); + m_room->join(); + + // treat muc participiants like contacts + connect(m_room, SIGNAL(messageReceived(jreen::Message, bool)), this, SLOT(onNewMessage(jreen::Message))); + connect(m_room, SIGNAL(presenceReceived(jreen::Presence,const jreen::MUCRoom::Participant*)), this, SLOT(onNewPresence(jreen::Presence))); +} + + +void +Jabber_p::onDisconnect( jreen::Client::DisconnectReason reason ) +{ + QString error; + bool reconnect = false; + int reconnectInSeconds = 0; + + switch( reason ) + { + case jreen::Client::User: + error = "User Interaction"; + break; + case jreen::Client::HostUnknown: + error = "Host is unknown"; + break; + case jreen::Client::ItemNotFound: + error = "Item not found"; + break; + case jreen::Client::AuthorizationError: + error = "Authorization Error"; + break; + case jreen::Client::RemoteStreamError: + error = "Remote Stream Error"; + reconnect = true; + break; + case jreen::Client::RemoteConnectionFailed: + error = "Remote Connection failed"; + break; + case jreen::Client::InternalServerError: + error = "Internal Server Error"; + reconnect = true; + break; + case jreen::Client::SystemShutdown: + error = "System shutdown"; + reconnect = true; + reconnectInSeconds = 60; + break; + case jreen::Client::Conflict: + error = "Conflict"; + break; + + case jreen::Client::Unknown: + error = "Unknown"; + break; + + default: + qDebug() << "Not all Client::DisconnectReasons checked"; + Q_ASSERT(false); + break; + } + + qDebug() << "Disconnected from server:" << error; + + if(reconnect) + QTimer::singleShot(reconnectInSeconds*1000, m_client, SLOT(connectToServer())); + + emit disconnected(); +} + +void +Jabber_p::onNewMessage( const jreen::Message& m ) +{ + QString from = m.from().full(); + QString msg = m.body(); + + if ( msg.isEmpty() ) + return; + + qDebug() << Q_FUNC_INFO << m.from().full() << ":" << m.body(); + emit msgReceived( from, msg ); +} + + +void Jabber_p::onNewPresence( const jreen::Presence& presence) +{ + + jreen::JID jid = presence.from(); + QString fulljid( jid.full() ); + + qDebug() << Q_FUNC_INFO << "handle presence" << fulljid << presence.subtype(); + + if( jid == m_jid ) + return; + + if ( presence.error() ) { + qDebug() << Q_FUNC_INFO << "presence error: no tomahawk"; + return; + } + + // ignore anyone not running tomahawk: + jreen::Capabilities::Ptr caps = presence.findExtension(); + if ( caps && (caps->node() == TOMAHAWK_CAP_NODE_NAME )) + { + qDebug() << Q_FUNC_INFO << presence.from().full() << "tomahawk detected by caps"; + } + // this is a hack actually as long as gloox based libsip_jabber is around + // remove this as soon as everyone is using jreen + else if( presence.from().resource().startsWith( QLatin1String("tomahawk") ) ) + { + qDebug() << Q_FUNC_INFO << presence.from().full() << "tomahawk detected by resource"; + } + else if( caps && caps->node() != TOMAHAWK_CAP_NODE_NAME ) + { + qDebug() << Q_FUNC_INFO << presence.from().full() << "*no tomahawk* detected by caps!" << caps->node() << presence.from().resource(); + return; + } + else if( !caps ) + { + qDebug() << Q_FUNC_INFO << "no tomahawk detected by resource and !caps"; + return; + } + + qDebug() << Q_FUNC_INFO << fulljid << " is a tomahawk resource."; + + // "going offline" event + if ( !presenceMeansOnline( presence.subtype() ) && + ( !m_peers.contains( fulljid ) || + presenceMeansOnline( m_peers.value( fulljid ) ) + ) + ) + { + m_peers[ fulljid ] = presence.subtype(); + qDebug() << Q_FUNC_INFO << "* Peer goes offline:" << fulljid; + emit peerOffline( fulljid ); + return; + } + + // "coming online" event + if( presenceMeansOnline( presence.subtype() ) && + ( !m_peers.contains( fulljid ) || + !presenceMeansOnline( m_peers.value( fulljid ) ) + ) + ) + { + m_peers[ fulljid ] = presence.subtype(); + qDebug() << Q_FUNC_INFO << "* Peer goes online:" << fulljid; + emit peerOnline( fulljid ); + return; + } + + //qDebug() << "Updating presence data for" << fulljid; + m_peers[ fulljid ] = presence.subtype(); + +} + +bool +Jabber_p::presenceMeansOnline( jreen::Presence::Type p ) +{ + switch(p) + { + case jreen::Presence::Invalid: + case jreen::Presence::Unavailable: + case jreen::Presence::Error: + return false; + break; + default: + return true; + } +} diff --git a/src/sip/jreen/jabber_p.h b/src/sip/jreen/jabber_p.h new file mode 100644 index 000000000..f10541c2c --- /dev/null +++ b/src/sip/jreen/jabber_p.h @@ -0,0 +1,102 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +/* + This is the Jabber client that the rest of the app sees + Gloox stuff should NOT leak outside this class. + We may replace jreen later, this interface should remain the same. +*/ +#ifndef JABBER_P_H +#define JABBER_P_H + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#if defined( WIN32 ) || defined( _WIN32 ) +# include +#endif + +#include "../sipdllmacro.h" +#include +#include + +class SIPDLLEXPORT Jabber_p : + public QObject +{ +Q_OBJECT + +public: + explicit Jabber_p( const QString& jid, const QString& password, const QString& server = "", const int port = -1 ); + virtual ~Jabber_p(); + + void setProxy( QNetworkProxy* proxy ); + +signals: + void msgReceived( const QString&, const QString& ); //from, msg + void peerOnline( const QString& ); + void peerOffline( const QString& ); + void connected(); + void disconnected(); + void jidChanged( const QString& ); + void authError( int, const QString& ); + +public slots: + void sendMsg( const QString& to, const QString& msg ); + void broadcastMsg( const QString& msg ); + void addContact( const QString& jid, const QString& msg = QString() ); + void disconnect(); + + void onDisconnect(jreen::Client::DisconnectReason reason); + void onConnect(); + +private slots: + virtual void onNewPresence( const jreen::Presence& presence ); + virtual void onNewMessage( const jreen::Message& msg ); + virtual void onError( const jreen::Connection::SocketError& e ) + { + qDebug() << e; + } + +private: + bool presenceMeansOnline( jreen::Presence::Type p ); + jreen::Client *m_client; + jreen::MUCRoom *m_room; + jreen::SimpleRoster *m_roster; + jreen::JID m_jid; + QMap m_presences; + QMap m_peers; + QString m_server; +}; + +#endif // JABBER_H diff --git a/src/sip/sipdllmacro.h b/src/sip/sipdllmacro.h new file mode 100644 index 000000000..74a8eec22 --- /dev/null +++ b/src/sip/sipdllmacro.h @@ -0,0 +1,32 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef SIPDLLMACRO_H +#define SIPDLLMACRO_H + +#ifdef WIN32 + #ifdef SIPDLLEXPORT_PRO + #define SIPDLLEXPORT __declspec(dllexport) + #else + #define SIPDLLEXPORT __declspec(dllimport) + #endif +#else + #define SIPDLLEXPORT +#endif + +#endif diff --git a/src/sip/twitter/CMakeLists.txt b/src/sip/twitter/CMakeLists.txt new file mode 100644 index 000000000..bb5b26346 --- /dev/null +++ b/src/sip/twitter/CMakeLists.txt @@ -0,0 +1,53 @@ +project( tomahawk ) + +include( ${QT_USE_FILE} ) +add_definitions( ${QT_DEFINITIONS} ) +add_definitions( -DQT_PLUGIN ) +add_definitions( -DQT_SHARED ) +add_definitions( -DSIPDLLEXPORT_PRO ) + +set( twitterSources + twitter.cpp + twitterconfigwidget.cpp +) + +set( twitterHeaders + twitter.h + twitterconfigwidget.h +) + +set( twitterUI + twitterconfigwidget.ui +) + +include_directories( . ${CMAKE_CURRENT_BINARY_DIR} .. + ${QT_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/thirdparty/qtweetlib/qtweetlib/src + ${CMAKE_SOURCE_DIR}/thirdparty/qtweetlib/tomahawk-custom +) + +qt4_wrap_cpp( twitterMoc ${twitterHeaders} ) +qt4_wrap_ui( twitterUI_H ${twitterUI} ) +add_library( tomahawk_siptwitter SHARED ${twitterUI_H} ${twitterSources} ${twitterMoc} ) + +IF( WIN32 ) +SET( OS_SPECIFIC_LINK_LIBRARIES + ${OS_SPECIFIC_LINK_LIBRARIES} + "winmm.dll" + "iphlpapi.a" + "${CMAKE_BINARY_DIR}/thirdparty/qtweetlib/libtomahawk_qtweetlib.dll" + "${CMAKE_BINARY_DIR}/src/libtomahawk/libtomahawklib.dll" +) +ENDIF( WIN32 ) + +target_link_libraries( tomahawk_siptwitter + ${QT_LIBRARIES} + ${OS_SPECIFIC_LINK_LIBRARIES} + tomahawklib +) + +IF( APPLE ) + SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) +ENDIF( APPLE ) + +install( TARGETS tomahawk_siptwitter DESTINATION lib${LIB_SUFFIX} ) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp new file mode 100644 index 000000000..843de4505 --- /dev/null +++ b/src/sip/twitter/twitter.cpp @@ -0,0 +1,666 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "twitter.h" + +#include "twitterconfigwidget.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +static QString s_gotTomahawkRegex = QString( "^(@[a-zA-Z0-9]+ )?(Got Tomahawk\\?) (\\{[a-fA-F0-9\\-]+\\}) (.*)$" ); + +TwitterPlugin::TwitterPlugin() + : SipPlugin() + , m_isAuthed( false ) + , m_isOnline( false ) + , m_checkTimer( this ) + , m_connectTimer( this ) + , m_cachedFriendsSinceId( 0 ) + , m_cachedMentionsSinceId( 0 ) + , m_cachedDirectMessagesSinceId( 0 ) + , m_cachedPeers() + , m_keyCache() + , m_finishedFriends( false ) + , m_finishedMentions( false ) + , m_configWidget( 0 ) +{ + qDebug() << Q_FUNC_INFO; + m_checkTimer.setInterval( 60000 ); + m_checkTimer.setSingleShot( false ); + connect( &m_checkTimer, SIGNAL( timeout() ), SLOT( checkTimerFired() ) ); + m_checkTimer.start(); + + m_connectTimer.setInterval( 60000 ); + m_connectTimer.setSingleShot( false ); + connect( &m_connectTimer, SIGNAL( timeout() ), SLOT( connectTimerFired() ) ); + m_connectTimer.start(); +} + +void +TwitterPlugin::configDialogAuthedSignalSlot( bool authed ) +{ + m_isAuthed = authed; + if ( !authed ) + { + TomahawkSettings::instance()->setTwitterScreenName( QString() ); + TomahawkSettings::instance()->setTwitterOAuthToken( QString() ); + TomahawkSettings::instance()->setTwitterOAuthTokenSecret( QString() ); + } +} + +bool +TwitterPlugin::isValid() +{ + return m_isAuthed; +} + +const QString +TwitterPlugin::name() +{ + return QString( MYNAME ); +} + +const QString +TwitterPlugin::friendlyName() +{ + return QString("Twitter"); +} + +const QString +TwitterPlugin::accountName() +{ + return QString( TomahawkSettings::instance()->twitterScreenName() ); +} + +QWidget* TwitterPlugin::configWidget() +{ + m_configWidget = new TwitterConfigWidget( this ); + + connect( m_configWidget, SIGNAL( twitterAuthed(bool) ), SLOT( configDialogAuthedSignalSlot(bool) ) ); + + return m_configWidget; +} + +bool +TwitterPlugin::connectPlugin( bool /*startup*/ ) +{ + qDebug() << Q_FUNC_INFO; + + TomahawkSettings *settings = TomahawkSettings::instance(); + + m_cachedPeers = settings->twitterCachedPeers(); + QList peerlist = m_cachedPeers.keys(); + qStableSort( peerlist.begin(), peerlist.end() ); + foreach( QString screenName, peerlist ) + { + QHash< QString, QVariant > cachedPeer = m_cachedPeers[screenName].toHash(); + foreach( QString prop, cachedPeer.keys() ) + qDebug() << "TwitterPlugin : " << screenName << ", key " << prop << ", value " << ( cachedPeer[prop].canConvert< QString >() ? cachedPeer[prop].toString() : QString::number( cachedPeer[prop].toInt() ) ); + QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&cachedPeer ) ); + } + + if ( settings->twitterOAuthToken().isEmpty() || settings->twitterOAuthTokenSecret().isEmpty() ) + { + qDebug() << "TwitterPlugin has empty Twitter credentials; not connecting"; + return m_cachedPeers.isEmpty(); + } + + if ( refreshTwitterAuth() ) + { + QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); + connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); + credVerifier->verify(); + } + + return true; +} + +bool +TwitterPlugin::refreshTwitterAuth() +{ + m_twitterAuth = QWeakPointer( new TomahawkOAuthTwitter( this ) ); + + TomahawkSettings *settings = TomahawkSettings::instance(); + + if ( m_twitterAuth.isNull() ) + return false; + + m_twitterAuth.data()->setNetworkAccessManager( TomahawkUtils::nam() ); + m_twitterAuth.data()->setOAuthToken( settings->twitterOAuthToken().toLatin1() ); + m_twitterAuth.data()->setOAuthTokenSecret( settings->twitterOAuthTokenSecret().toLatin1() ); + + return true; +} + +void +TwitterPlugin::disconnectPlugin() +{ + qDebug() << Q_FUNC_INFO; + if( !m_friendsTimeline.isNull() ) + m_friendsTimeline.data()->deleteLater(); + if( !m_twitterAuth.isNull() ) + m_twitterAuth.data()->deleteLater(); + + m_cachedPeers.empty(); + m_attemptedConnects.empty(); + delete m_twitterAuth.data(); + m_isOnline = false; +} + +void +TwitterPlugin::connectAuthVerifyReply( const QTweetUser &user ) +{ + if ( user.id() == 0 ) + { + qDebug() << "TwitterPlugin could not authenticate to Twitter"; + m_isAuthed = false; + } + else + { + qDebug() << "TwitterPlugin successfully authenticated to Twitter as user " << user.screenName(); + m_isAuthed = true; + if ( !m_twitterAuth.isNull() ) + { + TomahawkSettings::instance()->setTwitterScreenName( user.screenName() ); + m_friendsTimeline = QWeakPointer( new QTweetFriendsTimeline( m_twitterAuth.data(), this ) ); + m_mentions = QWeakPointer( new QTweetMentions( m_twitterAuth.data(), this ) ); + m_directMessages = QWeakPointer( new QTweetDirectMessages( m_twitterAuth.data(), this ) ); + m_directMessageNew = QWeakPointer( new QTweetDirectMessageNew( m_twitterAuth.data(), this ) ); + m_directMessageDestroy = QWeakPointer( new QTweetDirectMessageDestroy( m_twitterAuth.data(), this ) ); + connect( m_friendsTimeline.data(), SIGNAL( parsedStatuses(const QList< QTweetStatus > &) ), SLOT( friendsTimelineStatuses(const QList &) ) ); + connect( m_mentions.data(), SIGNAL( parsedStatuses(const QList< QTweetStatus > &) ), SLOT( mentionsStatuses(const QList &) ) ); + connect( m_directMessages.data(), SIGNAL( parsedDirectMessages(const QList &)), SLOT( directMessages(const QList &) ) ); + connect( m_directMessageNew.data(), SIGNAL( parsedDirectMessage(const QTweetDMStatus &)), SLOT( directMessagePosted(const QTweetDMStatus &) ) ); + connect( m_directMessageNew.data(), SIGNAL( error(QTweetNetBase::ErrorCode, const QString &) ), SLOT( directMessagePostError(QTweetNetBase::ErrorCode, const QString &) ) ); + connect( m_directMessageDestroy.data(), SIGNAL( parsedDirectMessage(const QTweetDMStatus &) ), SLOT( directMessageDestroyed(const QTweetDMStatus &) ) ); + m_isOnline = true; + QMetaObject::invokeMethod( this, "checkTimerFired", Qt::AutoConnection ); + QTimer::singleShot( 20000, this, SLOT( connectTimerFired() ) ); + } + else + { + if ( refreshTwitterAuth() ) + { + QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); + connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); + credVerifier->verify(); + } + else + { + qDebug() << "TwitterPlugin auth pointer was null!"; + m_isAuthed = false; + } + } + } +} + +void +TwitterPlugin::checkTimerFired() +{ + if ( !isValid() ) + return; + + if ( m_twitterAuth.isNull() ) + { + if ( refreshTwitterAuth() ) + { + QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); + connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); + credVerifier->verify(); + } + else + { + qDebug() << "TwitterPlugin auth went null somehow and could not refresh"; + return; + } + } + + if ( m_cachedFriendsSinceId == 0 ) + m_cachedFriendsSinceId = TomahawkSettings::instance()->twitterCachedFriendsSinceId(); + + qDebug() << "TwitterPlugin looking at friends timeline since id " << m_cachedFriendsSinceId; + + if ( !m_friendsTimeline.isNull() ) + m_friendsTimeline.data()->fetch( m_cachedFriendsSinceId, 0, 800 ); + + + if ( m_cachedMentionsSinceId == 0 ) + m_cachedMentionsSinceId = TomahawkSettings::instance()->twitterCachedMentionsSinceId(); + + qDebug() << "TwitterPlugin looking at mentions timeline since id " << m_cachedMentionsSinceId; + + if ( !m_mentions.isNull() ) + m_mentions.data()->fetch( m_cachedMentionsSinceId, 0, 800 ); +} + +void +TwitterPlugin::connectTimerFired() +{ + if ( !isValid() || m_cachedPeers.isEmpty() ) + return; + + if ( m_twitterAuth.isNull() ) + { + if ( refreshTwitterAuth() ) + { + QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this ); + connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) ); + credVerifier->verify(); + } + else + { + qDebug() << "TwitterPlugin auth went null somehow and could not refresh"; + return; + } + } + + QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); + QList peerlist = m_cachedPeers.keys(); + qStableSort( peerlist.begin(), peerlist.end() ); + foreach( QString screenName, peerlist ) + { + QHash< QString, QVariant > peerData = m_cachedPeers[screenName].toHash(); + + if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) ) + { + qDebug() << "TwitterPlugin does not have host, port and/or pkey values for " << screenName; + continue; + } + + QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); + } +} + +void +TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName, const QString &text ) +{ + QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); + qDebug() << "TwitterPlugin found an exact matching Got Tomahawk? mention or direct message from user " << screenName; + if ( text.startsWith( '@' ) && regex.captureCount() >= 2 && regex.cap( 1 ) != QString( '@' + myScreenName ) ) + { + qDebug() << "TwitterPlugin skipping mention because it's directed @someone that isn't us"; + return; + } + + QString node; + for ( int i = 0; i < regex.captureCount(); ++i ) + { + if ( regex.cap( i ) == QString( "Got Tomahawk?" ) ) + { + QString nodeCap = regex.cap( i + 1 ); + nodeCap.chop( 1 ); + node = nodeCap.mid( 1 ); + } + } + if ( node.isEmpty() ) + { + qDebug() << "TwitterPlugin could not parse node out of the tweet"; + return; + } + else + qDebug() << "TwitterPlugin parsed node " << node << " out of the tweet"; + + if ( screenName == myScreenName && node == Database::instance()->dbid() ) + { + qDebug() << "My screen name and my dbid found; ignoring"; + return; + } + + QHash< QString, QVariant > peerData; + if( m_cachedPeers.contains( screenName ) ) + { + peerData = m_cachedPeers[screenName].toHash(); + //force a re-send of info but no need to re-register + peerData["resend"] = QVariant::fromValue< bool >( true ); + } + peerData["node"] = QVariant::fromValue< QString >( node ); + QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); +} + +void +TwitterPlugin::friendsTimelineStatuses( const QList< QTweetStatus > &statuses ) +{ + qDebug() << Q_FUNC_INFO; + QRegExp regex( s_gotTomahawkRegex, Qt::CaseSensitive, QRegExp::RegExp2 ); + QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); + + QHash< QString, QTweetStatus > latestHash; + foreach ( QTweetStatus status, statuses ) + { + if ( !regex.exactMatch( status.text() ) ) + continue; + + if ( !latestHash.contains( status.user().screenName() ) ) + latestHash[status.user().screenName()] = status; + else + { + if ( status.id() > latestHash[status.user().screenName()].id() ) + latestHash[status.user().screenName()] = status; + } + } + + foreach( QTweetStatus status, latestHash.values() ) + { + if ( status.id() > m_cachedFriendsSinceId ) + m_cachedFriendsSinceId = status.id(); + + parseGotTomahawk( regex, status.user().screenName(), status.text() ); + } + + TomahawkSettings::instance()->setTwitterCachedFriendsSinceId( m_cachedFriendsSinceId ); + + m_finishedFriends = true; + QMetaObject::invokeMethod( this, "pollDirectMessages", Qt::AutoConnection ); +} + +void +TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses ) +{ + qDebug() << Q_FUNC_INFO; + QRegExp regex( s_gotTomahawkRegex, Qt::CaseSensitive, QRegExp::RegExp2 ); + + QHash< QString, QTweetStatus > latestHash; + foreach ( QTweetStatus status, statuses ) + { + if ( !regex.exactMatch( status.text() ) ) + continue; + + if ( !latestHash.contains( status.user().screenName() ) ) + latestHash[status.user().screenName()] = status; + else + { + if ( status.id() > latestHash[status.user().screenName()].id() ) + latestHash[status.user().screenName()] = status; + } + } + + foreach( QTweetStatus status, latestHash.values() ) + { + if ( status.id() > m_cachedMentionsSinceId ) + m_cachedMentionsSinceId = status.id(); + + parseGotTomahawk( regex, status.user().screenName(), status.text() ); + } + + TomahawkSettings::instance()->setTwitterCachedMentionsSinceId( m_cachedMentionsSinceId ); + + m_finishedMentions = true; + QMetaObject::invokeMethod( this, "pollDirectMessages", Qt::AutoConnection ); +} + +void +TwitterPlugin::pollDirectMessages() +{ + if ( !m_finishedMentions || !m_finishedFriends ) + return; + + m_finishedFriends = false; + m_finishedMentions = false; + + if ( !isValid() ) + return; + + if ( m_cachedDirectMessagesSinceId == 0 ) + m_cachedDirectMessagesSinceId = TomahawkSettings::instance()->twitterCachedDirectMessagesSinceId(); + + qDebug() << "TwitterPlugin looking for direct messages since id " << m_cachedDirectMessagesSinceId; + + if ( !m_directMessages.isNull() ) + m_directMessages.data()->fetch( m_cachedDirectMessagesSinceId, 0, 800 ); +} + +void +TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages ) +{ + qDebug() << Q_FUNC_INFO; + + QRegExp regex( s_gotTomahawkRegex, Qt::CaseSensitive, QRegExp::RegExp2 ); + QString myScreenName = TomahawkSettings::instance()->twitterScreenName(); + + QHash< QString, QTweetDMStatus > latestHash; + foreach ( QTweetDMStatus status, messages ) + { + if ( !regex.exactMatch( status.text() ) ) + { + QStringList splitList = status.text().split(':'); + if ( splitList.length() != 5 ) + continue; + if ( splitList[0] != "TOMAHAWKPEER" ) + continue; + if ( !splitList[1].startsWith( "Host=" ) || !splitList[2].startsWith( "Port=" ) || !splitList[3].startsWith( "Node=" ) || !splitList[4].startsWith( "PKey=" ) ) + continue; + int port = splitList[2].mid( 5 ).toInt(); + if ( port == 0 ) + continue; + } + + if ( !latestHash.contains( status.senderScreenName() ) ) + latestHash[status.senderScreenName()] = status; + else + { + if ( status.id() > latestHash[status.senderScreenName()].id() ) + latestHash[status.senderScreenName()] = status; + } + } + + foreach( QTweetDMStatus status, latestHash.values() ) + { + qDebug() << "TwitterPlugin checking direct message from " << status.senderScreenName() << " with content " << status.text(); + if ( status.id() > m_cachedDirectMessagesSinceId ) + m_cachedDirectMessagesSinceId = status.id(); + + if ( regex.exactMatch( status.text() ) ) + parseGotTomahawk( regex, status.sender().screenName(), status.text() ); + else + { + QStringList splitList = status.text().split(':'); + qDebug() << "TwitterPlugin found " << splitList.length() << " parts to the message; the parts are:"; + foreach( QString part, splitList ) + qDebug() << part; + //validity is checked above + int port = splitList[2].mid( 5 ).toInt(); + QString host = splitList[1].mid( 5 ); + QString node = splitList[3].mid( 5 ); + QString pkey = splitList[4].mid( 5 ); + QStringList splitNode = node.split('*'); + if ( splitNode.length() != 2 ) + { + qDebug() << "Old-style node info found, ignoring"; + continue; + } + qDebug() << "TwitterPlugin found a peerstart message from " << status.senderScreenName() << " with host " << host << " and port " << port << " and pkey " << pkey << " and node " << splitNode[0] << " destined for node " << splitNode[1]; + + + QHash< QString, QVariant > peerData = ( m_cachedPeers.contains( status.senderScreenName() ) ) ? + m_cachedPeers[status.senderScreenName()].toHash() : + QHash< QString, QVariant >(); + + peerData["host"] = QVariant::fromValue< QString >( host ); + peerData["port"] = QVariant::fromValue< int >( port ); + peerData["pkey"] = QVariant::fromValue< QString >( pkey ); + peerData["node"] = QVariant::fromValue< QString >( splitNode[0] ); + peerData["dirty"] = QVariant::fromValue< bool >( true ); + + QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, status.senderScreenName() ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); + + if ( Database::instance()->dbid().startsWith( splitNode[1] ) ) + { + qDebug() << "TwitterPlugin found message destined for this node; destroying it"; + if ( !m_directMessageDestroy.isNull() ) + m_directMessageDestroy.data()->destroyMessage( status.id() ); + } + } + } + + TomahawkSettings::instance()->setTwitterCachedDirectMessagesSinceId( m_cachedDirectMessagesSinceId ); +} + +void +TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, QVariant > &peerData ) +{ + qDebug() << Q_FUNC_INFO; + + bool peersChanged = false; + bool needToSend = false; + bool needToAddToCache = false; + + QString friendlyName = QString( '@' + screenName ); + + QHash< QString, QVariant > _peerData( peerData ); + + if ( _peerData.contains( "dirty" ) ) + { + peersChanged = true; + _peerData.remove( "dirty" ); + } + + if ( _peerData.contains( "resend" ) ) + { + needToSend = true; + peersChanged = true; + _peerData.remove( "resend" ); + } + + if ( !_peerData.contains( "okey" ) ) + { + QString okey = QUuid::createUuid().toString().split( '-' ).last(); + okey.chop( 1 ); + _peerData["okey"] = QVariant::fromValue< QString >( okey ); + peersChanged = true; + needToAddToCache = true; + needToSend = true; + } + + if ( !m_keyCache.contains( _peerData["okey"].toString() ) ) + needToAddToCache = true; + + if ( !_peerData.contains( "ohst" ) || !_peerData.contains( "oprt" ) || + _peerData["ohst"].toString() != Servent::instance()->externalAddress() || + _peerData["oprt"].toInt() != Servent::instance()->externalPort() + ) + needToSend = true; + + if( needToAddToCache && _peerData.contains( "node" ) ) + { + qDebug() << "TwitterPlugin registering offer to " << friendlyName << " with node " << _peerData["node"].toString() << " and offeredkey " << _peerData["okey"].toString(); + m_keyCache << Servent::instance()->createConnectionKey( friendlyName, _peerData["node"].toString(), _peerData["okey"].toString(), false ); + } + + if( needToSend && _peerData.contains( "node") ) + { + qDebug() << "TwitterPlugin needs to send and has node"; + _peerData["ohst"] = QVariant::fromValue< QString >( Servent::instance()->externalAddress() ); + _peerData["oprt"] = QVariant::fromValue< int >( Servent::instance()->externalPort() ); + peersChanged = true; + if( !Servent::instance()->externalAddress().isEmpty() && !Servent::instance()->externalPort() == 0 ) + QMetaObject::invokeMethod( this, "sendOffer", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&_peerData ) ); + else + qDebug() << "TwitterPlugin did not send offer because external address is " << Servent::instance()->externalAddress() << " and external port is " << Servent::instance()->externalPort(); + } + + if ( peersChanged ) + { + m_cachedPeers[screenName] = QVariant::fromValue< QHash< QString, QVariant > >( _peerData ); + TomahawkSettings::instance()->setTwitterCachedPeers( m_cachedPeers ); + m_attemptedConnects[screenName] = false; + } + + if ( m_isOnline && _peerData.contains( "host" ) && _peerData.contains( "port" ) && _peerData.contains( "pkey" ) ) + QMetaObject::invokeMethod( this, "makeConnection", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&_peerData ) ); + +} + +void +TwitterPlugin::sendOffer( const QString &screenName, const QHash< QString, QVariant > &peerData ) +{ + qDebug() << Q_FUNC_INFO; + QString offerString = QString( "TOMAHAWKPEER:Host=%1:Port=%2:Node=%3*%4:PKey=%5" ).arg( peerData["ohst"].toString() ) + .arg( peerData["oprt"].toString() ) + .arg( Database::instance()->dbid() ) + .arg( peerData["node"].toString().left( 8 ) ) + .arg( peerData["okey"].toString() ); + qDebug() << "TwitterPlugin sending message to " << screenName << ": " << offerString; + if( !m_directMessageNew.isNull() ) + m_directMessageNew.data()->post( screenName, offerString ); +} + +void +TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, QVariant > &peerData ) +{ + qDebug() << Q_FUNC_INFO; + if ( m_attemptedConnects.contains( screenName ) && m_attemptedConnects[screenName] ) + { + qDebug() << "Already attempted to connect to this peer with no change in their status, not trying again for now"; + return; + } + if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) || !peerData.contains( "node" ) ) + { + qDebug() << "TwitterPlugin could not find host and/or port and/or pkey for peer " << screenName; + return; + } + QString friendlyName = QString( '@' + screenName ); + if ( !Servent::instance()->connectedToSession( peerData["node"].toString() ) ) + Servent::instance()->connectToPeer( peerData["host"].toString(), + peerData["port"].toString().toInt(), + peerData["pkey"].toString(), + friendlyName, + peerData["node"].toString() ); + m_attemptedConnects[screenName] = true; +} + +void +TwitterPlugin::directMessagePosted( const QTweetDMStatus& message ) +{ + qDebug() << Q_FUNC_INFO; + qDebug() << "TwitterPlugin sent message to " << message.recipientScreenName() << " containing: " << message.text(); + +} + +void +TwitterPlugin::directMessagePostError( QTweetNetBase::ErrorCode errorCode, const QString &message ) +{ + qDebug() << Q_FUNC_INFO; + qDebug() << "TwitterPlugin received an error posting direct message: " << m_directMessageNew.data()->lastErrorMessage(); +} + +void +TwitterPlugin::directMessageDestroyed( const QTweetDMStatus& message ) +{ + qDebug() << Q_FUNC_INFO; + qDebug() << "TwitterPlugin destroyed message " << message.text(); +} + +void +TwitterPlugin::checkSettings() +{ + disconnectPlugin(); + connectPlugin( false ); +} + +Q_EXPORT_PLUGIN2( sip, TwitterPlugin ) diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h new file mode 100644 index 000000000..4e8a98a31 --- /dev/null +++ b/src/sip/twitter/twitter.h @@ -0,0 +1,119 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TWITTER_H +#define TWITTER_H + +#include "twitterconfigwidget.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../sipdllmacro.h" +#include "sip/SipPlugin.h" +#include "tomahawkoauthtwitter.h" + +#define MYNAME "SIPTWITTER" + +class SIPDLLEXPORT TwitterPlugin : public SipPlugin +{ + Q_OBJECT + Q_INTERFACES( SipPlugin ) + +public: + TwitterPlugin(); + + virtual ~TwitterPlugin() {} + + virtual bool isValid(); + virtual const QString name(); + virtual const QString accountName(); + virtual const QString friendlyName(); + + virtual QWidget* configWidget(); + +public slots: + virtual bool connectPlugin( bool startup ); + void disconnectPlugin(); + void checkSettings(); + + void sendMsg( const QString& to, const QString& msg ) + { + } + + void broadcastMsg( const QString &msg ) + { + } + + void addContact( const QString &jid, const QString& msg = QString() ) + { + } + +private slots: + void configDialogAuthedSignalSlot( bool authed ); + void connectAuthVerifyReply( const QTweetUser &user ); + void checkTimerFired(); + void connectTimerFired(); + void friendsTimelineStatuses( const QList< QTweetStatus > &statuses ); + void mentionsStatuses( const QList< QTweetStatus > &statuses ); + void pollDirectMessages(); + void directMessages( const QList< QTweetDMStatus > &messages ); + void directMessagePosted( const QTweetDMStatus &message ); + void directMessagePostError( QTweetNetBase::ErrorCode errorCode, const QString &message ); + void directMessageDestroyed( const QTweetDMStatus &message ); + void registerOffer( const QString &screenName, const QHash< QString, QVariant > &peerdata ); + void sendOffer( const QString &screenName, const QHash< QString, QVariant > &peerdata ); + void makeConnection( const QString &screenName, const QHash< QString, QVariant > &peerdata ); + +private: + bool refreshTwitterAuth(); + void parseGotTomahawk( const QRegExp ®ex, const QString &screenName, const QString &text ); + + QWeakPointer< TomahawkOAuthTwitter > m_twitterAuth; + QWeakPointer< QTweetFriendsTimeline > m_friendsTimeline; + QWeakPointer< QTweetMentions > m_mentions; + QWeakPointer< QTweetDirectMessages > m_directMessages; + QWeakPointer< QTweetDirectMessageNew > m_directMessageNew; + QWeakPointer< QTweetDirectMessageDestroy > m_directMessageDestroy; + bool m_isAuthed; + bool m_isOnline; + QTimer m_checkTimer; + QTimer m_connectTimer; + qint64 m_cachedFriendsSinceId; + qint64 m_cachedMentionsSinceId; + qint64 m_cachedDirectMessagesSinceId; + QHash< QString, QVariant > m_cachedPeers; + QHash< QString, bool > m_attemptedConnects; + QSet m_keyCache; + bool m_finishedFriends; + bool m_finishedMentions; + + TwitterConfigWidget *m_configWidget; +}; + +#endif diff --git a/src/sip/twitter/twitterconfigwidget.cpp b/src/sip/twitter/twitterconfigwidget.cpp new file mode 100644 index 000000000..85660318a --- /dev/null +++ b/src/sip/twitter/twitterconfigwidget.cpp @@ -0,0 +1,287 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "twitterconfigwidget.h" +#include "ui_twitterconfigwidget.h" + +#include "tomahawksettings.h" +#include "utils/tomahawkutils.h" +#include "database/database.h" + +#include "tomahawkoauthtwitter.h" +#include +#include +#include + +#include + +TwitterConfigWidget::TwitterConfigWidget( SipPlugin* plugin, QWidget *parent ) : + QWidget( parent ), + ui( new Ui::TwitterConfigWidget ), + m_plugin( plugin ) +{ + ui->setupUi( this ); + + connect( ui->twitterAuthenticateButton, SIGNAL( pressed() ), + this, SLOT( authDeauthTwitter() ) ); + connect( ui->twitterTweetGotTomahawkButton, SIGNAL( pressed() ), + this, SLOT( startPostGotTomahawkStatus() ) ); + connect( ui->twitterTweetComboBox, SIGNAL( currentIndexChanged( int ) ), + this, SLOT( tweetComboBoxIndexChanged( int ) ) ); + + ui->twitterTweetComboBox->setCurrentIndex( 0 ); + ui->twitterUserTweetLineEdit->setReadOnly( true ); + ui->twitterUserTweetLineEdit->setEnabled( false ); + + TomahawkSettings* s = TomahawkSettings::instance(); + if ( s->twitterOAuthToken().isEmpty() || s->twitterOAuthTokenSecret().isEmpty() || s->twitterScreenName().isEmpty() ) + { + ui->twitterStatusLabel->setText("Status: No saved credentials"); + ui->twitterAuthenticateButton->setText( "Authenticate" ); + ui->twitterInstructionsInfoLabel->setVisible( false ); + ui->twitterGlobalTweetLabel->setVisible( false ); + ui->twitterTweetGotTomahawkButton->setVisible( false ); + ui->twitterUserTweetLineEdit->setVisible( false ); + ui->twitterTweetComboBox->setVisible( false ); + + emit twitterAuthed( false ); + } + else + { + ui->twitterStatusLabel->setText("Status: Credentials saved for " + s->twitterScreenName() ); + ui->twitterAuthenticateButton->setText( "De-authenticate" ); + ui->twitterInstructionsInfoLabel->setVisible( true ); + ui->twitterGlobalTweetLabel->setVisible( true ); + ui->twitterTweetGotTomahawkButton->setVisible( true ); + ui->twitterUserTweetLineEdit->setVisible( true ); + ui->twitterTweetComboBox->setVisible( true ); + + emit twitterAuthed( true ); + } + +} + +TwitterConfigWidget::~TwitterConfigWidget() +{ + delete ui; +} + +void +TwitterConfigWidget::authDeauthTwitter() +{ + if ( ui->twitterAuthenticateButton->text() == "Authenticate" ) + authenticateTwitter(); + else + deauthenticateTwitter(); +} + +void +TwitterConfigWidget::authenticateTwitter() +{ + qDebug() << Q_FUNC_INFO; + TomahawkOAuthTwitter *twitAuth = new TomahawkOAuthTwitter( this ); + twitAuth->setNetworkAccessManager( TomahawkUtils::nam() ); + twitAuth->authorizePin(); + + TomahawkSettings* s = TomahawkSettings::instance(); + s->setTwitterOAuthToken( twitAuth->oauthToken() ); + s->setTwitterOAuthTokenSecret( twitAuth->oauthTokenSecret() ); + + QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( twitAuth, this ); + connect( credVerifier, SIGNAL( parsedUser( const QTweetUser & ) ), SLOT( authenticateVerifyReply( const QTweetUser & ) ) ); + connect( credVerifier, SIGNAL( error( QTweetNetBase::ErrorCode, QString ) ), SLOT( authenticateVerifyError( QTweetNetBase::ErrorCode, QString ) ) ); + credVerifier->verify(); +} + +void +TwitterConfigWidget::authenticateVerifyReply( const QTweetUser &user ) +{ + qDebug() << Q_FUNC_INFO; + if ( user.id() == 0 ) + { + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("The credentials could not be verified.\nYou may wish to try re-authenticating.") ); + emit twitterAuthed( false ); + return; + } + + TomahawkSettings* s = TomahawkSettings::instance(); + s->setTwitterScreenName( user.screenName() ); + s->setTwitterCachedFriendsSinceId( 0 ); + s->setTwitterCachedMentionsSinceId( 0 ); + + ui->twitterStatusLabel->setText("Status: Credentials saved for " + s->twitterScreenName() ); + ui->twitterAuthenticateButton->setText( "De-authenticate" ); + ui->twitterInstructionsInfoLabel->setVisible( true ); + ui->twitterGlobalTweetLabel->setVisible( true ); + ui->twitterTweetGotTomahawkButton->setVisible( true ); + ui->twitterUserTweetLineEdit->setVisible( true ); + ui->twitterTweetComboBox->setVisible( true ); + + m_plugin->connectPlugin( false ); + + emit twitterAuthed( true ); +} + +void +TwitterConfigWidget::authenticateVerifyError( QTweetNetBase::ErrorCode code, const QString &errorMsg ) +{ + qDebug() << Q_FUNC_INFO; + qDebug() << "Error validating credentials, error code is " << code << ", error message is " << errorMsg; + ui->twitterStatusLabel->setText("Status: Error validating credentials"); + emit twitterAuthed( false ); + return; +} + +void +TwitterConfigWidget::deauthenticateTwitter() +{ + qDebug() << Q_FUNC_INFO; + TomahawkSettings* s = TomahawkSettings::instance(); + s->setTwitterOAuthToken( QString() ); + s->setTwitterOAuthTokenSecret( QString() ); + s->setTwitterScreenName( QString() ); + + ui->twitterStatusLabel->setText("Status: No saved credentials"); + ui->twitterAuthenticateButton->setText( "Authenticate" ); + ui->twitterInstructionsInfoLabel->setVisible( false ); + ui->twitterGlobalTweetLabel->setVisible( false ); + ui->twitterTweetGotTomahawkButton->setVisible( false ); + ui->twitterUserTweetLineEdit->setVisible( false ); + ui->twitterTweetComboBox->setVisible( false ); + + emit twitterAuthed( false ); +} + +void +TwitterConfigWidget::tweetComboBoxIndexChanged( int index ) +{ + if( ui->twitterTweetComboBox->currentText() == "Global Tweet" ) + { + ui->twitterUserTweetLineEdit->setReadOnly( true ); + ui->twitterUserTweetLineEdit->setEnabled( false ); + } + else + { + ui->twitterUserTweetLineEdit->setReadOnly( false ); + ui->twitterUserTweetLineEdit->setEnabled( true ); + } + + if( ui->twitterTweetComboBox->currentText() == "Direct Message" ) + ui->twitterTweetGotTomahawkButton->setText( "Send Message!" ); + else + ui->twitterTweetGotTomahawkButton->setText( "Tweet!" ); +} + +void +TwitterConfigWidget::startPostGotTomahawkStatus() +{ + m_postGTtype = ui->twitterTweetComboBox->currentText(); + + if ( m_postGTtype != "Global Tweet" && ( ui->twitterUserTweetLineEdit->text().isEmpty() || ui->twitterUserTweetLineEdit->text() == "@" ) ) + { + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("You must enter a user name for this type of tweet.") ); + return; + } + + qDebug() << "Posting Got Tomahawk status"; + TomahawkSettings* s = TomahawkSettings::instance(); + if ( s->twitterOAuthToken().isEmpty() || s->twitterOAuthTokenSecret().isEmpty() || s->twitterScreenName().isEmpty() ) + { + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("Your saved credentials could not be loaded.\nYou may wish to try re-authenticating.") ); + emit twitterAuthed( false ); + return; + } + TomahawkOAuthTwitter *twitAuth = new TomahawkOAuthTwitter( this ); + twitAuth->setNetworkAccessManager( TomahawkUtils::nam() ); + twitAuth->setOAuthToken( s->twitterOAuthToken().toLatin1() ); + twitAuth->setOAuthTokenSecret( s->twitterOAuthTokenSecret().toLatin1() ); + QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( twitAuth, this ); + connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( postGotTomahawkStatusAuthVerifyReply(const QTweetUser &) ) ); + credVerifier->verify(); +} + +void +TwitterConfigWidget::postGotTomahawkStatusAuthVerifyReply( const QTweetUser &user ) +{ + if ( user.id() == 0 ) + { + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("Your saved credentials could not be verified.\nYou may wish to try re-authenticating.") ); + emit twitterAuthed( false ); + return; + } + TomahawkSettings* s = TomahawkSettings::instance(); + s->setTwitterScreenName( user.screenName() ); + TomahawkOAuthTwitter *twitAuth = new TomahawkOAuthTwitter( this ); + twitAuth->setNetworkAccessManager( TomahawkUtils::nam() ); + twitAuth->setOAuthToken( s->twitterOAuthToken().toLatin1() ); + twitAuth->setOAuthTokenSecret( s->twitterOAuthTokenSecret().toLatin1() ); + if ( m_postGTtype != "Direct Message" ) + { + QTweetStatusUpdate *statUpdate = new QTweetStatusUpdate( twitAuth, this ); + connect( statUpdate, SIGNAL( postedStatus(const QTweetStatus &) ), SLOT( postGotTomahawkStatusUpdateReply(const QTweetStatus &) ) ); + connect( statUpdate, SIGNAL( error(QTweetNetBase::ErrorCode, const QString&) ), SLOT( postGotTomahawkStatusUpdateError(QTweetNetBase::ErrorCode, const QString &) ) ); + QString uuid = QUuid::createUuid(); + QString message = QString( "Got Tomahawk? {" ) + Database::instance()->dbid() + QString( "} (" ) + uuid.mid( 1, 8 ) + QString( ")" ) + QString( " http://gettomahawk.com" ); + if ( m_postGTtype == "@Mention" ) + { + QString user = ui->twitterUserTweetLineEdit->text(); + if ( user.startsWith( "@" ) ) + user.remove( 0, 1 ); + message = QString( "@" ) + user + QString( " " ) + message; + } + statUpdate->post( message ); + } + else + { + QTweetDirectMessageNew *statUpdate = new QTweetDirectMessageNew( twitAuth, this ); + connect( statUpdate, SIGNAL( parsedDirectMessage(const QTweetDMStatus &)), SLOT( postGotTomahawkDirectMessageReply(const QTweetDMStatus &) ) ); + connect( statUpdate, SIGNAL( error(QTweetNetBase::ErrorCode, const QString&) ), SLOT( postGotTomahawkStatusUpdateError(QTweetNetBase::ErrorCode, const QString &) ) ); + QString uuid = QUuid::createUuid(); + QString message = QString( "Got Tomahawk? {" ) + Database::instance()->dbid() + QString( "} (" ) + uuid.mid( 1, 8 ) + QString( ")" ) + QString( " http://gettomahawk.com" ); + QString user = ui->twitterUserTweetLineEdit->text(); + if ( user.startsWith( "@" ) ) + user.remove( 0, 1 ); + statUpdate->post( user, message ); + } +} + +void +TwitterConfigWidget::postGotTomahawkStatusUpdateReply( const QTweetStatus& status ) +{ + if ( status.id() == 0 ) + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("There was an error posting your status -- sorry!") ); + else + QMessageBox::information( 0, QString("Tweeted!"), QString("Your tweet has been posted!") ); +} + +void +TwitterConfigWidget::postGotTomahawkDirectMessageReply( const QTweetDMStatus& status ) +{ + if ( status.id() == 0 ) + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("There was an error posting your direct message -- sorry!") ); + else + QMessageBox::information( 0, QString("Tweeted!"), QString("Your message has been posted!") ); +} + +void +TwitterConfigWidget::postGotTomahawkStatusUpdateError( QTweetNetBase::ErrorCode code, const QString& errorMsg ) +{ + qDebug() << Q_FUNC_INFO; + qDebug() << "Error posting Got Tomahawk message, error code is " << code << ", error message is " << errorMsg; + QMessageBox::critical( 0, QString("Tweetin' Error"), QString("There was an error posting your status -- sorry!") ); +} diff --git a/src/sip/twitter/twitterconfigwidget.h b/src/sip/twitter/twitterconfigwidget.h new file mode 100644 index 000000000..85fabce90 --- /dev/null +++ b/src/sip/twitter/twitterconfigwidget.h @@ -0,0 +1,67 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TWITTERCONFIGWIDGET_H +#define TWITTERCONFIGWIDGET_H + +#include "sip/SipPlugin.h" + +#include +#include +#include +#include + +#include + + +namespace Ui { + class TwitterConfigWidget; +} + +class TwitterConfigWidget : public QWidget +{ + Q_OBJECT + +public: + explicit TwitterConfigWidget( SipPlugin* plugin = 0, QWidget *parent = 0 ); + ~TwitterConfigWidget(); + +signals: + void twitterAuthed( bool authed ); + +private slots: + void authDeauthTwitter(); + void startPostGotTomahawkStatus(); + void authenticateVerifyReply( const QTweetUser &user ); + void authenticateVerifyError( QTweetNetBase::ErrorCode code, const QString &errorMsg ); + void postGotTomahawkStatusAuthVerifyReply( const QTweetUser &user ); + void postGotTomahawkStatusUpdateReply( const QTweetStatus &status ); + void postGotTomahawkDirectMessageReply( const QTweetDMStatus &status ); + void postGotTomahawkStatusUpdateError( QTweetNetBase::ErrorCode, const QString &errorMsg ); + void tweetComboBoxIndexChanged( int index ); + +private: + void authenticateTwitter(); + void deauthenticateTwitter(); + + Ui::TwitterConfigWidget *ui; + SipPlugin *m_plugin; + QString m_postGTtype; +}; + +#endif // TWITTERCONFIGWIDGET_H diff --git a/src/sip/twitter/twitterconfigwidget.ui b/src/sip/twitter/twitterconfigwidget.ui new file mode 100644 index 000000000..1bb728fd0 --- /dev/null +++ b/src/sip/twitter/twitterconfigwidget.ui @@ -0,0 +1,188 @@ + + + TwitterConfigWidget + + + + 0 + 0 + 795 + 509 + + + + + + + + + + 0 + 0 + + + + Authenticating with Twitter allows you to discover and play music from your Twitter friends running Tomahawk. + + + true + + + + + + + This feature works best when you have set a static host name in the "Network" settings tab under Advanced Settings, but may work even if you do not. Tomahawk uses Direct Messages and this will only work when both Twitter users have followed each other. + + + true + + + + + + + + + Status: No saved credentials + + + Qt::AutoText + + + + + + + Authenticate with Twitter + + + + + + + + + + + Here's how it works: just press one of the buttons below to tweet "Got Tomahawk?" and some necessary information. Then be (very) patient. Twitter is an asynchronous protocol so it can take a bit! + +If connections to peers seem to have been lost, just press the appropriate button again to re-post a tweet for resynchronization. + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + + + 0 + 0 + + + + Select the kind of tweet you would like, then press the button to post it: + + + + + + + + + + 0 + 0 + + + + + Global Tweet + + + + + @Mention + + + + + Direct Message + + + + + + + + + 0 + 0 + + + + + 250 + 0 + + + + e.g. @tomahawkplayer + + + + + + + + 0 + 0 + + + + Tweet! + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/sip/zeroconf/CMakeLists.txt b/src/sip/zeroconf/CMakeLists.txt new file mode 100644 index 000000000..199704dff --- /dev/null +++ b/src/sip/zeroconf/CMakeLists.txt @@ -0,0 +1,44 @@ +project( tomahawk ) + +include( ${QT_USE_FILE} ) +add_definitions( ${QT_DEFINITIONS} ) +add_definitions( -DQT_PLUGIN ) +add_definitions( -DQT_SHARED ) +add_definitions( -DSIPDLLEXPORT_PRO ) + +set( zeroconfSources + zeroconf.cpp +) + +set( zeroconfHeaders + zeroconf.h + tomahawkzeroconf.h +) + +include_directories( . ${CMAKE_CURRENT_BINARY_DIR} .. + ${QT_INCLUDE_DIR} +) + +qt4_wrap_cpp( zeroconfMoc ${zeroconfHeaders} ) +add_library( tomahawk_sipzeroconf SHARED ${zeroconfSources} ${zeroconfMoc} ) + +IF( WIN32 ) +SET( OS_SPECIFIC_LINK_LIBRARIES + ${OS_SPECIFIC_LINK_LIBRARIES} + "winmm.dll" + "iphlpapi.a" + ${CMAKE_BINARY_DIR}/src/libtomahawk/libtomahawklib.dll +) +ENDIF( WIN32 ) + +target_link_libraries( tomahawk_sipzeroconf + ${QT_LIBRARIES} + ${OS_SPECIFIC_LINK_LIBRARIES} + tomahawklib +) + +IF( APPLE ) +# SET( CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-undefined dynamic_lookup" ) +ENDIF( APPLE ) + +install( TARGETS tomahawk_sipzeroconf DESTINATION lib${LIB_SUFFIX} ) diff --git a/src/tomahawkzeroconf.h b/src/sip/zeroconf/tomahawkzeroconf.h similarity index 66% rename from src/tomahawkzeroconf.h rename to src/sip/zeroconf/tomahawkzeroconf.h index c88a1d06a..d9df53ada 100644 --- a/src/tomahawkzeroconf.h +++ b/src/sip/zeroconf/tomahawkzeroconf.h @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef TOMAHAWKZCONF #define TOMAHAWKZCONF @@ -10,9 +28,12 @@ #include #include -#include "tomahawk/tomahawkapp.h" +#include "database/database.h" +#include "network/servent.h" -class Node : public QObject +#include "../sipdllmacro.h" + +class SIPDLLEXPORT Node : public QObject { Q_OBJECT @@ -50,7 +71,7 @@ private: }; -class TomahawkZeroconf : public QObject +class SIPDLLEXPORT TomahawkZeroconf : public QObject { Q_OBJECT @@ -58,17 +79,23 @@ public: TomahawkZeroconf( int port, QObject* parent = 0 ) : QObject( parent ), m_sock( this ), m_port( port ) { + qDebug() << Q_FUNC_INFO; m_sock.bind( ZCONF_PORT, QUdpSocket::ShareAddress ); connect( &m_sock, SIGNAL( readyRead() ), this, SLOT( readPacket() ) ); } + virtual ~TomahawkZeroconf() + { + qDebug() << Q_FUNC_INFO; + } + public slots: void advertise() { qDebug() << "Advertising us on the LAN"; QByteArray advert = QString( "TOMAHAWKADVERT:%1:%2" ) .arg( m_port ) - .arg( TomahawkApp::instance()->nodeID() ) + .arg( Database::instance()->dbid() ) .toAscii(); m_sock.writeDatagram( advert.data(), advert.size(), QHostAddress::Broadcast, ZCONF_PORT ); @@ -100,12 +127,12 @@ private slots: { bool ok; int port = parts.at(1).toInt( &ok ); - if(ok && TomahawkApp::instance()->nodeID() != parts.at( 2 ) ) + if(ok && Database::instance()->dbid() != parts.at( 2 ) ) { qDebug() << "ADVERT received:" << sender << port; Node *n = new Node( sender.toString(), parts.at( 2 ), port ); - connect( n, SIGNAL( tomahawkHostFound( const QString&, int, const QString&, const QString& ) ), - this, SIGNAL( tomahawkHostFound( const QString&, int, const QString&, const QString& ) ) ); + connect( n, SIGNAL( tomahawkHostFound( QString, int, QString, QString ) ), + this, SIGNAL( tomahawkHostFound( QString, int, QString, QString ) ) ); n->resolve(); } } diff --git a/src/sip/zeroconf/zeroconf.cpp b/src/sip/zeroconf/zeroconf.cpp new file mode 100644 index 000000000..9625f9abc --- /dev/null +++ b/src/sip/zeroconf/zeroconf.cpp @@ -0,0 +1,96 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "zeroconf.h" + +#include + +const QString +ZeroconfPlugin::name() +{ + return QString( MYNAME ); +} + +const QString +ZeroconfPlugin::accountName() +{ + return QString(); +} + +const QString +ZeroconfPlugin::friendlyName() +{ + return QString( "Zeroconf" ); +} + +bool +ZeroconfPlugin::connectPlugin( bool /*startup*/ ) +{ + delete m_zeroconf; + m_zeroconf = new TomahawkZeroconf( Servent::instance()->port(), this ); + QObject::connect( m_zeroconf, SIGNAL( tomahawkHostFound( QString, int, QString, QString ) ), + SLOT( lanHostFound( QString, int, QString, QString ) ) ); + + m_zeroconf->advertise(); + m_isOnline = true; + + foreach( QStringList *currNode, m_cachedNodes ) + { + QStringList nodeSet = *currNode; + if ( !Servent::instance()->connectedToSession( nodeSet[3] ) ) + Servent::instance()->connectToPeer( nodeSet[0], nodeSet[1].toInt(), "whitelist", nodeSet[2], nodeSet[3] ); + + delete currNode; + } + + return true; +} + +void +ZeroconfPlugin::disconnectPlugin() +{ + m_isOnline = false; + + delete m_zeroconf; + m_zeroconf = 0; +} + +void +ZeroconfPlugin::lanHostFound( const QString& host, int port, const QString& name, const QString& nodeid ) +{ + if ( sender() != m_zeroconf ) + return; + + qDebug() << "Found LAN host:" << host << port << nodeid; + + if ( !m_isOnline ) + { + qDebug() << "Not online, so not connecting."; + QStringList *nodeSet = new QStringList(); + *nodeSet << host << QString::number( port ) << name << nodeid; + m_cachedNodes.insert( nodeSet ); + return; + } + + if ( !Servent::instance()->connectedToSession( nodeid ) ) + Servent::instance()->connectToPeer( host, port, "whitelist", name, nodeid ); + else + qDebug() << "Already connected to" << host; +} + +Q_EXPORT_PLUGIN2( sip, ZeroconfPlugin ) diff --git a/src/sip/zeroconf/zeroconf.h b/src/sip/zeroconf/zeroconf.h new file mode 100644 index 000000000..d538f9a67 --- /dev/null +++ b/src/sip/zeroconf/zeroconf.h @@ -0,0 +1,79 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef ZEROCONF_H +#define ZEROCONF_H + +#include "sip/SipPlugin.h" +#include "tomahawkzeroconf.h" + +#include "../sipdllmacro.h" + +#define MYNAME "SIPZEROCONF" + +class SIPDLLEXPORT ZeroconfPlugin : public SipPlugin +{ + Q_OBJECT + Q_INTERFACES( SipPlugin ) + +public: + ZeroconfPlugin() + : m_zeroconf( 0 ) + , m_isOnline( false ) + , m_cachedNodes() + { + qDebug() << Q_FUNC_INFO; + } + + virtual ~ZeroconfPlugin() + { + qDebug() << Q_FUNC_INFO; + } + + virtual bool isValid() { return true; } + virtual const QString name(); + virtual const QString friendlyName(); + virtual const QString accountName(); + +public slots: + virtual bool connectPlugin( bool startup ); + void disconnectPlugin(); + void checkSettings() {} + + void sendMsg( const QString& to, const QString& msg ) + { + } + + void broadcastMsg( const QString &msg ) + { + } + + void addContact( const QString &jid, const QString& msg = QString() ) + { + } + +private slots: + void lanHostFound( const QString& host, int port, const QString& name, const QString& nodeid ); + +private: + TomahawkZeroconf* m_zeroconf; + bool m_isOnline; + QSet< QStringList* > m_cachedNodes; +}; + +#endif diff --git a/src/source.cpp b/src/source.cpp deleted file mode 100644 index f8ea97d82..000000000 --- a/src/source.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include "tomahawk/source.h" - -#include "tomahawk/tomahawkapp.h" -#include "tomahawk/collection.h" - -#include "controlconnection.h" -#include "databasecommand_addsource.h" -#include "databasecommand_sourceoffline.h" -#include "database.h" - -using namespace Tomahawk; - - -Source::Source( const QString &username, ControlConnection* cc ) - : QObject() - , m_isLocal( false ) - , m_online( false ) - , m_username( username ) - , m_id( 0 ) - , m_cc( cc ) -{ - // source for local machine doesn't have a controlconnection. this is normal. - if ( cc ) - connect( cc, SIGNAL( finished() ), SLOT( remove() ), Qt::QueuedConnection ); -} - - -Source::Source( const QString &username ) - : QObject() - , m_isLocal( true ) - , m_online( false ) - , m_username( username ) - , m_id( 0 ) - , m_cc( 0 ) -{ -} - - -Source::~Source() -{ - qDebug() << Q_FUNC_INFO; - // TODO mark source as offline in database - DatabaseCommand_SourceOffline * cmd = new DatabaseCommand_SourceOffline( id() ); - APP->database()->enqueue( QSharedPointer(cmd) ); -} - - -collection_ptr -Source::collection() const -{ - if( m_collections.length() ) - return m_collections.first(); - - collection_ptr tmp; - return tmp; -} - - -void -Source::doDBSync() -{ - // ensure username is in the database - DatabaseCommand_addSource * cmd = new DatabaseCommand_addSource( m_username, m_friendlyname ); - connect( cmd, SIGNAL( done( unsigned int, QString ) ), - SLOT( dbLoaded( unsigned int, const QString& ) ) ); - APP->database()->enqueue( QSharedPointer(cmd) ); -} - - -void -Source::setStats( const QVariantMap& m ) -{ - m_stats = m; - emit stats( m_stats ); -} - - -void -Source::remove() -{ - qDebug() << Q_FUNC_INFO; - - m_cc = 0; - emit offline(); - APP->sourcelist().remove( this ); - m_collections.clear(); -} - - -QString -Source::friendlyName() const -{ - if ( m_friendlyname.isEmpty() ) - return m_username; - - if ( m_friendlyname.contains( "/tomahawk" ) ) - return m_friendlyname.left( m_friendlyname.indexOf( "/tomahawk" ) ); - - return m_friendlyname; -} - - -void -Source::addCollection( collection_ptr c ) -{ - Q_ASSERT( m_collections.length() == 0 ); // only 1 source supported atm - m_collections.append( c ); - emit collectionAdded( c ); -} - - -void -Source::removeCollection( collection_ptr c ) -{ - Q_ASSERT( m_collections.length() == 1 && m_collections.first() == c ); // only 1 source supported atm - m_collections.removeAll( c ); - emit collectionRemoved( c ); -} - - -void -Source::setOffline() -{ - if ( !m_online ) - return; - - m_online = false; - emit offline(); -} - - -void -Source::setOnline() -{ - if ( m_online ) - return; - - m_online = true; - emit online(); -} - - -void -Source::dbLoaded( unsigned int id, const QString& fname ) -{ - qDebug() << Q_FUNC_INFO << id << fname; - m_id = id; - m_friendlyname = fname; - emit syncedWithDatabase(); -} diff --git a/src/sourcelist.cpp b/src/sourcelist.cpp deleted file mode 100644 index 45a853640..000000000 --- a/src/sourcelist.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "tomahawk/sourcelist.h" - -#include - -using namespace Tomahawk; - -SourceList::SourceList( QObject* parent ) - : QObject( parent ) -{ -} - - -const source_ptr& -SourceList::getLocal() -{ - return m_local; -} - - -void -SourceList::add( const Tomahawk::source_ptr& s ) -{ - { - QMutexLocker lock( &m_mut ); - if ( m_sources.contains( s->userName() ) ) - return; - - m_sources.insert( s->userName(), s ); - if( !s->isLocal() ) - { - Q_ASSERT( s->id() ); - m_sources_id2name.insert( s->id(), s->userName() ); - } - qDebug() << "SourceList::add(" << s->userName() << "), total sources now:" << m_sources.size(); - if( s->isLocal() ) - { - Q_ASSERT( m_local.isNull() ); - m_local = s; - } - } - - emit sourceAdded( s ); -} - - -void -SourceList::remove( const Tomahawk::source_ptr& s ) -{ - remove( s.data() ); -} - - -void -SourceList::remove( Tomahawk::Source* s ) -{ - qDebug() << Q_FUNC_INFO; - source_ptr src; - { - QMutexLocker lock( &m_mut ); - if ( !m_sources.contains( s->userName() ) ) - return; - - src = m_sources.value( s->userName() ); - m_sources_id2name.remove( src->id() ); - m_sources.remove( s->userName() ); - qDebug() << "SourceList::remove(" << s->userName() << "), total sources now:" << m_sources.size(); - } - - emit sourceRemoved( src ); -} - - -QList -SourceList::sources() const -{ - QMutexLocker lock( &m_mut ); - return m_sources.values(); -} - - -source_ptr -SourceList::lookup( unsigned int id ) const -{ - QMutexLocker lock( &m_mut ); - return m_sources.value( m_sources_id2name.value( id ) ); -} - - -source_ptr -SourceList::lookup( const QString& username ) const -{ - QMutexLocker lock( &m_mut ); - return m_sources.value( username ); -} - - -unsigned int -SourceList::count() const -{ - QMutexLocker lock( &m_mut ); - return m_sources.size(); -} diff --git a/src/sourcetree/sourcesmodel.cpp b/src/sourcetree/sourcesmodel.cpp index 17bbae89d..bee554830 100644 --- a/src/sourcetree/sourcesmodel.cpp +++ b/src/sourcetree/sourcesmodel.cpp @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "sourcesmodel.h" #include @@ -6,21 +24,24 @@ #include #include "tomahawk/tomahawkapp.h" -#include "tomahawk/query.h" -#include "tomahawk/sourcelist.h" +#include "query.h" +#include "sourcelist.h" #include "sourcetreeitem.h" -#include "imagebutton.h" +#include "sourcetreeview.h" +#include "utils/imagebutton.h" using namespace Tomahawk; -SourcesModel::SourcesModel( QObject* parent ) +SourcesModel::SourcesModel( SourceTreeView* parent ) : QStandardItemModel( parent ) + , m_parent( parent ) { setColumnCount( 1 ); - connect( &APP->sourcelist(), SIGNAL( sourceAdded( Tomahawk::source_ptr ) ), SLOT( onSourceAdded( Tomahawk::source_ptr ) ) ); - connect( &APP->sourcelist(), SIGNAL( sourceRemoved( Tomahawk::source_ptr ) ), SLOT( onSourceRemoved( Tomahawk::source_ptr ) ) ); + onSourceAdded( SourceList::instance()->sources() ); + connect( SourceList::instance(), SIGNAL( sourceAdded( Tomahawk::source_ptr ) ), SLOT( onSourceAdded( Tomahawk::source_ptr ) ) ); + connect( SourceList::instance(), SIGNAL( sourceRemoved( Tomahawk::source_ptr ) ), SLOT( onSourceRemoved( Tomahawk::source_ptr ) ) ); connect( parent, SIGNAL( onOnline( QModelIndex ) ), SLOT( onItemOnline( QModelIndex ) ) ); connect( parent, SIGNAL( onOffline( QModelIndex ) ), SLOT( onItemOffline( QModelIndex ) ) ); @@ -50,12 +71,18 @@ SourcesModel::flags( const QModelIndex& index ) const if ( index.isValid() ) { - if ( indexType( index ) == 1 ) + if ( indexType( index ) == PlaylistSource ) { playlist_ptr playlist = indexToPlaylist( index ); if ( !playlist.isNull() && playlist->author()->isLocal() ) defaultFlags |= Qt::ItemIsEditable; } + else if ( indexType( index ) == DynamicPlaylistSource ) + { + dynplaylist_ptr playlist = indexToDynamicPlaylist( index ); + if ( !playlist.isNull() && playlist->author()->isLocal() ) + defaultFlags |= Qt::ItemIsEditable; + } return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; } @@ -79,13 +106,21 @@ SourcesModel::data( const QModelIndex& index, int role ) const void SourcesModel::loadSources() { - QList sources = APP->sourcelist().sources(); + QList sources = SourceList::instance()->sources(); foreach( const source_ptr& source, sources ) appendItem( source ); } +void +SourcesModel::onSourceAdded( const QList& sources ) +{ + foreach( const source_ptr& source, sources ) + appendItem( source ); +} + + void SourcesModel::onSourceAdded( const source_ptr& source ) { @@ -109,7 +144,15 @@ SourcesModel::appendItem( const source_ptr& source ) // qDebug() << "Appending source item:" << item->source()->username(); invisibleRootItem()->appendRow( item->columns() ); - ((QTreeView*)parent())->setIndexWidget( index( rowCount() - 1, 0 ), item->widget() ); + if ( !source.isNull() ) + { + connect( source.data(), SIGNAL( offline() ), SLOT( onSourceChanged() ) ); + connect( source.data(), SIGNAL( online() ), SLOT( onSourceChanged() ) ); + connect( source.data(), SIGNAL( stats( QVariantMap ) ), SLOT( onSourceChanged() ) ); + connect( source.data(), SIGNAL( playbackStarted( Tomahawk::query_ptr ) ), SLOT( onSourceChanged() ) ); + connect( source.data(), SIGNAL( stateChanged() ), SLOT( onSourceChanged() ) ); + } + return true; // FIXME } @@ -166,14 +209,14 @@ SourcesModel::onItemOffline( const QModelIndex& idx ) } -int +SourcesModel::SourceType SourcesModel::indexType( const QModelIndex& index ) { if ( !index.isValid() ) - return -1; + return Invalid; QModelIndex idx = index.model()->index( index.row(), 0, index.parent() ); - return idx.data( Qt::UserRole + 1 ).toInt(); + return static_cast( idx.data( SourceTreeItem::Type ).toInt() ); } @@ -184,10 +227,10 @@ SourcesModel::indexToPlaylist( const QModelIndex& index ) if ( !index.isValid() ) return res; - if ( indexType( index ) == 1 ) + if ( indexType( index ) == PlaylistSource ) { QModelIndex idx = index.model()->index( index.row(), 0, index.parent() ); - qlonglong pptr = idx.data( Qt::UserRole + 3 ).toLongLong(); + qlonglong pptr = idx.data( SourceTreeItem::PlaylistPointer ).toLongLong(); playlist_ptr* playlist = reinterpret_cast(pptr); if ( playlist ) return *playlist; @@ -197,6 +240,26 @@ SourcesModel::indexToPlaylist( const QModelIndex& index ) } +dynplaylist_ptr +SourcesModel::indexToDynamicPlaylist( const QModelIndex& index ) +{ + dynplaylist_ptr res; + if ( !index.isValid() ) + return res; + + if ( indexType( index ) == DynamicPlaylistSource ) + { + QModelIndex idx = index.model()->index( index.row(), 0, index.parent() ); + qlonglong pptr = idx.data( SourceTreeItem::DynamicPlaylistPointer ).toLongLong(); + dynplaylist_ptr* playlist = reinterpret_cast(pptr); + if ( playlist ) + return *playlist; + } + + return res; +} + + SourceTreeItem* SourcesModel::indexToTreeItem( const QModelIndex& index ) { @@ -204,10 +267,10 @@ SourcesModel::indexToTreeItem( const QModelIndex& index ) return 0; int type = indexType( index ); - if ( type == 0 || type == 1 ) + if ( type == CollectionSource || type == PlaylistSource || type == DynamicPlaylistSource ) { QModelIndex idx = index.model()->index( index.row(), 0, index.parent() ); - qlonglong pptr = idx.data( Qt::UserRole + 2 ).toLongLong(); + qlonglong pptr = idx.data( SourceTreeItem::SourceItemPointer ).toLongLong(); SourceTreeItem* item = reinterpret_cast(pptr); if ( item ) return item; @@ -217,6 +280,75 @@ SourcesModel::indexToTreeItem( const QModelIndex& index ) } +QModelIndex +SourcesModel::playlistToIndex( const Tomahawk::playlist_ptr& playlist ) +{ + for ( int i = 0; i < rowCount(); i++ ) + { + QModelIndex pidx = index( i, 0 ); + + for ( int j = 0; j < rowCount( pidx ); j++ ) + { + QModelIndex idx = index( j, 0, pidx ); + SourcesModel::SourceType type = SourcesModel::indexType( idx ); + + if ( type == SourcesModel::PlaylistSource ) + { + playlist_ptr p = SourcesModel::indexToPlaylist( idx ); + if ( playlist.data() == p.data() ) + return idx; + } + } + } + + return QModelIndex(); +} + + +QModelIndex +SourcesModel::dynamicPlaylistToIndex( const Tomahawk::dynplaylist_ptr& playlist ) +{ + for ( int i = 0; i < rowCount(); i++ ) + { + QModelIndex pidx = index( i, 0 ); + + for ( int j = 0; j < rowCount( pidx ); j++ ) + { + QModelIndex idx = index( j, 0, pidx ); + SourcesModel::SourceType type = SourcesModel::indexType( idx ); + + if ( type == SourcesModel::DynamicPlaylistSource ) + { + playlist_ptr p = SourcesModel::indexToDynamicPlaylist( idx ); + if ( playlist.data() == p.data() ) + return idx; + } + } + } + + return QModelIndex(); +} + + +QModelIndex +SourcesModel::collectionToIndex( const Tomahawk::collection_ptr& collection ) +{ + for ( int i = 0; i < rowCount(); i++ ) + { + QModelIndex idx = index( i, 0 ); + SourcesModel::SourceType type = SourcesModel::indexType( idx ); + if ( type == SourcesModel::CollectionSource ) + { + SourceTreeItem* sti = SourcesModel::indexToTreeItem( idx ); + if ( sti && !sti->source().isNull() && sti->source()->collection().data() == collection.data() ) + return idx; + } + } + + return QModelIndex(); +} + + bool SourcesModel::setData( const QModelIndex& index, const QVariant& value, int role ) { @@ -225,17 +357,46 @@ SourcesModel::setData( const QModelIndex& index, const QVariant& value, int role if ( !index.isValid() ) return false; - if ( indexType( index ) == 1 ) + playlist_ptr playlist; + if ( indexType( index ) == PlaylistSource ) { - playlist_ptr playlist = indexToPlaylist( index ); - if ( !playlist.isNull() ) - { - playlist->rename( value.toString() ); - QStandardItemModel::setData( index, value, Qt::DisplayRole ); - } - + playlist = indexToPlaylist( index ); + } + else if ( indexType( index ) == DynamicPlaylistSource ) + { + playlist = indexToDynamicPlaylist( index ).staticCast< Playlist >(); + } + + if ( !playlist.isNull() ) + { + playlist->rename( value.toString() ); + QStandardItemModel::setData( index, value, Qt::DisplayRole ); return true; } return false; } + + +void +SourcesModel::onSourceChanged() +{ + Source* src = qobject_cast< Source* >( sender() ); + + for ( int i = 0; i < rowCount(); i++ ) + { + QModelIndex idx = index( i, 0 ); + + if ( indexType( idx ) == CollectionSource ) + { + SourceTreeItem* sti = indexToTreeItem( idx ); + if ( sti ) + { + if ( sti->source().data() == src ) + { + emit dataChanged( idx, idx ); + } + } + } + } +} diff --git a/src/sourcetree/sourcesmodel.h b/src/sourcetree/sourcesmodel.h index e39f3b11c..a9ad5d74b 100644 --- a/src/sourcetree/sourcesmodel.h +++ b/src/sourcetree/sourcesmodel.h @@ -1,19 +1,46 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef SOURCESMODEL_H #define SOURCESMODEL_H #include -#include "tomahawk/source.h" -#include "tomahawk/typedefs.h" +#include "source.h" +#include "typedefs.h" class SourceTreeItem; +class SourceTreeView; class SourcesModel : public QStandardItemModel { Q_OBJECT -public: - explicit SourcesModel( QObject* parent = 0 ); +public: + enum SourceType { + Invalid = -1, + + CollectionSource = 0, + PlaylistSource = 1, + DynamicPlaylistSource = 2 + }; + + explicit SourcesModel( SourceTreeView* parent = 0 ); virtual QStringList mimeTypes() const; virtual Qt::DropActions supportedDropActions() const; @@ -23,10 +50,15 @@ public: bool appendItem( const Tomahawk::source_ptr& source ); bool removeItem( const Tomahawk::source_ptr& source ); - static int indexType( const QModelIndex& index ); + static SourceType indexType( const QModelIndex& index ); static Tomahawk::playlist_ptr indexToPlaylist( const QModelIndex& index ); + static Tomahawk::dynplaylist_ptr indexToDynamicPlaylist( const QModelIndex& index ); static SourceTreeItem* indexToTreeItem( const QModelIndex& index ); + QModelIndex playlistToIndex( const Tomahawk::playlist_ptr& playlist ); + QModelIndex dynamicPlaylistToIndex( const Tomahawk::dynplaylist_ptr& playlist ); + QModelIndex collectionToIndex( const Tomahawk::collection_ptr& collection ); + signals: void clicked( const QModelIndex& ); @@ -34,14 +66,20 @@ protected: bool setData( const QModelIndex& index, const QVariant& value, int role = Qt::EditRole ); private slots: + void onSourceAdded( const QList& sources ); void onSourceAdded( const Tomahawk::source_ptr& source ); void onSourceRemoved( const Tomahawk::source_ptr& source ); + void onSourceChanged(); + void onItemOnline( const QModelIndex& idx ); void onItemOffline( const QModelIndex& idx ); public slots: void loadSources(); + +private: + SourceTreeView* m_parent; }; #endif // SOURCESMODEL_H diff --git a/src/sourcetree/sourcesproxymodel.cpp b/src/sourcetree/sourcesproxymodel.cpp new file mode 100644 index 000000000..d93cf098e --- /dev/null +++ b/src/sourcetree/sourcesproxymodel.cpp @@ -0,0 +1,75 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "sourcesproxymodel.h" + +#include +#include + +#include "sourcesmodel.h" +#include "sourcetreeitem.h" + + +SourcesProxyModel::SourcesProxyModel( SourcesModel* model, QObject* parent ) + : QSortFilterProxyModel( parent ) + , m_model( model ) + , m_filtered( false ) +{ + setDynamicSortFilter( true ); + + setSourceModel( model ); +} + + +void +SourcesProxyModel::showOfflineSources() +{ + m_filtered = false; + invalidateFilter(); + +// Q_ASSERT( qobject_cast( parent() ) ); +// qobject_cast( parent() )->expandAll(); +} + + +void +SourcesProxyModel::hideOfflineSources() +{ + m_filtered = true; + invalidateFilter(); + +// Q_ASSERT( qobject_cast( parent() ) ); +// qobject_cast( parent() )->expandAll(); +} + + +bool +SourcesProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const +{ + if ( !m_filtered ) + return true; + + SourceTreeItem* sti = m_model->indexToTreeItem( sourceModel()->index( sourceRow, 0, sourceParent ) ); + if ( sti ) + { + if ( sti->source().isNull() || sti->source()->isOnline() ) + return true; + } + + return false; +} diff --git a/src/sourcetree/sourcesproxymodel.h b/src/sourcetree/sourcesproxymodel.h new file mode 100644 index 000000000..add57b257 --- /dev/null +++ b/src/sourcetree/sourcesproxymodel.h @@ -0,0 +1,46 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef SOURCESPROXYMODEL_H +#define SOURCESPROXYMODEL_H + +#include + +class SourcesModel; + +class SourcesProxyModel : public QSortFilterProxyModel +{ +Q_OBJECT + +public: + explicit SourcesProxyModel( SourcesModel* model, QObject* parent = 0 ); + +public slots: + void showOfflineSources(); + void hideOfflineSources(); + +protected: + bool filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const; + +private: + SourcesModel* m_model; + + bool m_filtered; +}; + +#endif // SOURCESPROXYMODEL_H diff --git a/src/sourcetree/sourcetreeitem.cpp b/src/sourcetree/sourcetreeitem.cpp index 2dc3fc931..c9ec16101 100644 --- a/src/sourcetree/sourcetreeitem.cpp +++ b/src/sourcetree/sourcetreeitem.cpp @@ -1,38 +1,90 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "sourcetreeitem.h" #include #include -#include "tomahawk/collection.h" -#include "tomahawk/playlist.h" +#include "collection.h" +#include "playlist.h" #include "tomahawk/tomahawkapp.h" +#include "sourcesmodel.h" using namespace Tomahawk; +static inline QList< playlist_ptr > dynListToPlaylists( const QList< Tomahawk::dynplaylist_ptr >& list ) +{ + QList< playlist_ptr > newptrs; + foreach( const dynplaylist_ptr& pl, list ) { + newptrs << pl.staticCast(); + } + return newptrs; +} + + SourceTreeItem::SourceTreeItem( const source_ptr& source, QObject* parent ) : QObject( parent ) , m_source( source ) { - QStandardItem* item = new QStandardItem( "" ); + QString name; + if( source.isNull() ) + name = tr( "Super Collection" ); + else + { + if( TomahawkApp::instance()->scrubFriendlyName() && source->friendlyName().contains( '@' ) ) + name = source->friendlyName().left( source->friendlyName().indexOf( '@' ) ); + else + name = source->friendlyName(); + } + + QStandardItem* item = new QStandardItem( name ); + item->setIcon( QIcon( RESPATH "images/user-avatar.png" ) ); item->setEditable( false ); - item->setData( 0, Qt::UserRole + 1 ); - item->setData( (qlonglong)this, Qt::UserRole + 2 ); + item->setData( SourcesModel::CollectionSource, Type ); + item->setData( (qlonglong)this, SourceItemPointer ); m_columns << item; if ( !source.isNull() ) { onPlaylistsAdded( source->collection()->playlists() ); + onDynamicPlaylistsAdded( source->collection()->dynamicPlaylists() ); connect( source->collection().data(), SIGNAL( playlistsAdded( QList ) ), SLOT( onPlaylistsAdded( QList ) ) ); - connect( source->collection().data(), SIGNAL( playlistsDeleted( QList ) ), SLOT( onPlaylistsDeleted( QList ) ) ); + + connect( source->collection().data(), SIGNAL( dynamicPlaylistsAdded( QList ) ), + SLOT( onDynamicPlaylistsAdded( QList ) ) ); + connect( source->collection().data(), SIGNAL( dynamicPlaylistsDeleted( QList ) ), + SLOT( onDynamicPlaylistsDeleted( QList ) ) ); } - m_widget = new SourceTreeItemWidget( source, (QWidget*)parent->parent() ); - connect( m_widget, SIGNAL( clicked() ), SLOT( onClicked() ) ); +/* m_widget = new SourceTreeItemWidget( source ); + connect( m_widget, SIGNAL( clicked() ), SLOT( onClicked() ) );*/ +} + + +SourceTreeItem::~SourceTreeItem() +{ + qDebug() << Q_FUNC_INFO; } @@ -62,27 +114,18 @@ SourceTreeItem::onPlaylistsAdded( const QList& playlists ) { // const-ness is important for getting the right pointer! foreach( const playlist_ptr& p, playlists ) - { + { m_playlists.append( p ); - qlonglong ptr = qlonglong( &m_playlists.last() ); - qDebug() << "Playlist added:" << p->title() << p->creator() << p->info() << ptr; + qlonglong ptr = reinterpret_cast( &m_playlists.last() ); connect( p.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), - SLOT( onPlaylistLoaded( Tomahawk::PlaylistRevision ) ), - Qt::QueuedConnection); + SLOT( onPlaylistLoaded( Tomahawk::PlaylistRevision ) ), Qt::QueuedConnection ); + connect( p.data(), SIGNAL( changed() ), + SLOT( onPlaylistChanged() ), Qt::QueuedConnection ); - QStandardItem* subitem = new QStandardItem( p->title() ); - subitem->setIcon( QIcon( RESPATH "images/playlist-icon.png" ) ); - subitem->setEditable( false ); - subitem->setEnabled( false ); - subitem->setData( ptr, Qt::UserRole + 3 ); - subitem->setData( 1, Qt::UserRole + 1 ); - subitem->setData( (qlonglong)this, Qt::UserRole + 2 ); + qDebug() << "Playlist added:" << p->title() << p->creator() << p->info() << ptr; - m_columns.at( 0 )->appendRow( subitem ); - ((QTreeView*)parent()->parent())->expandAll(); - - p->loadRevision(); + playlistAddedInternal( ptr, p, false ); } } @@ -101,14 +144,15 @@ SourceTreeItem::onPlaylistsDeleted( const QList& playlists ) for ( int i = rows - 1; i >= 0; i-- ) { QStandardItem* pi = item->child( i ); - qlonglong piptr = pi->data( Qt::UserRole + 3 ).toLongLong(); + qlonglong piptr = pi->data( PlaylistPointer ).toLongLong(); playlist_ptr* pl = reinterpret_cast(piptr); - int type = pi->data( Qt::UserRole + 1 ).toInt(); + SourcesModel::SourceType type = static_cast( pi->data( Type ).toInt() ); - if ( type == 1 && ptr == qlonglong( pl->data() ) ) + if ( type == SourcesModel::PlaylistSource && ptr == qlonglong( pl->data() ) ) { - m_playlists.removeAll( p ); item->removeRow( i ); + m_playlists.removeAll( p ); + break; } } } @@ -118,23 +162,153 @@ SourceTreeItem::onPlaylistsDeleted( const QList& playlists ) void SourceTreeItem::onPlaylistLoaded( Tomahawk::PlaylistRevision revision ) { - qlonglong ptr = qlonglong( sender() ); - //qDebug() << "sender ptr:" << ptr; + qlonglong ptr = reinterpret_cast( sender() ); QStandardItem* item = m_columns.at( 0 ); int rows = item->rowCount(); for ( int i = 0; i < rows; i++ ) { QStandardItem* pi = item->child( i ); - qlonglong piptr = pi->data( Qt::UserRole + 3 ).toLongLong(); + qlonglong piptr = pi->data( PlaylistPointer ).toLongLong(); playlist_ptr* pl = reinterpret_cast(piptr); - int type = pi->data( Qt::UserRole + 1 ).toInt(); + SourcesModel::SourceType type = static_cast( pi->data( Type ).toInt() ); - if ( type == 1 && ptr == qlonglong( pl->data() ) ) + if ( type == SourcesModel::PlaylistSource && ptr == qlonglong( pl->data() ) ) { - //qDebug() << "Found playlist!"; pi->setEnabled( true ); m_current_revisions.insert( pl->data()->guid(), revision.revisionguid ); + break; } } } + + +void +SourceTreeItem::onPlaylistChanged() +{ + qlonglong ptr = reinterpret_cast( sender() ); + + QStandardItem* item = m_columns.at( 0 ); + int rows = item->rowCount(); + for ( int i = 0; i < rows; i++ ) + { + QStandardItem* pi = item->child( i ); + SourcesModel::SourceType type = static_cast( pi->data( Type ).toInt() ); + + if ( type == SourcesModel::PlaylistSource ) + { + qlonglong piptr = pi->data( PlaylistPointer ).toLongLong(); + playlist_ptr* pl = reinterpret_cast(piptr); + + if ( ptr == qlonglong( pl->data() ) ) + { + pi->setText( pl->data()->title() ); + break; + } + } + if ( type == SourcesModel::DynamicPlaylistSource ) + { + qlonglong piptr = pi->data( DynamicPlaylistPointer ).toLongLong(); + dynplaylist_ptr* pl = reinterpret_cast(piptr); + + if ( ptr == qlonglong( pl->data() ) ) + { + pi->setText( pl->data()->title() ); + break; + } + } + } +} + + +void +SourceTreeItem::onDynamicPlaylistsAdded( const QList< dynplaylist_ptr >& playlists ) +{ + // const-ness is important for getting the right pointer! + foreach( const dynplaylist_ptr& p, playlists ) + { + m_dynplaylists.append( p ); + qlonglong ptr = reinterpret_cast( &m_dynplaylists.last() ); + + connect( p.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision) ), + SLOT( onDynamicPlaylistLoaded( Tomahawk::DynamicPlaylistRevision ) ), Qt::QueuedConnection ); + connect( p.data(), SIGNAL( changed() ), + SLOT( onPlaylistChanged() ), Qt::QueuedConnection ); + +// qDebug() << "Dynamic Playlist added:" << p->title() << p->creator() << p->info() << p->currentrevision() << ptr; + + playlistAddedInternal( ptr, p, true ); + } +} + + +void +SourceTreeItem::onDynamicPlaylistsDeleted( const QList< dynplaylist_ptr >& playlists ) +{ + // const-ness is important for getting the right pointer! + foreach( const dynplaylist_ptr& p, playlists ) + { + qlonglong ptr = qlonglong( p.data() ); +// qDebug() << "dynamic playlist removed:" << p->title() << p->creator() << p->info() << ptr; + + QStandardItem* item = m_columns.at( 0 ); + int rows = item->rowCount(); + for ( int i = rows - 1; i >= 0; i-- ) + { + QStandardItem* pi = item->child( i ); + qlonglong piptr = pi->data( DynamicPlaylistPointer ).toLongLong(); + dynplaylist_ptr* pl = reinterpret_cast(piptr); + SourcesModel::SourceType type = static_cast( pi->data( Type ).toInt() ); + + //qDebug() << "Deleting dynamic playlist:" << pl->isNull(); + if ( type == SourcesModel::DynamicPlaylistSource && ptr == qlonglong( pl->data() ) ) + { + item->removeRow( i ); + m_dynplaylists.removeAll( p ); + break; + } + } + } +} + + +void +SourceTreeItem::onDynamicPlaylistLoaded( DynamicPlaylistRevision revision ) +{ + qlonglong ptr = reinterpret_cast( sender() ); + + QStandardItem* item = m_columns.at( 0 ); + int rows = item->rowCount(); + for ( int i = 0; i < rows; i++ ) + { + QStandardItem* pi = item->child( i ); + qlonglong piptr = pi->data( DynamicPlaylistPointer ).toLongLong(); + playlist_ptr* pl = reinterpret_cast(piptr); + SourcesModel::SourceType type = static_cast( pi->data( Type ).toInt() ); + + if ( type == SourcesModel::DynamicPlaylistSource && ptr == qlonglong( pl->data() ) ) + { + pi->setEnabled( true ); + m_current_dynamic_revisions.insert( pl->data()->guid(), revision.revisionguid ); + break; + } + } +} + + +void SourceTreeItem::playlistAddedInternal( qlonglong ptr, const Tomahawk::playlist_ptr& p, bool dynamic ) +{ + QStandardItem* subitem = new QStandardItem( p->title() ); + subitem->setIcon( QIcon( RESPATH "images/playlist-icon.png" ) ); + subitem->setEditable( false ); + subitem->setEnabled( false ); + subitem->setData( ptr, dynamic ? DynamicPlaylistPointer : PlaylistPointer ); + subitem->setData( dynamic ? SourcesModel::DynamicPlaylistSource : SourcesModel::PlaylistSource, Type ); + subitem->setData( (qlonglong)this, SourceItemPointer ); + + m_columns.at( 0 )->appendRow( subitem ); +// Q_ASSERT( qobject_cast((parent()->parent()) ) ); +// qobject_cast((parent()->parent()))->expandAll(); + + p->loadRevision(); +} diff --git a/src/sourcetree/sourcetreeitem.h b/src/sourcetree/sourcetreeitem.h index 3020835a4..36f07d61b 100644 --- a/src/sourcetree/sourcetreeitem.h +++ b/src/sourcetree/sourcetreeitem.h @@ -1,22 +1,45 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef SOURCETREEITEM_H #define SOURCETREEITEM_H #include #include -#include "tomahawk/typedefs.h" +#include "typedefs.h" #include "sourcetreeitemwidget.h" +#include "playlist/dynamic/DynamicPlaylist.h" class SourceTreeItem : public QObject { Q_OBJECT - public: + + enum PlaylistItemType { + Type = Qt::UserRole + 1, /// Value is SourcesModel::SourceType + SourceItemPointer = Qt::UserRole + 2, /// value is the sourcetreeritem of the collection itself. + PlaylistPointer = Qt::UserRole + 3, /// Value is the playlist_ptr.data() + DynamicPlaylistPointer = Qt::UserRole + 4 /// Value is the dynplaylist_ptr.data() + }; + explicit SourceTreeItem( const Tomahawk::source_ptr& source, QObject* parent ); - virtual ~SourceTreeItem() - { - qDebug() << Q_FUNC_INFO; - } + virtual ~SourceTreeItem(); const Tomahawk::source_ptr& source() const { return m_source; }; QList columns() const { return m_columns; }; @@ -42,15 +65,24 @@ private slots: void onPlaylistsAdded( const QList& playlists ); void onPlaylistsDeleted( const QList& playlists ); void onPlaylistLoaded( Tomahawk::PlaylistRevision revision ); + void onPlaylistChanged(); + + void onDynamicPlaylistsAdded( const QList& playlists ); + void onDynamicPlaylistsDeleted( const QList& playlists ); + void onDynamicPlaylistLoaded( Tomahawk::DynamicPlaylistRevision revision ); private: + void playlistAddedInternal( qlonglong ptr, const Tomahawk::playlist_ptr& pl, bool dynamic ); + QList m_columns; Tomahawk::source_ptr m_source; SourceTreeItemWidget* m_widget; QList m_playlists; + QList m_dynplaylists; // playist->guid() -> currently loaded revision QMap m_current_revisions; + QMap m_current_dynamic_revisions; }; #endif // SOURCETREEITEM_H diff --git a/src/sourcetree/sourcetreeitemwidget.cpp b/src/sourcetree/sourcetreeitemwidget.cpp index b10aeecdb..9f8bc64a2 100644 --- a/src/sourcetree/sourcetreeitemwidget.cpp +++ b/src/sourcetree/sourcetreeitemwidget.cpp @@ -1,13 +1,31 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "sourcetreeitemwidget.h" #include "ui_sourcetreeitemwidget.h" #include "tomahawk/tomahawkapp.h" -#include "tomahawk/album.h" -#include "database.h" -#include "databasecommand_collectionstats.h" -#include "dbsyncconnection.h" -#include "playlistmanager.h" +#include "album.h" +#include "database/database.h" +#include "database/databasecommand_collectionstats.h" +#include "network/dbsyncconnection.h" +#include "playlist/playlistmanager.h" using namespace Tomahawk; @@ -21,6 +39,15 @@ SourceTreeItemWidget::SourceTreeItemWidget( const source_ptr& source, QWidget* p ui->setupUi( this ); ui->verticalLayout->setSpacing( 3 ); + ui->activityLabel->setType( QueryLabel::ArtistAndTrack ); + + QFont font = ui->nameLabel->font(); +// font.setPointSize( font.pointSize() - 1 ); + ui->nameLabel->setFont( font ); + + font.setPointSize( font.pointSize() - 1 ); + ui->infoLabel->setFont( font ); + ui->activityLabel->setFont( font ); QString displayname; if ( source.isNull() ) @@ -39,6 +66,9 @@ SourceTreeItemWidget::SourceTreeItemWidget( const source_ptr& source, QWidget* p connect( source.data(), SIGNAL( stats( QVariantMap ) ), SLOT( gotStats( QVariantMap ) ) ); + connect( source.data(), SIGNAL( playbackStarted( Tomahawk::query_ptr ) ), SLOT( onPlaybackStarted( Tomahawk::query_ptr ) ) ); + connect( source.data(), SIGNAL( offline() ), SLOT( onOffline() ) ); + ui->avatarImage->setPixmap( QPixmap( RESPATH "images/user-avatar.png" ) ); displayname = source->friendlyName(); @@ -51,13 +81,21 @@ SourceTreeItemWidget::SourceTreeItemWidget( const source_ptr& source, QWidget* p ui->infoButton->setPixmap( QPixmap( RESPATH "images/source-info.png" ) .scaledToHeight( 32, Qt::SmoothTransformation ) ); } + if ( source.isNull() || source->isLocal() ) + ui->activityLabel->setText( tr( "Idle" ) ); + else + onOffline(); + ui->nameLabel->setText( displayname ); ui->infoLabel->setForegroundRole( QPalette::Dark ); + ui->activityLabel->setForegroundRole( QPalette::Dark ); + + ui->nameLabel->setContentsMargins( 4, 0, 0, 0 ); + ui->infoLabel->setContentsMargins( 4, 0, 0, 0 ); + ui->activityLabel->setContentsMargins( 4, 0, 0, 0 ); connect( ui->onOffButton, SIGNAL( clicked() ), SIGNAL( clicked() ) ); connect( ui->infoButton, SIGNAL( clicked() ), SLOT( onInfoButtonClicked() ) ); - - onOffline(); } @@ -124,28 +162,31 @@ SourceTreeItemWidget::onLoadingStateChanged( DBSyncConnection::State newstate, D } +void +SourceTreeItemWidget::onPlaybackStarted( const Tomahawk::query_ptr& query ) +{ + qDebug() << Q_FUNC_INFO << query->toString(); + ui->activityLabel->setQuery( query ); +} + + void SourceTreeItemWidget::onOnline() { return; - - if ( !m_source.isNull() ) - ui->onOffButton->setPixmap( RESPATH "images/source-on-rest.png" ); } void SourceTreeItemWidget::onOffline() { + ui->activityLabel->setText( tr( "Offline" ) ); return; - - if ( !m_source.isNull() ) - ui->onOffButton->setPixmap( RESPATH "images/source-off-rest.png" ); } void SourceTreeItemWidget::onInfoButtonClicked() { - APP->playlistManager()->show( m_source ); + PlaylistManager::instance()->show( m_source ); } diff --git a/src/sourcetree/sourcetreeitemwidget.h b/src/sourcetree/sourcetreeitemwidget.h index 942ac9753..515b2de27 100644 --- a/src/sourcetree/sourcetreeitemwidget.h +++ b/src/sourcetree/sourcetreeitemwidget.h @@ -1,9 +1,27 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef SOURCETREEITEMWIDGET_H #define SOURCETREEITEMWIDGET_H #include -#include "tomahawk/source.h" +#include "source.h" namespace Ui { @@ -32,6 +50,8 @@ private slots: void gotStats( const QVariantMap& stats ); void onLoadingStateChanged( DBSyncConnection::State newstate, DBSyncConnection::State oldstate, const QString& info ); + void onPlaybackStarted( const Tomahawk::query_ptr& query ); + void onInfoButtonClicked(); private: diff --git a/src/sourcetree/sourcetreeitemwidget.ui b/src/sourcetree/sourcetreeitemwidget.ui index 030f82753..e87cc409c 100644 --- a/src/sourcetree/sourcetreeitemwidget.ui +++ b/src/sourcetree/sourcetreeitemwidget.ui @@ -7,27 +7,15 @@ 0 0 359 - 44 + 56 - + 0 0 - - - 0 - 44 - - - - - 16777215 - 44 - - Form @@ -92,7 +80,7 @@ - 4 + 2 20 @@ -101,16 +89,13 @@ - 4 - - - 0 + 2 - 4 + 2 - + 75 @@ -129,6 +114,13 @@ + + + + TextLabel + + + @@ -190,7 +182,17 @@ ImageButton QPushButton -
imagebutton.h
+
utils/imagebutton.h
+
+ + QueryLabel + QLabel +
utils/querylabel.h
+
+ + ElidedLabel + QLabel +
utils/elidedlabel.h
diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 6c0d1bb73..98d9e5b16 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -1,11 +1,31 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "sourcetreeview.h" -#include "tomahawk/tomahawkapp.h" -#include "tomahawk/playlist.h" -#include "collectionmodel.h" -#include "playlistmanager.h" +#include "playlist.h" +#include "playlist/collectionmodel.h" +#include "playlist/playlistmanager.h" #include "sourcetreeitem.h" #include "sourcesmodel.h" +#include "sourcesproxymodel.h" +#include "sourcelist.h" +#include "tomahawk/tomahawkapp.h" #include #include @@ -13,6 +33,7 @@ #include #include #include +#include using namespace Tomahawk; @@ -23,11 +44,12 @@ public: SourceDelegate( QAbstractItemView* parent = 0 ) : QStyledItemDelegate( parent ), m_parent( parent ) {} protected: - void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; - void updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const + virtual QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const; + virtual void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; + virtual void updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const { - if ( SourcesModel::indexType( index ) == 1 ) - editor->setGeometry( option.rect.adjusted( 32, 0, 0, 0 ) ); + if ( SourcesModel::indexType( index ) == SourcesModel::PlaylistSource || SourcesModel::indexType( index ) == SourcesModel::DynamicPlaylistSource ) + editor->setGeometry( option.rect.adjusted( 20, 0, 0, 0 ) ); else QStyledItemDelegate::updateEditorGeometry( editor, option, index ); } @@ -42,8 +64,13 @@ SourceTreeView::SourceTreeView( QWidget* parent ) , m_collectionModel( new CollectionModel( this ) ) , m_dragging( false ) { + setFrameShape( QFrame::NoFrame ); + setAttribute( Qt::WA_MacShowFocusRect, 0 ); + setContentsMargins( 0, 0, 0, 0 ); + setMinimumWidth( 220 ); + setHeaderHidden( true ); - setRootIsDecorated( false ); + setRootIsDecorated( true ); setExpandsOnDoubleClick( false ); setSelectionBehavior( QAbstractItemView::SelectRows ); @@ -52,27 +79,41 @@ SourceTreeView::SourceTreeView( QWidget* parent ) setDropIndicatorShown( false ); setAllColumnsShowFocus( true ); setUniformRowHeights( false ); - setIndentation( 0 ); - setAnimated( false ); - + setIndentation( 16 ); + setAnimated( true ); + setItemDelegate( new SourceDelegate( this ) ); setContextMenuPolicy( Qt::CustomContextMenu ); - connect( this, SIGNAL( customContextMenuRequested( const QPoint& ) ), SLOT( onCustomContextMenu( const QPoint& ) ) ); + connect( this, SIGNAL( customContextMenuRequested( QPoint ) ), SLOT( onCustomContextMenu( QPoint ) ) ); m_model = new SourcesModel( this ); - setModel( m_model ); + m_proxyModel = new SourcesProxyModel( m_model, this ); + setModel( m_proxyModel ); header()->setStretchLastSection( false ); header()->setResizeMode( 0, QHeaderView::Stretch ); connect( m_model, SIGNAL( clicked( QModelIndex ) ), SIGNAL( clicked( QModelIndex ) ) ); - connect( this, SIGNAL( clicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); + connect( this, SIGNAL( clicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); - connect( selectionModel(), SIGNAL( selectionChanged( const QItemSelection&, const QItemSelection& ) ), SLOT( onSelectionChanged() ) ); - connect( &APP->sourcelist(), SIGNAL( sourceRemoved( Tomahawk::source_ptr ) ), SLOT( onSourceOffline( Tomahawk::source_ptr ) ) ); + connect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), SLOT( onSelectionChanged() ) ); + connect( SourceList::instance(), SIGNAL( sourceRemoved( Tomahawk::source_ptr ) ), SLOT( onSourceOffline( Tomahawk::source_ptr ) ) ); m_model->appendItem( source_ptr() ); + + hideOfflineSources(); + + connect( PlaylistManager::instance(), SIGNAL( playlistActivated( Tomahawk::playlist_ptr ) ), + SLOT( onPlaylistActivated( Tomahawk::playlist_ptr ) ) ); + connect( PlaylistManager::instance(), SIGNAL( dynamicPlaylistActivated( Tomahawk::dynplaylist_ptr ) ), + SLOT( onDynamicPlaylistActivated( Tomahawk::dynplaylist_ptr ) ) ); + connect( PlaylistManager::instance(), SIGNAL( collectionActivated( Tomahawk::collection_ptr ) ), + SLOT( onCollectionActivated( Tomahawk::collection_ptr ) ) ); + connect( PlaylistManager::instance(), SIGNAL( superCollectionActivated() ), + SLOT( onSuperCollectionActivated() ) ); + connect( PlaylistManager::instance(), SIGNAL( tempPageActivated() ), + SLOT( onTempPageActivated() ) ); } @@ -87,26 +128,43 @@ SourceTreeView::setupMenus() m_deletePlaylistAction = m_playlistMenu.addAction( tr( "&Delete Playlist" ) ); bool readonly = true; - int type = SourcesModel::indexType( m_contextMenuIndex ); - if ( type == 1 ) + SourcesModel::SourceType type = SourcesModel::indexType( m_contextMenuIndex ); + if ( type == SourcesModel::PlaylistSource || type == SourcesModel::DynamicPlaylistSource ) { - playlist_ptr playlist = SourcesModel::indexToPlaylist( m_contextMenuIndex ); + playlist_ptr playlist = SourcesModel::indexToDynamicPlaylist( m_contextMenuIndex ); + if( playlist.isNull() ) + { + playlist = SourcesModel::indexToPlaylist( m_contextMenuIndex ); + } if ( !playlist.isNull() ) { readonly = !playlist->author()->isLocal(); } } - if ( readonly ) - { - m_deletePlaylistAction->setEnabled( !readonly ); - } + m_deletePlaylistAction->setEnabled( !readonly ); + m_renamePlaylistAction->setEnabled( !readonly ); connect( m_loadPlaylistAction, SIGNAL( triggered() ), SLOT( loadPlaylist() ) ); + connect( m_renamePlaylistAction, SIGNAL( triggered() ), SLOT( renamePlaylist() ) ); connect( m_deletePlaylistAction, SIGNAL( triggered() ), SLOT( deletePlaylist() ) ); } +void +SourceTreeView::showOfflineSources() +{ + m_proxyModel->showOfflineSources(); +} + + +void +SourceTreeView::hideOfflineSources() +{ + m_proxyModel->hideOfflineSources(); +} + + void SourceTreeView::onSourceOffline( Tomahawk::source_ptr src ) { @@ -114,39 +172,99 @@ SourceTreeView::onSourceOffline( Tomahawk::source_ptr src ) } +void +SourceTreeView::onPlaylistActivated( const Tomahawk::playlist_ptr& playlist ) +{ + QModelIndex idx = m_proxyModel->mapFromSource( m_model->playlistToIndex( playlist ) ); + if ( idx.isValid() ) + { + setCurrentIndex( idx ); + } +} + + +void +SourceTreeView::onDynamicPlaylistActivated( const Tomahawk::dynplaylist_ptr& playlist ) +{ + QModelIndex idx = m_proxyModel->mapFromSource( m_model->dynamicPlaylistToIndex( playlist ) ); + if ( idx.isValid() ) + { + setCurrentIndex( idx ); + } +} + + +void +SourceTreeView::onCollectionActivated( const Tomahawk::collection_ptr& collection ) +{ + QModelIndex idx = m_proxyModel->mapFromSource( m_model->collectionToIndex( collection ) ); + if ( idx.isValid() ) + { + setCurrentIndex( idx ); + } +} + + +void +SourceTreeView::onSuperCollectionActivated() +{ + QModelIndex idx = m_proxyModel->index( 0, 0 ); + if ( idx.isValid() ) + { + setCurrentIndex( idx ); + } +} + + +void +SourceTreeView::onTempPageActivated() +{ + clearSelection(); +} + + void SourceTreeView::onItemActivated( const QModelIndex& index ) { if ( !index.isValid() ) return; - int type = SourcesModel::indexType( index ); - if ( type == 0 ) + SourcesModel::SourceType type = SourcesModel::indexType( index ); + if ( type == SourcesModel::CollectionSource ) { SourceTreeItem* item = SourcesModel::indexToTreeItem( index ); if ( item ) { if ( item->source().isNull() ) { - APP->playlistManager()->showSuperCollection(); + PlaylistManager::instance()->showSuperCollection(); } else { qDebug() << "SourceTreeItem toggled:" << item->source()->userName(); - APP->playlistManager()->show( item->source()->collection() ); -// APP->playlistManager()->show( item->source() ); + PlaylistManager::instance()->show( item->source()->collection() ); } } } - else if ( type == 1 ) + else if ( type == SourcesModel::PlaylistSource ) { playlist_ptr playlist = SourcesModel::indexToPlaylist( index ); if ( !playlist.isNull() ) { qDebug() << "Playlist activated:" << playlist->title(); - APP->playlistManager()->show( playlist ); + PlaylistManager::instance()->show( playlist ); + } + } + else if ( type == SourcesModel::DynamicPlaylistSource ) + { + dynplaylist_ptr playlist = SourcesModel::indexToDynamicPlaylist( index ); + if ( !playlist.isNull() ) + { + qDebug() << "Dynamic Playlist activated:" << playlist->title(); + + PlaylistManager::instance()->show( playlist ); } } } @@ -174,8 +292,8 @@ SourceTreeView::deletePlaylist() if ( !idx.isValid() ) return; - int type = SourcesModel::indexType( idx ); - if ( type == 1 ) + SourcesModel::SourceType type = SourcesModel::indexType( idx ); + if ( type == SourcesModel::PlaylistSource ) { playlist_ptr playlist = SourcesModel::indexToPlaylist( idx ); if ( !playlist.isNull() ) @@ -183,10 +301,21 @@ SourceTreeView::deletePlaylist() qDebug() << "Playlist about to be deleted:" << playlist->title(); Playlist::remove( playlist ); } + } else if( type == SourcesModel::DynamicPlaylistSource ) { + dynplaylist_ptr playlist = SourcesModel::indexToDynamicPlaylist( idx ); + if( !playlist.isNull() ) + DynamicPlaylist::remove( playlist ); } } +void +SourceTreeView::renamePlaylist() +{ + edit( m_contextMenuIndex ); +} + + void SourceTreeView::onCustomContextMenu( const QPoint& pos ) { @@ -240,7 +369,7 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) const QRect rect = visualRect( index ); m_dropRect = rect; - if ( SourcesModel::indexType( index ) == 1 ) + if ( SourcesModel::indexType( index ) == SourcesModel::PlaylistSource ) { playlist_ptr playlist = SourcesModel::indexToPlaylist( index ); if ( !playlist.isNull() && playlist->author()->isLocal() ) @@ -277,7 +406,7 @@ SourceTreeView::dropEvent( QDropEvent* event ) if ( index.isValid() ) { - if ( SourcesModel::indexType( index ) == 1 ) + if ( SourcesModel::indexType( index ) == SourcesModel::PlaylistSource ) { playlist_ptr playlist = SourcesModel::indexToPlaylist( index ); if ( !playlist.isNull() && playlist->author()->isLocal() ) @@ -354,23 +483,133 @@ SourceTreeView::drawRow( QPainter* painter, const QStyleOptionViewItem& option, } +QSize +SourceDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + if ( index.data( SourceTreeItem::Type ) == SourcesModel::CollectionSource ) + return QSize( option.rect.width(), 44 ); + else + return QStyledItemDelegate::sizeHint( option, index ); +} + + void SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QStyleOptionViewItem o = option; - o.rect.adjust( 12, 0, 0, 0 ); +#ifdef Q_WS_MAC + QFont savedFont = painter->font(); + QFont smaller = savedFont; + smaller.setPointSize( smaller.pointSize() - 2 ); + painter->setFont( smaller ); + o.font = smaller; +#endif + if ( ( option.state & QStyle::State_Enabled ) == QStyle::State_Enabled ) { o.state = QStyle::State_Enabled; - if ( SourcesModel::indexType( index ) == 1 && - ( option.state & QStyle::State_Selected ) == QStyle::State_Selected ) + if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected ) { o.palette.setColor( QPalette::Text, o.palette.color( QPalette::HighlightedText ) ); } } + + QStyleOptionViewItemV4 o3 = option; + if ( index.data( SourceTreeItem::Type ) != SourcesModel::CollectionSource ) + o3.rect.setX( 0 ); + + QApplication::style()->drawControl( QStyle::CE_ItemViewItem, &o3, painter ); + + if ( index.data( SourceTreeItem::Type ) == SourcesModel::CollectionSource ) + { + painter->save(); - QStyledItemDelegate::paint( painter, option, index.model()->index( 0, 0 ) ); - QStyledItemDelegate::paint( painter, o, index ); + QFont normal = painter->font(); + QFont bold = painter->font(); + bold.setBold( true ); + + SourceTreeItem* sti = SourcesModel::indexToTreeItem( index ); + bool status = !( !sti || sti->source().isNull() || !sti->source()->isOnline() ); + QString tracks; + int figWidth = 0; + + if ( status ) + { + tracks = QString::number( sti->source()->trackCount() ); + figWidth = painter->fontMetrics().width( tracks ); + } + + QRect iconRect = option.rect.adjusted( 4, 6, -option.rect.width() + option.rect.height() - 12 + 4, -6 ); + painter->drawPixmap( iconRect, QPixmap( RESPATH "images/user-avatar.png" ).scaledToHeight( iconRect.height(), Qt::SmoothTransformation ) ); + + if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected ) + { + painter->setPen( o.palette.color( QPalette::HighlightedText ) ); + } + + QRect textRect = option.rect.adjusted( iconRect.width() + 8, 6, -figWidth - 24, 0 ); + if ( status || sti->source().isNull() ) + painter->setFont( bold ); + QString text = painter->fontMetrics().elidedText( index.data().toString(), Qt::ElideRight, textRect.width() ); + painter->drawText( textRect, text ); + + QString desc = status ? sti->source()->textStatus() : tr( "Offline" ); + if ( sti->source().isNull() ) + desc = tr( "All available tracks" ); + if ( status && desc.isEmpty() && !sti->source()->currentTrack().isNull() ) + desc = sti->source()->currentTrack()->artist() + " - " + sti->source()->currentTrack()->track(); + if ( desc.isEmpty() ) + desc = tr( "Online" ); + + textRect = option.rect.adjusted( iconRect.width() + 8, painter->fontMetrics().height() + 10, -figWidth - 24, 0 ); + painter->setFont( normal ); + text = painter->fontMetrics().elidedText( desc, Qt::ElideRight, textRect.width() ); + painter->drawText( textRect, text ); + + if ( status ) + { + painter->setRenderHint( QPainter::Antialiasing ); + + QRect figRect = o.rect.adjusted( o.rect.width() - figWidth - 18, 0, -10, -o.rect.height() + 16 ); + int hd = ( option.rect.height() - figRect.height() ) / 2; + figRect.adjust( 0, hd, 0, hd ); + + QColor figColor( 167, 183, 211 ); + painter->setPen( figColor ); + painter->setBrush( figColor ); + + QPen origpen = painter->pen(); + QPen pen = origpen; + pen.setWidth( 1.0 ); + painter->setPen( pen ); + painter->drawRect( figRect ); + + QPainterPath ppath; + ppath.moveTo( QPoint( figRect.x(), figRect.y() ) ); + ppath.quadTo( QPoint( figRect.x() - 8, figRect.y() + figRect.height() / 2 ), QPoint( figRect.x(), figRect.y() + figRect.height() ) ); + painter->drawPath( ppath ); + ppath.moveTo( QPoint( figRect.x() + figRect.width(), figRect.y() ) ); + ppath.quadTo( QPoint( figRect.x() + figRect.width() + 8, figRect.y() + figRect.height() / 2 ), QPoint( figRect.x() + figRect.width(), figRect.y() + figRect.height() ) ); + painter->drawPath( ppath ); + + painter->setPen( origpen ); + + QTextOption to( Qt::AlignCenter ); + painter->setFont( bold ); + painter->setPen( Qt::white ); + painter->drawText( figRect, tracks, to ); + } + + painter->restore(); + } + else + { + QStyledItemDelegate::paint( painter, o, index ); + } + +#ifdef Q_WS_MAC + painter->setFont( savedFont ); +#endif } diff --git a/src/sourcetree/sourcetreeview.h b/src/sourcetree/sourcetreeview.h index 169f710eb..710c609cb 100644 --- a/src/sourcetree/sourcetreeview.h +++ b/src/sourcetree/sourcetreeview.h @@ -1,14 +1,33 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef SOURCETREEVIEW_H #define SOURCETREEVIEW_H #include #include -#include "tomahawk/source.h" +#include "source.h" class CollectionModel; class PlaylistModel; class SourcesModel; +class SourcesProxyModel; class SourceTreeView : public QTreeView { @@ -17,22 +36,33 @@ Q_OBJECT public: explicit SourceTreeView( QWidget* parent = 0 ); +public slots: + void showOfflineSources(); + void hideOfflineSources(); + signals: void onOnline( const QModelIndex& index ); void onOffline( const QModelIndex& index ); private slots: + void onPlaylistActivated( const Tomahawk::playlist_ptr& playlist ); + void onDynamicPlaylistActivated( const Tomahawk::dynplaylist_ptr& playlist ); + void onCollectionActivated( const Tomahawk::collection_ptr& collection ); + void onSuperCollectionActivated(); + void onTempPageActivated(); + void onItemActivated( const QModelIndex& index ); void onSelectionChanged(); void loadPlaylist(); void deletePlaylist(); - + void renamePlaylist(); + void onCustomContextMenu( const QPoint& pos ); void onSourceOffline( Tomahawk::source_ptr ); protected: - void drawBranches( QPainter* painter, const QRect& rect, const QModelIndex& index ) const {} +// void drawBranches( QPainter* painter, const QRect& rect, const QModelIndex& index ) const {} void drawRow( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; virtual void paintEvent( QPaintEvent* event ); @@ -47,6 +77,7 @@ private: CollectionModel* m_collectionModel; SourcesModel* m_model; + SourcesProxyModel* m_proxyModel; QModelIndex m_contextMenuIndex; QMenu m_playlistMenu; diff --git a/src/tomahawk.protocol b/src/tomahawk.protocol new file mode 100644 index 000000000..3a393aa61 --- /dev/null +++ b/src/tomahawk.protocol @@ -0,0 +1,12 @@ +[Protocol] +exec=/home/leo/kde/tomahawk/build/tomahawk "%u" +protocol=tomahawk +input=none +output=none +helper=true +listing= +reading=false +writing=false +makedir=false +deleting=false + diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index d81b745fd..02d98e1ce 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -1,28 +1,58 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "tomahawk/tomahawkapp.h" +#include "config.h" + #include #include #include #include #include +#include +#include +#include -#include "tomahawk/artist.h" -#include "tomahawk/album.h" -#include "tomahawk/collection.h" +#include "artist.h" +#include "album.h" +#include "collection.h" #include "tomahawk/infosystem.h" #include "database/database.h" #include "database/databasecollection.h" #include "database/databasecommand_collectionstats.h" #include "database/databaseresolver.h" -#include "jabber/jabber.h" +#include "sip/SipHandler.h" +#include "playlist/dynamic/GeneratorFactory.h" +#include "playlist/dynamic/echonest/EchonestGenerator.h" #include "utils/tomahawkutils.h" -#include "xmppbot/xmppbot.h" #include "web/api_v1.h" -#include "scriptresolver.h" +#include "resolvers/scriptresolver.h" +#include "resolvers/qtscriptresolver.h" +#include "sourcelist.h" +#include "shortcuthandler.h" +#include "scanmanager.h" +#include "tomahawksettings.h" -#include "audioengine.h" -#include "controlconnection.h" -#include "tomahawkzeroconf.h" +#include "audio/audioengine.h" +#include "utils/xspfloader.h" + +#include "config.h" #ifndef TOMAHAWK_HEADLESS #include "tomahawkwindow.h" @@ -30,12 +60,20 @@ #include #endif +// should go to a plugin actually +#ifdef GLOOX_FOUND + #include "xmppbot/xmppbot.h" +#endif + +#ifdef Q_WS_MAC +#include "mac/macshortcuthandler.h" +#endif + #include #include -#define LOGFILE TomahawkUtils::appDataDir().filePath( "tomahawk.log" ).toLocal8Bit() +#define LOGFILE TomahawkUtils::appLogDir().filePath( "Tomahawk.log" ).toLocal8Bit() #define LOGFILE_SIZE 1024 * 512 -#include "tomahawksettings.h" using namespace std; ofstream logfile; @@ -102,15 +140,25 @@ using namespace Tomahawk; TomahawkApp::TomahawkApp( int& argc, char *argv[] ) : TOMAHAWK_APPLICATION( argc, argv ) + , m_database( 0 ) , m_audioEngine( 0 ) - , m_zeroconf( 0 ) - , m_settings( 0 ) - , m_nam( 0 ) - , m_proxy( 0 ) + , m_sipHandler( 0 ) + , m_servent( 0 ) + , m_shortcutHandler( 0 ) + , m_scrubFriendlyName( false ) + , m_mainwindow( 0 ) , m_infoSystem( 0 ) { qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); - + + // send the first arg to an already running instance, but don't open twice no matter what + if( ( argc > 1 && sendMessage( argv[ 1 ] ) ) || sendMessage( "" ) ) { + qDebug() << "Sent message, already exists"; + throw runtime_error( "Already Running" ); + } + + connect( this, SIGNAL( messageReceived( QString ) ), this, SLOT( messageReceived( QString ) ) ); + #ifdef TOMAHAWK_HEADLESS m_headless = true; #else @@ -119,98 +167,125 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) setWindowIcon( QIcon( RESPATH "icons/tomahawk-icon-128x128.png" ) ); #endif -#ifndef NO_LIBLASTFM - m_scrobbler = 0; -#endif - qDebug() << "TomahawkApp thread:" << this->thread(); - setOrganizationName( "Tomahawk" ); - setOrganizationDomain( "tomahawk.org" ); - setApplicationName( "Player" ); - setApplicationVersion( "1.0" ); // FIXME: last.fm "tst" auth requires 1.0 version according to docs, will change when we get our own identifier + setOrganizationName( QLatin1String( ORGANIZATION_NAME ) ); + setOrganizationDomain( QLatin1String( ORGANIZATION_DOMAIN ) ); + setApplicationName( QLatin1String( APPLICATION_NAME ) ); + setApplicationVersion( QLatin1String( VERSION ) ); registerMetaTypes(); setupLogfile(); - - m_settings = new TomahawkSettings( this ); + + Echonest::Config::instance()->setAPIKey( "JRIHWEP6GPOER2QQ6" ); + + new TomahawkSettings( this ); m_audioEngine = new AudioEngine; + new ScanManager( this ); + new Pipeline( this ); + + m_servent = new Servent( this ); + connect( m_servent, SIGNAL( ready() ), SLOT( setupSIP() ) ); + + qDebug() << "Init Database."; setupDatabase(); -#ifndef NO_LIBLASTFM - m_scrobbler = new Scrobbler( this ); - m_nam = new lastfm::NetworkAccessManager( this ); + qDebug() << "Init Echonest Factory."; + GeneratorFactory::registerFactory( "echonest", new EchonestFactory ); + + m_scrubFriendlyName = arguments().contains( "--demo" ); + // Register shortcut handler for this platform +#ifdef Q_WS_MAC + m_shortcutHandler = new MacShortcutHandler( this ); + Tomahawk::setShortcutHandler( static_cast( m_shortcutHandler) ); - connect( m_audioEngine, SIGNAL( started( const Tomahawk::result_ptr& ) ), - m_scrobbler, SLOT( trackStarted( const Tomahawk::result_ptr& ) ), Qt::QueuedConnection ); - - connect( m_audioEngine, SIGNAL( paused() ), - m_scrobbler, SLOT( trackPaused() ), Qt::QueuedConnection ); - - connect( m_audioEngine, SIGNAL( resumed() ), - m_scrobbler, SLOT( trackResumed() ), Qt::QueuedConnection ); - - connect( m_audioEngine, SIGNAL( stopped() ), - m_scrobbler, SLOT( trackStopped() ), Qt::QueuedConnection ); -#else - m_nam = new QNetworkAccessManager; + Tomahawk::setApplicationHandler( this ); #endif -#ifndef TOMAHAWK_HEADLESS - if ( !m_headless ) + // Connect up shortcuts + if ( m_shortcutHandler ) { - - m_mainwindow = new TomahawkWindow(); - m_mainwindow->show(); - connect( m_mainwindow, SIGNAL( settingsChanged() ), SIGNAL( settingsChanged() ) ); + connect( m_shortcutHandler, SIGNAL( playPause() ), m_audioEngine, SLOT( playPause() ) ); + connect( m_shortcutHandler, SIGNAL( pause() ), m_audioEngine, SLOT( pause() ) ); + connect( m_shortcutHandler, SIGNAL( stop() ), m_audioEngine, SLOT( stop() ) ); + connect( m_shortcutHandler, SIGNAL( previous() ), m_audioEngine, SLOT( previous() ) ); + connect( m_shortcutHandler, SIGNAL( next() ), m_audioEngine, SLOT( next() ) ); + connect( m_shortcutHandler, SIGNAL( volumeUp() ), m_audioEngine, SLOT( raiseVolume() ) ); + connect( m_shortcutHandler, SIGNAL( volumeDown() ), m_audioEngine, SLOT( lowerVolume() ) ); + connect( m_shortcutHandler, SIGNAL( mute() ), m_audioEngine, SLOT( mute() ) ); } + + qDebug() << "Init InfoSystem."; + m_infoSystem = new Tomahawk::InfoSystem::InfoSystem( this ); + +#ifdef LIBLASTFM_FOUND + qDebug() << "Init Scrobbler."; + m_scrobbler = new Scrobbler( this ); + qDebug() << "Setting NAM."; + TomahawkUtils::setNam( new lastfm::NetworkAccessManager( this ) ); + + connect( m_audioEngine, SIGNAL( started( const Tomahawk::result_ptr& ) ), + m_scrobbler, SLOT( trackStarted( const Tomahawk::result_ptr& ) ), Qt::QueuedConnection ); + + connect( m_audioEngine, SIGNAL( paused() ), + m_scrobbler, SLOT( trackPaused() ), Qt::QueuedConnection ); + + connect( m_audioEngine, SIGNAL( resumed() ), + m_scrobbler, SLOT( trackResumed() ), Qt::QueuedConnection ); + + connect( m_audioEngine, SIGNAL( stopped() ), + m_scrobbler, SLOT( trackStopped() ), Qt::QueuedConnection ); +#else + qDebug() << "Setting NAM."; + TomahawkUtils::setNam( new QNetworkAccessManager ); #endif // Set up proxy - if( m_settings->proxyType() != QNetworkProxy::NoProxy && !m_settings->proxyHost().isEmpty() ) + //FIXME: This overrides the lastfm proxy above? + if( TomahawkSettings::instance()->proxyType() != QNetworkProxy::NoProxy && + !TomahawkSettings::instance()->proxyHost().isEmpty() ) { qDebug() << "Setting proxy to saved values"; - m_proxy = new QNetworkProxy( static_cast(m_settings->proxyType()), m_settings->proxyHost(), m_settings->proxyPort(), m_settings->proxyUsername(), m_settings->proxyPassword() ); - qDebug() << "Proxy type = " << QString::number( static_cast(m_proxy->type()) ); - qDebug() << "Proxy host = " << m_proxy->hostName(); - QNetworkAccessManager* nam = TomahawkApp::instance()->nam(); - nam->setProxy( *m_proxy ); + TomahawkUtils::setProxy( new QNetworkProxy( static_cast(TomahawkSettings::instance()->proxyType()), TomahawkSettings::instance()->proxyHost(), TomahawkSettings::instance()->proxyPort(), TomahawkSettings::instance()->proxyUsername(), TomahawkSettings::instance()->proxyPassword() ) ); + qDebug() << "Proxy type =" << QString::number( static_cast(TomahawkUtils::proxy()->type()) ); + qDebug() << "Proxy host =" << TomahawkUtils::proxy()->hostName(); + TomahawkUtils::nam()->setProxy( *TomahawkUtils::proxy() ); } else - m_proxy = new QNetworkProxy( QNetworkProxy::NoProxy ); + TomahawkUtils::setProxy( new QNetworkProxy( QNetworkProxy::NoProxy ) ); - QNetworkProxy::setApplicationProxy( *m_proxy ); + QNetworkProxy::setApplicationProxy( *TomahawkUtils::proxy() ); - m_infoSystem = new Tomahawk::InfoSystem::InfoSystem( this ); - - boost::function(result_ptr)> fac = - boost::bind( &TomahawkApp::httpIODeviceFactory, this, _1 ); - this->registerIODeviceFactory( "http", fac ); - - setupPipeline(); - initLocalCollection(); - startServent(); - //loadPlugins(); - - if( arguments().contains( "--http" ) || settings()->value( "network/http", true ).toBool() ) - startHTTP(); - - if( !arguments().contains("--nojabber") ) setupJabber(); - m_xmppBot = new XMPPBot( this ); - - if ( !arguments().contains( "--nozeroconf" ) ) - { - // advertise our servent on the LAN - m_zeroconf = new TomahawkZeroconf( m_servent.port(), this ); - connect( m_zeroconf, SIGNAL( tomahawkHostFound( const QString&, int, const QString&, const QString& ) ), - SLOT( lanHostFound( const QString&, int, const QString&, const QString& ) ) ); - m_zeroconf->advertise(); - } + qDebug() << "Init SIP system."; + m_sipHandler = new SipHandler( this ); #ifndef TOMAHAWK_HEADLESS - if ( !m_settings->hasScannerPath() ) + if ( !m_headless ) + { + qDebug() << "Init MainWindow."; + m_mainwindow = new TomahawkWindow(); + m_mainwindow->setWindowTitle( "Tomahawk" ); + m_mainwindow->show(); + } +#endif + + qDebug() << "Init Local Collection."; + initLocalCollection(); + qDebug() << "Init Pipeline."; + setupPipeline(); + qDebug() << "Init Servent."; + startServent(); + + if( arguments().contains( "--http" ) || TomahawkSettings::instance()->value( "network/http", true ).toBool() ) + { + qDebug() << "Init HTTP Server."; + startHTTP(); + } + +#ifndef TOMAHAWK_HEADLESS + if ( !TomahawkSettings::instance()->hasScannerPath() ) { m_mainwindow->showSettingsDialog(); } - #endif +#endif } @@ -218,21 +293,15 @@ TomahawkApp::~TomahawkApp() { qDebug() << Q_FUNC_INFO; - if ( !m_jabber.isNull() ) - { - m_jabber.clear(); - } + delete m_sipHandler; + delete m_servent; #ifndef TOMAHAWK_HEADLESS delete m_mainwindow; delete m_audioEngine; #endif - delete m_zeroconf; - delete m_db; - - // always last thing, incase other objects save state on exit: - delete m_settings; + delete m_database; } @@ -249,13 +318,6 @@ TomahawkApp::audioControls() { return m_mainwindow->audioControls(); } - - -PlaylistManager* -TomahawkApp::playlistManager() -{ - return m_mainwindow->playlistManager(); -} #endif @@ -270,30 +332,40 @@ TomahawkApp::registerMetaTypes() qRegisterMetaType< QList >("QList"); qRegisterMetaType< Connection* >("Connection*"); qRegisterMetaType< QAbstractSocket::SocketError >("QAbstractSocket::SocketError"); + qRegisterMetaType< QTcpSocket* >("QTcpSocket*"); qRegisterMetaType< QSharedPointer >("QSharedPointer"); qRegisterMetaType< QFileInfo >("QFileInfo"); + qRegisterMetaType< QHostAddress >("QHostAddress"); qRegisterMetaType< QMap >("QMap"); qRegisterMetaType< QMap< QString, plentry_ptr > >("QMap< QString, plentry_ptr >"); qRegisterMetaType< QHash< QString, QMap > >("QHash< QString, QMap >"); - + + qRegisterMetaType< GeneratorMode>("GeneratorMode"); + qRegisterMetaType("Tomahawk::GeneratorMode"); // Extra definition for namespaced-versions of signals/slots required + qRegisterMetaType< Tomahawk::source_ptr >("Tomahawk::source_ptr"); qRegisterMetaType< Tomahawk::collection_ptr >("Tomahawk::collection_ptr"); qRegisterMetaType< Tomahawk::result_ptr >("Tomahawk::result_ptr"); + qRegisterMetaType< Tomahawk::query_ptr >("Tomahawk::query_ptr"); qRegisterMetaType< Tomahawk::source_ptr >("Tomahawk::source_ptr"); + qRegisterMetaType< Tomahawk::dyncontrol_ptr >("Tomahawk::dyncontrol_ptr"); + qRegisterMetaType< Tomahawk::geninterface_ptr >("Tomahawk::geninterface_ptr"); qRegisterMetaType< QList >("QList"); + qRegisterMetaType< QList >("QList"); + qRegisterMetaType< QList >("QList"); + qRegisterMetaType< QList >("QList"); qRegisterMetaType< QList >("QList"); qRegisterMetaType< QList >("QList"); qRegisterMetaType< QList >("QList"); qRegisterMetaType< QList >("QList"); qRegisterMetaType< QList >("QList"); + qRegisterMetaType< QList >("QList"); qRegisterMetaType< QMap< QString, Tomahawk::plentry_ptr > >("QMap< QString, Tomahawk::plentry_ptr >"); qRegisterMetaType< Tomahawk::PlaylistRevision >("Tomahawk::PlaylistRevision"); + qRegisterMetaType< Tomahawk::DynamicPlaylistRevision >("Tomahawk::DynamicPlaylistRevision"); qRegisterMetaType< Tomahawk::QID >("Tomahawk::QID"); - qRegisterMetaType< QTcpSocket* >("QTcpSocket*"); - #ifndef TOMAHAWK_HEADLESS qRegisterMetaType< AudioErrorCode >("AudioErrorCode"); - #endif } @@ -311,18 +383,8 @@ TomahawkApp::setupDatabase() } qDebug() << "Using database:" << dbpath; - m_db = new Database( dbpath, this ); - m_pipeline.databaseReady(); -} - - -void -TomahawkApp::lanHostFound( const QString& host, int port, const QString& name, const QString& nodeid ) -{ - qDebug() << "Found LAN host:" << host << port << nodeid; - - if ( !m_servent.connectedToSession( nodeid ) ) - m_servent.connectToPeer( host, port, "whitelist", name, nodeid ); + m_database = new Database( dbpath, this ); + Pipeline::instance()->databaseReady(); } @@ -346,352 +408,136 @@ void TomahawkApp::setupPipeline() { // setup resolvers for local content, and (cached) remote collection content - m_pipeline.addResolver( new DatabaseResolver( true, 100 ) ); - m_pipeline.addResolver( new DatabaseResolver( false, 90 ) ); + Pipeline::instance()->addResolver( new DatabaseResolver( 100 ) ); -// new ScriptResolver("/home/rj/src/tomahawk-core/contrib/magnatune/magnatune-resolver.php"); + // load script resolvers + foreach( QString resolver, TomahawkSettings::instance()->scriptResolvers() ) + addScriptResolver( resolver ); +} + + +void +TomahawkApp::addScriptResolver( const QString& path ) +{ + const QFileInfo fi( path ); + if ( fi.suffix() == "js" || fi.suffix() == "script" ) + m_scriptResolvers << new QtScriptResolver( path ); + else + m_scriptResolvers << new ScriptResolver( path ); +} + + +void +TomahawkApp::removeScriptResolver( const QString& path ) +{ + foreach( Tomahawk::ExternalResolver* r, m_scriptResolvers ) + { + if( r->filePath() == path ) + { + m_scriptResolvers.removeAll( r ); + connect( r, SIGNAL( finished() ), r, SLOT( deleteLater() ) ); + r->stop(); + return; + } + } } void TomahawkApp::initLocalCollection() { - source_ptr src( new Source( "My Collection" ) ); + source_ptr src( new Source( 0, "My Collection" ) ); collection_ptr coll( new DatabaseCollection( src ) ); src->addCollection( coll ); - this->sourcelist().add( src ); - - boost::function(result_ptr)> fac = - boost::bind( &TomahawkApp::localFileIODeviceFactory, this, _1 ); - this->registerIODeviceFactory( "file", fac ); + SourceList::instance()->setLocal( src ); +// src->collection()->tracks(); // to make the stats signal be emitted by our local source // this will update the sidebar, etc. DatabaseCommand_CollectionStats* cmd = new DatabaseCommand_CollectionStats( src ); connect( cmd, SIGNAL( done( const QVariantMap& ) ), src.data(), SLOT( setStats( const QVariantMap& ) ), Qt::QueuedConnection ); - database()->enqueue( QSharedPointer( cmd ) ); + Database::instance()->enqueue( QSharedPointer( cmd ) ); } void TomahawkApp::startServent() { - bool upnp = arguments().contains( "--upnp" ) || settings()->value( "network/upnp", true ).toBool(); - if ( !m_servent.startListening( QHostAddress( QHostAddress::Any ), upnp ) ) + bool upnp = !arguments().contains( "--noupnp" ) && TomahawkSettings::instance()->value( "network/upnp", true ).toBool() && !TomahawkSettings::instance()->preferStaticHostPort(); + int port = TomahawkSettings::instance()->externalPort(); + if ( !Servent::instance()->startListening( QHostAddress( QHostAddress::Any ), upnp, port ) ) { qDebug() << "Failed to start listening with servent"; exit( 1 ); } - - //QString key = m_servent.createConnectionKey(); - //qDebug() << "Generated an offer key: " << key; - - boost::function(result_ptr)> fac = - boost::bind( &Servent::remoteIODeviceFactory, &m_servent, _1 ); - - this->registerIODeviceFactory( "servent", fac ); } - void -TomahawkApp::loadPlugins() -{ - // look in same dir as executable for plugins - QDir dir( TomahawkApp::instance()->applicationDirPath() ); - QStringList filters; - filters << "*.so" << "*.dll" << "*.dylib"; - - QStringList files = dir.entryList( filters ); - foreach( const QString& filename, files ) - { - qDebug() << "Attempting to load" << QString( "%1/%2" ).arg( dir.absolutePath() ).arg( filename ); - - QPluginLoader loader( dir.absoluteFilePath( filename ) ); - if ( QObject* inst = loader.instance() ) - { - TomahawkPlugin* pluginst = qobject_cast(inst); - if ( !pluginst ) - continue; - - PluginAPI* api = new PluginAPI( this->pipeline() ); - TomahawkPlugin* plugin = pluginst->factory( api ); - qDebug() << "Loaded Plugin:" << plugin->name(); - qDebug() << plugin->description(); - m_plugins.append( plugin ); - - // plugins responsibility to register itself as a resolver/collection - // all we need to do is create an instance of it. - } - else - { - qDebug() << "PluginLoader failed to create instance:" << filename << " Err:" << loader.errorString(); - } - } -} - - -void -TomahawkApp::setupJabber() -{ - qDebug() << Q_FUNC_INFO; - if ( !m_jabber.isNull() ) - return; - if ( !m_settings->value( "jabber/autoconnect", true ).toBool() ) - return; - - QString jid = m_settings->value( "jabber/username" ).toString(); - QString server = m_settings->value( "jabber/server" ).toString(); - QString password = m_settings->value( "jabber/password" ).toString(); - unsigned int port = m_settings->value( "jabber/port", 5222 ).toUInt(); - - // gtalk check - if( server.isEmpty() && ( jid.contains("@gmail.com") || jid.contains("@googlemail.com") ) ) - { - qDebug() << "Setting jabber server to talk.google.com"; - server = "talk.google.com"; - } - - if ( port < 1 || port > 65535 || jid.isEmpty() || password.isEmpty() ) - { - qDebug() << "Jabber credentials look wrong, not connecting"; - return; - } - - m_jabber = QSharedPointer( new Jabber( jid, password, server, port ) ); - - connect( m_jabber.data(), SIGNAL( peerOnline( QString ) ), SLOT( jabberPeerOnline( QString ) ) ); - connect( m_jabber.data(), SIGNAL( peerOffline( QString ) ), SLOT( jabberPeerOffline( QString ) ) ); - connect( m_jabber.data(), SIGNAL( msgReceived( QString, QString ) ), SLOT( jabberMessage( QString, QString ) ) ); - - connect( m_jabber.data(), SIGNAL( connected() ), SLOT( jabberConnected() ) ); - connect( m_jabber.data(), SIGNAL( disconnected() ), SLOT( jabberDisconnected() ) ); - connect( m_jabber.data(), SIGNAL( authError( int, QString ) ), SLOT( jabberAuthError( int, QString ) ) ); - - m_jabber->setProxy( m_proxy ); - m_jabber->start(); -} - - -void -TomahawkApp::reconnectJabber() -{ - m_jabber.clear(); - setupJabber(); -} - - -void -TomahawkApp::jabberAuthError( int code, const QString& msg ) -{ - qWarning() << "Failed to connect to jabber" << code << msg; - -#ifndef TOMAHAWK_HEADLESS - if( m_mainwindow ) - { - m_mainwindow->setWindowTitle( QString("Tomahawk [jabber: %1, portfwd: %2]") - .arg( "AUTH_ERROR" ) - .arg( (servent().externalPort() > 0) ? QString( "YES:%1" ).arg(servent().externalPort()) :"NO" ) ); - - if ( code == gloox::ConnAuthenticationFailed ) - { - QMessageBox::warning( m_mainwindow, - "Jabber Auth Error", - QString("Error connecting to Jabber (%1) %2").arg(code).arg(msg), - QMessageBox::Ok ); - } - } -#endif - - if ( code != gloox::ConnAuthenticationFailed ) - QTimer::singleShot( 10000, this, SLOT( reconnectJabber() ) ); -} - - -void -TomahawkApp::jabberConnected() +TomahawkApp::setupSIP() { qDebug() << Q_FUNC_INFO; -#ifndef TOMAHAWK_HEADLESS - if( m_mainwindow ) + //FIXME: jabber autoconnect is really more, now that there is sip -- should be renamed and/or split out of jabber-specific settings + if( !arguments().contains( "--nosip" ) && TomahawkSettings::instance()->jabberAutoConnect() ) { - m_mainwindow->setWindowTitle( QString("Tomahawk [jabber: %1, portfwd: %2]") - .arg( "CONNECTED" ) - .arg( (servent().externalPort() > 0) ? QString( "YES:%1" ).arg(servent().externalPort()):"NO" ) ); + #ifdef GLOOX_FOUND + m_xmppBot = new XMPPBot( this ); + #endif + + qDebug() << "Connecting SIP classes"; + m_sipHandler->connectPlugins( true ); +// m_sipHandler->setProxy( *TomahawkUtils::proxy() ); } +} + + +void +TomahawkApp::activate() +{ +#ifndef TOMAHAWK_HEADLESS + mainWindow()->show(); #endif } -void -TomahawkApp::jabberDisconnected() +bool +TomahawkApp::loadUrl( const QString& url ) { - qDebug() << Q_FUNC_INFO; - -#ifndef TOMAHAWK_HEADLESS - if( m_mainwindow ) - { - m_mainwindow->setWindowTitle( QString("Tomahawk [jabber: %1, portfwd: %2]") - .arg( "DISCONNECTED" ) - .arg( (servent().externalPort() > 0) ? QString( "YES:%1" ).arg(servent().externalPort()):"NO" ) ); + if( url.contains( "tomahawk://" ) ) { + QString cmd = url.mid( 11 ); + qDebug() << "tomahawk!s" << cmd; + if( cmd.startsWith( "load/?" ) ) { + cmd = cmd.mid( 6 ); + qDebug() << "loading.." << cmd; + if( cmd.startsWith( "xspf=" ) ) { + XSPFLoader* l = new XSPFLoader( true, this ); + qDebug() << "Loading spiff:" << cmd.mid( 5 ); + l->load( QUrl( cmd.mid( 5 ) ) ); + } + } + } else { + QFile f( url ); + QFileInfo info( f ); + if( f.exists() && info.suffix() == "xspf" ) { + XSPFLoader* l = new XSPFLoader( true, this ); + qDebug() << "Loading spiff:" << url; + l->load( QUrl( url ) ); + } } -#endif + return true; } -void -TomahawkApp::jabberPeerOnline( const QString& jid ) +void +TomahawkApp::messageReceived( const QString& msg ) { -// qDebug() << Q_FUNC_INFO; -// qDebug() << "Jabber Peer online:" << jid; - - QVariantMap m; - if( m_servent.visibleExternally() ) - { - QString key = uuid(); - ControlConnection* conn = new ControlConnection( &m_servent ); - - const QString& nodeid = APP->nodeID(); - conn->setName( jid.left( jid.indexOf( "/" ) ) ); - conn->setId( nodeid ); - - // FIXME strip /resource, but we should use a UID per database install - //QString uniqname = jid.left( jid.indexOf("/") ); - //conn->setName( uniqname ); //FIXME - - // FIXME: - //QString ouruniqname = m_settings->value( "jabber/username" ).toString() - // .left( m_settings->value( "jabber/username" ).toString().indexOf("/") ); - - m_servent.registerOffer( key, conn ); - m["visible"] = true; - m["ip"] = m_servent.externalAddress().toString(); - m["port"] = m_servent.externalPort(); - m["key"] = key; - m["uniqname"] = nodeid; - - qDebug() << "Asking them to connect to us:" << m; - } - else - { - m["visible"] = false; - qDebug() << "We are not visible externally:" << m; - } - - QJson::Serializer ser; - QByteArray ba = ser.serialize( m ); - m_jabber->sendMsg( jid, QString::fromAscii( ba ) ); -} - - -void -TomahawkApp::jabberPeerOffline( const QString& jid ) -{ -// qDebug() << Q_FUNC_INFO; -// qDebug() << "Jabber Peer offline:" << jid; -} - - -void -TomahawkApp::jabberMessage( const QString& from, const QString& msg ) -{ - qDebug() << Q_FUNC_INFO; - qDebug() << "Jabber Message:" << from << msg; - - QJson::Parser parser; - bool ok; - QVariant v = parser.parse( msg.toAscii(), &ok ); - if ( !ok || v.type() != QVariant::Map ) - { - qDebug() << "Invalid JSON in XMPP msg"; + qDebug() << "MESSAGE RECEIVED" << msg; + if( msg.isEmpty() ) { return; } - - QVariantMap m = v.toMap(); - /* - If only one party is externally visible, connection is obvious - If both are, peer with lowest IP address initiates the connection. - This avoids dupe connections. - */ - if ( m.value( "visible" ).toBool() ) - { - if( !m_servent.visibleExternally() || - m_servent.externalAddress().toString() <= m.value( "ip" ).toString() ) - { - qDebug() << "Initiate connection to" << from; - m_servent.connectToPeer( m.value( "ip" ).toString(), - m.value( "port" ).toInt(), - m.value( "key" ).toString(), - from, - m.value( "uniqname" ).toString() ); - } - else - { - qDebug() << Q_FUNC_INFO << "They should be conecting to us..."; - } - } - else - { - qDebug() << Q_FUNC_INFO << "They are not visible, doing nothing atm"; - if ( m_servent.visibleExternally() ) - jabberPeerOnline( from ); // HACK FIXME - } + + loadUrl( msg ); } - -void -TomahawkApp::registerIODeviceFactory( const QString &proto, boost::function(Tomahawk::result_ptr)> fac ) -{ - m_iofactories.insert( proto, fac ); - qDebug() << "Registered IODevice Factory for" << proto; -} - - -QSharedPointer -TomahawkApp::getIODeviceForUrl( const Tomahawk::result_ptr& result ) -{ - qDebug() << Q_FUNC_INFO << thread(); - QSharedPointer sp; - - QRegExp rx( "^([a-zA-Z0-9]+)://(.+)$" ); - if ( rx.indexIn( result->url() ) == -1 ) - return sp; - - const QString proto = rx.cap( 1 ); - //const QString urlpart = rx.cap( 2 ); - if ( !m_iofactories.contains( proto ) ) - return sp; - - return m_iofactories.value( proto )( result ); -} - - -QSharedPointer -TomahawkApp::localFileIODeviceFactory( const Tomahawk::result_ptr& result ) -{ - // ignore "file://" at front of url - QFile * io = new QFile( result->url().mid( QString( "file://" ).length() ) ); - if ( io ) - io->open( QIODevice::ReadOnly ); - - return QSharedPointer( io ); -} - - -QSharedPointer -TomahawkApp::httpIODeviceFactory( const Tomahawk::result_ptr& result ) -{ - qDebug() << Q_FUNC_INFO << result->url(); - QNetworkRequest req( result->url() ); - QNetworkReply* reply = APP->nam()->get( req ); - return QSharedPointer( reply ); -} - - -const QString& -TomahawkApp::nodeID() const -{ - return m_db->dbid(); -} diff --git a/src/tomahawkapp_macdelegate.h b/src/tomahawkapp_macdelegate.h new file mode 100644 index 000000000..01ef7a401 --- /dev/null +++ b/src/tomahawkapp_macdelegate.h @@ -0,0 +1,43 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#import + +#include "config.h" + +// this file copied and inspired by mac_startup.* in clementine player, +// copyright David Sansome 2010 +namespace Tomahawk { + class PlatformInterface; +} + +#ifdef SNOW_LEOPARD +@interface AppDelegate : NSObject { +#else +@interface AppDelegate : NSObject { +#endif + Tomahawk::PlatformInterface* application_handler_; + //NSMenu* dock_menu_; +} + +- (id) initWithHandler: (Tomahawk::PlatformInterface*)handler; +// NSApplicationDelegate +- (BOOL) applicationShouldHandleReopen: (NSApplication*)app hasVisibleWindows:(BOOL)flag; +//- (NSMenu*) applicationDockMenu: (NSApplication*)sender; +//- (void) setDockMenu: (NSMenu*)menu; +@end diff --git a/src/tomahawksettings.cpp b/src/tomahawksettings.cpp deleted file mode 100644 index d2c8441d0..000000000 --- a/src/tomahawksettings.cpp +++ /dev/null @@ -1,383 +0,0 @@ -#include "tomahawk/tomahawkapp.h" -#include "tomahawksettings.h" - -#ifndef TOMAHAWK_HEADLESS - #include - #include "settingsdialog.h" -#endif - -#include -#include - - -TomahawkSettings::TomahawkSettings( QObject* parent ) - : QSettings( parent ) -{ - #ifndef TOMAHAWK_HEADLESS - if( !contains( "configversion") ) - { - setValue( "configversion", SettingsDialog::VERSION ); - } - else if( value( "configversion" ).toUInt() != SettingsDialog::VERSION ) - { - qDebug() << "Config version outdated, old:" << value( "configversion" ).toUInt() - << "new:" << SettingsDialog::VERSION - << "Doing upgrade, if any..."; - - // insert upgrade code here as required - setValue( "configversion", SettingsDialog::VERSION ); - } - #endif -} - - -TomahawkSettings::~TomahawkSettings() -{ -} - - -QString -TomahawkSettings::scannerPath() const -{ - #ifndef TOMAHAWK_HEADLESS - return value( "scannerpath", QDesktopServices::storageLocation( QDesktopServices::MusicLocation ) ).toString(); - #else - return value( "scannerpath", "" ).toString(); - #endif -} - - -void -TomahawkSettings::setScannerPath( const QString& path ) -{ - setValue( "scannerpath", path ); -} - - -bool -TomahawkSettings::hasScannerPath() const -{ - return contains( "scannerpath" ); -} - - -bool -TomahawkSettings::httpEnabled() const -{ - return value( "network/http", true ).toBool(); -} - - -void -TomahawkSettings::setHttpEnabled( bool enable ) -{ - setValue( "network/http", enable ); -} - - -QString -TomahawkSettings::proxyHost() const -{ - return value( "network/proxy/host", QString() ).toString(); -} - - -void -TomahawkSettings::setProxyHost( const QString& host ) -{ - setValue( "network/proxy/host", host ); -} - - -qulonglong -TomahawkSettings::proxyPort() const -{ - return value( "network/proxy/port", 1080 ).toULongLong(); -} - - -void -TomahawkSettings::setProxyPort( const qulonglong port ) -{ - setValue( "network/proxy/port", port ); -} - - -QString -TomahawkSettings::proxyUsername() const -{ - return value( "network/proxy/username", QString() ).toString(); -} - - -void -TomahawkSettings::setProxyUsername( const QString& username ) -{ - setValue( "network/proxy/username", username ); -} - - -QString -TomahawkSettings::proxyPassword() const -{ - return value( "network/proxy/password", QString() ).toString(); -} - - -void -TomahawkSettings::setProxyPassword( const QString& password ) -{ - setValue( "network/proxy/password", password ); -} - - -int -TomahawkSettings::proxyType() const -{ - return value( "network/proxy/type", 0 ).toInt(); -} - - -void -TomahawkSettings::setProxyType( const int type ) -{ - setValue( "network/proxy/type", type ); -} - - -QByteArray -TomahawkSettings::mainWindowGeometry() const -{ - return value( "ui/mainwindow/geometry" ).toByteArray(); -} - - -void -TomahawkSettings::setMainWindowGeometry( const QByteArray& geom ) -{ - setValue( "ui/mainwindow/geometry", geom ); -} - - -QByteArray -TomahawkSettings::mainWindowState() const -{ - return value( "ui/mainwindow/state" ).toByteArray(); -} - - -void -TomahawkSettings::setMainWindowState( const QByteArray& state ) -{ - setValue( "ui/mainwindow/state", state ); -} - - -QList -TomahawkSettings::playlistColumnSizes() const -{ - return value( "ui/playlist/columnSize" ).toList(); -} - - -void -TomahawkSettings::setPlaylistColumnSizes( const QList& cols ) -{ - setValue( "ui/playlist/geometry", cols ); -} - - -bool -TomahawkSettings::jabberAutoConnect() const -{ - return value( "jabber/autoconnect", true ).toBool(); -} - - -void -TomahawkSettings::setJabberAutoConnect( bool autoconnect ) -{ - setValue( "jabber/autoconnect", autoconnect ); -} - - -int -TomahawkSettings::jabberPort() const -{ - return value( "jabber/port", 5222 ).toInt(); -} - - -void -TomahawkSettings::setJabberPort( int port ) -{ - setValue( "jabber/port", port ); -} - - -QString -TomahawkSettings::jabberServer() const -{ - return value( "jabber/server" ).toString(); -} - - -void -TomahawkSettings::setJabberServer( const QString& server ) -{ - setValue( "jabber/server", server ); -} - - -QString -TomahawkSettings::jabberUsername() const -{ - return value( "jabber/username" ).toString(); -} - - -void -TomahawkSettings::setJabberUsername( const QString& username ) -{ - setValue( "jabber/username", username ); -} - - -QString -TomahawkSettings::jabberPassword() const -{ - return value( "jabber/password" ).toString(); -} - - -void -TomahawkSettings::setJabberPassword( const QString& pw ) -{ - setValue( "jabber/password", pw ); -} - - -bool -TomahawkSettings::upnpEnabled() const -{ - return value( "network/upnp", true ).toBool(); -} - - -void -TomahawkSettings::setUPnPEnabled( bool enable ) -{ - setValue( "network/upnp", enable ); -} - - -QString -TomahawkSettings::lastFmPassword() const -{ - return value( "lastfm/password" ).toString(); -} - - -void -TomahawkSettings::setLastFmPassword( const QString& password ) -{ - setValue( "lastfm/password", password ); -} - - -QByteArray -TomahawkSettings::lastFmSessionKey() const -{ - return value( "lastfm/sessionkey" ).toByteArray(); -} - - -void -TomahawkSettings::setLastFmSessionKey( const QByteArray& key ) -{ - setValue( "lastfm/sessionkey", key ); -} - - -QString -TomahawkSettings::lastFmUsername() const -{ - return value( "lastfm/username" ).toString(); -} - - -void -TomahawkSettings::setLastFmUsername( const QString& username ) -{ - setValue( "lastfm/username", username ); -} - - -bool -TomahawkSettings::scrobblingEnabled() const -{ - return value( "lastfm/enablescrobbling", false ).toBool(); -} - - -void -TomahawkSettings::setScrobblingEnabled( bool enable ) -{ - setValue( "lastfm/enablescrobbling", enable ); -} - - -QString -TomahawkSettings::xmppBotServer() const -{ - return value( "xmppBot/server", QString() ).toString(); -} - - -void -TomahawkSettings::setXmppBotServer( const QString& server ) -{ - setValue( "xmppBot/server", server ); -} - - -QString -TomahawkSettings::xmppBotJid() const -{ - return value( "xmppBot/jid", QString() ).toString(); -} - - -void -TomahawkSettings::setXmppBotJid( const QString& component ) -{ - setValue( "xmppBot/jid", component ); -} - - -QString -TomahawkSettings::xmppBotPassword() const -{ - return value( "xmppBot/password", QString() ).toString(); -} - - -void -TomahawkSettings::setXmppBotPassword( const QString& password ) -{ - setValue( "xmppBot/password", password ); -} - - -int -TomahawkSettings::xmppBotPort() const -{ - return value( "xmppBot/port", -1 ).toInt(); -} - - -void -TomahawkSettings::setXmppBotPort( const int port ) -{ - setValue( "xmppBot/port", -1 ); -} diff --git a/src/tomahawksettings.h b/src/tomahawksettings.h deleted file mode 100644 index fa7ac3b0a..000000000 --- a/src/tomahawksettings.h +++ /dev/null @@ -1,96 +0,0 @@ -#ifndef TOMAHAWK_SETTINGS_H -#define TOMAHAWK_SETTINGS_h - -#include - -/** - * Convenience wrapper around QSettings for tomahawk-specific config - */ -class TomahawkSettings : public QSettings -{ - Q_OBJECT -public: - explicit TomahawkSettings(QObject* parent = 0); - virtual ~TomahawkSettings(); - - /// General settings - QString scannerPath() const; /// QDesktopServices::MusicLocation by default - void setScannerPath( const QString& path ); - bool hasScannerPath() const; - - /// UI settings - QByteArray mainWindowGeometry() const; - void setMainWindowGeometry( const QByteArray& geom ); - - QByteArray mainWindowState() const; - void setMainWindowState( const QByteArray& state ); - - QList playlistColumnSizes() const; - void setPlaylistColumnSizes( const QList& cols ); - - /// Jabber settings - bool jabberAutoConnect() const; /// true by default - void setJabberAutoConnect( bool autoconnect = false ); - - QString jabberUsername() const; - void setJabberUsername( const QString& username ); - - QString jabberPassword() const; - void setJabberPassword( const QString& pw ); - - QString jabberServer() const; - void setJabberServer( const QString& server ); - - int jabberPort() const; // default is 5222 - void setJabberPort( int port ); - - /// Network settings - bool httpEnabled() const; /// true by default - void setHttpEnabled( bool enable ); - - bool upnpEnabled() const; /// true by default - void setUPnPEnabled( bool enable ); - - QString proxyHost() const; - void setProxyHost( const QString &host ); - - qulonglong proxyPort() const; - void setProxyPort( const qulonglong port ); - - QString proxyUsername() const; - void setProxyUsername( const QString &username ); - - QString proxyPassword() const; - void setProxyPassword( const QString &password ); - - int proxyType() const; - void setProxyType( const int type ); - - /// Last.fm settings - bool scrobblingEnabled() const; /// false by default - void setScrobblingEnabled( bool enable ); - - QString lastFmUsername() const; - void setLastFmUsername( const QString& username ); - - QString lastFmPassword() const; - void setLastFmPassword( const QString& password ); - - QByteArray lastFmSessionKey() const; - void setLastFmSessionKey( const QByteArray& key ); - - /// XMPP Component Settings - QString xmppBotServer() const; - void setXmppBotServer( const QString &server ); - - QString xmppBotJid() const; - void setXmppBotJid( const QString &component ); - - QString xmppBotPassword() const; - void setXmppBotPassword( const QString &password ); - - int xmppBotPort() const; - void setXmppBotPort( const int port ); -}; - -#endif diff --git a/src/tomahawktrayicon.cpp b/src/tomahawktrayicon.cpp new file mode 100644 index 000000000..e49a813ef --- /dev/null +++ b/src/tomahawktrayicon.cpp @@ -0,0 +1,177 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "tomahawktrayicon.h" + +#include + +#include "tomahawk/tomahawkapp.h" +#include "artist.h" + +#include "audio/audioengine.h" +#include "tomahawkwindow.h" + + +TomahawkTrayIcon::TomahawkTrayIcon( QObject* parent ) + : QSystemTrayIcon( parent ) + , m_currentAnimationFrame( 0 ) +{ + QIcon icon( RESPATH "icons/tomahawk-icon-128x128.png" ); + setIcon( icon ); + + refreshToolTip(); + + m_contextMenu = new QMenu(); + setContextMenu( m_contextMenu ); + + m_playAction = m_contextMenu->addAction( tr( "Play" ) ); + m_pauseAction = m_contextMenu->addAction( tr( "Pause" ) ); + m_stopAction = m_contextMenu->addAction( tr( "Stop" ) ); + m_contextMenu->addSeparator(); + m_prevAction = m_contextMenu->addAction( tr( "Previous Track" ) ); + m_nextAction = m_contextMenu->addAction( tr( "Next Track" ) ); + m_contextMenu->addSeparator(); + m_quitAction = m_contextMenu->addAction( tr( "Quit" ) ); + + connect( AudioEngine::instance(), SIGNAL( loading( Tomahawk::result_ptr ) ), SLOT( setResult( Tomahawk::result_ptr ) ) ); + + connect( m_playAction, SIGNAL( triggered() ), AudioEngine::instance(), SLOT( play() ) ); + connect( m_pauseAction, SIGNAL( triggered() ), AudioEngine::instance(), SLOT( pause() ) ); + connect( m_stopAction, SIGNAL( triggered() ), AudioEngine::instance(), SLOT( stop() ) ); + connect( m_prevAction, SIGNAL( triggered() ), AudioEngine::instance(), SLOT( previous() ) ); + connect( m_nextAction, SIGNAL( triggered() ), AudioEngine::instance(), SLOT( next() ) ); + connect( m_quitAction, SIGNAL( triggered() ), (QObject*)APP, SLOT( quit() ) ); + + connect( &m_animationTimer, SIGNAL( timeout() ), SLOT( onAnimationTimer() ) ); + connect( this, SIGNAL( activated( QSystemTrayIcon::ActivationReason ) ), SLOT( onActivated( QSystemTrayIcon::ActivationReason ) ) ); + + show(); +} + + +TomahawkTrayIcon::~TomahawkTrayIcon() +{ + delete m_contextMenu; +} + + +void +TomahawkTrayIcon::setResult( const Tomahawk::result_ptr& result ) +{ + m_currentTrack = result; + refreshToolTip(); +} + + +void +TomahawkTrayIcon::refreshToolTip() +{ + #ifdef Q_WS_MAC + // causes issues with OS X menubar, also none + // of the other OS X menubar icons have a tooltip + return; + #endif + + QString tip; + if ( !m_currentTrack.isNull() ) + { + tip = m_currentTrack->artist()->name() + " " + QChar( 8211 ) /*en dash*/ + " " + m_currentTrack->track(); + } + else + { + tip = tr( "Currently not playing." ); + } + + #ifdef WIN32 + // Good old crappy Win32 + tip.replace( "&", "&&&" ); + #endif + + setToolTip( tip ); +} + + +void +TomahawkTrayIcon::onAnimationTimer() +{ + /*if( m_animationPixmaps.isEmpty() ) + { + stopIpodScrobblingAnimation(); + Q_ASSERT( !"Animation should not be started without frames being loaded" ); + return; + } + + m_currentAnimationFrame++; + if( m_currentAnimationFrame >= m_animationPixmaps.count() ) + m_currentAnimationFrame = 0; + + setIcon( m_animationPixmaps.at( m_currentAnimationFrame ) );*/ +} + + +void +TomahawkTrayIcon::onActivated( QSystemTrayIcon::ActivationReason reason ) +{ +#ifdef Q_WS_MAC + return; +#endif + + switch( reason ) + { + case QSystemTrayIcon::Trigger: + { + TomahawkWindow* mainwindow = APP->mainWindow(); + if ( mainwindow->isVisible() ) + { + mainwindow->hide(); + } + else + { + mainwindow->show(); + } + } + break; + + default: + break; + } +} + + +bool +TomahawkTrayIcon::event( QEvent* e ) +{ + // Beginning with Qt 4.3, QSystemTrayIcon supports wheel events, but only + // on X11. Let's make it adjust the volume. + if ( e->type() == QEvent::Wheel ) + { + if ( ((QWheelEvent*)e)->delta() > 0 ) + { + AudioEngine::instance()->raiseVolume(); + } + else + { + AudioEngine::instance()->lowerVolume(); + } + + return true; + } + + return QSystemTrayIcon::event( e ); +} + diff --git a/src/tomahawktrayicon.h b/src/tomahawktrayicon.h new file mode 100644 index 000000000..2cef11872 --- /dev/null +++ b/src/tomahawktrayicon.h @@ -0,0 +1,63 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TOMAHAWK_TRAYICON_H +#define TOMAHAWK_TRAYICON_H + +#include +#include +#include + +#include "result.h" + +class TomahawkTrayIcon : public QSystemTrayIcon +{ + Q_OBJECT + +public: + TomahawkTrayIcon( QObject* parent ); + virtual bool event( QEvent* e ); + +public slots: + void setResult( const Tomahawk::result_ptr& result ); + +private slots: + void onAnimationTimer(); + void onActivated( QSystemTrayIcon::ActivationReason reason ); + +private: + void refreshToolTip(); + ~TomahawkTrayIcon(); + + QTimer m_animationTimer; + Tomahawk::result_ptr m_currentTrack; + + QList m_animationPixmaps; + int m_currentAnimationFrame; + + QMenu* m_contextMenu; + QAction* m_playAction; + QAction* m_pauseAction; + QAction* m_stopAction; + QAction* m_prevAction; + QAction* m_nextAction; + QAction* m_quitAction; +}; + +#endif // TOMAHAWK_TRAYICON_H + diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index e8d3729fc..868f1609b 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -1,7 +1,26 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "tomahawkwindow.h" #include "ui_tomahawkwindow.h" #include +#include #include #include #include @@ -12,24 +31,35 @@ #include #include "tomahawk/tomahawkapp.h" -#include "tomahawk/functimeout.h" -#include "tomahawk/playlist.h" -#include "tomahawk/query.h" +#include "playlist.h" +#include "query.h" +#include "artist.h" +#include "audio/audioengine.h" +#include "database/database.h" #include "database/databasecommand_collectionstats.h" -#include "topbar/topbar.h" +#include "network/controlconnection.h" +#include "playlist/playlistmanager.h" +#include "sip/SipHandler.h" +#include "sourcetree/sourcetreeview.h" +#include "utils/animatedsplitter.h" +#include "utils/proxystyle.h" +#include "utils/widgetdragfilter.h" +#include "utils/xspfloader.h" +#include "widgets/newplaylistwidget.h" #include "audiocontrols.h" -#include "controlconnection.h" -#include "database.h" -#include "musicscanner.h" -#include "playlistmanager.h" -#include "proxystyle.h" #include "settingsdialog.h" -#include "xspfloader.h" -#include "proxystyle.h" #include "tomahawksettings.h" -#include "widgetdragfilter.h" +#include "sourcelist.h" +#include "transferview.h" +#include "tomahawktrayicon.h" +#include "playlist/dynamic/GeneratorInterface.h" +#include "scanmanager.h" + +#ifdef Q_OS_WIN32 +#include +#endif using namespace Tomahawk; @@ -37,9 +67,8 @@ using namespace Tomahawk; TomahawkWindow::TomahawkWindow( QWidget* parent ) : QMainWindow( parent ) , ui( new Ui::TomahawkWindow ) - , m_topbar( new TopBar( this ) ) , m_audioControls( new AudioControls( this ) ) - , m_playlistManager( new PlaylistManager( this ) ) + , m_trayIcon( new TomahawkTrayIcon( this ) ) { qApp->setStyle( new ProxyStyle() ); setWindowIcon( QIcon( RESPATH "icons/tomahawk-icon-128x128.png" ) ); @@ -48,32 +77,112 @@ TomahawkWindow::TomahawkWindow( QWidget* parent ) setUnifiedTitleAndToolBarOnMac( true ); #endif + PlaylistManager* pm = new PlaylistManager( this ); + connect( pm, SIGNAL( historyBackAvailable( bool ) ), SLOT( onHistoryBackAvailable( bool ) ) ); + connect( pm, SIGNAL( historyForwardAvailable( bool ) ), SLOT( onHistoryForwardAvailable( bool ) ) ); + + connect( m_audioControls, SIGNAL( playPressed() ), pm, SLOT( onPlayClicked() ) ); + connect( m_audioControls, SIGNAL( pausePressed() ), pm, SLOT( onPauseClicked() ) ); + ui->setupUi( this ); -#ifndef Q_WS_MAC - ui->centralWidget->layout()->setContentsMargins( 4, 4, 4, 2 ); -#else -// ui->actionProgress->setAttribute( Qt::WA_MacShowFocusRect, 0 ); -// ui->playlistView->setAttribute( Qt::WA_MacShowFocusRect, 0 ); - ui->sourceTreeView->setAttribute( Qt::WA_MacShowFocusRect, 0 ); -#endif - + delete ui->sidebarWidget; delete ui->playlistWidget; - ui->splitter->addWidget( m_playlistManager->widget() ); + + ui->centralWidget->setContentsMargins( 0, 0, 0, 0 ); + ui->centralWidget->layout()->setContentsMargins( 0, 0, 0, 0 ); + ui->centralWidget->layout()->setMargin( 0 ); + + QWidget* sidebarWidget = new QWidget(); + sidebarWidget->setLayout( new QVBoxLayout() ); + + AnimatedSplitter* sidebar = new AnimatedSplitter(); + sidebar->setOrientation( Qt::Vertical ); + sidebar->setChildrenCollapsible( false ); + sidebar->setGreedyWidget( 0 ); + sidebar->setStretchFactor( 0, 3 ); + sidebar->setStretchFactor( 1, 1 ); + + SourceTreeView* stv = new SourceTreeView(); + TransferView* transferView = new TransferView(); + + connect( ui->actionHideOfflineSources, SIGNAL( triggered() ), stv, SLOT( hideOfflineSources() ) ); + connect( ui->actionShowOfflineSources, SIGNAL( triggered() ), stv, SLOT( showOfflineSources() ) ); + + sidebar->addWidget( stv ); + sidebar->addWidget( transferView ); + sidebar->hide( 1, false ); + +/* QWidget* buttonWidget = new QWidget(); + buttonWidget->setLayout( new QVBoxLayout() ); + m_statusButton = new QPushButton(); + buttonWidget->layout()->addWidget( m_statusButton );*/ + + sidebarWidget->layout()->addWidget( sidebar ); +// sidebarWidget->layout()->addWidget( buttonWidget ); + + sidebarWidget->setContentsMargins( 0, 0, 0, 0 ); + sidebarWidget->layout()->setContentsMargins( 0, 0, 0, 0 ); + sidebarWidget->layout()->setMargin( 0 ); + sidebarWidget->layout()->setSpacing( 0 ); +/* buttonWidget->setContentsMargins( 0, 0, 0, 0 ); + buttonWidget->layout()->setContentsMargins( 0, 0, 0, 0 ); + buttonWidget->layout()->setMargin( 0 ); + buttonWidget->layout()->setSpacing( 0 );*/ + + ui->splitter->addWidget( sidebarWidget ); + ui->splitter->addWidget( PlaylistManager::instance()->widget() ); + ui->splitter->setStretchFactor( 0, 1 ); ui->splitter->setStretchFactor( 1, 3 ); + ui->splitter->setCollapsible( 1, false ); + ui->splitter->setHandleWidth( 1 ); QToolBar* toolbar = addToolBar( "TomahawkToolbar" ); toolbar->setObjectName( "TomahawkToolbar" ); - toolbar->addWidget( m_topbar ); toolbar->setMovable( false ); toolbar->setFloatable( false ); + toolbar->setIconSize( QSize( 32, 32 ) ); + toolbar->setToolButtonStyle( Qt::ToolButtonFollowStyle ); toolbar->installEventFilter( new WidgetDragFilter( toolbar ) ); +#if defined( Q_OS_DARWIN ) && defined( HAVE_SPARKLE ) + QAction* checkForUpdates = ui->menu_Help->addAction( tr( "Check for updates...") ); + checkForUpdates->setMenuRole( QAction::ApplicationSpecificRole ); + connect(checkForUpdates, SIGNAL( triggered( bool ) ), SLOT( checkForUpdates() ) ); +#elif defined( WIN32 ) + QUrl updaterUrl; + + if ( qApp->arguments().contains( "--debug" ) ) + updaterUrl.setUrl( "http://download.tomahawk-player.org/sparklewin-debug" ); + else + updaterUrl.setUrl( "http://download.tomahawk-player.org/sparklewin" ); + + qtsparkle::Updater* updater = new qtsparkle::Updater( updaterUrl, this ); + updater->SetNetworkAccessManager( TomahawkUtils::nam() ); + updater->SetVersion( VERSION ); + + ui->menu_Help->addSeparator(); + QAction* checkForUpdates = ui->menu_Help->addAction( tr( "Check for updates...") ); + connect( checkForUpdates, SIGNAL( triggered() ), updater, SLOT( CheckNow() ) ); +#endif + + m_backAvailable = toolbar->addAction( QIcon( RESPATH "images/back.png" ), tr( "Back" ), PlaylistManager::instance(), SLOT( historyBack() ) ); + m_forwardAvailable = toolbar->addAction( QIcon( RESPATH "images/forward.png" ), tr( "Forward" ), PlaylistManager::instance(), SLOT( historyForward() ) ); + toolbar->addAction( QIcon( RESPATH "images/home.png" ), tr( "Home" ), PlaylistManager::instance(), SLOT( showWelcomePage() ) ); + statusBar()->addPermanentWidget( m_audioControls, 1 ); + // propagate sip menu + foreach( SipPlugin *plugin, APP->sipHandler()->plugins() ) + { + connect( plugin, SIGNAL( addMenu( QMenu* ) ), this, SLOT( pluginMenuAdded( QMenu* ) ) ); + connect( plugin, SIGNAL( removeMenu( QMenu* ) ), this, SLOT( pluginMenuRemoved( QMenu* ) ) ); + } + loadSettings(); setupSignals(); + PlaylistManager::instance()->showWelcomePage(); } @@ -87,71 +196,81 @@ TomahawkWindow::~TomahawkWindow() void TomahawkWindow::loadSettings() { - TomahawkSettings* s = APP->settings(); + TomahawkSettings* s = TomahawkSettings::instance(); + + // Workaround for broken window geometry restoring on Qt Cocoa when setUnifiedTitleAndToolBarOnMac is true. + // See http://bugreports.qt.nokia.com/browse/QTBUG-3116 and + // http://lists.qt.nokia.com/pipermail/qt-interest/2009-August/011491.html + // for the 'fix' +#ifdef QT_MAC_USE_COCOA + bool workaround = !isVisible(); + if( workaround ) { + // make "invisible" + setWindowOpacity( 0 ); + // let Qt update its frameStruts + show(); + } +#endif if ( !s->mainWindowGeometry().isEmpty() ) restoreGeometry( s->mainWindowGeometry() ); if ( !s->mainWindowState().isEmpty() ) restoreState( s->mainWindowState() ); + if ( !s->mainWindowSplitterState().isEmpty() ) + ui->splitter->restoreState( s->mainWindowSplitterState() ); + +#ifdef QT_MAC_USE_COCOA + if( workaround ) { + // Make it visible again + setWindowOpacity( 1 ); + } +#endif } void TomahawkWindow::saveSettings() { - TomahawkSettings* s = APP->settings(); + TomahawkSettings* s = TomahawkSettings::instance(); s->setMainWindowGeometry( saveGeometry() ); s->setMainWindowState( saveState() ); + s->setMainWindowSplitterState( ui->splitter->saveState() ); } void TomahawkWindow::setupSignals() { - connect( ui->actionExit, SIGNAL( triggered() ), - qApp, SLOT( closeAllWindows() ) ); - - connect( ui->actionLoadXSPF, SIGNAL( triggered() ), SLOT( loadSpiff() )); - connect( ui->actionCreatePlaylist, SIGNAL( triggered() ), SLOT( createPlaylist() )); - - // - connect( m_topbar, SIGNAL( filterTextChanged( const QString& ) ), - playlistManager(), SLOT( setFilter( const QString& ) ) ); - - connect( playlistManager(), SIGNAL( numSourcesChanged( unsigned int ) ), - m_topbar, SLOT( setNumSources( unsigned int ) ) ); - - connect( playlistManager(), SIGNAL( numTracksChanged( unsigned int ) ), - m_topbar, SLOT( setNumTracks( unsigned int ) ) ); - - connect( playlistManager(), SIGNAL( numArtistsChanged( unsigned int ) ), - m_topbar, SLOT( setNumArtists( unsigned int ) ) ); - - connect( playlistManager(), SIGNAL( numShownChanged( unsigned int ) ), - m_topbar, SLOT( setNumShown( unsigned int ) ) ); - - connect( m_topbar, SIGNAL( flatMode() ), - m_playlistManager, SLOT( setTableMode() ) ); - - connect( m_topbar, SIGNAL( artistMode() ), - m_playlistManager, SLOT( setTreeMode() ) ); - // - connect( playlistManager(), SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ), + connect( PlaylistManager::instance(), SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ), m_audioControls, SLOT( onRepeatModeChanged( PlaylistInterface::RepeatMode ) ) ); - connect( playlistManager(), SIGNAL( shuffleModeChanged( bool ) ), + connect( PlaylistManager::instance(), SIGNAL( shuffleModeChanged( bool ) ), m_audioControls, SLOT( onShuffleModeChanged( bool ) ) ); // - connect( (QObject*)APP->audioEngine(), SIGNAL( loading( const Tomahawk::result_ptr& ) ), - SLOT( onPlaybackLoading( const Tomahawk::result_ptr& ) ) ); + connect( AudioEngine::instance(), SIGNAL( loading( const Tomahawk::result_ptr& ) ), + SLOT( onPlaybackLoading( const Tomahawk::result_ptr& ) ) ); // connect( ui->actionPreferences, SIGNAL( triggered() ), SLOT( showSettingsDialog() ) ); - connect( ui->actionAddPeerManually, SIGNAL( triggered() ), SLOT( addPeerManually() ) ); - connect( ui->actionRescanCollection, SIGNAL( triggered() ), SLOT( rescanCollectionManually() ) ); - connect( ui->actionAbout_Tomahawk, SIGNAL( triggered() ), SLOT( showAboutTomahawk() ) ); + connect( ui->actionToggleConnect, SIGNAL( triggered() ), APP->sipHandler(), SLOT( toggleConnect() ) ); +// connect( ui->actionAddPeerManually, SIGNAL( triggered() ), SLOT( addPeerManually() ) ); + connect( ui->actionRescanCollection, SIGNAL( triggered() ), SLOT( updateCollectionManually() ) ); + connect( ui->actionLoadXSPF, SIGNAL( triggered() ), SLOT( loadSpiff() )); + connect( ui->actionCreatePlaylist, SIGNAL( triggered() ), SLOT( createPlaylist() )); + connect( ui->actionCreateAutomaticPlaylist, SIGNAL( triggered() ), SLOT( createAutomaticPlaylist() )); + connect( ui->actionCreate_New_Station, SIGNAL( triggered() ), SLOT( createStation() )); + connect( ui->actionAboutTomahawk, SIGNAL( triggered() ), SLOT( showAboutTomahawk() ) ); + connect( ui->actionExit, SIGNAL( triggered() ), APP, SLOT( quit() ) ); + + // + connect( APP->sipHandler(), SIGNAL( connected() ), SLOT( onSipConnected() ) ); + connect( APP->sipHandler(), SIGNAL( disconnected() ), SLOT( onSipDisconnected() ) ); + connect( APP->sipHandler(), SIGNAL( authError() ), SLOT( onSipError() ) ); + + // set initial connection state + onSipDisconnected(); } @@ -172,10 +291,19 @@ TomahawkWindow::changeEvent( QEvent* e ) } -PlaylistManager* -TomahawkWindow::playlistManager() +void +TomahawkWindow::closeEvent( QCloseEvent* e ) { - return m_playlistManager; +#ifndef Q_WS_MAC + if ( QSystemTrayIcon::isSystemTrayAvailable() ) + { + hide(); + e->ignore(); + return; + } +#endif + + e->accept(); } @@ -185,54 +313,30 @@ TomahawkWindow::showSettingsDialog() qDebug() << Q_FUNC_INFO; SettingsDialog win; win.exec(); - - // settings are written in SettingsDialog destructor, bleh - QTimer::singleShot( 0, this, SIGNAL( settingsChanged() ) ); -} - - -/// scan stuff -void -TomahawkWindow::rescanCollectionManually() -{ - TomahawkSettings* s = APP->settings(); - bool ok; - QString path = QInputDialog::getText( this, tr( "Enter path to music dir:" ), - tr( "Path pls" ), QLineEdit::Normal, - s->scannerPath(), &ok ); - s->setValue( "scannerpath", path ); - if ( ok && !path.isEmpty() ) - { - MusicScanner* scanner = new MusicScanner( path ); - connect( scanner, SIGNAL( finished() ), this, SLOT( scanFinished() ) ); - scanner->start(); - } } void -TomahawkWindow::scanFinished() +TomahawkWindow::updateCollectionManually() { - qDebug() << Q_FUNC_INFO; - MusicScanner* scanner = (MusicScanner*) sender(); - scanner->deleteLater(); + if ( TomahawkSettings::instance()->hasScannerPath() ) + ScanManager::instance()->runManualScan( TomahawkSettings::instance()->scannerPath() ); } void TomahawkWindow::addPeerManually() { - TomahawkSettings* s = APP->settings(); - // stealing this for connecting to peers for now: + TomahawkSettings* s = TomahawkSettings::instance(); bool ok; - QString addr = QInputDialog::getText( this, tr( "Connect to peer" ), + QString addr = QInputDialog::getText( this, tr( "Connect To Peer" ), tr( "Enter peer address:" ), QLineEdit::Normal, s->value( "connip" ).toString(), &ok ); // FIXME if ( !ok ) return; s->setValue( "connip", addr ); - QString ports = QInputDialog::getText( this, tr( "Connect to peer" ), + QString ports = QInputDialog::getText( this, tr( "Connect To Peer" ), tr( "Enter peer port:" ), QLineEdit::Normal, s->value( "connport", "50210" ).toString(), &ok ); if ( !ok ) @@ -240,14 +344,35 @@ TomahawkWindow::addPeerManually() s->setValue( "connport", ports ); int port = ports.toInt(); - QString key = QInputDialog::getText( this, tr( "Connect to peer" ), + QString key = QInputDialog::getText( this, tr( "Connect To Peer" ), tr( "Enter peer key:" ), QLineEdit::Normal, "whitelist", &ok ); if ( !ok ) return; - qDebug() << "Attempting to connect to " << addr; - APP->servent().connectToPeer( addr, port, key ); + qDebug() << "Attempting to connect to" << addr; + Servent::instance()->connectToPeer( addr, port, key ); +} + + +void +TomahawkWindow::pluginMenuAdded( QMenu* menu ) +{ + ui->menuNetwork->addMenu( menu ); +} + + +void +TomahawkWindow::pluginMenuRemoved( QMenu* menu ) +{ + foreach( QAction* action, ui->menuNetwork->actions() ) + { + if( action->menu() == menu ) + { + ui->menuNetwork->removeAction( action ); + return; + } + } } @@ -266,21 +391,48 @@ TomahawkWindow::loadSpiff() } -void -TomahawkWindow::createPlaylist() +void +TomahawkWindow::createAutomaticPlaylist() { - qDebug() << Q_FUNC_INFO; - bool ok; - QString name = QInputDialog::getText( this, "Create New Playlist", "Name:", QLineEdit::Normal, "New Playlist", &ok ); + QString name = QInputDialog::getText( this, "Create New Automatic Playlist", "Name:", QLineEdit::Normal, "New Automatic Playlist", &ok ); if ( !ok || name.isEmpty() ) return; - - source_ptr author = APP->sourcelist().getLocal(); + + source_ptr author = SourceList::instance()->getLocal(); QString id = uuid(); QString info = ""; // FIXME QString creator = "someone"; // FIXME - Playlist::create( author, id, name, info, creator, false /* shared */ ); + dynplaylist_ptr playlist = DynamicPlaylist::create( author, id, name, info, creator, false ); + playlist->setMode( Static ); + playlist->createNewRevision( uuid(), playlist->currentrevision(), playlist->type(), playlist->generator()->controls(), playlist->entries() ); + PlaylistManager::instance()->show( playlist ); +} + + +void +TomahawkWindow::createStation() +{ + bool ok; + QString name = QInputDialog::getText( this, "Create New Station", "Name:", QLineEdit::Normal, "New Station", &ok ); + if ( !ok || name.isEmpty() ) + return; + + source_ptr author = SourceList::instance()->getLocal(); + QString id = uuid(); + QString info = ""; // FIXME + QString creator = "someone"; // FIXME + dynplaylist_ptr playlist = DynamicPlaylist::create( author, id, name, info, creator, false ); + playlist->setMode( OnDemand ); + playlist->createNewRevision( uuid(), playlist->currentrevision(), playlist->type(), playlist->generator()->controls() ); + PlaylistManager::instance()->show( playlist ); +} + + +void +TomahawkWindow::createPlaylist() +{ + PlaylistManager::instance()->show( new NewPlaylistWidget() ); } @@ -292,6 +444,46 @@ TomahawkWindow::onPlaybackLoading( const Tomahawk::result_ptr& result ) } +void +TomahawkWindow::onHistoryBackAvailable( bool avail ) +{ + m_backAvailable->setEnabled( avail ); +} + + +void +TomahawkWindow::onHistoryForwardAvailable( bool avail ) +{ + m_forwardAvailable->setEnabled( avail ); +} + + +void +TomahawkWindow::onSipConnected() +{ + ui->actionToggleConnect->setText( tr( "Go &offline" ) ); +} + + +void +TomahawkWindow::onSipDisconnected() +{ + ui->actionToggleConnect->setText( tr( "Go &online" ) ); +} + + +void +TomahawkWindow::onSipError() +{ + onSipDisconnected(); + + QMessageBox::warning( this, + tr( "Authentication Error" ), + QString( "Error connecting to SIP: Authentication failed!" ), + QMessageBox::Ok ); +} + + void TomahawkWindow::setWindowTitle( const QString& title ) { @@ -310,5 +502,16 @@ TomahawkWindow::setWindowTitle( const QString& title ) void TomahawkWindow::showAboutTomahawk() { - QMessageBox::about( this, "About Tomahawk", "Copyright 2010 Christian Muehlhaeuser \n\nThanks to: Leo Franchi, Jeff Mitchell, Dominik Schmidt and Steve Robertson" ); + QMessageBox::about( this, "About Tomahawk", + tr( "

Tomahawk %1

Copyright 2010, 2011
Christian Muehlhaeuser <muesli@tomahawk-player.org>

" + "Thanks to: Leo Franchi, Jeff Mitchell, Dominik Schmidt, Jason Herskowitz, Alejandro Wainzinger, Harald Sitter and Steve Robertson" ) + .arg( qApp->applicationVersion() ) ); +} + +void +TomahawkWindow::checkForUpdates() +{ +#ifdef Q_WS_MAC + Tomahawk::checkForUpdates(); +#endif } diff --git a/src/tomahawkwindow.h b/src/tomahawkwindow.h index 02ed0b365..2f24f9f65 100644 --- a/src/tomahawkwindow.h +++ b/src/tomahawkwindow.h @@ -1,19 +1,38 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef TOMAHAWKWINDOW_H #define TOMAHAWKWINDOW_H #include #include #include +#include #include #include -#include "tomahawk/pipeline.h" +#include "result.h" class QAction; +class MusicScanner; class AudioControls; -class PlaylistManager; -class TopBar; +class TomahawkTrayIcon; namespace Ui { @@ -28,31 +47,38 @@ public: TomahawkWindow( QWidget* parent = 0 ); ~TomahawkWindow(); - PlaylistManager* playlistManager(); AudioControls* audioControls() { return m_audioControls; } QStackedWidget* playlistStack(); void setWindowTitle( const QString& title ); -signals: - void settingsChanged(); - protected: void changeEvent( QEvent* e ); + void closeEvent( QCloseEvent* e ); public slots: + void createAutomaticPlaylist(); + void createStation(); void createPlaylist(); void loadSpiff(); void showSettingsDialog(); - + void updateCollectionManually(); + void pluginMenuAdded(QMenu*); + void pluginMenuRemoved(QMenu*); + private slots: - void scanFinished(); - void rescanCollectionManually(); + void onSipConnected(); + void onSipDisconnected(); + void onSipError(); + void addPeerManually(); void onPlaybackLoading( const Tomahawk::result_ptr& result ); - + void onHistoryBackAvailable( bool avail ); + void onHistoryForwardAvailable( bool avail ); + void showAboutTomahawk(); + void checkForUpdates(); private: void loadSettings(); @@ -60,11 +86,14 @@ private: void setupSignals(); Ui::TomahawkWindow* ui; - TopBar* m_topbar; AudioControls* m_audioControls; - PlaylistManager* m_playlistManager; + TomahawkTrayIcon* m_trayIcon; QNetworkAccessManager m_nam; + QPushButton* m_statusButton; + QAction* m_backAvailable; + QAction* m_forwardAvailable; + Tomahawk::result_ptr m_currentTrack; QString m_windowTitle; }; diff --git a/src/tomahawkwindow.ui b/src/tomahawkwindow.ui index 221992fb7..6b142c616 100644 --- a/src/tomahawkwindow.ui +++ b/src/tomahawkwindow.ui @@ -6,28 +6,24 @@ 0 0 - 800 - 480 + 1000 + 660 Tomahawk - + Qt::Horizontal - - - - 250 - 0 - - - + + 1 + + @@ -38,7 +34,7 @@ 0 0 - 800 + 1000 20 @@ -50,10 +46,13 @@ - Music &Player + &Music Player + + + @@ -61,20 +60,23 @@ &Playlist + + - Network + &Network - + + &Help - + @@ -94,14 +96,19 @@ QAction::QuitRole - + - &Add Peer Manually + Go &online + + + + + Add &Friend... - Re&scan Collection + Re&scan Collection... @@ -114,7 +121,7 @@ - Load &XSPF + Load &XSPF... @@ -122,20 +129,36 @@ Create &New Playlist... - + - About &Tomahawk + About &Tomahawk... + + + QAction::AboutRole + + + + + Create New &Automatic Playlist + + + + + Create New &Station + + + + + Show Offline Sources + + + + + Hide Offline Sources - - - SourceTreeView - QTreeView -
sourcetreeview.h
-
-
diff --git a/src/topbar/topbar.h b/src/topbar/topbar.h deleted file mode 100644 index e768bfdb5..000000000 --- a/src/topbar/topbar.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef TOPBAR_H -#define TOPBAR_H - -#include -#include -#include - -#include "tomahawk/sourcelist.h" - -namespace Ui -{ - class TopBar; -} - -class TopBar : public QWidget -{ -Q_OBJECT - -public: - TopBar( QWidget* parent = 0 ); - ~TopBar(); - -signals: - void filterTextChanged( const QString& newtext ); - void flatMode(); - void artistMode(); - -public slots: - void setNumSources( unsigned int ); - void setNumTracks( unsigned int ); - void setNumArtists( unsigned int ); - void setNumShown( unsigned int ); - - void addSource(); - void removeSource(); - -protected: - void changeEvent( QEvent* e ); - -private: - void fadeOutDude( unsigned int i ); - void fadeInDude( unsigned int i ); - - Ui::TopBar* ui; - - unsigned int m_sources, m_tracks, m_artists, m_shown; - QList m_dudes; - - Tomahawk::source_ptr m_onesource; -}; - -#endif // TOPBAR_H diff --git a/src/transferview.cpp b/src/transferview.cpp new file mode 100644 index 000000000..8baa3d1f3 --- /dev/null +++ b/src/transferview.cpp @@ -0,0 +1,142 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "transferview.h" + +#include +#include + +#include "tomahawk/tomahawkapp.h" +#include "artist.h" +#include "source.h" +#include "network/filetransferconnection.h" +#include "network/servent.h" + + +TransferView::TransferView( AnimatedSplitter* parent ) + : AnimatedWidget( parent ) + , m_parent( parent ) +{ + setHiddenSize( QSize( 0, 0 ) ); + setLayout( new QVBoxLayout() ); + m_tree = new QTreeWidget( this ); + + layout()->setMargin( 0 ); + layout()->addWidget( m_tree ); + + connect( Servent::instance(), SIGNAL( fileTransferStarted( FileTransferConnection* ) ), SLOT( fileTransferRegistered( FileTransferConnection* ) ) ); + connect( Servent::instance(), SIGNAL( fileTransferFinished( FileTransferConnection* ) ), SLOT( fileTransferFinished( FileTransferConnection* ) ) ); + + QStringList headers; + headers << tr( "Peer" ) << tr( "Rate" ) << tr( "Track" ); + m_tree->setHeaderLabels( headers ); + + m_tree->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Ignored ); + m_tree->setColumnCount( 3 ); + m_tree->setColumnWidth( 0, 80 ); + m_tree->setColumnWidth( 1, 65 ); + m_tree->setColumnWidth( 2, 10 ); + + m_tree->header()->setStretchLastSection( true ); + m_tree->setRootIsDecorated( false ); + + m_tree->setFrameShape( QFrame::NoFrame ); + m_tree->setAttribute( Qt::WA_MacShowFocusRect, 0 ); +} + + +void +TransferView::fileTransferRegistered( FileTransferConnection* ftc ) +{ + qDebug() << Q_FUNC_INFO; + connect( ftc, SIGNAL( updated() ), SLOT( onTransferUpdate() ) ); +} + + +void +TransferView::fileTransferFinished( FileTransferConnection* ftc ) +{ + if ( !m_index.contains( ftc ) ) + return; + + QPersistentModelIndex i = m_index.take( ftc ); + delete m_tree->invisibleRootItem()->takeChild( i.row() ); + + if ( m_tree->invisibleRootItem()->childCount() > 0 ) + emit showWidget(); + else + emit hideWidget(); + +/* if ( m_index.contains( ftc ) ) + { + int i = m_index.value( ftc ); + m_tree->invisibleRootItem()->child( i )->setText( 1, tr( "Finished" ) ); + }*/ +} + + +void +TransferView::onTransferUpdate() +{ + FileTransferConnection* ftc = (FileTransferConnection*)sender(); +// qDebug() << Q_FUNC_INFO << ftc->track().isNull() << ftc->source().isNull(); + + if ( ftc->track().isNull() || ftc->source().isNull() ) + return; + + QTreeWidgetItem* ti = 0; + + if ( m_index.contains( ftc ) ) + { + QPersistentModelIndex i = m_index.value( ftc ); + ti = m_tree->invisibleRootItem()->child( i.row() ); + } + else + { + ti = new QTreeWidgetItem( m_tree ); + m_index.insert( ftc, QPersistentModelIndex( m_tree->model()->index( m_tree->invisibleRootItem()->childCount() - 1, 0 ) ) ); + emit showWidget(); + } + + if ( !ti ) + return; + + ti->setText( 0, ftc->source()->friendlyName() ); + ti->setText( 1, QString( "%1 kb/s" ).arg( ftc->transferRate() / 1024 ) ); + ti->setText( 2, QString( "%1 - %2" ).arg( ftc->track()->artist()->name() ).arg( ftc->track()->track() ) ); + + if ( isHidden() ) + emit showWidget(); +} + + +QSize +TransferView::sizeHint() const +{ + unsigned int y = 0; + y += m_tree->header()->height(); + y += m_tree->contentsMargins().top() + m_tree->contentsMargins().bottom(); + + if ( m_tree->invisibleRootItem()->childCount() ) + { + unsigned int rowheight = m_tree->sizeHintForRow( 0 ); + y += rowheight * m_tree->invisibleRootItem()->childCount() + 2; + } + + return QSize( 0, y ); +} diff --git a/src/transferview.h b/src/transferview.h new file mode 100644 index 000000000..d006d047c --- /dev/null +++ b/src/transferview.h @@ -0,0 +1,57 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#ifndef TRANSFERVIEW_H +#define TRANSFERVIEW_H + +#include +#include + +#include "typedefs.h" +#include "utils/animatedsplitter.h" + +class FileTransferConnection; + +class TransferView : public AnimatedWidget +{ +Q_OBJECT + +public: + explicit TransferView( AnimatedSplitter* parent = 0 ); + virtual ~TransferView() + { + qDebug() << Q_FUNC_INFO; + } + + QSize sizeHint() const; + +signals: + +private slots: + void fileTransferRegistered( FileTransferConnection* ftc ); + void fileTransferFinished( FileTransferConnection* ftc ); + + void onTransferUpdate(); + +private: + QHash< FileTransferConnection*, QPersistentModelIndex > m_index; + QTreeWidget* m_tree; + AnimatedSplitter* m_parent; +}; + +#endif // TRANSFERVIEW_H diff --git a/src/utils/modeltest.cpp b/src/utils/modeltest.cpp deleted file mode 100644 index 40d7d282a..000000000 --- a/src/utils/modeltest.cpp +++ /dev/null @@ -1,539 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2007 Trolltech ASA. All rights reserved. -** -** This file is part of the Qt Concurrent project on Trolltech Labs. -** -** This file may be used under the terms of the GNU General Public -** License version 2.0 as published by the Free Software Foundation -** and appearing in the file LICENSE.GPL included in the packaging of -** this file. Please review the following information to ensure GNU -** General Public Licensing requirements will be met: -** http://www.trolltech.com/products/qt/opensource.html -** -** If you are unsure which license is appropriate for your use, please -** review the following information: -** http://www.trolltech.com/products/qt/licensing.html or contact the -** sales department at sales@trolltech.com. -** -** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -** -****************************************************************************/ - -#include - -#include "modeltest.h" - -Q_DECLARE_METATYPE(QModelIndex) - -/*! - Connect to all of the models signals. Whenever anything happens recheck everything. -*/ -ModelTest::ModelTest(QAbstractItemModel *_model, QObject *parent) : QObject(parent), model(_model), fetchingMore(false) -{ - Q_ASSERT(model); - - connect(model, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(columnsInserted(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(columnsRemoved(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(layoutAboutToBeChanged ()), this, SLOT(runAllTests())); - connect(model, SIGNAL(layoutChanged ()), this, SLOT(runAllTests())); - connect(model, SIGNAL(modelReset ()), this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), - this, SLOT(runAllTests())); - - // Special checks for inserting/removing - connect(model, SIGNAL(layoutAboutToBeChanged()), - this, SLOT(layoutAboutToBeChanged())); - connect(model, SIGNAL(layoutChanged()), - this, SLOT(layoutChanged())); - - connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), - this, SLOT(rowsAboutToBeInserted(const QModelIndex &, int, int))); - connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), - this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int))); - connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)), - this, SLOT(rowsInserted(const QModelIndex &, int, int))); - connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), - this, SLOT(rowsRemoved(const QModelIndex &, int, int))); - - runAllTests(); -} - -void ModelTest::runAllTests() -{ - if (fetchingMore) - return; - nonDestructiveBasicTest(); - rowCount(); - columnCount(); - hasIndex(); - index(); - parent(); - data(); -} - -/*! - nonDestructiveBasicTest tries to call a number of the basic functions (not all) - to make sure the model doesn't outright segfault, testing the functions that makes sense. -*/ -void ModelTest::nonDestructiveBasicTest() -{ - Q_ASSERT(model->buddy(QModelIndex()) == QModelIndex()); - model->canFetchMore(QModelIndex()); - Q_ASSERT(model->columnCount(QModelIndex()) >= 0); - Q_ASSERT(model->data(QModelIndex()) == QVariant()); - fetchingMore = true; - model->fetchMore(QModelIndex()); - fetchingMore = false; - Qt::ItemFlags flags = model->flags(QModelIndex()); - Q_ASSERT(flags == Qt::ItemIsDropEnabled || flags == 0); - model->hasChildren(QModelIndex()); - model->hasIndex(0, 0); - model->headerData(0, Qt::Horizontal); - model->index(0, 0); - Q_ASSERT(model->index(-1, -1) == QModelIndex()); - model->itemData(QModelIndex()); - QVariant cache; - model->match(QModelIndex(), -1, cache); - model->mimeTypes(); - Q_ASSERT(model->parent(QModelIndex()) == QModelIndex()); - Q_ASSERT(model->rowCount() >= 0); - QVariant variant; - model->setData(QModelIndex(), variant, -1); - model->setHeaderData(-1, Qt::Horizontal, QVariant()); - model->setHeaderData(0, Qt::Horizontal, QVariant()); - model->setHeaderData(999999, Qt::Horizontal, QVariant()); - QMap roles; - model->sibling(0, 0, QModelIndex()); - model->span(QModelIndex()); - model->supportedDropActions(); -} - -/*! - Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren() - - Models that are dynamically populated are not as fully tested here. - */ -void ModelTest::rowCount() -{ - // check top row - QModelIndex topIndex = model->index(0, 0, QModelIndex()); - int rows = model->rowCount(topIndex); - Q_ASSERT(rows >= 0); - if (rows > 0) - Q_ASSERT(model->hasChildren(topIndex) == true); - - QModelIndex secondLevelIndex = model->index(0, 0, topIndex); - if (secondLevelIndex.isValid()) { // not the top level - // check a row count where parent is valid - rows = model->rowCount(secondLevelIndex); - Q_ASSERT(rows >= 0); - if (rows > 0) - Q_ASSERT(model->hasChildren(secondLevelIndex) == true); - } - - // The models rowCount() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren() - */ -void ModelTest::columnCount() -{ - // check top row - QModelIndex topIndex = model->index(0, 0, QModelIndex()); - Q_ASSERT(model->columnCount(topIndex) >= 0); - - // check a column count where parent is valid - QModelIndex childIndex = model->index(0, 0, topIndex); - if (childIndex.isValid()) - Q_ASSERT(model->columnCount(childIndex) >= 0); - - // columnCount() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::hasIndex() - */ -void ModelTest::hasIndex() -{ - // Make sure that invalid values returns an invalid index - Q_ASSERT(model->hasIndex(-2, -2) == false); - Q_ASSERT(model->hasIndex(-2, 0) == false); - Q_ASSERT(model->hasIndex(0, -2) == false); - - int rows = model->rowCount(); - int columns = model->columnCount(); - - // check out of bounds - Q_ASSERT(model->hasIndex(rows, columns) == false); - Q_ASSERT(model->hasIndex(rows + 1, columns + 1) == false); - - if (rows > 0) - Q_ASSERT(model->hasIndex(0, 0) == true); - - // hasIndex() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::index() - */ -void ModelTest::index() -{ - // Make sure that invalid values returns an invalid index - Q_ASSERT(model->index(-2, -2) == QModelIndex()); - Q_ASSERT(model->index(-2, 0) == QModelIndex()); - Q_ASSERT(model->index(0, -2) == QModelIndex()); - - int rows = model->rowCount(); - int columns = model->columnCount(); - - if (rows == 0) - return; - - // Catch off by one errors - Q_ASSERT(model->index(rows, columns) == QModelIndex()); - Q_ASSERT(model->index(0, 0).isValid() == true); - - // Make sure that the same index is *always* returned - QModelIndex a = model->index(0, 0); - QModelIndex b = model->index(0, 0); - Q_ASSERT(a == b); - - // index() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::parent() - */ -void ModelTest::parent() -{ - // Make sure the model wont crash and will return an invalid QModelIndex - // when asked for the parent of an invalid index. - Q_ASSERT(model->parent(QModelIndex()) == QModelIndex()); - - if (model->rowCount() == 0) - return; - - // Column 0 | Column 1 | - // QModelIndex() | | - // \- topIndex | topIndex1 | - // \- childIndex | childIndex1 | - - // Common error test #1, make sure that a top level index has a parent - // that is a invalid QModelIndex. - QModelIndex topIndex = model->index(0, 0, QModelIndex()); - Q_ASSERT(model->parent(topIndex) == QModelIndex()); - - // Common error test #2, make sure that a second level index has a parent - // that is the first level index. - if (model->rowCount(topIndex) > 0) { - QModelIndex childIndex = model->index(0, 0, topIndex); - Q_ASSERT(model->parent(childIndex) == topIndex); - } - - // Common error test #3, the second column should NOT have the same children - // as the first column in a row. - // Usually the second column shouldn't have children. - QModelIndex topIndex1 = model->index(0, 1, QModelIndex()); - if (model->rowCount(topIndex1) > 0) { - QModelIndex childIndex = model->index(0, 0, topIndex); - QModelIndex childIndex1 = model->index(0, 0, topIndex1); - Q_ASSERT(childIndex != childIndex1); - } - - // Full test, walk n levels deep through the model making sure that all - // parent's children correctly specify their parent. - checkChildren(QModelIndex()); -} - -/*! - Called from the parent() test. - - A model that returns an index of parent X should also return X when asking - for the parent of the index. - - This recursive function does pretty extensive testing on the whole model in an - effort to catch edge cases. - - This function assumes that rowCount(), columnCount() and index() already work. - If they have a bug it will point it out, but the above tests should have already - found the basic bugs because it is easier to figure out the problem in - those tests then this one. - */ -void ModelTest::checkChildren(const QModelIndex &parent, int currentDepth) -{ - // First just try walking back up the tree. - QModelIndex p = parent; - while (p.isValid()) - p = p.parent(); - - // For models that are dynamically populated - if (model->canFetchMore(parent)) { - fetchingMore = true; - model->fetchMore(parent); - fetchingMore = false; - } - - int rows = model->rowCount(parent); - int columns = model->columnCount(parent); - - if (rows > 0) - Q_ASSERT(model->hasChildren(parent)); - - // Some further testing against rows(), columns(), and hasChildren() - Q_ASSERT(rows >= 0); - Q_ASSERT(columns >= 0); - if (rows > 0) - Q_ASSERT(model->hasChildren(parent) == true); - - //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows - // << "columns:" << columns << "parent column:" << parent.column(); - - Q_ASSERT(model->hasIndex(rows + 1, 0, parent) == false); - for (int r = 0; r < rows; ++r) { - if (model->canFetchMore(parent)) { - fetchingMore = true; - model->fetchMore(parent); - fetchingMore = false; - } - Q_ASSERT(model->hasIndex(r, columns + 1, parent) == false); - for (int c = 0; c < columns; ++c) { - Q_ASSERT(model->hasIndex(r, c, parent) == true); - QModelIndex index = model->index(r, c, parent); - // rowCount() and columnCount() said that it existed... - Q_ASSERT(index.isValid() == true); - - // index() should always return the same index when called twice in a row - QModelIndex modifiedIndex = model->index(r, c, parent); - Q_ASSERT(index == modifiedIndex); - - // Make sure we get the same index if we request it twice in a row - QModelIndex a = model->index(r, c, parent); - QModelIndex b = model->index(r, c, parent); - Q_ASSERT(a == b); - - // Some basic checking on the index that is returned - Q_ASSERT(index.model() == model); - Q_ASSERT(index.row() == r); - Q_ASSERT(index.column() == c); - // While you can technically return a QVariant usually this is a sign - // of an bug in data() Disable if this really is ok in your model. - //Q_ASSERT(model->data(index, Qt::DisplayRole).isValid() == true); - - // If the next test fails here is some somewhat useful debug you play with. - /* - if (model->parent(index) != parent) { - qDebug() << r << c << currentDepth << model->data(index).toString() - << model->data(parent).toString(); - qDebug() << index << parent << model->parent(index); - // And a view that you can even use to show the model. - //QTreeView view; - //view.setModel(model); - //view.show(); - }*/ - - // Check that we can get back our real parent. - QModelIndex p = model->parent(index); - //qDebug() << "child:" << index; - //qDebug() << p; - //qDebug() << parent; - Q_ASSERT(model->parent(index) == parent); - - // recursively go down the children - if (model->hasChildren(index) && currentDepth < 10 ) { - //qDebug() << r << c << "has children" << model->rowCount(index); - checkChildren(index, ++currentDepth); - }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/ - - // make sure that after testing the children that the index doesn't change. - QModelIndex newerIndex = model->index(r, c, parent); - Q_ASSERT(index == newerIndex); - } - } -} - -/*! - Tests model's implementation of QAbstractItemModel::data() - */ -void ModelTest::data() -{ - // Invalid index should return an invalid qvariant - Q_ASSERT(!model->data(QModelIndex()).isValid()); - - if (model->rowCount() == 0) - return; - - // A valid index should have a valid QVariant data - Q_ASSERT(model->index(0, 0).isValid()); - - // shouldn't be able to set data on an invalid index - Q_ASSERT(model->setData(QModelIndex(), QLatin1String("foo"), Qt::DisplayRole) == false); - - // General Purpose roles that should return a QString - QVariant variant = model->data(model->index(0, 0), Qt::ToolTipRole); - if (variant.isValid()) { - Q_ASSERT(qVariantCanConvert(variant)); - } - variant = model->data(model->index(0, 0), Qt::StatusTipRole); - if (variant.isValid()) { - Q_ASSERT(qVariantCanConvert(variant)); - } - variant = model->data(model->index(0, 0), Qt::WhatsThisRole); - if (variant.isValid()) { - Q_ASSERT(qVariantCanConvert(variant)); - } - - // General Purpose roles that should return a QSize - variant = model->data(model->index(0, 0), Qt::SizeHintRole); - if (variant.isValid()) { - Q_ASSERT(qVariantCanConvert(variant)); - } - - // General Purpose roles that should return a QFont - QVariant fontVariant = model->data(model->index(0, 0), Qt::FontRole); - if (fontVariant.isValid()) { - Q_ASSERT(qVariantCanConvert(fontVariant)); - } - - // Check that the alignment is one we know about - QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole); - if (textAlignmentVariant.isValid()) { - int alignment = textAlignmentVariant.toInt(); - Q_ASSERT(alignment == Qt::AlignLeft || - alignment == Qt::AlignRight || - alignment == Qt::AlignHCenter || - alignment == Qt::AlignJustify || - alignment == Qt::AlignTop || - alignment == Qt::AlignBottom || - alignment == Qt::AlignVCenter || - alignment == Qt::AlignCenter || - alignment == Qt::AlignAbsolute || - alignment == Qt::AlignLeading || - alignment == Qt::AlignTrailing); - } - - // General Purpose roles that should return a QColor - QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundColorRole); - if (colorVariant.isValid()) { - Q_ASSERT(qVariantCanConvert(colorVariant)); - } - - colorVariant = model->data(model->index(0, 0), Qt::TextColorRole); - if (colorVariant.isValid()) { - Q_ASSERT(qVariantCanConvert(colorVariant)); - } - - // Check that the "check state" is one we know about. - QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole); - if (checkStateVariant.isValid()) { - int state = checkStateVariant.toInt(); - Q_ASSERT(state == Qt::Unchecked || - state == Qt::PartiallyChecked || - state == Qt::Checked); - } -} - -/*! - Store what is about to be inserted to make sure it actually happens - - \sa rowsInserted() - */ -void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end) -{ - Q_UNUSED(end); - Changing c; - c.parent = parent; - c.oldSize = model->rowCount(parent); - c.last = model->data(model->index(start - 1, 0, parent)); - c.next = model->data(model->index(start, 0, parent)); - insert.push(c); -} - -/*! - Confirm that what was said was going to happen actually did - - \sa rowsAboutToBeInserted() - */ -void ModelTest::rowsInserted(const QModelIndex & parent, int start, int end) -{ - Changing c = insert.pop(); - Q_ASSERT(c.parent == parent); - Q_ASSERT(c.oldSize + (end - start + 1) == model->rowCount(parent)); - Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent))); - /* - if (c.next != model->data(model->index(end + 1, 0, c.parent))) { - qDebug() << start << end; - for (int i=0; i < model->rowCount(); ++i) - qDebug() << model->index(i, 0).data().toString(); - qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent)); - } - */ - Q_ASSERT(c.next == model->data(model->index(end + 1, 0, c.parent))); -} - -void ModelTest::layoutAboutToBeChanged() -{ - for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i) - changing.append(QPersistentModelIndex(model->index(i, 0))); -} - -void ModelTest::layoutChanged() -{ - for (int i = 0; i < changing.count(); ++i) { - QPersistentModelIndex p = changing[i]; - Q_ASSERT(p == model->index(p.row(), p.column(), p.parent())); - } - changing.clear(); -} - -/*! - Store what is about to be inserted to make sure it actually happens - - \sa rowsRemoved() - */ -void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) -{ - Changing c; - c.parent = parent; - c.oldSize = model->rowCount(parent); - c.last = model->data(model->index(start - 1, 0, parent)); - c.next = model->data(model->index(end + 1, 0, parent)); - remove.push(c); -} - -/*! - Confirm that what was said was going to happen actually did - - \sa rowsAboutToBeRemoved() - */ -void ModelTest::rowsRemoved(const QModelIndex & parent, int start, int end) -{ - Changing c = remove.pop(); - Q_ASSERT(c.parent == parent); - Q_ASSERT(c.oldSize - (end - start + 1) == model->rowCount(parent)); - Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent))); - Q_ASSERT(c.next == model->data(model->index(start, 0, c.parent))); -} - diff --git a/src/utils/modeltest.h b/src/utils/modeltest.h deleted file mode 100644 index 38b6b2bed..000000000 --- a/src/utils/modeltest.h +++ /dev/null @@ -1,76 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2007 Trolltech ASA. All rights reserved. -** -** This file is part of the Qt Concurrent project on Trolltech Labs. -** -** This file may be used under the terms of the GNU General Public -** License version 2.0 as published by the Free Software Foundation -** and appearing in the file LICENSE.GPL included in the packaging of -** this file. Please review the following information to ensure GNU -** General Public Licensing requirements will be met: -** http://www.trolltech.com/products/qt/opensource.html -** -** If you are unsure which license is appropriate for your use, please -** review the following information: -** http://www.trolltech.com/products/qt/licensing.html or contact the -** sales department at sales@trolltech.com. -** -** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -** -****************************************************************************/ - -#ifndef MODELTEST_H -#define MODELTEST_H - -#include -#include -#include - -class ModelTest : public QObject -{ - Q_OBJECT - -public: - ModelTest(QAbstractItemModel *model, QObject *parent = 0); - -private Q_SLOTS: - void nonDestructiveBasicTest(); - void rowCount(); - void columnCount(); - void hasIndex(); - void index(); - void parent(); - void data(); - -protected Q_SLOTS: - void runAllTests(); - void layoutAboutToBeChanged(); - void layoutChanged(); - void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end); - void rowsInserted(const QModelIndex & parent, int start, int end); - void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); - void rowsRemoved(const QModelIndex & parent, int start, int end); - -private: - void checkChildren(const QModelIndex &parent, int currentDepth = 0); - - QAbstractItemModel *model; - - struct Changing - { - QModelIndex parent; - int oldSize; - QVariant last; - QVariant next; - }; - QStack insert; - QStack remove; - - bool fetchingMore; - - QList changing; -}; - -#endif diff --git a/src/utils/progresstreeview.cpp b/src/utils/progresstreeview.cpp deleted file mode 100644 index 58ca9edef..000000000 --- a/src/utils/progresstreeview.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "progresstreeview.h" - -ProgressTreeView::ProgressTreeView( QWidget* parent ) - : QTreeView( parent ) - , m_progressBar( 0 ) -{ -} diff --git a/src/utils/progresstreeview.h b/src/utils/progresstreeview.h deleted file mode 100644 index 89caba11e..000000000 --- a/src/utils/progresstreeview.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef PROGRESSTREEVIEW_H -#define PROGRESSTREEVIEW_H - -#include -#include - -class ProgressTreeView : public QTreeView -{ -Q_OBJECT - -public: - ProgressTreeView( QWidget* parent ); - - void connectProgressBar( QProgressBar* progressBar ) { m_progressBar = progressBar; progressBar->setVisible( false ); } - - void setProgressStarted( int length ) { if ( m_progressBar ) { m_progressBar->setRange( 0, length ); m_progressBar->setValue( 0 ); m_progressBar->setVisible( true ); } } - void setProgressEnded() { if ( m_progressBar ) m_progressBar->setVisible( false ); } - void setProgressCompletion( int completion ) { if ( m_progressBar ) m_progressBar->setValue( completion ); } - -private: - QProgressBar* m_progressBar; -}; - -#endif // PROGRESSTREEVIEW_H diff --git a/src/utils/proxystyle.cpp b/src/utils/proxystyle.cpp deleted file mode 100644 index edac0a743..000000000 --- a/src/utils/proxystyle.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "proxystyle.h" - -#include -#include -#include - - -void -ProxyStyle::drawPrimitive( PrimitiveElement pe, const QStyleOption* opt, QPainter* p, const QWidget* w ) const -{ - if ( pe != PE_FrameStatusBar ) - QProxyStyle::drawPrimitive( pe, opt, p, w ); -} diff --git a/src/utils/proxystyle.h b/src/utils/proxystyle.h deleted file mode 100644 index 356175f8b..000000000 --- a/src/utils/proxystyle.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef PROXYSTYLE_H -#define PROXYSTYLE_H - -#include -#include - -class ProxyStyle : public QProxyStyle -{ -public: - ProxyStyle() {} - - virtual void drawPrimitive( PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w = 0 ) const; -}; - -#endif // PROXYSTYLE_H diff --git a/src/utils/tomahawkutils.cpp b/src/utils/tomahawkutils.cpp deleted file mode 100644 index b7b6b494c..000000000 --- a/src/utils/tomahawkutils.cpp +++ /dev/null @@ -1,194 +0,0 @@ -#include "tomahawkutils.h" - -#include -#include -#include -#include - -#ifdef WIN32 - #include - #include -#endif - -#ifdef Q_WS_MAC - #include - #include -#endif - - -namespace TomahawkUtils -{ - -#ifdef Q_WS_MAC - -QString -appSupportFolderPath() -{ - // honestly, it is *always* this --mxcl - return QDir::home().filePath( "Library/Application Support" ); -} - -#endif // Q_WS_MAC - - -QDir -appConfigDir() -{ - QDir ret; - -#ifdef Q_WS_MAC - if( getenv( "HOME" ) ) - { - return QDir( QString( "%1" ).arg( getenv( "HOME" ) ) ); - } - else - { - qDebug() << "Error, $HOME not set."; - throw "$HOME not set"; - return QDir( "/tmp" ); - } - -#elif defined(Q_WS_WIN) - throw "TODO"; - return QDir( "c:\\" ); //TODO refer to Qt documentation to get code to do this - -#else - if( getenv( "XDG_CONFIG_HOME" ) ) - { - ret = QDir( QString( "%1/Tomahawk" ).arg( getenv( "XDG_CONFIG_HOME" ) ) ); - } - else if( getenv( "HOME" ) ) - { - ret = QDir( QString( "%1/.config/Tomahawk" ).arg( getenv( "HOME" ) ) ); - } - else - { - qDebug() << "Error, $HOME or $XDG_CONFIG_HOME not set."; - throw "Error, $HOME or $XDG_CONFIG_HOME not set."; - ret = QDir( "/tmp" ); - } - - if( !ret.exists() ) - { - ret.mkpath( ret.canonicalPath() ); - } - - return ret; -#endif -} - - -QDir -appDataDir() -{ - QString path; - - #ifdef WIN32 - if ( ( QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based ) == 0 ) - { - // Use this for non-DOS-based Windowses - char acPath[MAX_PATH]; - HRESULT h = SHGetFolderPathA( NULL, CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE, - NULL, 0, acPath ); - if ( h == S_OK ) - { - path = QString::fromLocal8Bit( acPath ); - } - } - #elif defined(Q_WS_MAC) - path = appSupportFolderPath(); - #elif defined(Q_WS_X11) - path = QDir::home().filePath( ".local/share" ); - #else - path = QCoreApplication::applicationDirPath(); - #endif - - path += "/" + QCoreApplication::organizationName(); - QDir d( path ); - d.mkpath( path ); - - return d; -} - - -QString -timeToString( int seconds ) -{ - int hrs = seconds / 60 / 60; - int mins = seconds / 60 % 60; - int secs = seconds % 60; - - if ( seconds < 0 ) - { - hrs = mins = secs = 0; - } - - return QString( "%1%2:%3" ).arg( hrs > 0 ? hrs < 10 ? "0" + QString::number( hrs ) + ":" : QString::number( hrs ) + ":" : "" ) - .arg( mins < 10 ? "0" + QString::number( mins ) : QString::number( mins ) ) - .arg( secs < 10 ? "0" + QString::number( secs ) : QString::number( secs ) ); -} - -QString -ageToString( const QDateTime& time ) -{ - QDateTime now = QDateTime::currentDateTime(); - - int mins = time.secsTo( now ) / 60; - int hours = mins / 60; - int days = time.daysTo( now ); - int weeks = days / 7; - int months = days / 30.42; - int years = months / 12; - - if ( years ) - { - if ( years > 1 ) - return QString( "%1 years ago" ).arg( years ); - else - return QString( "%1 year ago" ).arg( years ); - } - - if ( months ) - { - if ( months > 1 ) - return QString( "%1 months ago" ).arg( months ); - else - return QString( "%1 month ago" ).arg( months ); - } - - if ( weeks ) - { - if ( weeks > 1 ) - return QString( "%1 weeks ago" ).arg( weeks ); - else - return QString( "%1 week ago" ).arg( weeks ); - } - - if ( days ) - { - if ( days > 1 ) - return QString( "%1 days ago" ).arg( days ); - else - return QString( "%1 day ago" ).arg( days ); - } - - if ( hours ) - { - if ( hours > 1 ) - return QString( "%1 hours ago" ).arg( hours ); - else - return QString( "%1 hour ago" ).arg( hours ); - } - - if ( mins ) - { - if ( mins > 1 ) - return QString( "%1 minutes ago" ).arg( mins ); - else - return QString( "%1 minute ago" ).arg( mins ); - } - - return QString(); -} - -} // ns diff --git a/src/utils/tomahawkutils.h b/src/utils/tomahawkutils.h deleted file mode 100644 index f4919254a..000000000 --- a/src/utils/tomahawkutils.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef TOMAHAWKUTILS_H -#define TOMAHAWKUTILS_H - -class QDir; -class QDateTime; -class QString; - -namespace TomahawkUtils -{ - QDir appConfigDir(); - QDir appDataDir(); - - QString timeToString( int seconds ); - QString ageToString( const QDateTime& time ); -} - -#endif // TOMAHAWKUTILS_H diff --git a/src/utils/widgetdragfilter.h b/src/utils/widgetdragfilter.h deleted file mode 100644 index 2c4235a1a..000000000 --- a/src/utils/widgetdragfilter.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef WIDGET_DRAG_FILTER_H -#define WIDGET_DRAG_FILTER_H - -#include -#include -#include - -class QMouseEvent; -class QEvent; - -/** - * This class encapsulates an event filter on a widget that lets any drag events over the widget - * translate into move events for the whole application. - */ -class WidgetDragFilter : public QObject -{ - Q_OBJECT -public: - explicit WidgetDragFilter(QObject* parent = 0); - - virtual bool eventFilter(QObject* , QEvent* ); -private: - bool canDrag( QObject* obj, QMouseEvent* ev ) const; - - QWeakPointer m_target; // in case it's deleted under us - QPoint m_dragPoint; - bool m_dragStarted; -}; - -#endif diff --git a/src/web/api_v1.cpp b/src/web/api_v1.cpp index e69de29bb..67b2e4d8b 100644 --- a/src/web/api_v1.cpp +++ b/src/web/api_v1.cpp @@ -0,0 +1,352 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + +#include "api_v1.h" + +#include + +void +Api_v1::auth_1( QxtWebRequestEvent* event, QString arg ) +{ + qDebug() << "AUTH_1 HTTP" << event->url.toString() << arg; + + if ( !event->url.hasQueryItem( "website" ) || !event->url.hasQueryItem( "name" ) ) + { + qDebug() << "Malformed HTTP resolve request"; + send404( event ); + } + + QString formToken = uuid(); + + if ( event->url.hasQueryItem( "json" ) ) + { + // JSON response + QVariantMap m; + m[ "formtoken" ] = formToken; + sendJSON( m, event ); + } + else + { + // webpage request + QString authPage = RESPATH "www/auth.html"; + QHash< QString, QString > args; + if( event->url.hasQueryItem( "receiverurl" ) ) + args[ "url" ] = QUrl::fromPercentEncoding( event->url.queryItemValue( "receiverurl" ).toUtf8() ); + + args[ "formtoken" ] = formToken; + args[ "website" ] = QUrl::fromPercentEncoding( event->url.queryItemValue( "website" ).toUtf8() ); + args[ "name" ] = QUrl::fromPercentEncoding( event->url.queryItemValue( "name" ).toUtf8() ); + sendWebpageWithArgs( event, authPage, args ); + } +} + + +void +Api_v1::auth_2( QxtWebRequestEvent* event, QString arg ) +{ + qDebug() << "AUTH_2 HTTP" << event->url.toString() << arg; + QString params = QUrl::fromPercentEncoding( event->content->readAll() ); + params = params.mid( params.indexOf( '?' ) ); + QStringList pieces = params.split( '&' ); + QHash< QString, QString > queryItems; + foreach( const QString& part, pieces ) { + QStringList keyval = part.split( '=' ); + if( keyval.size() == 2 ) + queryItems.insert( keyval.first(), keyval.last() ); + else + qDebug() << "Failed parsing url parameters: " << part; + } + + qDebug() << "has query items:" << pieces; + if( !params.contains( "website" ) || !params.contains( "name" ) || !params.contains( "formtoken" ) ) + { + qDebug() << "Malformed HTTP resolve request"; + send404( event ); + return; + } + + QString website = queryItems[ "website" ]; + QString name = queryItems[ "name" ]; + QByteArray authtoken = uuid().toLatin1(); + qDebug() << "HEADERS:" << event->headers; + if( !queryItems.contains( "receiverurl" ) || queryItems.value( "receiverurl" ).isEmpty() ) + { + //no receiver url, so do it ourselves + if( queryItems.contains( "json" ) ) + { + QVariantMap m; + m[ "authtoken" ] = authtoken; + + sendJSON( m, event ); + } + else + { + QString authPage = RESPATH "www/auth.na.html"; + QHash< QString, QString > args; + args[ "authcode" ] = authtoken; + args[ "website" ] = website; + args[ "name" ] = name; + sendWebpageWithArgs( event, authPage, args ); + } + } + else + { + // do what the client wants + QUrl receiverurl = QUrl( queryItems.value( "receiverurl" ), QUrl::TolerantMode ); + receiverurl.addEncodedQueryItem( "authtoken", "#" + authtoken ); + qDebug() << "Got receiver url:" << receiverurl.toString(); + + QxtWebRedirectEvent* e = new QxtWebRedirectEvent( event->sessionID, event->requestID, receiverurl.toString() ); + postEvent( e ); + // TODO validation of receiverurl? + } + + DatabaseCommand_AddClientAuth* dbcmd = new DatabaseCommand_AddClientAuth( authtoken, website, name, event->headers.key( "ua" ) ); + Database::instance()->enqueue( QSharedPointer(dbcmd) ); +} + + +// all v1 api calls go to /api/ +void +Api_v1::api( QxtWebRequestEvent* event ) +{ + qDebug() << "HTTP" << event->url.toString(); + + const QUrl& url = event->url; + if( url.hasQueryItem( "method" ) ) + { + const QString method = url.queryItemValue( "method" ); + + if( method == "stat" ) return stat( event ); + if( method == "resolve" ) return resolve( event ); + if( method == "get_results" ) return get_results( event ); + } + + send404( event ); +} + + +// request for stream: /sid/ +void +Api_v1::sid( QxtWebRequestEvent* event, QString unused ) +{ + using namespace Tomahawk; + RID rid = event->url.path().mid( 5 ); + qDebug() << "Request for sid " << rid; + + result_ptr rp = Pipeline::instance()->result( rid ); + if( rp.isNull() ) + { + return send404( event ); + } + + QSharedPointer iodev = Servent::instance()->getIODeviceForUrl( rp ); + if( iodev.isNull() ) + { + return send404( event ); // 503? + } + + QxtWebPageEvent* e = new QxtWebPageEvent( event->sessionID, event->requestID, iodev ); + e->streaming = iodev->isSequential(); + e->contentType = rp->mimetype().toAscii(); + e->headers.insert( "Content-Length", QString::number( rp->size() ) ); + postEvent( e ); +} + + +void +Api_v1::send404( QxtWebRequestEvent* event ) +{ + qDebug() << "404" << event->url.toString(); + QxtWebPageEvent* wpe = new QxtWebPageEvent( event->sessionID, event->requestID, "

Not Found

" ); + wpe->status = 404; + wpe->statusMessage = "not feventound"; + postEvent( wpe ); +} + + +void +Api_v1::stat( QxtWebRequestEvent* event ) +{ + qDebug() << "Got Stat request:" << event->url.toString(); + m_storedEvent = event; + + if( !event->content.isNull() ) + qDebug() << "BODY:" << event->content->readAll(); + + if( event->url.hasQueryItem( "auth" ) ) + { + // check for auth status + DatabaseCommand_ClientAuthValid* dbcmd = new DatabaseCommand_ClientAuthValid( event->url.queryItemValue( "auth" ) ); + connect( dbcmd, SIGNAL( authValid( QString, QString, bool ) ), this, SLOT( statResult( QString, QString, bool ) ) ); + Database::instance()->enqueue( QSharedPointer(dbcmd) ); + } + else + { + statResult( QString(), QString(), false ); + } +} + + +void +Api_v1::statResult( const QString& clientToken, const QString& name, bool valid ) +{ + QVariantMap m; + m.insert( "name", "playdar" ); + m.insert( "version", "0.1.1" ); // TODO (needs to be >=0.1.1 for JS to work) + m.insert( "authenticated", valid ); // TODO + m.insert( "capabilities", QVariantList() ); + sendJSON( m, m_storedEvent ); + + m_storedEvent = 0; +} + + +void +Api_v1::resolve( QxtWebRequestEvent* event ) +{ + if( !event->url.hasQueryItem( "artist" ) || + !event->url.hasQueryItem( "track" ) ) + { + qDebug() << "Malformed HTTP resolve request"; + send404( event ); + } + + QString qid; + if ( event->url.hasQueryItem( "qid" ) ) + qid = event->url.queryItemValue( "qid" ); + else + qid = uuid(); + + Tomahawk::query_ptr qry = Tomahawk::Query::get( event->url.queryItemValue( "artist" ), event->url.queryItemValue( "track" ), event->url.queryItemValue( "album" ), qid ); + + QVariantMap r; + r.insert( "qid", qid ); + sendJSON( r, event ); +} + + +void +Api_v1::staticdata( QxtWebRequestEvent* event, const QString& str ) +{ + qDebug() << "STATIC request:" << event << str; + if( str.contains( "tomahawk_auth_logo.png" ) ) + { + QFile f( RESPATH "www/tomahawk_banner_small.png" ); + f.open( QIODevice::ReadOnly ); + QByteArray data = f.readAll(); + QxtWebPageEvent * e = new QxtWebPageEvent( event->sessionID, event->requestID, data ); + e->contentType = "image/png"; + postEvent( e ); + } +} + + +void +Api_v1::get_results( QxtWebRequestEvent* event ) +{ + if( !event->url.hasQueryItem("qid") ) + { + qDebug() << "Malformed HTTP get_results request"; + send404(event); + } + + using namespace Tomahawk; + query_ptr qry = Pipeline::instance()->query( event->url.queryItemValue( "qid" ) ); + if( qry.isNull() ) + { + send404( event ); + return; + } + + QVariantMap r; + r.insert( "qid", qry->id() ); + r.insert( "poll_interval", 1000 ); + r.insert( "refresh_interval", 1000 ); + r.insert( "poll_limit", 6 ); + r.insert( "solved", qry->solved() ); + r.insert( "query", qry->toVariant() ); + + QVariantList res; + foreach( Tomahawk::result_ptr rp, qry->results() ) + { + res << rp->toVariant(); + } + r.insert( "results", res ); + + sendJSON( r, event ); +} + + +void +Api_v1::sendJSON( const QVariantMap& m, QxtWebRequestEvent* event ) +{ + QJson::Serializer ser; + QByteArray ctype; + QByteArray body = ser.serialize( m ); + + if( event->url.hasQueryItem("jsonp") && !event->url.queryItemValue( "jsonp" ).isEmpty() ) + { + ctype = "text/javascript; charset=utf-8"; + body.prepend( QString("%1( ").arg( event->url.queryItemValue( "jsonp" ) ).toAscii() ); + body.append( " );" ); + } + else + { + ctype = "appplication/json; charset=utf-8"; + } + + QxtWebPageEvent * e = new QxtWebPageEvent( event->sessionID, event->requestID, body ); + e->contentType = ctype; + e->headers.insert( "Content-Length", QString::number( body.length() ) ); + postEvent( e ); + qDebug() << "JSON response" << event->url.toString() << body; +} + + +// load an html template from a file, replace args from map +// then serve +void +Api_v1::sendWebpageWithArgs( QxtWebRequestEvent* event, const QString& filenameSource, const QHash< QString, QString >& args ) +{ + if( !QFile::exists( filenameSource ) ) + qWarning() << "Passed invalid file for html source:" << filenameSource; + + QFile f( filenameSource ); + f.open( QIODevice::ReadOnly ); + QByteArray html = f.readAll(); + + foreach( const QString& param, args.keys() ) + { + html.replace( QString( "<%%1%>" ).arg( param.toUpper() ), args.value( param ).toUtf8() ); + } + // workaround for receiverurl + if( !args.keys().contains( "URL" ) ) + html.replace( QString( "<%URL%>" ).toLatin1(), QByteArray() ); + + + QxtWebPageEvent* e = new QxtWebPageEvent( event->sessionID, event->requestID, html ); + postEvent( e ); +} + + +void +Api_v1::index( QxtWebRequestEvent* event ) +{ + send404( event ); +} diff --git a/src/web/api_v1.h b/src/web/api_v1.h index 89b65b822..f7476990e 100644 --- a/src/web/api_v1.h +++ b/src/web/api_v1.h @@ -1,10 +1,28 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #ifndef TOMAHAWK_WEBAPI_V1 #define TOMAHAWK_WEBAPI_V1 // See: http://doc.libqxt.org/tip/qxtweb.html -#include "tomahawk/tomahawkapp.h" -#include "tomahawk/query.h" +#include "query.h" +#include "pipeline.h" #include "QxtHttpServerConnector" #include "QxtHttpSessionManager" @@ -17,6 +35,15 @@ #include #include +#include + +#include "network/servent.h" +#include "tomahawkutils.h" +#include "tomahawk/tomahawkapp.h" +#include +#include +#include +#include class Api_v1 : public QxtWebSlotService { @@ -24,163 +51,38 @@ Q_OBJECT public: - Api_v1(QxtAbstractWebSessionManager * sm, QObject * parent = 0 ) - : QxtWebSlotService(sm, parent) + Api_v1( QxtAbstractWebSessionManager* sm, QObject* parent = 0 ) + : QxtWebSlotService( sm, parent ) { } public slots: + // authenticating uses /auth_1 + // we redirect to /auth_2 for the callback + void auth_1( QxtWebRequestEvent* event, QString unused = QString() ); + void auth_2( QxtWebRequestEvent* event, QString unused = QString() ); // all v1 api calls go to /api/ - void api(QxtWebRequestEvent* event) - { - qDebug() << "HTTP" << event->url.toString(); - - const QUrl& url = event->url; - if( url.hasQueryItem( "method" ) ) - { - const QString method = url.queryItemValue( "method" ); - - if( method == "stat" ) return stat(event); - if( method == "resolve" ) return resolve(event); - if( method == "get_results" ) return get_results(event); - } - - send404( event ); - } + void api( QxtWebRequestEvent* event ); // request for stream: /sid/ - void sid(QxtWebRequestEvent* event, QString unused = "") - { - using namespace Tomahawk; - RID rid = event->url.path().mid(5); - qDebug() << "Request for sid " << rid; - result_ptr rp = APP->pipeline()->result( rid ); - if( rp.isNull() ) - { - return send404( event ); - } - QSharedPointer iodev = APP->getIODeviceForUrl( rp ); - if( iodev.isNull() ) - { - return send404( event ); // 503? - } - QxtWebPageEvent* e = new QxtWebPageEvent( event->sessionID, event->requestID, iodev ); - e->streaming = iodev->isSequential(); - e->contentType = rp->mimetype().toAscii(); - e->headers.insert("Content-Length", QString::number( rp->size() ) ); - postEvent(e); - return; - } + void sid( QxtWebRequestEvent* event, QString unused = QString() ); + void send404( QxtWebRequestEvent* event ); + void stat( QxtWebRequestEvent* event ); + void statResult( const QString& clientToken, const QString& name, bool valid ); + void resolve( QxtWebRequestEvent* event ); + void staticdata( QxtWebRequestEvent* event,const QString& ); + void get_results( QxtWebRequestEvent* event ); + void sendJSON( const QVariantMap& m, QxtWebRequestEvent* event ); - void send404( QxtWebRequestEvent* event ) - { - qDebug() << "404" << event->url.toString(); - QxtWebPageEvent* wpe = new QxtWebPageEvent(event->sessionID, event->requestID, "

Not Found

"); - wpe->status = 404; - wpe->statusMessage = "not found"; - postEvent( wpe ); - } + // load an html template from a file, replace args from map + // then serve + void sendWebpageWithArgs( QxtWebRequestEvent* event, const QString& filenameSource, const QHash< QString, QString >& args ); - void stat( QxtWebRequestEvent* event ) - { - QVariantMap m; - m.insert( "name", "playdar" ); - m.insert( "version", "0.1.1" ); // TODO (needs to be >=0.1.1 for JS to work) - m.insert( "authenticated", true ); // TODO - m.insert( "capabilities", QVariantList() ); - sendJSON( m, event ); - } - - void resolve( QxtWebRequestEvent* event ) - { - if( !event->url.hasQueryItem("artist") || - !event->url.hasQueryItem("track") ) - { - qDebug() << "Malformed HTTP resolve request"; - send404(event); - } - QString qid; - if( event->url.hasQueryItem("qid") ) qid = event->url.queryItemValue("qid"); - else qid = uuid(); - - QVariantMap m; - m.insert( "artist", event->url.queryItemValue("artist") ); - m.insert( "album", event->url.queryItemValue("album") ); - m.insert( "track", event->url.queryItemValue("track") ); - m.insert( "qid", qid ); - - Tomahawk::query_ptr qry( new Tomahawk::Query( m ) ); - APP->pipeline()->add( qry ); - - QVariantMap r; - r.insert( "qid", qid ); - sendJSON( r, event ); - } - - void get_results( QxtWebRequestEvent* event ) - { - if( !event->url.hasQueryItem("qid") ) - { - qDebug() << "Malformed HTTP get_results request"; - send404(event); - } - - using namespace Tomahawk; - query_ptr qry = APP->pipeline()->query( event->url.queryItemValue("qid") ); - if( qry.isNull() ) - { - send404( event ); - return; - } - - QVariantMap r; - r.insert( "qid", qry->id() ); - r.insert( "poll_interval", 1000 ); - r.insert( "refresh_interval", 1000 ); - r.insert( "poll_limit", 6 ); - r.insert( "solved", qry->solved() ); - r.insert( "query", qry->toVariant() ); - QVariantList res; - foreach( Tomahawk::result_ptr rp, qry->results() ) - { - res << rp->toVariant(); - } - r.insert( "results", res ); - - sendJSON( r, event ); - } - - void sendJSON( const QVariantMap& m, QxtWebRequestEvent* event ) - { - QJson::Serializer ser; - QByteArray ctype; - QByteArray body = ser.serialize( m ); - if( event->url.hasQueryItem("jsonp") && !event->url.queryItemValue( "jsonp" ).isEmpty() ) - { - ctype = "text/javascript; charset=utf-8"; - body.prepend( QString("%1( ").arg( event->url.queryItemValue( "jsonp" ) ).toAscii() ); - body.append( " );" ); - } - else - { - ctype = "appplication/json; charset=utf-8"; - } - QxtWebPageEvent * e = new QxtWebPageEvent( event->sessionID, event->requestID, body ); - e->contentType = ctype; - e->headers.insert( "Content-Length", QString::number( body.length() ) ); - postEvent( e ); - qDebug() << "JSON response" << event->url.toString() << body; - } - - - void index(QxtWebRequestEvent* event) - { - send404( event ); - return; - - } + void index( QxtWebRequestEvent* event ); +private: + QxtWebRequestEvent* m_storedEvent; }; #endif diff --git a/src/xmppbot/xmppbot.cpp b/src/xmppbot/xmppbot.cpp index 9da87516b..e6be8a57c 100644 --- a/src/xmppbot/xmppbot.cpp +++ b/src/xmppbot/xmppbot.cpp @@ -1,11 +1,30 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk 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. + * + * Tomahawk 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 Tomahawk. If not, see . + */ + #include "xmppbot.h" #include "tomahawk/tomahawkapp.h" #include "tomahawk/infosystem.h" -#include "tomahawk/album.h" -#include "tomahawk/typedefs.h" -#include -#include