diff --git a/4shared/4shared-icon.png b/4shared/content/contents/code/4shared-icon.png similarity index 100% rename from 4shared/4shared-icon.png rename to 4shared/content/contents/code/4shared-icon.png diff --git a/4shared/4shared-resolver.js b/4shared/content/contents/code/4shared-resolver.js similarity index 76% rename from 4shared/4shared-resolver.js rename to 4shared/content/contents/code/4shared-resolver.js index 612a37241..7562c99d1 100644 --- a/4shared/4shared-resolver.js +++ b/4shared/content/contents/code/4shared-resolver.js @@ -1,6 +1,21 @@ -/* - * (c) 2011 lasconic +/* === This file is part of Tomahawk Player - === + * + * Copyright 2011, lasconic + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . */ + var FSharedResolver = Tomahawk.extend(TomahawkResolver, { settings: { name: '4shared', diff --git a/4shared/content/contents/images/icon.png b/4shared/content/contents/images/icon.png new file mode 100644 index 000000000..14e4cff77 Binary files /dev/null and b/4shared/content/contents/images/icon.png differ diff --git a/4shared/content/metadata.json b/4shared/content/metadata.json new file mode 100644 index 000000000..18fa742d8 --- /dev/null +++ b/4shared/content/metadata.json @@ -0,0 +1,18 @@ +{ + "name": "4Shared", + "pluginName": "4shared", + "author": "lasconic", + "email": "lasconic@gmail.com", + "version": "0.1.5", + "website": "http://gettomahawk.com", + "description": "Looks for tracks to play from www.4shared.com", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/4shared-resolver.js", + "scripts": [], + "icon": "contents/images/icon.png", + "resources": [ + "contents/code/4shared-icon.png" + ] + } +} diff --git a/8tracks/8tracks.js b/8tracks/content/contents/code/8tracks.js similarity index 78% rename from 8tracks/8tracks.js rename to 8tracks/content/contents/code/8tracks.js index 6878d44fa..589ca1958 100644 --- a/8tracks/8tracks.js +++ b/8tracks/content/contents/code/8tracks.js @@ -1,7 +1,22 @@ -/* - * (c) 2011 Janez Troha (https://github.com/dz0ny) - * (c) 2011 Leo Franchi === + * + * Copyright 2011, Janez Troha (https://github.com/dz0ny) + * Copyright 2011, Leo Franchi + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . */ + var EightTracksResolver = Tomahawk.extend(TomahawkResolver, { settings: { name: '8tracks Resolver', diff --git a/8tracks/content/contents/images/icon.png b/8tracks/content/contents/images/icon.png new file mode 100644 index 000000000..155e894cb Binary files /dev/null and b/8tracks/content/contents/images/icon.png differ diff --git a/8tracks/content/metadata.json b/8tracks/content/metadata.json new file mode 100644 index 000000000..15a341c92 --- /dev/null +++ b/8tracks/content/metadata.json @@ -0,0 +1,16 @@ +{ + "name": "8tracks", + "pluginName": "8tracks", + "author": "Janez and Leo", + "email": "lfranchi@kde.org", + "version": "0.1", + "website": "http://gettomahawk.com", + "description": "Looks for tracks to play from 8tracks.com", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/8tracks.js", + "scripts": [], + "icon": "contents/images/icon.png", + "resources": [] + } +} diff --git a/HACKING.md b/HACKING.md new file mode 100644 index 000000000..e1d119379 --- /dev/null +++ b/HACKING.md @@ -0,0 +1,114 @@ +# Tomahawk Resolvers Developer Documentation + +## Developing resolvers + +The Tomahawk resolver API is currently still in flux. It is probably a good idea to use the existing resolvers as example. + +The API you should develop against is defined in [`tomahawk.js`](https://github.com/tomahawk-player/tomahawk/blob/master/data/js/tomahawk.js) and [`QtScriptResolverHelper`](https://github.com/tomahawk-player/tomahawk/blob/master/src/libtomahawk/resolvers/QtScriptResolver.h) in the Tomahawk main repo. + +If you have questions, look for us in #tomahawk on irc.freenode.net. + +### Licensing + +Tomahawk resolvers are considered derivative works of Tomahawk, specifically through the previously mentioned `tomahawk.js` and `QtScriptResolver.h` files. These files are released under a GNU General Public License, version 3 or later. + +Thus, developers who release a resolver are expected to +* release the resolver code they produce under a license compatible with Tomahawk, and specifically with Tomahawk's resolver interface, and +* add a copyright and licensing statement at the beginning of those resolver file(s) that interface with Tomahawk, with a wording that makes their licensing (and compatibility) clear. + +Exceptions to this requirement can be evaluated upon request by the Tomahawk team. + +## Packaging resolvers + +### Structure overview + +Starting with Tomahawk 0.7, all resolver directories must be structured as follows. + +Mandatory: +``` +content/ + + metadata.json +``` +Suggested: +``` +content/ + + metadata.json + + contents/ + + code/ + + .js + + config.ui + + + + images/ + + icon.png +``` + +This structure is commonly referred to as a resolver bundle, i.e. a resolver script with all related files and metadata. + +A bundle can be packaged or unpackaged (i.e. as it is in this repo), thus resolvers can be installed manually in two ways: +* from an unpackaged bundle, +* from a package (or axe). + +### Installing a resolver bundle + +To install a resolver from an unpackaged bundle (the preferred way for testing and development), in Tomahawk's Settings dialog click on "Install from file" and select the resolver's main .js file. + +Keep in mind that with such a path, Tomahawk expects to find the file `metadata.json` in `../..` from the main script's path. If `metadata.json` is not found, it is likely that your resolver directory is not structured properly. The resolver's main script will still be loaded, but any additional scripts will not and the accounts list in the Settings dialog will not show any metadata for the resolver (e.g. author, version, etc.). This is a **bad thing**. The only reason why a resolver without `metadata.json` is still loaded is backward compatibility. Plain unbundled .js files as resolvers are deprecated. You should update your resolver to a proper bundle structure as soon as possible. + +A packaged resolver bundle is a file with file extension `axe`. It is a compressed archive with all the contents of a resolver directory. To install such a bundle (the preferred way for end users who wish to install a resolver manually), in Tomahawk's Settings dialog click on "Install from file" and select the package file (`.axe`). + +**WARNING** for developers and testers: the installation process for an *unpackaged* bundle loads the resolver in-place. This means that any changed to the resolver script are applied immediately, simply by disabling and re-enabling the "installed" resolver with the account's checkbox in the accounts list. There is usually no need to remove and re-install the resolver. This also means that changes to the directory structure may make the resolver stop functioning. On the other hand, packaged bundles (axes) are decompressed and copied to a Tomahawk-managed directory (`/manualresolvers`) during the installation process, so any change to the axe can only be applied by re-installing. + +### Packaging + +#### metadata.json + +Every resolver bundle directory must contain a metadata file. This file must be named `metadata.json`, and it must be located in the directory `content` relative to the top-level resolver bundle directory. + +For example, this is a `metadata.json` file for Subsonic: +``` +{ + "name": "Subsonic", + "pluginName": "subsonic", + "author": "mack_t and Teo", + "email": "teo@kde.org", + "version": "0.5", + "website": "http://gettomahawk.com", + "description": "Searches your Subsonic server for music to play", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/subsonic.js", + "scripts": [], + "icon": "contents/images/icon.png", + "resources": [ + "contents/code/config.ui", + "contents/code/runnersid-icon.png", + "contents/code/subsonic-icon.png", + "contents/code/subsonic.png" + ] + } +} +``` + +For most purposes all the fields are mandatory. + +**WARNING**: the manifest object **must** list all the files required by the resolver. Unlisted scripts will not be loaded, and any unlisted files will not be packaged. + +#### makeaxe.rb + +If your resolver directory conforms to the previously described structure and your `metadata.json` is complete, the harder part is done. The Tomahawk team provides you with a script to automate the packaging process: [`makeaxe.rb`](admin/makeaxe.rb). You will need [ruby](http://www.ruby-lang.org/en/) 1.9.2 or later and the [zip](https://rubygems.org/gems/zip) gem. + +To create a package from a resolver directory, simply run `makeaxe.rb` with the directory path passed as parameter. + +E.g. on Linux and Mac OS X, for Subsonic and from the repository root, you would do the following: +``` +% ruby admin/makeaxe.rb subsonic +% ls subsonic +content/ subsonic-0.5.axe subsonic-0.5.md5 +``` +In this case, `subsonic-0.5.axe` is the compressed bundle and `subsonic-0.5.md5` is the MD5 checksum file. + +Please note that `makeaxe.rb` does not simply compress the contents of the directory, it also checks the metadata file and adds additional data, including a packaging timestamp and the revision hash, if any. + +Optionally, if you pass the `--release` parameter to `makeaxe.rb` it will not include the commit hash in the axe, as would be expected in a release-worthy package. + +Happy packaging! diff --git a/README b/README deleted file mode 100644 index 6746a6551..000000000 --- a/README +++ /dev/null @@ -1,3 +0,0 @@ -Here go the resolvers :-) - -http://tomahawk-player.org/ diff --git a/README.md b/README.md new file mode 100644 index 000000000..8d7c36d98 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Tomahawk Resolvers + +Supported resolvers are distributed and updated automatically through Tomahawk's Settings dialog. +To manually intstall a resolver: either +* clone this repo, +* download the .zip, or +* download all the files within the individual resolver folder you are installing. + +After you have the files locally, open Tomahawk's preferences and from the "Services" tab click "Install from File" and select the .axe or .js file for the resolver you are installing. + +Since March 2013 Tomahawk resolvers have switched to a new directory structure for easy packaging. Ideally, you should download nightly .axe files, if available. + +For developer documentation, see [HACKING.md](HACKING.md). diff --git a/admin/json2desktop.rb b/admin/json2desktop.rb new file mode 100755 index 000000000..33b9408a9 --- /dev/null +++ b/admin/json2desktop.rb @@ -0,0 +1,105 @@ +#!/usr/bin/env ruby +# === This file is part of Tomahawk Player - === +# +# Copyright 2013, Teo Mrnjavac +# +# Tomahawk is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Tomahawk is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU 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 script converts a json metadata/manifest file into a desktop file for Synchrotron. +# + +require 'json' + +if ARGV.length < 1 + puts "This script converts a Tomahawk resolver's metadata/manifest JSON file" + puts "into a desktop file for Synchrotron." + puts "Usage: ruby json2desktop.rb path_to_metadata_file.json" + exit +end + +inputPath = File.absolute_path( ARGV[0] ) +outputPath = File.join( File.dirname( inputPath ), "metadata.desktop" ) + +if not File.exists?( inputPath ) or not File.readable?( inputPath ) + puts "Cannot read input file." + exit +end + +if File.exists?( outputPath ) and not File.writable?( outputPath ) + puts "Cannot write to output file." + exit +end + +inputFile = File.open( inputPath, 'r' ) +inputString = inputFile.read +input = JSON.parse( inputString ) +inputFile.close unless inputFile == nil + +# check if outputPath exists, maybe save stuff and/or overwrite, yes? +File.open( outputPath, 'w' ) do |f| + f.write "\ +############################################################################ +## Desktop file generated from JSON file '#{File.basename( inputPath )}' +## +## Created: #{Time.now.utc.to_s} +## by: json2desktop.rb, https://github.com/tomahawk-player +## +## #### WARNING! #### +## All changes made to this file will be lost! +############################################################################ + +[Desktop Entry]\n" + + unless input["name"].nil? || input["name"].empty? + f.write "Name=#{input["name"]}\n" + end + + unless input["description"].nil? || input["description"].empty? + f.write "Comment=#{input["description"]}\n" + end + + f.write "\nType=Service\nX-KDE-ServiceTypes=Tomahawk/Resolver\n" + + unless input["manifest"].nil? + unless input["manifest"]["main"].nil? || input["manifest"]["main"].empty? + f.write "X-Synchrotron-MainScript=#{input["manifest"]["main"]}\n" + end + end + + f.write "\n" + + unless input["pluginName"].nil? || input["pluginName"].empty? + f.write "X-KDE-PluginInfo-Name=#{input["pluginName"]}\n" + end + + f.write "X-KDE-PluginInfo-Category=Resolver\n" + + unless input["author"].nil? || input["author"].empty? + f.write "X-KDE-PluginInfo-Author=#{input["author"]}\n" + end + + unless input["email"].nil? || input["email"].empty? + f.write "X-KDE-PluginInfo-Email=#{input["email"]}\n" + end + + unless input["version"].nil? || input["version"].empty? + f.write "X-KDE-PluginInfo-Version=#{input["version"]}\n" + end + + unless input["website"].nil? || input["website"].empty? + f.write "X-KDE-PluginInfo-Website=#{input["website"]}\n" + end +end diff --git a/admin/makeaxe.rb b/admin/makeaxe.rb new file mode 100755 index 000000000..4903708b5 --- /dev/null +++ b/admin/makeaxe.rb @@ -0,0 +1,166 @@ +#!/usr/bin/env ruby +# === This file is part of Tomahawk Player - === +# +# Copyright 2013, Teo Mrnjavac +# +# Tomahawk is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Tomahawk is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU 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 script reads a json metadata/manifest file and creates a Tomahawk +# resolver bundle (axe). +# The script should be executed with the top-level resolver directory path as +# parameter, and expects the following structure: +# Mandatory: +# content/ +# + metadata.json +# Suggested: +# content/ +# + metadata.json +# + contents/ +# + code/ +# + .js +# + config.ui +# + +# + images/ +# + icon.png +# + +require 'json' +require 'rubygems' +require 'zip/zip' +require 'digest/md5' + +BUNDLEVERSION = 1 #might never be used but best to plan ahead + +def usage + puts "This script creates a Tomahawk resolver bundle." + puts "\nMake sure you have the zip gem." + puts "\nUsage: ruby makeaxe.rb path_to_resolver_directory [options]" + puts " --release\tskip trying to add the git revision hash to the bundle" + puts " --help\t\tthis help message" +end + +if ARGV.length < 1 or not ARGV.delete( "--help" ).nil? + usage + exit +end + +if not ARGV.delete( "--release" ).nil? + release = true +else + release = false +end + +inputPath = File.absolute_path( ARGV[0] ) + +if not Dir.exists?( inputPath ) + puts "Bad input directory path." + exit +end + +metadataRelPath = "content/metadata.json" +metadataPath = File.join( inputPath, metadataRelPath ) + +if not File.exists?( metadataPath ) or not File.readable?( metadataPath ) + puts "Cannot find metadata file." + puts "Make sure #{metadataRelPath} exists and is readable." + exit +end + +metadataFile = File.open( metadataPath, 'r' ) +metadataString = metadataFile.read +metadata = JSON.parse( metadataString ) +metadataFile.close unless metadataFile == nil + +if not metadata["pluginName"].nil? and + not metadata["name"].nil? and + not metadata["version"].nil? and + not metadata["description"].nil? and + not metadata["type"].nil? and + not metadata["manifest"].nil? and + not metadata["manifest"]["main"].nil? and + not metadata["manifest"]["icon"].nil? + outputPath = File.join( inputPath, metadata["pluginName"] + "-" + metadata["version"] + ".axe" ) + puts "Bundle metadata looks ok." +else + puts "Bad metadata file." + exit +end + +# Let's add some stuff to the metadata file, this is information that's much +# easier to fill in automatically now than manually whenever. +# * Timestamp of right now i.e. packaging time. +# * Git revision because it makes sense, especially during development. +# * Bundle format version, which might never be used but we add it just in +# case we ever need to distinguish one bundle format from another. +# We save it all as _metadata.json, which then gets added to the archive as +# metadata.json instead of the original one. +_metadataPath = File.join( inputPath, "content/_metadata.json" ) +if not File.exists?( _metadataPath ) or File.writable?( _metadataPath ) + File.open( _metadataPath, 'w' ) do |f| + metadata["timestamp"] = Time.now.utc.to_i + + unless release + gitCmd = "git rev-parse --short HEAD 2>&1" + inGit = system( gitCmd + "&>/dev/null" ) #will return true only if we're in a repo + if inGit + revision = %x[ #{gitCmd} ].sub( "\n", "" ) + metadata["revision"] = revision + end + end + + metadata["bundleVersion"] = BUNDLEVERSION + + f.write( JSON.pretty_generate( metadata ) ) + end +end + +# Let's do some zipping according to the manifest. +filesToZip = [] +begin + m = metadata["manifest"] + filesToZip << File.join( "content", m["main"] ) + m["scripts"].each do |s| + filesToZip << File.join( "content", s ) + end + filesToZip << File.join( "content", m["icon"] ) + m["resources"].each do |s| + filesToZip << File.join( "content", s ) + end +end + +puts "Creating package for #{metadata["name"]}: '#{File.basename( outputPath )}'." + +if File.exists?( outputPath ) + File.delete( outputPath ) +end + +Zip::ZipFile.open( outputPath, Zip::ZipFile::CREATE ) do |z| + filesToZip.each do |relPath| + z.add( relPath, File.join( inputPath, relPath ) ) + end + z.add( metadataRelPath, _metadataPath ) +end + +puts "Cleaning up." + +File.delete( _metadataPath ) +File.open( outputPath, 'r' ) do |f| + File.open( outputPath.sub( "axe", "md5" ), 'w' ) do |g| + g.write( Digest::MD5.hexdigest( f.read ).to_s + "\t" + File.basename( outputPath ) ) + end +end + +puts "All done. Have a nice day." diff --git a/admin/linux/create_synchrotron.rb b/admin/spotify-synchrotron/linux/create_synchrotron.rb similarity index 100% rename from admin/linux/create_synchrotron.rb rename to admin/spotify-synchrotron/linux/create_synchrotron.rb diff --git a/admin/mac/create_synchrotron.rb b/admin/spotify-synchrotron/mac/create_synchrotron.rb similarity index 100% rename from admin/mac/create_synchrotron.rb rename to admin/spotify-synchrotron/mac/create_synchrotron.rb diff --git a/admin/win/create_synchrotron.rb b/admin/spotify-synchrotron/win/create_synchrotron.rb similarity index 100% rename from admin/win/create_synchrotron.rb rename to admin/spotify-synchrotron/win/create_synchrotron.rb diff --git a/ampache/ampache.png b/ampache/ampache.png deleted file mode 100644 index a106239a8..000000000 Binary files a/ampache/ampache.png and /dev/null differ diff --git a/ampache/content/contents/code/ampache-icon.png b/ampache/content/contents/code/ampache-icon.png new file mode 100644 index 000000000..6c4155f06 Binary files /dev/null and b/ampache/content/contents/code/ampache-icon.png differ diff --git a/ampache/ampache-resolver.js b/ampache/content/contents/code/ampache-resolver.js similarity index 76% rename from ampache/ampache-resolver.js rename to ampache/content/contents/code/ampache-resolver.js index 59b834ac0..59ddbe706 100644 --- a/ampache/ampache-resolver.js +++ b/ampache/content/contents/code/ampache-resolver.js @@ -29,6 +29,7 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { timeout: 5, limit: 10 }, + getConfigUi: function () { var uiData = Tomahawk.readBase64("config.ui"); return { @@ -54,6 +55,7 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { }] }; }, + newConfigSaved: function () { var userConfig = this.getUserConfig(); if ((userConfig.username != this.username) || (userConfig.password != this.password) || (userConfig.ampache != this.ampache)) { @@ -67,6 +69,41 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { this.init(); } }, + + prepareHandshake: function() + { + // prepare handshake arguments + var time = Tomahawk.timestamp(); + var key = Tomahawk.sha256(this.password); + this.passphrase = Tomahawk.sha256(time + key); + + // do the handshake + this.params = { + timestamp: time, + version: 350001, + user: this.username + } + }, + + applyHandshake: function(xmlDoc) + { + var roots = xmlDoc.getElementsByTagName("root"); + Tomahawk.log("Old auth token: " + this.auth); + this.auth = roots[0] === undefined ? false : Tomahawk.valueForSubNode(roots[0], "auth"); + Tomahawk.log("New auth token: " + this.auth); + var pingInterval = parseInt(roots[0] === undefined ? 0 : Tomahawk.valueForSubNode(roots[0], "session_length")) * 1000; + var trackCount = roots[0] === undefined ? (-1) : Tomahawk.valueForSubNode(roots[0], "songs"); + if ( trackCount > -1 ) + this.trackCount = parseInt(trackCount); + + // all fine, set the resolver to ready state + this.ready = true; + window.sessionStorage["ampacheAuth"] = this.auth; + + // setup pingTimer + if (pingInterval) window.setInterval(this.ping, pingInterval - 60); + }, + init: function () { // check resolver is properly configured var userConfig = this.getUserConfig(); @@ -85,42 +122,24 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { this.password = userConfig.password; this.ampache = userConfig.ampache; - // prepare handshake arguments - var time = Tomahawk.timestamp(); - var key = Tomahawk.sha256(this.password); - var passphrase = Tomahawk.sha256(time + key); + this.prepareHandshake(); - // do the handshake - var params = { - timestamp: time, - version: 350001, - user: this.username - } try { var that = this; - this.apiCall('handshake', passphrase, params, function (xhr) { - + Tomahawk.asyncRequest(this.generateUrl('handshake', this.passphrase, this.params), function (xhr){ Tomahawk.log(xhr.responseText); // parse the result var domParser = new DOMParser(); xmlDoc = domParser.parseFromString(xhr.responseText, "text/xml"); - var roots = xmlDoc.getElementsByTagName("root"); - that.auth = roots[0] === undefined ? false : Tomahawk.valueForSubNode(roots[0], "auth"); - var pingInterval = parseInt(roots[0] === undefined ? 0 : Tomahawk.valueForSubNode(roots[0], "session_length")) * 1000; + + that.applyHandshake(xmlDoc); // inform the user if something went wrong if (!that.auth) { Tomahawk.log("INVALID HANDSHAKE RESPONSE: " + xhr.responseText); } - // all fine, set the resolver to ready state - that.ready = true; - window.sessionStorage["ampacheAuth"] = that.auth; - - // setup pingTimer - if (pingInterval) window.setInterval(that.ping, pingInterval - 60); - Tomahawk.log("Ampache Resolver properly initialised!"); Tomahawk.reportCapabilities( TomahawkResolverCapability.Browsable | TomahawkResolverCapability.AccountFactory ); @@ -132,6 +151,7 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { this.element = document.createElement('div'); }, + generateUrl: function (action, auth, params) { var ampacheUrl = this.ampache + "/server/xml.server.php?"; if (params === undefined) params = []; @@ -147,7 +167,7 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { return ampacheUrl; }, - apiCallSync: function (action, auth, params) { + apiCallSync: function (action, auth, params) { //do not use this because it doesn't do re-auth var ampacheUrl = this.generateUrl(action, auth, params); return Tomahawk.syncRequest(ampacheUrl); @@ -157,23 +177,55 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { var ampacheUrl = this.generateUrl(action, auth, params); Tomahawk.log("Ampache API call: " + ampacheUrl ); - Tomahawk.asyncRequest(ampacheUrl, callback); + + var that = this; + Tomahawk.asyncRequest(ampacheUrl, function(xhr) { + var result = xhr.responseText; + Tomahawk.log(result); + + var domParser = new DOMParser(); + xmlDoc = domParser.parseFromString(result, "text/xml"); + + var error = xmlDoc.getElementsByTagName("error")[0]; + + if ( typeof error != 'undefined' && + error.getAttribute("code") == "401" ) //session expired + { + Tomahawk.log("Let's reauth!"); + that.prepareHandshake(); + Tomahawk.asyncRequest(that.generateUrl('handshake',that.passphrase,that.params), function(xhr){ + var hsResponse = xhr.responseText; + Tomahawk.log(hsResponse); + xmlDoc = domParser.parseFromString(hsResponse, "text/xml"); + that.applyHandshake(xmlDoc); + + //reauth done, let's retry the API call + ampacheUrl = that.generateUrl(action,that.auth,params); + Tomahawk.asyncRequest(ampacheUrl, function(xhr){ + result = xhr.responseText; + Tomahawk.log(result); + xmlDoc = domParser.parseFromString(result, "text/xml"); + callback(xmlDoc); + }); + }); + } + else + callback(xmlDoc) + }); }, ping: function () { // this is called from window scope (setInterval), so we need to make methods and data accessible from there Tomahawk.log(AmpacheResolver.apiCall('ping', AmpacheResolver.auth, {}, function () {})); }, + decodeEntity : function(str) { this.element.innerHTML = str; return this.element.textContent; }, - parseSongResponse: function(responseString) { - // parse xml - var domParser = new DOMParser(); - xmlDoc = domParser.parseFromString(responseString, "text/xml"); + parseSongResponse: function(xmlDoc) { var results = new Array(); // check the repsonse var songElements = xmlDoc.getElementsByTagName("song")[0]; @@ -204,8 +256,9 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { } return results; }, - parseSearchResponse: function (qid, responseString) { - var results = this.parseSongResponse(responseString); + + parseSearchResponse: function (qid, xmlDoc) { + var results = this.parseSongResponse(xmlDoc); // prepare the return var return1 = { @@ -216,9 +269,11 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { Tomahawk.addTrackResults(return1); //Tomahawk.dumpResult( return1 ); }, + resolve: function (qid, artist, album, title) { return this.search(qid, title); }, + search: function (qid, searchString) { if (!this.ready) return { qid: qid @@ -232,8 +287,8 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { }; var that = this; - this.apiCall("search_songs", AmpacheResolver.auth, params, function (xhr) { - that.parseSearchResponse(qid, xhr.responseText); + this.apiCall("search_songs", AmpacheResolver.auth, params, function (xmlDoc) { + that.parseSearchResponse(qid, xmlDoc); }); //Tomahawk.log( searchResult ); @@ -244,15 +299,7 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { var that = this; this.artistIds = {}; - this.apiCall("artists", AmpacheResolver.auth, [], function (xhr) { - var searchResult = xhr.responseText; - - Tomahawk.log(searchResult); - - // parse xml - var domParser = new DOMParser(); - xmlDoc = domParser.parseFromString(searchResult, "text/xml"); - + this.apiCall("artists", AmpacheResolver.auth, [], function (xmlDoc) { var results = []; // check the repsonse @@ -276,6 +323,7 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { Tomahawk.addArtistResults( return_artists ); } ); }, + albums: function (qid, artist) { var artistId = this.artistIds[artist]; this.albumIdsForArtist = {}; @@ -285,15 +333,7 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { filter: artistId }; - this.apiCall("artist_albums", AmpacheResolver.auth, params, function (xhr) { - var searchResult = xhr.responseText; - - Tomahawk.log( searchResult ); - - // parse xml - var domParser = new DOMParser(); - xmlDoc = domParser.parseFromString(searchResult, "text/xml"); - + this.apiCall("artist_albums", AmpacheResolver.auth, params, function (xmlDoc) { var results = []; // check the repsonse @@ -322,6 +362,7 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { Tomahawk.addAlbumResults( return_albums ); } ); }, + tracks: function (qid, artist, album) { var artistObject = this.albumIdsForArtist[artist]; var albumId = artistObject[album]; @@ -333,12 +374,8 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { filter: albumId }; - this.apiCall("album_songs", AmpacheResolver.auth, params, function (xhr) { - var searchResult = xhr.responseText; - - Tomahawk.log( searchResult ); - - var tracks_result = that.parseSongResponse(searchResult); + this.apiCall("album_songs", AmpacheResolver.auth, params, function (xmlDoc) { + var tracks_result = that.parseSongResponse(xmlDoc); tracks_result.sort( function(a,b) { if ( a.albumpos < b.albumpos ) return -1; @@ -357,6 +394,32 @@ var AmpacheResolver = Tomahawk.extend(TomahawkResolver, { Tomahawk.log("Ampache tracks about to return: " + JSON.stringify( return_tracks )); Tomahawk.addAlbumTrackResults( return_tracks ); } ); + }, + + collection: function() + { + //strip http:// and trailing slash + var desc = this.ampache.replace(/^http:\/\//,"") + .replace(/\/$/, "") + .replace(/\/remote.php\/ampache/, ""); + + var return_object = { + prettyname: "Ampache", + description: desc, + iconfile: "ampache-icon.png" + }; + + if ( typeof( this.trackCount ) !== 'undefined' ) + return_object["trackcount"] = this.trackCount; + + //stupid check if it's an ownCloud instance + if (this.ampache.indexOf("/remote.php/ampache") !== -1) + { + return_object["prettyname"] = "ownCloud"; + return_object["iconfile"] = "owncloud-icon.png"; + } + + return return_object; } }); diff --git a/ampache/content/contents/code/ampache.png b/ampache/content/contents/code/ampache.png new file mode 100644 index 000000000..286664244 Binary files /dev/null and b/ampache/content/contents/code/ampache.png differ diff --git a/ampache/config.ui b/ampache/content/contents/code/config.ui similarity index 83% rename from ampache/config.ui rename to ampache/content/contents/code/config.ui index 5bddd082e..c9efa9597 100644 --- a/ampache/config.ui +++ b/ampache/content/contents/code/config.ui @@ -7,7 +7,7 @@ 0 0 447 - 300 + 318 @@ -41,13 +41,11 @@ - 12 - true + false - For owncloud installs, server url is -http://[owncloud url]/apps/media + <html><head/><body><p>For ownCloud instances, the Server URL is<br/>http://<span style=" color:#585858;">ownCloud base url</span>/remote.php/ampache</p></body></html> Qt::AlignCenter @@ -59,7 +57,7 @@ http://[owncloud url]/apps/media - Username + Username: @@ -69,7 +67,7 @@ http://[owncloud url]/apps/media - Password + Password: @@ -83,7 +81,7 @@ http://[owncloud url]/apps/media - Server URL + Server URL: diff --git a/ampache/ampache-icon.png b/ampache/content/contents/code/owncloud-icon.png similarity index 100% rename from ampache/ampache-icon.png rename to ampache/content/contents/code/owncloud-icon.png diff --git a/ampache/owncloud.png b/ampache/content/contents/code/owncloud.png similarity index 100% rename from ampache/owncloud.png rename to ampache/content/contents/code/owncloud.png diff --git a/ampache/content/contents/images/icon.png b/ampache/content/contents/images/icon.png new file mode 100644 index 000000000..6c4155f06 Binary files /dev/null and b/ampache/content/contents/images/icon.png differ diff --git a/ampache/content/metadata.json b/ampache/content/metadata.json new file mode 100644 index 000000000..d6fa395ab --- /dev/null +++ b/ampache/content/metadata.json @@ -0,0 +1,22 @@ +{ + "name": "Ampache", + "pluginName": "ampache", + "author": "Dominik, Leo and Teo", + "email": "teo@kde.org", + "version": "0.3", + "website": "http://gettomahawk.com", + "description": "Connects to an Ampache or ownCloud server and resolves tracks", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/ampache-resolver.js", + "scripts": [], + "icon": "contents/images/icon.png", + "resources": [ + "contents/code/config.ui", + "contents/code/ampache-icon.png", + "contents/code/ampache.png", + "contents/code/owncloud-icon.png", + "contents/code/owncloud.png" + ] + } +} diff --git a/beets/beets.js b/beets/beets.js deleted file mode 100644 index 785fed9d7..000000000 --- a/beets/beets.js +++ /dev/null @@ -1,114 +0,0 @@ -// Map all the audio types supported by beets to extensions and MIME types. -var AUDIO_TYPES = { - 'MP3': ['mp3', 'audio/mpeg'], - 'AAC': ['m4a', 'audio/mp4'], - 'OGG': ['ogg', 'audio/ogg'], - 'FLAC': ['flac', 'audio/x-flac'], - 'APE': ['ape', 'audio/ape'], - 'WavPack': ['wv', 'audio/x-wavpack'], - 'MusePack': ['mpc', 'audio/x-musepack'] -}; - -var BeetsResolver = Tomahawk.extend(TomahawkResolver, -{ - // Basic setup. - settings: - { - name: 'beets', - weight: 95, - timeout: 5 - }, - - // Resolution. - resolve: function( qid, artist, album, title ) - { - this.beetsQuery(qid, - ['artist:' + artist, 'album:' + album, 'title:' + title] - ); - }, - search: function( qid, searchString ) - { - this.beetsQuery(qid, searchString.split(' ')); - }, - baseUrl: function() { - return 'http://' + this.host + ':' + this.port; - }, - beetsQuery: function( qid, queryParts ) - { - var baseUrl = this.baseUrl(); - var url = baseUrl + '/item/query/'; - for (var i = 0; i < queryParts.length; ++i) { - url += encodeURIComponent(queryParts[i]); - url += '/'; - } - url = url.substring(0, url.length - 1); // Remove last /. - - Tomahawk.asyncRequest(url, function(xhr) { - var resp = JSON.parse(xhr.responseText); - var items = resp['results']; - - var searchResults = []; - for (var i = 0; i < items.length; ++i) { - item = items[i]; - var type_info = AUDIO_TYPES[item['format']]; - searchResults.push({ - artist: item['artist'], - album: item['album'], - track: item['title'], - albumpos: item['track'], - source: "beets", - url: baseUrl + '/item/' + item['id'] + '/file', - bitrate: Math.floor(item['bitrate'] / 1024), - duration: Math.floor(item['length']), - size: (item['size'] || 0), - score: 1.0, - extension: type_info[0], - mimetype: type_info[1], - year: item['year'] - }); - } - - Tomahawk.addTrackResults({ - qid: qid, - results: searchResults - }) - }); - }, - - // Configuration. - getConfigUi: function () { - var uiData = Tomahawk.readBase64("config.ui"); - return { - "widget": uiData, - "fields": [{ - name: "host", - widget: "hostField", - property: "text" - }, { - name: "port", - widget: "portField", - property: "text" - }] - }; - }, - newConfigSaved: function () { - var userConfig = this.getUserConfig(); - - this.host = userConfig.host || 'localhost'; - - var port = userConfig.port; - port = parseInt(port); - if (isNaN(port) || !port) { - port = 8337; - } - userConfig.port = port; - - this.port = port; - }, - - // Defaults. - host: 'localhost', - port: 8337 -}); - -Tomahawk.resolver.instance = BeetsResolver; diff --git a/beets/content/contents/code/beets-icon.png b/beets/content/contents/code/beets-icon.png new file mode 100644 index 000000000..3f8f6b286 Binary files /dev/null and b/beets/content/contents/code/beets-icon.png differ diff --git a/beets/content/contents/code/beets-resolver.js b/beets/content/contents/code/beets-resolver.js new file mode 100644 index 000000000..6e0e5d1c9 --- /dev/null +++ b/beets/content/contents/code/beets-resolver.js @@ -0,0 +1,119 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012, Adrian Sampson + * Copyright 2013, Uwe L. Korn + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +// Map all the audio types supported by beets to extensions and MIME types. +var AUDIO_TYPES = { + 'MP3': ['mp3', 'audio/mpeg'], + 'AAC': ['m4a', 'audio/mp4'], + 'OGG': ['ogg', 'audio/ogg'], + 'FLAC': ['flac', 'audio/x-flac'], + 'APE': ['ape', 'audio/ape'], + 'WavPack': ['wv', 'audio/x-wavpack'], + 'MusePack': ['mpc', 'audio/x-musepack'] +}; + +var BeetsResolver = Tomahawk.extend(TomahawkResolver, { + settings: { + name: 'beets', + icon: 'beets-icon.png', + weight: 95, + timeout: 5 + }, + + // Resolution. + resolve: function (qid, artist, album, title) { + this.beetsQuery(qid, ['artist:' + artist, 'album:' + album, 'title:' + title]); + }, + + search: function (qid, searchString) { + this.beetsQuery(qid, searchString.split(' ')); + }, + + baseUrl: function () { + return 'http://' + this.host + ':' + this.port; + }, + + beetsQuery: function (qid, queryParts) { + var baseUrl = this.baseUrl(), + url = this.baseUrl() + '/item/query/'; + queryParts.forEach(function (item) { + url += encodeURIComponent(item); + url += '/'; + }); + url = url.substring(0, url.length - 1); // Remove last /. + + Tomahawk.asyncRequest(url, function (xhr) { + var resp = JSON.parse(xhr.responseText), + items = resp.results, + searchResults = []; + items.forEach(function (item) { + var type_info = AUDIO_TYPES[item.format]; + searchResults.push({ + artist: item.artist, + album: item.album, + track: item.title, + albumpos: item.track, + source: "beets", + url: baseUrl + '/item/' + item.id + '/file', + bitrate: Math.floor(item.bitrate / 1024), + duration: Math.floor(item.length), + size: (item.size || 0), + score: 1.0, + extension: type_info[0], + mimetype: type_info[1], + year: item.year + }); + }); + Tomahawk.addTrackResults({ + qid: qid, + results: searchResults + }); + }); + }, + + // Configuration. + getConfigUi: function () { + var uiData = Tomahawk.readBase64("config.ui"); + return { + "widget": uiData, + "fields": [{ + name: "host", + widget: "hostField", + property: "text" + }, { + name: "port", + widget: "portField", + property: "text" + }] + }; + }, + newConfigSaved: function () { + this.init(); + }, + init: function () { + var userConfig = this.getUserConfig(); + this.host = userConfig.host || 'localhost'; + this.port = parseInt(userConfig.port, 10); + if (isNaN(this.port) || !this.port) { + this.port = 8337; + } + }, +}); + +Tomahawk.resolver.instance = BeetsResolver; diff --git a/beets/config.ui b/beets/content/contents/code/config.ui similarity index 100% rename from beets/config.ui rename to beets/content/contents/code/config.ui diff --git a/beets/content/contents/images/icon.png b/beets/content/contents/images/icon.png new file mode 100644 index 000000000..3f8f6b286 Binary files /dev/null and b/beets/content/contents/images/icon.png differ diff --git a/beets/content/metadata.json b/beets/content/metadata.json new file mode 100644 index 000000000..f40218960 --- /dev/null +++ b/beets/content/metadata.json @@ -0,0 +1,19 @@ +{ + "name": "Beets", + "pluginName": "beets", + "author": "Adrian and Uwe", + "email": "uwelk@xhochy.com", + "version": "0.3", + "website": "http://gettomahawk.com", + "description": "Connects to a beets server and resolves tracks", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/beets-resolver.js", + "scripts": [], + "icon": "contents/images/icon.png", + "resources": [ + "contents/code/config.ui" + "contents/code/beets-icon.png" + ] + } +} diff --git a/dilandau/dilandau-icon.png b/dilandau/content/contents/code/dilandau-icon.png similarity index 100% rename from dilandau/dilandau-icon.png rename to dilandau/content/contents/code/dilandau-icon.png diff --git a/dilandau/dilandau.js b/dilandau/content/contents/code/dilandau.js similarity index 77% rename from dilandau/dilandau.js rename to dilandau/content/contents/code/dilandau.js index c8f2c404d..81e40d9d4 100644 --- a/dilandau/dilandau.js +++ b/dilandau/content/contents/code/dilandau.js @@ -1,8 +1,23 @@ -/* - * (c) 2011 lasconic - * (c) 2011 leo franchi - * (c) 2012 thierry göckel +/* === This file is part of Tomahawk Player - === + * + * Copyright 2011, lasconic + * Copyright 2011, Leo Franchi + * Copyright 2012, Thierry Göckel + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . */ + var DilandauResolver = Tomahawk.extend(TomahawkResolver, { settings: { diff --git a/dilandau/content/contents/images/icon.png b/dilandau/content/contents/images/icon.png new file mode 100644 index 000000000..8cbb5addf Binary files /dev/null and b/dilandau/content/contents/images/icon.png differ diff --git a/dilandau/content/metadata.json b/dilandau/content/metadata.json new file mode 100644 index 000000000..7353bd59c --- /dev/null +++ b/dilandau/content/metadata.json @@ -0,0 +1,18 @@ +{ + "name": "Dilandau", + "pluginName": "dilandau", + "author": "lasconic", + "email": "lasconic@gmail.com", + "version": "0.4.1", + "website": "http://gettomahawk.com", + "description": "Uses mp3 search engine http://www.dilandau.eu/ to find tracks", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/dilandau.js", + "scripts": [], + "icon": "contents/images/icon.png", + "resources": [ + "contents/code/dilandau-icon.png" + ] + } +} diff --git a/dropbox/content/contents/code/config.ui b/dropbox/content/contents/code/config.ui new file mode 100644 index 000000000..1fd0df7f9 --- /dev/null +++ b/dropbox/content/contents/code/config.ui @@ -0,0 +1,91 @@ + + + Form + + + + 0 + 0 + 447 + 305 + + + + Form + + + + + + + + + dropbox.svg + + + Qt::AlignCenter + + + + + + + QFormLayout::FieldsStayAtSizeHint + + + Qt::AlignJustify|Qt::AlignVCenter + + + Qt::AlignJustify|Qt::AlignTop + + + + + Associate + + + + + + + true + + + Delete + + + + + + + OAuth developer key : + + + + + + + App Key + + + + + + + App Secret + + + + + + + + + + + + + + + + diff --git a/dropbox/content/contents/code/dropbox.js b/dropbox/content/contents/code/dropbox.js new file mode 100644 index 000000000..91ccf765e --- /dev/null +++ b/dropbox/content/contents/code/dropbox.js @@ -0,0 +1,460 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2013, Rémi Benoit + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +var DropboxResolver = Tomahawk.extend(TomahawkResolver, { + uid: '', + cursor: '', + getFileUrl: 'https://api-content.dropbox.com/1/files/dropbox', + + settings: { + name: 'Dropbox', + weight: 60, + icon : 'dropbox.svg', + timeout: 15 + }, + + getConfigUi: function () { + var uiData = Tomahawk.readBase64("config.ui"); + return { + + "widget": uiData, + fields: [{ + name: "associateButton", + widget: "associateButton", + property: "text", + connections : [ { + signal: "clicked()", + javascriptCallback: "resolver.associateClicked();" + }] + }, + { + name: "deleteButton", + widget: "deleteButton", + property: "text", + connections : [ { + signal: "clicked()", + javascriptCallback: "resolver.deleteClicked();" + }] + }, + { + name: "consumerKey", + widget: "appKeyLineEdit", + property: "text" + }, + { + name: "consumerSecret", + widget: "appSecretLineEdit", + property: "text" + },], + images: [{ + "dropbox.svg": Tomahawk.readBase64(this.settings.icon) + }, ] + }; + }, + + newConfigSaved: function () { + var userConfig = this.getUserConfig(); + + if (this.oauth.oauthSettings.consumerKey !== userConfig.consumerKey || + this.oauth.oauthSettings.consumerSecret !== userConfig.consumerSecret) + { + this.oauth.oauthSettings.consumerKey = userConfig.consumerKey.trim(); + this.oauth.oauthSettings.consumerSecret = userConfig.consumerSecret.trim(); + + dbLocal.setItem('dropbox.consumerKey', this.oauth.oauthSettings.consumerKey); + dbLocal.setItem('dropbox.consumerSecret', this.oauth.oauthSettings.consumerSecret); + + this.oauth.init(); + } + }, + + associateClicked: function () { + Tomahawk.log("Associate was clicked"); + this.oauth.associate(this.updateDatabase); + }, + + deleteClicked: function () { + Tomahawk.log("Delete was clicked"); + + this.cursor = ''; + dbLocal.setItem('dropbox.cursor',''); + + this.oauth.deleteAssociation(); + musicManager.flushDatabase(); + + }, + + queryFailure: function(data) { + Tomahawk.log("Request Failed : " + data.text); + }, + + init: function () { + Tomahawk.log("Beginnning INIT of Dropbox resovler"); + + //dbLocal.setItem("dropbox.cursor",""); + + this.cursor = dbLocal.getItem('dropbox.cursor',''); + + this.oauth.init(); + musicManager.initDatabase() ; + + Tomahawk.addCustomUrlHandler( "dropbox", "getStreamUrl" ); + Tomahawk.reportCapabilities( TomahawkResolverCapability.Browsable | TomahawkResolverCapability.AccountFactory ); + + //TODO updateDatabase every 30 min (and handle if a user asked for a DB refresh before) + //TODO update only if asscociated to an account + this.updateDatabase(); + }, + + updateDatabase: function(){ + Tomahawk.log("Sending Delta Query : "); + Tomahawk.log("with cursor : "+ this.cursor); + + var url = 'https://api.dropbox.com/1/delta' + (this.cursor === '' ? '' : '?cursor='+this.cursor); + + this.oauth.opostJSON(url, {'cursor' : this.cursor}, this.deltaCallback.bind(this), this.queryFailure.bind(this)); + }, + + deltaCallback: function(response){ + Tomahawk.log("Delta returned!"); + Tomahawk.log("Cursor : " + response.cursor); + Tomahawk.log("Hasmore : " + response.has_more); + Tomahawk.log("Entries length : " + response.entries.length); + + for(var i = 0; i < response.entries.length; i++){ + var path = response.entries[i][0]; + var meta = response.entries[i][1]; + //Tomahawk.log("Entry n°" + i + ", Path: " + path /*+ ", Meta: " + DumpObjectIndented(meta)*/); + if(!meta){ + Tomahawk.log("Deleting : " + path); + musicManager.deleteTrack({id:path}); + }else{ + if(!meta['is_dir'] && this.isMimeTypeSupported(meta['mime_type'])){ + //Tomahawk.log(DumpObjectIndented(meta)); + //Get ID3 Tag + Tomahawk.log("Get ID3Tag for : " + path); + //Tomahawk.log("size : " + meta['bytes']); + //Tomahawk.log("mime : " + meta['mime_type']); + //Tomahawk.log('request : ' + DumpObjectIndented( this.getStreamUrl(path) )); + Tomahawk.readCloudFile(path, path, meta['bytes'], meta['mime_type'], this.getStreamUrl(path), "onID3TagCallback", "getStreamUrl" + ); + } + } + } + + this.cursor = response.cursor; + dbLocal.setItem('dropbox.cursor', response.cursor); + + if(response.has_more){ + Tomahawk.log("Updating again"); + this.updateDatabase(); + } + }, + + onID3TagCallback: function(tags) + { + var trackInfo = { + 'id' : tags['fileId'], + 'url' : 'dropbox://path/' + tags['fileId'], + 'track' : tags['track'], + 'artist' : tags['artist'], + 'album' : tags['album'], + 'albumpos' : tags['albumpos'], + 'year' : tags['year'], + 'bitrate' : tags['bitrate'], + 'mimetype' : tags['mimetype'], + 'size' : tags['size'], + 'duration' : tags['duration'], + }; + + Tomahawk.log("Adding : " + DumpObjectIndented(trackInfo)); + musicManager.addTrack(trackInfo); + }, + + resolve: function (qid, artist, album, title) { + musicManager.resolve(artist, album, title, function(results) { + var return_songs = { + qid: qid, + results: results + }; + Tomahawk.log("Resolved query : " + artist + ", "+ album+ ", "+ title+" returned: " + DumpObjectIndented(return_songs.results)); + Tomahawk.addTrackResults(return_songs); + }); + + }, + + search: function (qid, searchString) { + // set up a limit for the musicManager search Query + Tomahawk.log("search query"); + musicManager.searchQuery(searchString,function(results){ + var return_songs = { + qid: qid, + results: results + }; + + Tomahawk.log("Search query : " + searchString +" , result: " + DumpObjectIndented(return_songs.results)); + Tomahawk.addTrackResults(return_songs); + }); + }, + + artists: function( qid ) + { + Tomahawk.log("artists query"); + musicManager.allArtistsQuery(function(results){ + var return_artists = { + qid: qid, + artists: results + }; + Tomahawk.log("google drive artists returned: "); + Tomahawk.addArtistResults(return_artists); + }); + this.updateDatabase(); + }, + + albums: function( qid, artist ) + { + Tomahawk.log("albums query"); + musicManager.albumsQuery(artist, function(results){ + var return_albums = { + qid: qid, + artist: artist, + albums: results + }; + Tomahawk.log("google drive albums returned: "); + Tomahawk.addAlbumResults(return_albums); + }); + }, + + tracks: function( qid, artist, album ) + { + Tomahawk.log("tracks query"); + musicManager.tracksQuery(artist, album, function(results){ + var return_tracks = { + qid: qid, + artist: artist, + album: album, + results: results + }; + Tomahawk.log("Google Drive tracks returned:"); + Tomahawk.addAlbumTrackResults(return_tracks); + }); + }, + + collection: function() + { + //strip http:// and trailing slash + var desc = "cloud de dropbox"; + + var return_object = { + prettyname: "Dropbox", + description: desc, + iconfile: this.settings.icon + }; + + return return_object; + }, + + isMimeTypeSupported: function(mimeType) + { + //Tomahawk.log("Checking : "+ mimeType); + var mimes = [ "audio/mpeg" , "application/ogg" , "application/ogg" , "audio/x-musepack" , "audio/x-ms-wma" , "audio/mp4" , "audio/mp4" , "audio/mp4" , "audio/flac" , "audio/aiff" , "audio/aiff" , "audio/x-wavpack" ]; + return (mimes.lastIndexOf(mimeType) != -1); + }, + + getStreamUrl: function (ourUrl) { + var path = ourUrl.replace("dropbox://path/", ""); + Tomahawk.log(">>>>> file+path : " + this.getFileUrl + path); + var url = this.oauth.oAuthGetUrl(encodeURI(this.getFileUrl + path)); + Tomahawk.log(">>>>> streamUrl : " + url); + return url; + }, + + oauth: { + + init: function(){ + this.oauthSettings.consumerKey = dbLocal.getItem('dropbox.consumerKey', ''); + this.oauthSettings.consumerSecret = dbLocal.getItem('dropbox.consumerSecret', ''); + this.oauthSettings.accessTokenKey = dbLocal.getItem('dropbox.accessTokenKey',''); + this.oauthSettings.accessTokenSecret = dbLocal.getItem('dropbox.accessTokenSecret',''); + + this.oauthEngine = OAuth(this.oauthSettings); + }, + + //associate a new User + //If the association is succesfull the previous token is discarded + associate: function(callback){ + this.oauthEngine.fetchRequestToken(function(data){ + this.resolver.oauth.openAcceptPage(data, callback); + }, this.queryFailure); + }, + + deleteAssociation: function(){ + dbLocal.setItem('dropbox.accessTokenKey',''); + dbLocal.setItem('dropbox.accessTokenSecret',''); + + this.oauthSettings.accessTokenKey = ''; + this.oauthSettings.accessTokenSecret = ''; + + this.oauthEngine = OAuth(this.oauthSettings); + + }, + + isAssociated: function(){ + var accessKey = dbLocal.getItem('dropbox.accessTokenKey',''); + var accessSecret = dbLocal.getItem('dropbox.accessTokenSecret',''); + return( !(accessKey === '') && !(accessSecret === '') ); + }, + + opostJSON: function(url, data, success, failure){ + if(!this.isAssociated()){ + //TODO throw error NoAccountAssociated ? + Tomahawk.log("REFUSED Post to "+ url + " : No account associated"); + }else{ + this.oauthEngine.postJSON(url, data, success, failure); + } + }, + + ogetJSON: function(url, success, failure){ + if(!this.isAssociated()){ + //TODO throw error NoAccountAssociated ? + Tomahawk.log("REFUSED Get to "+ url + " : No account associated"); + }else{ + this.oauthEngine.getJSON(url, success, failure); + } + }, + + oAuthGetUrl: function (url, success, failure) { + return this.oauthEngine.oAuthGetUrl(url,success, failure); + }, + + //Private member + oauthEngine: null, + + oauthSettings: { + consumerKey: '', + consumerSecret: '', + requestTokenUrl: 'https://api.dropbox.com/1/oauth/request_token', + authorizationUrl: 'https://www.dropbox.com/1/oauth/authorize', + accessTokenUrl: 'https://api.dropbox.com/1/oauth/access_token', + accessTokenKey: '', + accessTokenSecret: '' + }, + + openAcceptPage: function(url, callback) { + Tomahawk.requestWebView("acceptPage", url); + + //acceptPage.setWindowModality(2); + //acceptPage.resize(acceptPage.height(), 800); + + acceptPage.show(); + acceptPage.urlChanged.connect(Tomahawk.resolver.instance.oauth, function(url){ + this.onUrlChanged(url.toString(), callback); + }); + }, + + onUrlChanged: function(url, callback){ + Tomahawk.log("URL returned : \'" + url+"\'"); + + if(url === 'https://www.dropbox.com/1/oauth/authorize'){ + this.oauthEngine.fetchAccessToken(function(data){ + this.resolver.oauth.onAccessTokenReceived(data, callback); + }, this.queryFailure); + } + + if(url === 'https://www.dropbox.com/home'){ + Tomahawk.log("Refused"); + //close webpage + } + }, + + onAccessTokenReceived: function(data, callback){ + //parse response + var i = 0, arr = data.text.split('&'), len = arr.length, obj = {}; + for (; i < len; ++i) { + var pair = arr[i].split('='); + obj[OAuth.urlDecode(pair[0])] = OAuth.urlDecode(pair[1]); + } + + //TODO close webpage + + this.oauthSettings.accessTokenKey = obj.oauth_token; + this.oauthSettings.accessTokenSecret = obj.oauth_token_secret; + + dbLocal.setItem('dropbox.accessTokenKey',obj.oauth_token); + dbLocal.setItem('dropbox.accessTokenSecret',obj.oauth_token_secret); + + if(! (typeof callback === 'undefined')){ + callback.call(Tomahawk.resolver.instance); + } + }, + + queryFailure: function(data) { + Tomahawk.log("Request Failed : " + data.text); + } + + } +}); + +var dbLocal = { + setItem: function(a1, a2){ + window.localStorage.setItem(a1,a2); + }, + getItem: function (key, defaultResponse){ + var result = window.localStorage.getItem(key); + result = (result == null)? defaultResponse : result; + + Tomahawk.log("DB: loaded "+key+" : '"+ result+"' "); + + return result; + } +}; + +Tomahawk.resolver.instance = DropboxResolver; + +function DumpObjectIndented(obj, indent) +{ + var result = ""; + if (indent == null) indent = ""; + + for (var property in obj) + { + var value = obj[property]; + if (typeof value == 'string') + value = "'" + value + "'"; + else if (typeof value == 'object') + { + if (value instanceof Array) + { + // Just let JS convert the Array to a string! + value = "[ " + value + " ]"; + } + else + { + // Recursive dump + // (replace " " by "\t" or something else if you prefer) + var od = DumpObjectIndented(value, indent + " "); + // If you like { on the same line as the key + //value = "{\n" + od + "\n" + indent + "}"; + // If you prefer { and } to be aligned + value = "\n" + indent + "{\n" + od + "\n" + indent + "}"; + } + } + result += indent + "'" + property + "' : " + value + ",\n"; + } + return result.replace(/,\n$/, ""); +} diff --git a/dropbox/content/contents/code/dropbox.svg b/dropbox/content/contents/code/dropbox.svg new file mode 100644 index 000000000..6f63627d1 --- /dev/null +++ b/dropbox/content/contents/code/dropbox.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dropbox/content/contents/code/jsOAuth-1.3.6.min.js b/dropbox/content/contents/code/jsOAuth-1.3.6.min.js new file mode 100644 index 000000000..dad0930b7 --- /dev/null +++ b/dropbox/content/contents/code/jsOAuth-1.3.6.min.js @@ -0,0 +1 @@ +var exports=exports||this;exports.OAuth=function(a){function b(a){var e,b=arguments,c=b.callee,f=(b.length,this);if(!(this instanceof c))return new c(a);for(e in a)a.hasOwnProperty(e)&&(f[e]=a[e]);return f}function c(){}function d(a){var d,f,g,h,i,j,k,b=arguments,c=b.callee,l=/^([^:\/?#]+?:\/\/)*([^\/:?#]*)?(:[^\/?#]*)*([^?#]*)(\?[^#]*)?(#(.*))*/,m=this;return this instanceof c?(m.scheme="",m.host="",m.port="",m.path="",m.query=new e,m.anchor="",null!==a&&(d=a.match(l),f=d[1],g=d[2],h=d[3],i=d[4],j=d[5],k=d[6],f=void 0!==f?f.replace("://","").toLowerCase():"http",h=h?h.replace(":",""):"https"===f?"443":"80",f="http"==f&&"443"===h?"https":f,j=j?j.replace("?",""):"",k=k?k.replace("#",""):"",("https"===f&&"443"!==h||"http"===f&&"80"!==h)&&(g=g+":"+h),m.scheme=f,m.host=g,m.port=h,m.path=i||"/",m.query.setQueryParams(j),m.anchor=k||""),void 0):new c(a)}function e(a){var e,b=arguments,c=b.callee,f=(b.length,this);if(g.urlDecode,!(this instanceof c))return new c(a);if(void 0!=a)for(e in a)a.hasOwnProperty(e)&&(f[e]=a[e]);return f}function g(a){return this instanceof g?this.init(a):new g(a)}function h(a){var c,d,b=[];for(c in a)a[c]&&void 0!==a[c]&&""!==a[c]&&("realm"===c?d=c+'="'+a[c]+'"':b.push(c+'="'+g.urlEncode(a[c]+"")+'"'));return b.sort(),d&&b.unshift(d),b.join(", ")}function i(a,b,c,d){var f,e=[],h=g.urlEncode;for(f in c)void 0!==c[f]&&""!==c[f]&&e.push([g.urlEncode(f),g.urlEncode(c[f]+"")]);for(f in d)void 0!==d[f]&&""!==d[f]&&(c[f]||e.push([h(f),h(d[f]+"")]));return e=e.sort(function(a,b){return a[0]b[0]?1:a[1]b[1]?1:0}).map(function(a){return a.join("=")}),[a,h(b),h(e.join("&"))].join("&")}function j(){return parseInt(+new Date/1e3,10)}function k(a){function b(){return Math.floor(Math.random()*h.length)}a=a||64;var g,c=a/8,d="",e=c/4,f=c%4,h=["20","21","22","23","24","25","26","27","28","29","2A","2B","2C","2D","2E","2F","30","31","32","33","34","35","36","37","38","39","3A","3B","3C","3D","3E","3F","40","41","42","43","44","45","46","47","48","49","4A","4B","4C","4D","4E","4F","50","51","52","53","54","55","56","57","58","59","5A","5B","5C","5D","5E","5F","60","61","62","63","64","65","66","67","68","69","6A","6B","6C","6D","6E","6F","70","71","72","73","74","75","76","77","78","79","7A","7B","7C","7D","7E"];for(g=0;e>g;g++)d+=h[b()]+h[b()]+h[b()]+h[b()];for(g=0;f>g;g++)d+=h[b()];return d}function l(){var b;if(a.Titanium!==void 0&&a.Titanium.Network.createHTTPClient!==void 0)b=a.Titanium.Network.createHTTPClient();else if("undefined"!=typeof require)try{b=new require("xhr").XMLHttpRequest()}catch(c){if(void 0===a.XMLHttpRequest)throw"No valid request transport found.";b=new a.XMLHttpRequest}else{if(void 0===a.XMLHttpRequest)throw"No valid request transport found.";b=new a.XMLHttpRequest}return b}function m(a){var b=Array(++a);return b.join(0).split("")}function n(a){var c,d,b=[];for(d=0;a.length>d;d++)c=a.charCodeAt(d),128>c?b.push(c):2048>c?b.push(192+(c>>6),128+(63&c)):65536>c?b.push(224+(c>>12),128+(63&c>>6),128+(63&c)):2097152>c&&b.push(240+(c>>18),128+(63&c>>12),128+(63&c>>6),128+(63&c));return b}function o(a){var c,b=[];for(c=0;32*a.length>c;c+=8)b.push(255&a[c>>>5]>>>24-c%32);return b}function p(a){var d,b=[],c=a.length;for(d=0;c>d;d++)b.push((a[d]>>>4).toString(16)),b.push((15&a[d]).toString(16));return b.join("")}function q(a){var d,b="",c=a.length;for(d=0;c>d;d++)b+=String.fromCharCode(a[d]);return b}function r(a,b){return a<>>32-b}function s(a){if(void 0!==a){var c,d,b=a;return b.constructor===String&&(b=n(b)),c=this instanceof s?this:new s(a),d=c.hash(b),p(d)}return this instanceof s?this:new s}function t(a,b,c,d){var h,i,j,k,e=n(b),f=n(c),g=e.length;for(g>a.blocksize&&(e=a.hash(e),g=e.length),e=e.concat(m(a.blocksize-g)),i=e.slice(0),j=e.slice(0),k=0;a.blocksize>k;k++)i[k]^=92,j[k]^=54;return h=a.hash(i.concat(a.hash(j.concat(f)))),d?p(h):q(h)}c.prototype={join:function(a){return a=a||"",this.values().join(a)},keys:function(){var a,b=[],c=this;for(a in c)c.hasOwnProperty(a)&&b.push(a);return b},values:function(){var a,b=[],c=this;for(a in c)c.hasOwnProperty(a)&&b.push(c[a]);return b},shift:function(){throw"not implimented"},unshift:function(){throw"not implimented"},push:function(){throw"not implimented"},pop:function(){throw"not implimented"},sort:function(){throw"not implimented"},ksort:function(a){var d,e,f,b=this,c=b.keys();for(void 0==a?c.sort():c.sort(a),d=0;c.length>d;d++)f=c[d],e=b[f],delete b[f],b[f]=e;return b},toObject:function(){var b,a={},c=this;for(b in c)c.hasOwnProperty(b)&&(a[b]=c[b]);return a}},b.prototype=new c,d.prototype={scheme:"",host:"",port:"",path:"",query:"",anchor:"",toString:function(){var a=this,b=a.query+"";return a.scheme+"://"+a.host+a.path+(""!=b?"?"+b:"")+(""!==a.anchor?"#"+a.anchor:"")}},e.prototype=new b,e.prototype.toString=function(){var a,b=this,c=[],d="",e="",f=g.urlEncode;b.ksort();for(a in b)b.hasOwnProperty(a)&&void 0!=a&&void 0!=b[a]&&(e=f(a)+"="+f(b[a]),c.push(e));return c.length>0&&(d=c.join("&")),d},e.prototype.setQueryParams=function(a){var d,e,f,i,b=arguments,c=b.length,h=this,j=g.urlDecode;if(1==c){if("object"==typeof a)for(d in a)a.hasOwnProperty(d)&&(h[d]=j(a[d]));else if("string"==typeof a)for(e=a.split("&"),d=0,f=e.length;f>d;d++)i=e[d].split("="),""!=i[0]&&(h[i[0]]=j(i[1]))}else for(d=0;c>d;d+=2)h[b[d]]=j(b[d+1])};var f="1.0";return g.prototype={realm:"",requestTokenUrl:"",authorizationUrl:"",accessTokenUrl:"",init:function(a){var b="",c={enablePrivilege:a.enablePrivilege||!1,proxyUrl:a.proxyUrl,callbackUrl:a.callbackUrl||"oob",consumerKey:a.consumerKey,consumerSecret:a.consumerSecret,accessTokenKey:a.accessTokenKey||b,accessTokenSecret:a.accessTokenSecret||b,verifier:b,signatureMethod:a.signatureMethod||"HMAC-SHA1"};return this.realm=a.realm||b,this.requestTokenUrl=a.requestTokenUrl||b,this.authorizationUrl=a.authorizationUrl||b,this.accessTokenUrl=a.accessTokenUrl||b,this.getAccessToken=function(){return[c.accessTokenKey,c.accessTokenSecret]},this.getAccessTokenKey=function(){return c.accessTokenKey},this.getAccessTokenSecret=function(){return c.accessTokenSecret},this.setAccessToken=function(a,b){b&&(a=[a,b]),c.accessTokenKey=a[0],c.accessTokenSecret=a[1]},this.getVerifier=function(){return c.verifier},this.setVerifier=function(a){c.verifier=a},this.setCallbackUrl=function(a){c.callbackUrl=a},this.request=function(a){var b,e,m,n,o,p,q,r,s,t,u,v,x,z,A,B,w=[],y={};b=a.method||"GET",e=d(a.url),m=a.data||{},n=a.headers||{},o=a.success||function(){},p=a.failure||function(){},A=function(){var a=!1;for(var b in m)(m[b]instanceof File||m[b].fileName!==void 0)&&(a=!0);return a}(),x=a.appendQueryString?a.appendQueryString:!1,c.enablePrivilege&&netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite"),q=l(),q.onreadystatechange=function(){if(4===q.readyState){var e,a=/^(.*?):\s*(.*?)\r?$/gm,b=n,c={},d="";if(q.getAllResponseHeaders)for(d=q.getAllResponseHeaders();e=a.exec(d);)c[e[1]]=e[2];else if(q.getResponseHeaders){d=q.getResponseHeaders();for(var f=0,g=d.length;g>f;++f)c[d[f][0]]=d[f][1]}var h=!1;"Content-Type"in c&&"text/xml"==c["Content-Type"]&&(h=!0);var i={text:q.responseText,xml:h?q.responseXML:"",requestHeaders:b,responseHeaders:c};q.status>=200&&226>=q.status||304==q.status||0===q.status?o(i):q.status>=400&&0!==q.status&&p(i)}},s={oauth_callback:c.callbackUrl,oauth_consumer_key:c.consumerKey,oauth_token:c.accessTokenKey,oauth_signature_method:c.signatureMethod,oauth_timestamp:j(),oauth_nonce:k(),oauth_verifier:c.verifier,oauth_version:f},t=c.signatureMethod,z=e.query.toObject();for(r in z)y[r]=z[r];if(!("Content-Type"in n&&"application/x-www-form-urlencoded"!=n["Content-Type"]||A))for(r in m)y[r]=m[r];if(B=e.scheme+"://"+e.host+e.path,u=i(b,B,s,y),v=g.signatureMethod[t](c.consumerSecret,c.accessTokenSecret,u),s.oauth_signature=v,this.realm&&(s.realm=this.realm),c.proxyUrl&&(e=d(c.proxyUrl+e.path)),x||"GET"==b)e.query.setQueryParams(m),w=null;else if(A){if(A){w=new FormData;for(r in m)w.append(r,m[r])}}else if("string"==typeof m)w=m,"Content-Type"in n||(n["Content-Type"]="text/plain");else{for(r in m)w.push(g.urlEncode(r)+"="+g.urlEncode(m[r]+""));w=w.sort().join("&"),"Content-Type"in n||(n["Content-Type"]="application/x-www-form-urlencoded")}q.open(b,e+"",!0),q.setRequestHeader("Authorization","OAuth "+h(s)),q.setRequestHeader("X-Requested-With","XMLHttpRequest");for(r in n)q.setRequestHeader(r,n[r]);q.send(w)},this.createOauthUrl=function(a){var b,e,l,m,n,o,q,r,s,t,u,w,y,z,A,v=[],x={};b=a.method||"GET",e=d(a.url),l=a.data||{},m=a.headers||{},n=a.success||function(){},o=a.failure||function(){},z=function(){var a=!1;for(var b in l)(l[b]instanceof File||l[b].fileName!==void 0)&&(a=!0);return a}(),w=a.appendQueryString?a.appendQueryString:!1,c.enablePrivilege&&netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite"),r={oauth_callback:c.callbackUrl,oauth_consumer_key:c.consumerKey,oauth_token:c.accessTokenKey,oauth_signature_method:c.signatureMethod,oauth_timestamp:j(),oauth_nonce:k(),oauth_verifier:c.verifier,oauth_version:f},s=c.signatureMethod,y=e.query.toObject();for(q in y)x[q]=y[q];if(!("Content-Type"in m&&"application/x-www-form-urlencoded"!=m["Content-Type"]||z))for(q in l)x[q]=l[q];if(A=e.scheme+"://"+e.host+e.path,t=i(b,A,r,x),u=g.signatureMethod[s](c.consumerSecret,c.accessTokenSecret,t),r.oauth_signature=u,this.realm&&(r.realm=this.realm),c.proxyUrl&&(e=d(c.proxyUrl+e.path)),w||"GET"==b)e.query.setQueryParams(l),v=null;else if(z){if(z){v=new FormData;for(q in l)v.append(q,l[q])}}else if("string"==typeof l)v=l,"Content-Type"in m||(m["Content-Type"]="text/plain");else{for(q in l)v.push(g.urlEncode(q)+"="+g.urlEncode(l[q]+""));v=v.sort().join("&"),"Content-Type"in m||(m["Content-Type"]="application/x-www-form-urlencoded")}var B={};B.Authorization="OAuth "+h(r);for(q in m)B[q]=m[q];return{url:""+e,headers:B}},this},get:function(a,b,c){this.request({url:a,success:b,failure:c})},post:function(a,b,c,d){this.request({method:"POST",url:a,data:b,success:c,failure:d})},getJSON:function(a,b,c){this.get(a,function(a){b(JSON.parse(a.text))},c)},postJSON:function(a,b,c,d){this.request({method:"POST",url:a,data:JSON.stringify(b),success:function(a){c(JSON.parse(a.text))},failure:d,headers:{"Content-Type":"application/json"}})},oAuthGetUrl:function(a,b,c){return this.createOauthUrl({url:a,success:b,failure:c})},oAuthPostUrl:function(a,b,c,d){return this.createOauthUrl({method:"POST",url:a,data:b,success:c,failure:d})},parseTokenRequest:function(a,b){switch(b){case"text/xml":var c=a.xml.getElementsByTagName("token"),d=a.xml.getElementsByTagName("secret");i[g.urlDecode(c[0])]=g.urlDecode(d[0]);break;default:for(var e=0,f=a.text.split("&"),h=f.length,i={};h>e;++e){var j=f[e].split("=");i[g.urlDecode(j[0])]=g.urlDecode(j[1])}}return i},fetchRequestToken:function(a,b){var c=this;c.setAccessToken("","");var d=c.authorizationUrl;this.get(this.requestTokenUrl,function(b){var e=c.parseTokenRequest(b,b.responseHeaders["Content-Type"]||void 0);c.setAccessToken([e.oauth_token,e.oauth_token_secret]),a(d+"?"+b.text)},b)},fetchAccessToken:function(a,b){var c=this;this.get(this.accessTokenUrl,function(b){var d=c.parseTokenRequest(b,b.responseHeaders["Content-Type"]||void 0);c.setAccessToken([d.oauth_token,d.oauth_token_secret]),c.setVerifier(""),a(b)},b)}},g.signatureMethod={"HMAC-SHA1":function(b,c,d){var e,f,h=g.urlEncode;return b=h(b),c=h(c||""),e=b+"&"+c,f=t(s.prototype,e,d),a.btoa(f)}},g.urlEncode=function(a){function b(a){var b=a.toString(16).toUpperCase();return 2>b.length&&(b=0+b),"%"+b}if(!a)return"";a+="";var e,g,c=/[ \r\n!*"'();:@&=+$,\/?%#\[\]<>{}|`^\\\u0080-\uffff]/,d=a.length,f=a.split("");for(e=0;d>e;e++)(g=f[e].match(c))&&(g=g[0].charCodeAt(0),128>g?f[e]=b(g):2048>g?f[e]=b(192+(g>>6))+b(128+(63&g)):65536>g?f[e]=b(224+(g>>12))+b(128+(63&g>>6))+b(128+(63&g)):2097152>g&&(f[e]=b(240+(g>>18))+b(128+(63&g>>12))+b(128+(63&g>>6))+b(128+(63&g))));return f.join("")},g.urlDecode=function(a){return a?a.replace(/%[a-fA-F0-9]{2}/gi,function(a){return String.fromCharCode(parseInt(a.replace("%",""),16))}):""},s.prototype=new s,s.prototype.blocksize=64,s.prototype.hash=function(a){function A(a,b,c,d){switch(a){case 0:return b&c|~b&d;case 1:case 3:return b^c^d;case 2:return b&c|b&d|c&d}return-1}var d,e,f,g,h,i,j,k,l,p,q,s,t,u,v,w,x,y,z,b=[1732584193,4023233417,2562383102,271733878,3285377520],c=[1518500249,1859775393,2400959708,3395469782];for(a.constructor===String&&(a=n(a.encodeUTF8())),f=a.length,g=Math.ceil((f+9)/this.blocksize)*this.blocksize-(f+9),e=Math.floor(f/4294967296),d=Math.floor(f%4294967296),h=[255&8*e>>24,255&8*e>>16,255&8*e>>8,255&8*e,255&8*d>>24,255&8*d>>16,255&8*d>>8,255&8*d],a=a.concat([128],m(g),h),i=Math.ceil(a.length/this.blocksize),j=0;i>j;j++){for(k=a.slice(j*this.blocksize,(j+1)*this.blocksize),l=k.length,p=[],q=0;l>q;q++)p[q>>>2]|=k[q]<<24-8*(q-4*(q>>2));for(s=b[0],t=b[1],u=b[2],v=b[3],w=b[4],x=0;80>x;x++)x>=16&&(p[x]=r(p[x-3]^p[x-8]^p[x-14]^p[x-16],1)),y=Math.floor(x/20),z=r(s,5)+A(y,t,u,v)+w+c[y]+p[x],w=v,v=u,u=r(t,30),t=s,s=z;b[0]+=s,b[1]+=t,b[2]+=u,b[3]+=v,b[4]+=w}return o(b)},g}(exports);var exports=exports||this;(function(a){var b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";a.btoa=a.btoa||function(a){for(var e,f,c=0,d=a.length,g="";d>c;c+=3)e=[a.charCodeAt(c),a.charCodeAt(c+1),a.charCodeAt(c+2)],f=[e[0]>>2,(3&e[0])<<4|e[1]>>4,(15&e[1])<<2|e[2]>>6,63&e[2]],isNaN(e[1])&&(f[2]=64),isNaN(e[2])&&(f[3]=64),g+=b.charAt(f[0])+b.charAt(f[1])+b.charAt(f[2])+b.charAt(f[3]);return g}})(exports); \ No newline at end of file diff --git a/dropbox/content/contents/code/musicManager.js b/dropbox/content/contents/code/musicManager.js new file mode 100644 index 000000000..aaa95e5d5 --- /dev/null +++ b/dropbox/content/contents/code/musicManager.js @@ -0,0 +1,363 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2013, Franck Arrecot + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + + +Tomahawk.log(" Music Manager begining"); + +var musicManager = { + + dbName : "MyDropBoxDB" , + dbSQL: null , + searchLimitResults : 500 , + + + initDatabase : function() + { + Tomahawk.log("Init webSQL Db : "); + if (!this.dbSQL) this.dbSQL = openDatabase(this.dbName, '1.0', 'Muic Database', 2 * 1024 * 1024) ; + this.dbSQL.transaction(function (tx) { + tx.executeSql('CREATE TABLE IF NOT EXISTS track (id primary key, track, artist, album, albumpos, year, genre, size, duration, mimetype, bitrate, url)'); + }); + }, + + showDatabase: function() + { + Tomahawk.log("Displaying Content of Database"); var log = "" ; + this.dbSQL.transaction(function (tx) { + tx.executeSql('SELECT * FROM track', [], function (tx, resultsQuery ) { + var results = musicManager.parseSongAttriutes(resultsQuery) ; + // Parsing to display information on each music + var len = results.length ; var i = 0 ; + for (i ; i < len ; i ++) { + for (row in results[i]) { + log += ""+row+": "+ results[i][row] +"," ; + } + Tomahawk.log (log) ; + } + }); + }); + }, + + // delete the database + flushDatabase : function() + { + this.dbSQL.transaction(function (tx) { + tx.executeSql('DROP TABLE track'); + }); + musicManager.initDatabase() ; + Tomahawk.log("webSQL db cleaned out"); + }, + + addTrack : function(tabTrackDetails) + { + var id = tabTrackDetails["id"] || ''; + var track = tabTrackDetails["track"] || ''; + var artist = tabTrackDetails["artist"] || ''; + var album = tabTrackDetails["album"] || 'undefined'; + var albumpos = tabTrackDetails["albumpos"] || ''; + var year = tabTrackDetails["year"] || ''; + var genre = tabTrackDetails["genre"] || '' ; + var size = tabTrackDetails["size"] || '' ; + var duration = tabTrackDetails["duration"] || '' ; + var mimetype = tabTrackDetails["mimetype"] || '' ; + var bitrate = tabTrackDetails["bitrate"] || '' ; + var url = tabTrackDetails["url"] || '' ; + + // check core information provided + if (id == "" || track == "" || artist =="" || url=="") { + Tomahawk.log("Insertion Failed : core information track isn't provided to "+this.dbName); + return ; + } + else + { + // Check presence in the database before adding + var db = this.dbName ; + this.dbSQL.transaction(function (tx) { + tx.executeSql('SELECT id FROM track where id=?', [id], function (tx, resultsQuery ) { + if (resultsQuery.rows.length > 0) { + Tomahawk.log("Insertion abort : data already inside the "+db+""); + return ; + } + }); + }); + } + // Track Insertion + this.dbSQL.transaction(function (tx) { + tx.executeSql('INSERT INTO track (id, track, artist, album, albumpos, year, genre, size, duration, mimetype, bitrate, url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + [id, track, artist, album, albumpos, year, genre, size, duration, mimetype, bitrate, url]); + }); + Tomahawk.log("Insertion inside "+this.dbName+" : " + id); + }, + + deleteTrack: function (tabTrackDetails) + { + var id = tabTrackDetails["id"] || ''; + if (id == "" || !id) { Tomahawk.log("Deletion intented without an id key"); return ; } + this.dbSQL.transaction(function (tx) { + tx.executeSql('DELETE FROM track WHERE id = ?', [id], function (tx,resultsQuery){}); + }); + Tomahawk.log("Deletion inside "+this.dbName+""); + }, + + + parseSongAttriutes: function (resultsQuery) + { + //Tomahawk.log("parsing Attribute"); + var results = [] ; var song ; + var len = resultsQuery.rows.length + for (i = 0; i < len; i++) { + song = { + id: resultsQuery.rows.item(i).id , + track: resultsQuery.rows.item(i).track , + artist: resultsQuery.rows.item(i).artist , + album: resultsQuery.rows.item(i).album , + albumpos: resultsQuery.rows.item(i).albumpos , + year: resultsQuery.rows.item(i).year , + genre: resultsQuery.rows.item(i).genre , + size: resultsQuery.rows.item(i).size , + duration: resultsQuery.rows.item(i).duration , + mimetype: resultsQuery.rows.item(i).mimetype , + bitrate: resultsQuery.rows.item(i).bitrate, + url: resultsQuery.rows.item(i).url , + }; + results.push(song) ; + } + return results ; + }, + + // Return all the artists + allArtistsQuery : function(callBack) + { + var results = []; + this.dbSQL.transaction(function (tx) { + tx.executeSql('SELECT DISTINCT artist FROM track', [], function (tx, resultsQuery ) { + var len = resultsQuery.rows.length, i; + Tomahawk.log("Number of artists results : "+ len); + for (i = 0; i < len; i++) { + results.push(resultsQuery.rows.item(i).artist) ; + } + callBack(results); + }, function(tx, error){ Tomahawk.log(error.message); }); + }); + }, + + // return all the albums name for this artist + albumsQuery: function(artist,callBack) + { + var results = [] ; + this.dbSQL.transaction(function (tx) { + tx.executeSql('SELECT DISTINCT album FROM track WHERE LOWER(artist)=LOWER(?)', [artist], function (tx, resultsQuery ) { + var len = resultsQuery.rows.length, i; + //Tomahawk.log("Number of albums results : "+ len); + for (i = 0; i < len; i++) { + results.push (resultsQuery.rows.item(i).album) ; + } + callBack(results); + }, function(tx, error){ Tomahawk.log(error.message); }); + }); + }, + + // return all the tracks of this artist' album + tracksQuery: function(artist , album, callBack) + { + this.dbSQL.transaction(function (tx) { + tx.executeSql('SELECT * FROM track WHERE LOWER(artist)=LOWER(?) and LOWER(album)=LOWER(?)', [artist,album], + function (tx, resultsQuery ) { + var results = musicManager.parseSongAttriutes(resultsQuery) ; + Tomahawk.log("Number of results : "+results.length+ " "+ DumpObjectIndented(results)); + callBack(results) ; + }, function(tx, error){ Tomahawk.log(error.message); }); + }); + }, + + // Parse track, Album , Artist + searchQuery: function (searchString,callBack) + { + this.dbSQL.transaction(function (tx) { + // Select first or limit mechanisim ? + tx.executeSql("SELECT * FROM track WHERE (LOWER(artist) LIKE LOWER(?)) or (LOWER(album) LIKE LOWER(?)) or (LOWER(track) LIKE LOWER(?))", ["%"+searchString+"%","%"+searchString+"%","%"+searchString+"%"], + function (tx, resultsQuery ) { + var len = resultsQuery.rows.length, i; + var results = musicManager.parseSongAttriutes(resultsQuery) ; + //Tomahawk.log("Number of track results for query : "+results.length); + callBack(results) ; + }, function(tx, error){ Tomahawk.log(error.message); }); + }); + }, + + // Only one Track matching + resolve: function(artist, album, track, callBack) + { + var results = [] ; + this.dbSQL.transaction(function (tx) { + tx.executeSql('SELECT * FROM track WHERE (LOWER(artist)=LOWER(?) and LOWER(album)=LOWER(?) and LOWER(track)=LOWER(?)) OR (LOWER(artist)=LOWER(?) and LOWER(track)=LOWER(?)) ', [artist,album,track,artist,track], // Select first or limit mechanisim ? + function (tx, resultsQuery ) { + var results = musicManager.parseSongAttriutes(resultsQuery) ; + //Tomahawk.log("Number of track results for resolve : "+results.length); + // Filter to give only ONE row : improvement possible : set up a limit ( even if tomahawk is already doing it ) + callBack(results) ; + }, function(tx, error){ Tomahawk.log(error.message); }); + }); + }, +}; + + // Testing Object + var musicManagerTester = { + tabTrackDetails: [] , + + init: function() { + this.tabTrackDetails = {"id":"22" , "track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","genre": "Divin" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + //musicManager.addTrack(this.tabTrackDetails) ; + }, + + populateDatabase: function (rows){ + musicManager.flushDatabase() ; + var i = 0 ; + for (i ; i < rows ; i++) { + for (index in this.tabTrackDetails){ + this.tabTrackDetails[index] = index+i ; + } + musicManager.addTrack(this.tabTrackDetails) ; + } + }, + + showDatabase: function() { + musicManager.showDatabase() ; + }, + + addTrackTest: function (){ + musicManager.addTrack(this.tabTrackDetails) ; + }, + + deleteTrackTest: function() { + musicManager.deleteTrack(this.tabTrackDetails) ; + }, + + flushDatabaseTest: function() { + musicManager.flushDatabase() ; + }, + + flushDatabaseAndWaitTest: function() { + musicManager.dbSQL.transaction(function (tx) { + tx.executeSql('DROP TABLE track'); + }); + musicManager.initDatabase() ; + Tomahawk.log("webSQL db cleaned out"); + return true ; + }, + + resolveTest: function() { + var artist = this.tabTrackDetails["artist"]; + var album = this.tabTrackDetails["album"]; + var track = this.tabTrackDetails["track"]; + musicManager.resolve(artist,album,track, function(results){ + Tomahawk.log("Return songs track "+results.track); + }); + }, + + allArtistsQueryTest: function() { + musicManager.allArtistsQuery(function(results){ + var len = results.length ; var i = 0; + for (i ; i < len ; i++) { + Tomahawk.log("Return artist name num "+i+" : "+results[i]); + } + }); + }, + + tracksQueryTest: function() { + var artist = this.tabTrackDetails["artist"]; + var album = this.tabTrackDetails["album"]; + + musicManager.tracksQuery(artist, album , function(results){ + var len = results.length ; var i = 0; + for (i ; i < len ; i++) { + Tomahawk.log("Return track track name num "+i+" : "+results[i].track); + } + }); + }, + + albumsQueryTest: function() { + var artist = this.tabTrackDetails["artist"]; + musicManager.albumsQuery(artist, function(results){ + var len = results.length ; var i = 0; + for (i ; i < len ; i++) { + Tomahawk.log("Return album track name num "+i+" : "+results[i]); + } + }); + }, + + searchQueryTest: function() { + var qString = "art"; + musicManager.searchQuery(qString, function(results){ + var len = results.length ; var i = 0; + for (i ; i < len ; i++) { + Tomahawk.log("Return of a search query size : "+i+" : "+results[i]); + } + }); + }, + + // Test Scenario + insertionDuplicateTest:function() { + Tomahawk.log("Test Scenario : duplicate insertion"); + this.tabTrackDetails = {"id":"22" , "track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","genre": "Divin" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.addTrack(this.tabTrackDetails) ; // should log a duplicate error + musicManager.addTrack(this.tabTrackDetails) ; + }, + + + insertionWithoutCoreTest:function() { + this.tabTrackDetails = {"id":"" , "track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","genre": "Divin" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.addTrack(this.tabTrackDetails) ; // should log a core unprovided error + this.tabTrackDetails = {"id":null , "track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","genre": "Divin" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.addTrack(this.tabTrackDetails) ; // should log a core unprovided error + }, + + insertionUndefinedAlbumTest:function() { + this.tabTrackDetails = {"id":"22" , "track": "Division Bell", "artist": "PinkFloyd", "album": "", "albumpos": "Track1" ,"year": "1980","genre": "Divin" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.addTrack(this.tabTrackDetails) ; // should add album undefined row + + }, + + deletionWithoutKeyTest:function() { + this.tabTrackDetails = {"id":"" , "track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","genre": "Divin" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.deleteTrack(this.tabTrackDetails) ; + this.tabTrackDetails = {"track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","genre": "Divin" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.deleteTrack(this.tabTrackDetails) ; + }, + + retrieveRowEmptyGenreTest:function() { + + this.tabTrackDetails = {"id":"23" , "track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","genre": "" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.addTrack(this.tabTrackDetails) ; + + this.tabTrackDetails = {"id":"24" , "track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.addTrack(this.tabTrackDetails) ; + + var qString = "Division" ; var log ; + musicManager.searchQuery(qString, function(results){ + var len = results.length ; var i = 0; + for (i ; i < len ; i++) { + for (row in results[i]){ + log += ""+row+": "+ results[i][row] +"," ; + } + } + Tomahawk.log(log); + }); + }, +}; + diff --git a/dropbox/content/contents/images/icon.svg b/dropbox/content/contents/images/icon.svg new file mode 100644 index 000000000..6f63627d1 --- /dev/null +++ b/dropbox/content/contents/images/icon.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dropbox/content/metadata.json b/dropbox/content/metadata.json new file mode 100644 index 000000000..d6db240ae --- /dev/null +++ b/dropbox/content/metadata.json @@ -0,0 +1,19 @@ +{ + "name": "Dropbox", + "pluginName": "dropbox", + "author": "ISI-Peasy team", + "email": "isi-peasy@googlegroups.com", + "version": "0.2", + "website": "http://gettomahawk.com", + "description": "Browse your music files on Dropbox", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/dropbox.js", + "scripts": ["contents/code/musicManager.js", "contents/code/jsOAuth-1.3.6.min.js"], + "icon": "contents/images/icon.svg", + "resources": [ + "contents/code/config.ui", + "contents/code/dropbox.svg" + ] + } +} diff --git a/exfm/exfm.js b/exfm/exfm.js index a129bd99b..55267b33b 100644 --- a/exfm/exfm.js +++ b/exfm/exfm.js @@ -15,7 +15,7 @@ var ExfmResolver = Tomahawk.extend(TomahawkResolver, { url += encodeURIComponent(title); - url += "?start=0&results=20"; + url += "?start=0&results=20&client_id=tomahawk"; // send request and parse it into javascript var that = this; diff --git a/googledrive/content/contents/code/config.ui b/googledrive/content/contents/code/config.ui new file mode 100755 index 000000000..802e7c1d8 --- /dev/null +++ b/googledrive/content/contents/code/config.ui @@ -0,0 +1,97 @@ + + + Form + + + + 0 + 0 + 322 + 339 + + + + Form + + + + + + + 304 + 260 + + + + + + + googledrive.svg + + + Qt::AlignCenter + + + + + + + QFormLayout::FieldsStayAtSizeHint + + + Qt::AlignJustify|Qt::AlignVCenter + + + Qt::AlignJustify|Qt::AlignTop + + + + + Associate + + + + + + + true + + + Delete + + + + + + + OAuth developer key : + + + + + + + App Key + + + + + + + App Secret + + + + + + + + + + + + + + + + diff --git a/googledrive/content/contents/code/googledrive.js b/googledrive/content/contents/code/googledrive.js new file mode 100644 index 000000000..bf3a41c58 --- /dev/null +++ b/googledrive/content/contents/code/googledrive.js @@ -0,0 +1,578 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2013, Rémi Benoit + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +var GoogleDriveResolver = Tomahawk.extend(TomahawkResolver, { + uid: '', + cursor: '', + maxResults: '150', + getFileUrl: '', + + settings: { + name: 'Google Drive', + weight: 60, + icon : 'googledrive.svg', + timeout: 15 + }, + + getConfigUi: function () { + var uiData = Tomahawk.readBase64("config.ui"); + return { + + "widget": uiData, + fields: [{ + name: "associateButton", + widget: "associateButton", + property: "text", + connections : [ { + signal: "clicked()", + javascriptCallback: "resolver.associateClicked();" + }] + }, + { + name: "deleteButton", + widget: "deleteButton", + property: "text", + connections : [ { + signal: "clicked()", + javascriptCallback: "resolver.deleteClicked();" + }] + }, + { + name: "clientId", + widget: "appKeyLineEdit", + property: "text" + }, + { + name: "clientSecret", + widget: "appSecretLineEdit", + property: "text" + },], + images: [{ + 'googledrive.svg': Tomahawk.readBase64(this.settings.icon) + }, ] + }; + }, + + newConfigSaved: function () { + var userConfig = this.getUserConfig(); + + if (this.oauth.clientId !== userConfig.clientId || + this.oauth.clientSecret !== userConfig.clientSecret) + { + this.oauth.clientId = userConfig.clientId.trim(); + this.oauth.clientSecret = userConfig.clientSecret.trim(); + + dbLocal.setItem('googledrive.clientId', this.oauth.clientId); + dbLocal.setItem('googledrive.clientSecret', this.oauth.clientSecret); + + } + }, + + associateClicked: function () { + Tomahawk.log("Associate was clicked"); + this.oauth.associate(this.updateDatabase.bind(this)); + }, + + deleteClicked: function () { + Tomahawk.log("Delete was clicked"); + + this.cursor = ''; + dbLocal.setItem('googledrive.cursor',''); + + this.oauth.deleteAssociation(); + musicManager.flushDatabase(); + + }, + + queryFailure: function(data) { + Tomahawk.log("Request Failed : " + data.text); + }, + + init: function () { + Tomahawk.log("Beginnning INIT of Google Drive resovler"); + //dbLocal.setItem("googledrive.expiresOn","1"); + //dbLocal.setItem("googledrive.cursor",""); + + //Tomahawk.showWebInspector(); + + this.cursor = dbLocal.getItem('googledrive.cursor',''); + + this.oauth.init(); + musicManager.initDatabase() ; + + //musicManager.showDatabase(); + //this.googleDriveMusicManagerTests() ; + + Tomahawk.addCustomUrlHandler( "googledrive", "getStreamUrl", "true" ); + Tomahawk.reportCapabilities( TomahawkResolverCapability.Browsable | TomahawkResolverCapability.AccountFactory ); + + //TODO updateDatabase when? + this.updateDatabase(); + }, + + updateDatabase: function(pageToken){ + Tomahawk.log("Sending Delta Query : "); + pageToken = pageToken || (this.cursor === '' ? '1' : this.cursor); + + var url = 'https://www.googleapis.com/drive/v2/changes?' + +'maxResults=' + this.maxResults + +'&pageToken=' + pageToken; + + Tomahawk.log("URL used : "+ url); + this.oauth.ogetJSON(url, this.deltaCallback.bind(this)); + }, + + deltaCallback: function(response){ + //TODO set cursor in DB + Tomahawk.log("Delta returned!"); + Tomahawk.log("nextPageToken : " + response.nextPageToken); + Tomahawk.log("largestChangeId : " + response.largestChangeId); + //Tomahawk.log(DumpObjectIndented(response)); + + + for( var i = 0; i < response.items.length; i++){ + var item = response.items[i]; + if(item['deleted']){ + Tomahawk.log("Deleting : " + item['fileId']); + //dbSQL.deleteTrack(item.file.id); + musicManager.deleteTrack({'id' : item['fileId']}); + }else{ + var file = item['file']; + //Tomahawk.log("File : " + item['file']['title']+ " is supported : " + this.isMimeTypeSupported(item['file']['mimeType'])); + + if(this.isMimeTypeSupported(file['mimeType'])){ + //Tomahawk.log(DumpObjectIndented(item)); + //Get ID3 Tag + Tomahawk.log("Get ID3Tag from : " + file['originalFilename']); + //Tomahawk.log("size : " + item['file']['fileSize']); + //Tomahawk.log("mime : " + item['file']['mimeType']); + //Tomahawk.log('url : ' + this.getStreamUrl(item['file']['id'])); + Tomahawk.readCloudFile(file['originalFilename'], file['id'], file['fileSize'], file['mimeType'], this.oauth.createOauthUrl(file['downloadUrl']), "onID3TagCallback", ""); + } + } + } + if(response.nextPageToken){ + this.updateDatabase(response.nextPageToken); + }else{ + this.cursor = parseInt(response.largestChangeId) + 1; + dbLocal.setItem('googledrive.cursor', this.cursor); + } + }, + + resolve: function (qid, artist, album, title) { + musicManager.resolve(artist, album, title, function(results) { + var return_songs = { + qid: qid, + results: results + }; + Tomahawk.log("google drive resolved query : " + artist + ", "+ album+ ", "+ title+" returned: " + DumpObjectIndented(return_songs.results)); + Tomahawk.addTrackResults(return_songs); + }); + + }, + + search: function (qid, searchString) { + // set up a limit for the musicManager search Query + Tomahawk.log("search query"); + musicManager.searchQuery(searchString,function(results){ + var return_songs = { + qid: qid, + results: results + }; + Tomahawk.log("google drive search query : " + searchString +" , result: " + DumpObjectIndented(return_songs.results)); + Tomahawk.addTrackResults(return_songs); + }); + }, + + artists: function( qid ) + { + Tomahawk.log("artists query"); + musicManager.allArtistsQuery(function(results){ + var return_artists = { + qid: qid, + artists: results + }; + Tomahawk.log("google drive artists returned: "); + Tomahawk.addArtistResults(return_artists); + }); + this.updateDatabase(); + }, + + albums: function( qid, artist ) + { + Tomahawk.log("albums query"); + musicManager.albumsQuery(artist, function(results){ + var return_albums = { + qid: qid, + artist: artist, + albums: results + }; + Tomahawk.log("google drive albums returned: "); + Tomahawk.addAlbumResults(return_albums); + }); + }, + + tracks: function( qid, artist, album ) + { + Tomahawk.log("tracks query"); + musicManager.tracksQuery(artist, album, function(results){ + var return_tracks = { + qid: qid, + artist: artist, + album: album, + results: results + }; + Tomahawk.log("google drive tracks returned: "); + //Tomahawk.log("Google Drive tracks for ("+artist + " , "+ album +") returned:"+ DumpObjectIndented(return_tracks.results)); + Tomahawk.addAlbumTrackResults(return_tracks); + }); + }, + + collection: function() + { + //strip http:// and trailing slash + var desc = "cloud de google drive"; + + var return_object = { + prettyname: "Google Drive", + description: desc, + iconfile: this.settings.icon + }; + + return return_object; + }, + + getStreamUrl: function (quid, ourUrl) { + var songId = ourUrl.replace("googledrive://id/", ""); + this.oauth.ogetJSON('https://www.googleapis.com/drive/v2/files/' + songId, function(meta) { + var url = this.oauth.createOauthUrl(meta['downloadUrl']); + Tomahawk.reportStreamUrl(quid, url);}.bind(this)); + }, + + googleDriveMusicManagerTests: function() { + //musicManagerTester.flushDatabaseTest() ; + musicManagerTester.init() ; + //musicManagerTester.insertionUndefinedAlbumTest() ; + //musicManagerTester.addTrackTest() ; + //musicManager.deletionWithoutKeyTest(); + //musicManagerTester.populateDatabase(1) ; + //musicManagerTester.searchQueryTest() ; + //~ musicManagerTester.resolveTest() ; + //~ musicManagerTester.allArtistsQueryTest() ; + //~ musicManagerTester.tracksQueryTest() ; + //~ musicManagerTester.albumsQueryTest() ; + //musicManagerTester.searchQueryTest() ; + //musicManagerTester.retrieveRowEmptyGenreTest() ; + //musicManagerTester.insertionDuplicateTest() ; + //musicManagerTester.retrieveRowEmptyGenreTest() ; + //musicManagerTester.insertionWithoutCoreTest() ; + //musicManagerTester.insertionWithoutCoreTest() ; + //musicManagerTester.deletionWithoutKeyTest() ; + //musicManagerTester.showDatabase() ; + }, + + onID3TagCallback: function(tags) + { + var trackInfo = { + 'id' : tags['fileId'], + 'url' : 'googledrive://id/' + tags['fileId'], + 'track' : tags['track'], + 'artist' : tags['artist'], + 'album' : tags['album'], + 'albumpos' : tags['albumpos'], + 'year' : tags['year'], + 'bitrate' : tags['bitrate'], + 'mimetype' : tags['mimetype'], + 'size' : tags['size'], + 'duration' : tags['duration'], + }; + + Tomahawk.log("Adding : " + DumpObjectIndented(trackInfo)); + musicManager.addTrack(trackInfo); + }, + + //TODO: put that in QTScriptResolverHelper + isMimeTypeSupported: function(mimeType) + { + //Tomahawk.log("Checking : "+ mimeType); + var mimes = [ "audio/mpeg" , "application/ogg" , "application/ogg" , "audio/x-musepack" , "audio/x-ms-wma" , "audio/mp4" , "audio/mp4" , "audio/mp4" , "audio/flac" , "audio/aiff" , "audio/aiff" , "audio/x-wavpack" ]; + return (mimes.lastIndexOf(mimeType) != -1); + }, + + oauth: { + + init: function(){ + this.clientId = dbLocal.getItem('googledrive.clientId',''); + this.clientSecret = dbLocal.getItem('googledrive.clientSecret',''); + this.accessToken = dbLocal.getItem('googledrive.accessToken',''); + this.refreshToken = dbLocal.getItem('googledrive.refreshToken',''); + this.expiresOn = dbLocal.getItem('googledrive.expiresOn',''); + + this.setupAutoRefreshToken(); + }, + + //associate a new User + //If the association is succesfull the previous token is discarded + associate: function(callback){ + var url = this.oauthUrl + '?response_type=code' + + '&client_id=' + this.clientId + + '&redirect_uri=' + this.redirectUri + + '&scope=' + this.scopes; + this.openAcceptPage(url, callback); + }, + + deleteAssociation: function(){ + dbLocal.setItem('googledrive.accessToken',''); + dbLocal.setItem('googledrive.refreshToken',''); + dbLocal.setItem('googledrive.expiresOn',''); + + this.accessToken = ''; + this.refreshToken = ''; + this.expiresOn = ''; + }, + + isAssociated: function(){ + var accessToken = dbLocal.getItem('googledrive.accessToken',''); + var refreshToken = dbLocal.getItem('googledrive.refreshToken',''); + return( !(accessToken === '') && !(refreshToken === '') ); + }, + + opostJSON: function(url, data, success){ + //var that = this; + if(!this.isAssociated()){ + //TODO throw error NoAccountAssociated ? + Tomahawk.log("REFUSED Post to "+ url + " : No account associated"); + }else{ + if(this.tokenExpired()){ + Tomahawk.log("Token expired"); + this.getRefreshedAccessToken(function (){this.opostJSON(url, data, success);}.bind(this)); + }else{ + //TODO treat case no parameters given + Tomahawk.asyncFormPostRequest(url, data, function (data) { + success(JSON.parse(data.responseText)); + }, {'Authorization': 'Bearer '+ this.accessToken}); + } + } + }, + + ogetJSON: function(url, success){ + if(!this.isAssociated()){ + //TODO throw error NoAccountAssociated ? + Tomahawk.log("REFUSED Get to "+ url + " : No account associated"); + }else{ + if(this.tokenExpired()){ + Tomahawk.log("Token expired"); + this.getRefreshedAccessToken(function (){this.ogetJSON(url, success);}.bind(this)); + }else{ + //TODO treat case no parameters given + Tomahawk.asyncRequest(url, function (data) { + success(JSON.parse(data.responseText)); + }, {'Authorization': 'Bearer '+ this.accessToken}); + } + } + }, + + ogetSyncJSON: function(url){ + if(!this.isAssociated()){ + //TODO throw error NoAccountAssociated ? + Tomahawk.log("REFUSED Get to "+ url + " : No account associated"); + }else{ + if(this.tokenExpired()){ + Tomahawk.log("Token expired"); + this.getRefreshedAccessToken(); + }else{ + //TODO treat case no parameters given + return Tomahawk.syncRequest(url, {'Authorization': 'Bearer '+ this.accessToken}); + } + } + }, + + createOauthUrl: function(url){ + if(!this.isAssociated()){ + //TODO throw error NoAccountAssociated ? + Tomahawk.log("REFUSED Creation to "+ url + " : No account associated"); + }else{ + if(this.tokenExpired()){ + Tomahawk.log("REFUSED Creation to "+ url + " : token expired"); + this.getRefreshedAccessToken(); + }else{ + return (url + '&access_token=' + this.accessToken); + } + } + }, + + //Private member + + clientId: '', + clientSecret: '', + oauthUrl: 'https://accounts.google.com/o/oauth2/auth', + tokenUrl: 'https://accounts.google.com/o/oauth2/token', + scopes: 'https://www.googleapis.com/auth/drive.readonly', + redirectUri: 'urn:ietf:wg:oauth:2.0:oob', + accessToken: '', + refreshToken: '', + expiresOn: '', + + + openAcceptPage: function(url, callback) { + Tomahawk.log('Opening : ' + url); + Tomahawk.requestWebView("acceptPage", url); + + //acceptPage.setWindowModality(2); + //acceptPage.resize(acceptPage.height(), 800); + + acceptPage.show(); + acceptPage.titleChanged.connect(Tomahawk.resolver.instance.oauth, function(title){ + this.onTitleChanged(title, callback); + }); + }, + + onTitleChanged: function(title, callback){ + //Tomahawk.log("Title changed : \'" + title+"\'"); + + var result = title.split('='); + + if(result[0] === 'Success code'){ + Tomahawk.log("Accepted"); + var that = this; + var params = 'grant_type=authorization_code' + + '&code=' + result[1] + + '&client_id=' + this.clientId + + '&client_secret='+ this.clientSecret + + '&redirect_uri=' + this.redirectUri; + + Tomahawk.asyncFormPostRequest(this.tokenUrl, params, function(data){ + this.onAccessTokenReceived(data, callback); + }.bind(this)); + } + + if(result[0] === 'Denied error'){ + Tomahawk.log("Refused"); + //close webpage + } + }, + + onAccessTokenReceived: function(data, callback){ + //parse response + var ret = JSON.parse(data.responseText); + + //TODO close webpage, or not? + + this.accessToken = ret.access_token; + this.expiresOn = Date.now() + (ret.expires_in*1000); + + dbLocal.setItem('googledrive.accessToken',this.accessToken); + dbLocal.setItem('googledrive.expiresOn',this.expiresOn); + + if(typeof ret.refresh_token !=='undefined'){ + Tomahawk.log("Setting refresh token : " + ret.refresh_token); + this.refreshToken = ret.refresh_token; + dbLocal.setItem('googledrive.refreshToken',this.refreshToken); + } + + this.setupAutoRefreshToken(); + + if(typeof callback !== 'undefined'){ + callback(); + } + }, + + tokenExpired: function(){ + return (Date.now() > this.expiresOn); + }, + + getRefreshedAccessToken: function(callback){ + Tomahawk.log("Refrshing access token."); + var params = 'grant_type=refresh_token' + + '&refresh_token=' + this.refreshToken + + '&client_id=' + this.clientId + + '&client_secret=' + this.clientSecret; + + Tomahawk.asyncFormPostRequest(this.tokenUrl, params, function(data){ + this.onAccessTokenReceived(data, callback); + }.bind(this)); + }, + + setupAutoRefreshToken : function(){ + if(this.isAssociated()){ + if(this.tokenExpired()){ + Tomahawk.log("Token expired on auto"); + this.getRefreshedAccessToken(); + }else{ + Tomahawk.log("Setting timeout in " + (this.expiresOn - Date.now() - 2000) + "ms to getRefreshToken from init"); + //Tomahawk.log(this.getRefreshedAccessToken.bind(this)); + window.setTimeout(this.getRefreshedAccessToken.bind(this), (this.expiresOn - Date.now() - 2000)); + } + } + }, + + queryFailure: function(data) { + Tomahawk.log("Request Failed : " + data.text); + } + } +}); + +var dbLocal = { + setItem: function(a1, a2){ + window.localStorage.setItem(a1,a2); + }, + getItem: function (key, defaultResponse){ + var result = window.localStorage.getItem(key); + result = (result == null)? defaultResponse : result; + + Tomahawk.log("DB: loaded "+key+" : '"+ result+"' "); + + return result; + } +}; + + +Tomahawk.resolver.instance = GoogleDriveResolver; + +function DumpObjectIndented(obj, indent) +{ + var result = ""; + if (indent == null) indent = ""; + + for (var property in obj) + { + var value = obj[property]; + if (typeof value == 'string') + value = "'" + value + "'"; + else if (typeof value == 'object') + { + if (value instanceof Array) + { + // Just let JS convert the Array to a string! + value = "[ " + value + " ]"; + } + else + { + // Recursive dump + // (replace " " by "\t" or something else if you prefer) + var od = DumpObjectIndented(value, indent + " "); + // If you like { on the same line as the key + //value = "{\n" + od + "\n" + indent + "}"; + // If you prefer { and } to be aligned + value = "\n" + indent + "{\n" + od + "\n" + indent + "}"; + } + } + result += indent + "'" + property + "' : " + value + ",\n"; + } + return result.replace(/,\n$/, ""); +} diff --git a/googledrive/content/contents/code/googledrive.svg b/googledrive/content/contents/code/googledrive.svg new file mode 100644 index 000000000..0f6af9439 --- /dev/null +++ b/googledrive/content/contents/code/googledrive.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/googledrive/content/contents/code/musicManager.js b/googledrive/content/contents/code/musicManager.js new file mode 100644 index 000000000..98817080c --- /dev/null +++ b/googledrive/content/contents/code/musicManager.js @@ -0,0 +1,353 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012, mack-t + * Copyright 2012, Peter Loron + * Copyright 2013, Teo Mrnjavac + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + + +Tomahawk.log(" Music Manager begining"); + +var musicManager = { + + dbName : "GoogleDriveDB" , + dbSQL: null , + searchLimitResults : 500 , + + initDatabase : function() + { + Tomahawk.log("Init webSQL Db : "); + if (!this.dbSQL) this.dbSQL = openDatabase(this.dbName, '1.0', 'Muic Database', 2 * 1024 * 1024) ; + this.dbSQL.transaction(function (tx) { + tx.executeSql('CREATE TABLE IF NOT EXISTS track (id primary key, track, artist, album, albumpos, year, genre, size, duration, mimetype, bitrate, url)'); + }); + }, + + showDatabase: function() + { + Tomahawk.log("Displaying Content of Database"); var log = "" ; + this.dbSQL.transaction(function (tx) { + tx.executeSql('SELECT * FROM track', [], function (tx, resultsQuery ) { + var results = musicManager.parseSongAttriutes(resultsQuery) ; + // Parsing to display information on each music + var len = results.length ; var i = 0 ; + for (i ; i < len ; i ++) { + for (row in results[i]) { + log += ""+row+": "+ results[i][row] +"," ; + } + Tomahawk.log (log) ; + } + }); + }); + }, + + // delete the database + flushDatabase : function() + { + this.dbSQL.transaction(function (tx) { + tx.executeSql('DROP TABLE track'); + }); + musicManager.initDatabase() ; + Tomahawk.log("webSQL db cleaned out"); + }, + + addTrack : function(tabTrackDetails) + { + var id = tabTrackDetails["id"] || ''; + var track = tabTrackDetails["track"] || ''; + var artist = tabTrackDetails["artist"] || ''; + var album = tabTrackDetails["album"] || 'undefined'; + var albumpos = tabTrackDetails["albumpos"] || ''; + var year = tabTrackDetails["year"] || ''; + var genre = tabTrackDetails["genre"] || '' ; + var size = tabTrackDetails["size"] || '' ; + var duration = tabTrackDetails["duration"] || '' ; + var mimetype = tabTrackDetails["mimetype"] || '' ; + var bitrate = tabTrackDetails["bitrate"] || '' ; + var url = tabTrackDetails["url"] || '' ; + + // check core information provided + if (id == "" || track == "" || artist =="" || url=="") { + Tomahawk.log("Insertion Failed : core information track isn't provided to "+this.dbName); + return ; + } + else + { + // Check presence in the database before adding + var db = this.dbName ; + this.dbSQL.transaction(function (tx) { + tx.executeSql('SELECT id FROM track where id=?', [id], function (tx, resultsQuery ) { + if (resultsQuery.rows.length > 0) { + Tomahawk.log("Insertion abort : data already inside the "+db+""); + return ; + } + }); + }); + } + // Track Insertion + this.dbSQL.transaction(function (tx) { + tx.executeSql('INSERT INTO track (id, track, artist, album, albumpos, year, genre, size, duration, mimetype, bitrate, url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + [id, track, artist, album, albumpos, year, genre, size, duration, mimetype, bitrate, url]); + }); + Tomahawk.log("Insertion inside "+this.dbName+""); + }, + + deleteTrack: function (tabTrackDetails) + { + var id = tabTrackDetails["id"] || ''; + if (id == "" || !id) { Tomahawk.log("Deletion intented without an id key"); return ; } + this.dbSQL.transaction(function (tx) { + tx.executeSql('DELETE FROM track WHERE id = ?', [id], function (tx,resultsQuery){}); + }); + Tomahawk.log("Deletion inside "+this.dbName+""); + }, + + + parseSongAttriutes: function (resultsQuery) + { + //Tomahawk.log("parsing Attribute"); + var results = [] ; var song ; + var len = resultsQuery.rows.length + for (i = 0; i < len; i++) { + song = { + id: resultsQuery.rows.item(i).id , + track: resultsQuery.rows.item(i).track , + artist: resultsQuery.rows.item(i).artist , + album: resultsQuery.rows.item(i).album , + albumpos: resultsQuery.rows.item(i).albumpos , + year: resultsQuery.rows.item(i).year , + genre: resultsQuery.rows.item(i).genre , + size: resultsQuery.rows.item(i).size , + duration: resultsQuery.rows.item(i).duration , + mimetype: resultsQuery.rows.item(i).mimetype , + bitrate: resultsQuery.rows.item(i).bitrate, + url: resultsQuery.rows.item(i).url , + }; + results.push(song) ; + } + return results ; + }, + + // Return all the artists + allArtistsQuery : function(callBack) + { + var results = []; + this.dbSQL.transaction(function (tx) { + tx.executeSql('SELECT DISTINCT artist FROM track', [], function (tx, resultsQuery ) { + var len = resultsQuery.rows.length, i; + Tomahawk.log("Number of artists results : "+ len); + for (i = 0; i < len; i++) { + results.push(resultsQuery.rows.item(i).artist) ; + } + callBack(results); + }, function(tx, error){ Tomahawk.log(error.message); }); + }); + }, + + // return all the albums name for this artist + albumsQuery: function(artist,callBack) + { + var results = [] ; + this.dbSQL.transaction(function (tx) { + tx.executeSql('SELECT DISTINCT album FROM track WHERE LOWER(artist)=LOWER(?)', [artist], function (tx, resultsQuery ) { + var len = resultsQuery.rows.length, i; + //Tomahawk.log("Number of albums results : "+ len); + for (i = 0; i < len; i++) { + results.push (resultsQuery.rows.item(i).album) ; + } + callBack(results); + }, function(tx, error){ Tomahawk.log(error.message); }); + }); + }, + + // return all the tracks of this artist' album + tracksQuery: function(artist , album, callBack) + { + this.dbSQL.transaction(function (tx) { + tx.executeSql('SELECT * FROM track WHERE LOWER(artist)=LOWER(?) and LOWER(album)=LOWER(?)', [artist,album], + function (tx, resultsQuery ) { + var results = musicManager.parseSongAttriutes(resultsQuery) ; + Tomahawk.log("Number of results : "+results.length+ " "+ DumpObjectIndented(results)); + callBack(results) ; + }, function(tx, error){ Tomahawk.log(error.message); }); + }); + }, + + // Parse track, Album , Artist + searchQuery: function (searchString,callBack) + { + this.dbSQL.transaction(function (tx) { + // Select first or limit mechanisim ? + tx.executeSql("SELECT * FROM track WHERE (LOWER(artist) LIKE LOWER(?)) or (LOWER(album) LIKE LOWER(?)) or (LOWER(track) LIKE LOWER(?))", ["%"+searchString+"%","%"+searchString+"%","%"+searchString+"%"], + function (tx, resultsQuery ) { + var len = resultsQuery.rows.length, i; + var results = musicManager.parseSongAttriutes(resultsQuery) ; + //Tomahawk.log("Number of track results for query : "+results.length); + callBack(results) ; + }, function(tx, error){ Tomahawk.log(error.message); }); + }); + }, + + // Only one Track matching + resolve: function(artist, album, track, callBack) + { + var results = [] ; + this.dbSQL.transaction(function (tx) { + tx.executeSql('SELECT * FROM track WHERE (LOWER(artist)=LOWER(?) and LOWER(album)=LOWER(?) and LOWER(track)=LOWER(?)) OR (LOWER(artist)=LOWER(?) and LOWER(track)=LOWER(?)) ', [artist,album,track,artist,track], // Select first or limit mechanisim ? + function (tx, resultsQuery ) { + var results = musicManager.parseSongAttriutes(resultsQuery) ; + //Tomahawk.log("Number of track results for resolve : "+results.length); + // Filter to give only ONE row : improvement possible : set up a limit ( even if tomahawk is already doing it ) + callBack(results) ; + }, function(tx, error){ Tomahawk.log(error.message); }); + }); + }, +}; + + // Testing Object + var musicManagerTester = { + tabTrackDetails: [] , + + init: function() { + this.tabTrackDetails = {"id":"22" , "track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","genre": "Divin" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + //musicManager.addTrack(this.tabTrackDetails) ; + }, + + populateDatabase: function (rows){ + musicManager.flushDatabase() ; + var i = 0 ; + for (i ; i < rows ; i++) { + for (index in this.tabTrackDetails){ + this.tabTrackDetails[index] = index+i ; + } + musicManager.addTrack(this.tabTrackDetails) ; + } + }, + + showDatabase: function() { + musicManager.showDatabase() ; + }, + + addTrackTest: function (){ + musicManager.addTrack(this.tabTrackDetails) ; + }, + + deleteTrackTest: function() { + musicManager.deleteTrack(this.tabTrackDetails) ; + }, + + flushDatabaseTest: function() { + musicManager.flushDatabase() ; + }, + + resolveTest: function() { + var artist = this.tabTrackDetails["artist"]; + var album = this.tabTrackDetails["album"]; + var track = this.tabTrackDetails["track"]; + musicManager.resolve(artist,album,track, function(results){ + Tomahawk.log("Return songs track "+results.track); + }); + }, + + allArtistsQueryTest: function() { + musicManager.allArtistsQuery(function(results){ + var len = results.length ; var i = 0; + for (i ; i < len ; i++) { + Tomahawk.log("Return artist name num "+i+" : "+results[i]); + } + }); + }, + + tracksQueryTest: function() { + var artist = this.tabTrackDetails["artist"]; + var album = this.tabTrackDetails["album"]; + + musicManager.tracksQuery(artist, album , function(results){ + var len = results.length ; var i = 0; + for (i ; i < len ; i++) { + Tomahawk.log("Return track track name num "+i+" : "+results[i].track); + } + }); + }, + + albumsQueryTest: function() { + var artist = this.tabTrackDetails["artist"]; + musicManager.albumsQuery(artist, function(results){ + var len = results.length ; var i = 0; + for (i ; i < len ; i++) { + Tomahawk.log("Return album track name num "+i+" : "+results[i]); + } + }); + }, + + searchQueryTest: function() { + var qString = "art"; + musicManager.searchQuery(qString, function(results){ + var len = results.length ; var i = 0; + for (i ; i < len ; i++) { + Tomahawk.log("Return of a search query size : "+i+" : "+results[i]); + } + }); + }, + + // Test Scenario + insertionDuplicateTest:function() { + Tomahawk.log("Test Scenario : duplicate insertion"); + this.tabTrackDetails = {"id":"22" , "track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","genre": "Divin" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.addTrack(this.tabTrackDetails) ; // should log a duplicate error + musicManager.addTrack(this.tabTrackDetails) ; + }, + + insertionWithoutCoreTest:function() { + this.tabTrackDetails = {"id":"" , "track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","genre": "Divin" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.addTrack(this.tabTrackDetails) ; // should log a core unprovided error + this.tabTrackDetails = {"id":null , "track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","genre": "Divin" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.addTrack(this.tabTrackDetails) ; // should log a core unprovided error + }, + + insertionUndefinedAlbumTest:function() { + this.tabTrackDetails = {"id":"22" , "track": "Division Bell", "artist": "PinkFloyd", "album": "", "albumpos": "Track1" ,"year": "1980","genre": "Divin" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.addTrack(this.tabTrackDetails) ; // should add album undefined row + + }, + + deletionWithoutKeyTest:function() { + this.tabTrackDetails = {"id":"" , "track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","genre": "Divin" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.deleteTrack(this.tabTrackDetails) ; + this.tabTrackDetails = {"track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","genre": "Divin" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.deleteTrack(this.tabTrackDetails) ; + }, + + retrieveRowEmptyGenreTest:function() { + + this.tabTrackDetails = {"id":"23" , "track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","genre": "" ,"size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.addTrack(this.tabTrackDetails) ; + + this.tabTrackDetails = {"id":"24" , "track": "Division Bell", "artist": "PinkFloyd", "album": "Division Bell", "albumpos": "Track1" ,"year": "1980","size": "3000","duration":"3:06","mimetype":"flac","bitrate":"256mps","url":"www.pinkFloyd.com/DivisionBell" }; + musicManager.addTrack(this.tabTrackDetails) ; + + var qString = "Division" ; var log ; + musicManager.searchQuery(qString, function(results){ + var len = results.length ; var i = 0; + for (i ; i < len ; i++) { + for (row in results[i]){ + log += ""+row+": "+ results[i][row] +"," ; + } + } + Tomahawk.log(log); + }); + }, +}; diff --git a/googledrive/content/contents/images/icon.svg b/googledrive/content/contents/images/icon.svg new file mode 100644 index 000000000..0f6af9439 --- /dev/null +++ b/googledrive/content/contents/images/icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/googledrive/content/metadata.json b/googledrive/content/metadata.json new file mode 100644 index 000000000..cf7b0c4d9 --- /dev/null +++ b/googledrive/content/metadata.json @@ -0,0 +1,19 @@ +{ + "name": "Google Drive", + "pluginName": "googledrive", + "author": "ISI-Peasy team", + "email": "isi-peasy@googlegroups.com", + "version": "0.2", + "website": "http://gettomahawk.com", + "description": "Browse your music files on Google Drive", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/googledrive.js", + "scripts": ["contents/code/musicManager.js"], + "icon": "contents/images/icon.svg", + "resources": [ + "contents/code/config.ui", + "contents/code/googledrive.svg" + ] + } +} diff --git a/grooveshark/config.ui b/grooveshark/content/contents/code/config.ui similarity index 100% rename from grooveshark/config.ui rename to grooveshark/content/contents/code/config.ui diff --git a/grooveshark/grooveshark-icon.png b/grooveshark/content/contents/code/grooveshark-icon.png similarity index 100% rename from grooveshark/grooveshark-icon.png rename to grooveshark/content/contents/code/grooveshark-icon.png diff --git a/grooveshark/grooveshark.js b/grooveshark/content/contents/code/grooveshark.js similarity index 96% rename from grooveshark/grooveshark.js rename to grooveshark/content/contents/code/grooveshark.js index 370c22841..76693fd14 100644 --- a/grooveshark/grooveshark.js +++ b/grooveshark/content/contents/code/grooveshark.js @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2011-2012, Leo Franchi + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + // for removing diacritics [extracted from : http://lehelk.com/2011/05/06/script-to-remove-diacritics/] var defaultDiacriticsRemovalMap = [ {'base':'A', 'letters':/[\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g}, diff --git a/grooveshark/grooveshark.png b/grooveshark/content/contents/code/grooveshark.png similarity index 100% rename from grooveshark/grooveshark.png rename to grooveshark/content/contents/code/grooveshark.png diff --git a/grooveshark/grooveshark.svg b/grooveshark/content/contents/code/grooveshark.svg similarity index 100% rename from grooveshark/grooveshark.svg rename to grooveshark/content/contents/code/grooveshark.svg diff --git a/grooveshark/content/contents/images/icon.png b/grooveshark/content/contents/images/icon.png new file mode 100644 index 000000000..0aabe3cd9 Binary files /dev/null and b/grooveshark/content/contents/images/icon.png differ diff --git a/grooveshark/content/metadata.json b/grooveshark/content/metadata.json new file mode 100644 index 000000000..15b9939b8 --- /dev/null +++ b/grooveshark/content/metadata.json @@ -0,0 +1,21 @@ +{ + "name": "Grooveshark", + "pluginName": "grooveshark", + "author": "Leo Franchi", + "email": "lfranchi@kde.org", + "version": "0.6.4", + "website": "http://gettomahawk.com", + "description": "Searches Grooveshark for playable tracks, requires premium account", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/grooveshark.js", + "scripts": [], + "icon": "contents/images/icon.png", + "resources": [ + "contents/code/config.ui", + "contents/code/grooveshark.png", + "contents/code/grooveshark.svg", + "contents/code/grooveshark-icon.png" + ] + } +} diff --git a/jamendo/jamendo-icon.png b/jamendo/content/contents/code/jamendo-icon.png similarity index 100% rename from jamendo/jamendo-icon.png rename to jamendo/content/contents/code/jamendo-icon.png diff --git a/jamendo/jamendo-resolver.js b/jamendo/content/contents/code/jamendo-resolver.js similarity index 79% rename from jamendo/jamendo-resolver.js rename to jamendo/content/contents/code/jamendo-resolver.js index ec1d586d7..683f5c50e 100644 --- a/jamendo/jamendo-resolver.js +++ b/jamendo/content/contents/code/jamendo-resolver.js @@ -1,6 +1,21 @@ -/* - * (c) 2011 lasconic +/* === This file is part of Tomahawk Player - === + * + * Copyright 2011, lasconic + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . */ + var JamendoResolver = Tomahawk.extend(TomahawkResolver, { settings: { name: 'Jamendo', diff --git a/jamendo/content/contents/images/icon.png b/jamendo/content/contents/images/icon.png new file mode 100644 index 000000000..1b0bb1550 Binary files /dev/null and b/jamendo/content/contents/images/icon.png differ diff --git a/jamendo/content/metadata.json b/jamendo/content/metadata.json new file mode 100644 index 000000000..bb849e31b --- /dev/null +++ b/jamendo/content/metadata.json @@ -0,0 +1,18 @@ +{ + "name": "Jamendo", + "pluginName": "jamendo", + "author": "lasconic", + "email": "lasconic@gmail.com", + "version": "0.1.5", + "website": "http://gettomahawk.com", + "description": "Searches Jamendo's free music database", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/jamendo-resolver.js", + "scripts": [], + "icon": "contents/images/icon.png", + "resources": [ + "contents/code/jamendo-icon.png" + ] + } +} diff --git a/lastfm/lastfm-icon.png b/lastfm/content/contents/code/lastfm-icon.png similarity index 100% rename from lastfm/lastfm-icon.png rename to lastfm/content/contents/code/lastfm-icon.png diff --git a/lastfm/lastfm.js b/lastfm/content/contents/code/lastfm.js similarity index 71% rename from lastfm/lastfm.js rename to lastfm/content/contents/code/lastfm.js index 1e1206883..65083bc88 100644 --- a/lastfm/lastfm.js +++ b/lastfm/content/contents/code/lastfm.js @@ -1,3 +1,21 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2011-2012, Thierry Göckel + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + var LastfmResolver = Tomahawk.extend(TomahawkResolver, { settings: { name: 'Last.fm', diff --git a/lastfm/content/contents/images/icon.png b/lastfm/content/contents/images/icon.png new file mode 100644 index 000000000..982d86383 Binary files /dev/null and b/lastfm/content/contents/images/icon.png differ diff --git a/lastfm/content/metadata.json b/lastfm/content/metadata.json new file mode 100644 index 000000000..aeab8d5b1 --- /dev/null +++ b/lastfm/content/metadata.json @@ -0,0 +1,18 @@ +{ + "name": "Last.fm", + "pluginName": "lastfm", + "author": "Thierry Göckel", + "email": "thierry@strayrayday.lu", + "version": "0.2.4", + "website": "http://gettomahawk.com", + "description": "Resolves to freely downloadable tracks on Last.fm", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/lastfm.js", + "scripts": [], + "icon": "contents/images/icon.png", + "resources": [ + "contents/code/lastfm-icon.png" + ] + } +} diff --git a/official.fm/officialfm-icon.png b/official.fm/content/contents/code/officialfm-icon.png similarity index 100% rename from official.fm/officialfm-icon.png rename to official.fm/content/contents/code/officialfm-icon.png diff --git a/official.fm/officialfm.js b/official.fm/content/contents/code/officialfm.js similarity index 85% rename from official.fm/officialfm.js rename to official.fm/content/contents/code/officialfm.js index f86ce0b43..01db89913 100644 --- a/official.fm/officialfm.js +++ b/official.fm/content/contents/code/officialfm.js @@ -1,3 +1,22 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2011, lasconic + * Copyright 2011, Leo Franchi + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + var OfficialfmResolver = Tomahawk.extend(TomahawkResolver, { settings: { name: 'Official.fm', diff --git a/official.fm/content/contents/images/icon.png b/official.fm/content/contents/images/icon.png new file mode 100644 index 000000000..e9f01d6a0 Binary files /dev/null and b/official.fm/content/contents/images/icon.png differ diff --git a/official.fm/content/metadata.json b/official.fm/content/metadata.json new file mode 100644 index 000000000..289b74a1f --- /dev/null +++ b/official.fm/content/metadata.json @@ -0,0 +1,18 @@ +{ + "name": "Official.fm", + "pluginName": "officialfm", + "author": "Leo and lasconic", + "email": "lasconic@gmail.com", + "version": "1.0.5", + "website": "http://gettomahawk.com", + "description": "Searches Official.fm for playable tracks", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/officialfm.js", + "scripts": [], + "icon": "contents/images/icon.png", + "resources": [ + "contents/code/officialfm-icon.png" + ] + } +} diff --git a/qobuz/config.ui b/qobuz/content/contents/code/config.ui similarity index 100% rename from qobuz/config.ui rename to qobuz/content/contents/code/config.ui diff --git a/qobuz/qobuz.js b/qobuz/content/contents/code/qobuz.js similarity index 100% rename from qobuz/qobuz.js rename to qobuz/content/contents/code/qobuz.js diff --git a/qobuz/qobuz.png b/qobuz/content/contents/code/qobuz.png similarity index 100% rename from qobuz/qobuz.png rename to qobuz/content/contents/code/qobuz.png diff --git a/qobuz/content/contents/images/icon.png b/qobuz/content/contents/images/icon.png new file mode 100644 index 000000000..9b3e5f195 Binary files /dev/null and b/qobuz/content/contents/images/icon.png differ diff --git a/qobuz/content/metadata.json b/qobuz/content/metadata.json new file mode 100644 index 000000000..8e330dee4 --- /dev/null +++ b/qobuz/content/metadata.json @@ -0,0 +1,19 @@ +{ + "name": "Qobuz", + "pluginName": "qobuz", + "author": "Qobuz Sarl", + "email": "regbasket@gmail.com", + "version": "0.1", + "website": "http://gettomahawk.com", + "description": "Searches Qobuz for playable tracks", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/qobuz.js", + "scripts": [], + "icon": "contents/images/icon.png", + "resources": [ + "contents/code/qobuz.png", + "contents/code/config.ui" + ] + } +} diff --git a/soundcloud/config.ui b/soundcloud/content/contents/code/config.ui similarity index 100% rename from soundcloud/config.ui rename to soundcloud/content/contents/code/config.ui diff --git a/soundcloud/content/contents/code/soundcloud-icon.png b/soundcloud/content/contents/code/soundcloud-icon.png new file mode 100644 index 000000000..b685e9b84 Binary files /dev/null and b/soundcloud/content/contents/code/soundcloud-icon.png differ diff --git a/soundcloud/soundcloud.js b/soundcloud/content/contents/code/soundcloud.js similarity index 91% rename from soundcloud/soundcloud.js rename to soundcloud/content/contents/code/soundcloud.js index c5600eb0d..ad2b0341e 100644 --- a/soundcloud/soundcloud.js +++ b/soundcloud/content/contents/code/soundcloud.js @@ -1,6 +1,21 @@ -/* - * (c) 2012 thierry göckel +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012, Thierry Göckel + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . */ + var SoundcloudResolver = Tomahawk.extend(TomahawkResolver, { getConfigUi: function () { diff --git a/soundcloud/soundcloud.png b/soundcloud/content/contents/code/soundcloud.png similarity index 100% rename from soundcloud/soundcloud.png rename to soundcloud/content/contents/code/soundcloud.png diff --git a/soundcloud/content/contents/images/icon.png b/soundcloud/content/contents/images/icon.png new file mode 100644 index 000000000..b685e9b84 Binary files /dev/null and b/soundcloud/content/contents/images/icon.png differ diff --git a/soundcloud/content/metadata.json b/soundcloud/content/metadata.json new file mode 100644 index 000000000..cd0b1b1f9 --- /dev/null +++ b/soundcloud/content/metadata.json @@ -0,0 +1,20 @@ +{ + "name": "SoundCloud", + "pluginName": "soundcloud", + "author": "Thierry Göckel", + "email": "thierry@strayrayday.lu", + "version": "0.9.1", + "website": "http://gettomahawk.com", + "description": "Resolves to playable tracks on SoundCloud, including live, cover and remixed versions if specified", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/soundcloud.js", + "scripts": [], + "icon": "contents/images/icon.png", + "resources": [ + "contents/code/soundcloud.png", + "contents/code/soundcloud-icon.png", + "contents/code/config.ui" + ] + } +} diff --git a/soundcloud/soundcloud-icon.png b/soundcloud/soundcloud-icon.png deleted file mode 100644 index 71c74c254..000000000 Binary files a/soundcloud/soundcloud-icon.png and /dev/null differ diff --git a/spotify/CMakeLists.txt b/spotify/CMakeLists.txt index dff0d2a9f..80c575e7e 100644 --- a/spotify/CMakeLists.txt +++ b/spotify/CMakeLists.txt @@ -7,11 +7,16 @@ find_package(Qt4 REQUIRED) find_package(QJSON REQUIRED) find_package(libspotify REQUIRED) -find_package(Boost) +find_package(Boost REQUIRED) add_subdirectory(qxtweb-standalone) -include_directories( qxtweb-standalone/qxtweb kdsingleapplicationguard/ ${QT_INCLUDES} ${LIBSPOTIFY_INCLUDE_DIR} ${QJSON_INCLUDE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) +include_directories( qxtweb-standalone/qxtweb kdsingleapplicationguard/ + ${QT_INCLUDES} + ${LIBSPOTIFY_INCLUDE_DIR} + ${QJSON_INCLUDE_DIR} + ${Boost_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR}) if(NOT EXISTS "${CMAKE_BINARY_DIR}/spotify-sourceicon.png") message("Copying spotify source icon to binary dir") diff --git a/spotify/spotify_key.h b/spotify/spotify_key.h index 14fd2801e..34fcb3432 100644 --- a/spotify/spotify_key.h +++ b/spotify/spotify_key.h @@ -1,6 +1,3 @@ -#include -#include - // The Spotify terms of service require that application keys are not // accessible to third parties. Therefore this application key is heavily // encrypted here in the source to prevent third parties from viewing it. @@ -12,4 +9,4 @@ static const char* spotifyApiKey = "WOQ9D5vJpcgvPcB9pjIQjWhoJQpeUXAPV3TcJigP7EzaoPsjscXzs+b2a+11jHU6WMYAxZZewoJl" "/siCHHVKXa1Nyv+UOXmSH9kIJlEmDs2pegNcBXNvx8RplP4lS5MWn2sOsavQba2BHJR7tXLDYZ5g" "l2XZ/rko77EjedPgI5OItDUNWUh1UUnjwFtLz8YwtiGZRVm7Wb9nN74wgYut0E+ZnZ7jNsJ2FDky" -"BzKJ1bs+nvtyKfxMBnE5uPUUx/WyDRr1hIZRLDFT4ta2ssve"; \ No newline at end of file +"BzKJ1bs+nvtyKfxMBnE5uPUUx/WyDRr1hIZRLDFT4ta2ssve"; diff --git a/spotify/spotifyplayback.cpp b/spotify/spotifyplayback.cpp index c24b6897b..28311675e 100644 --- a/spotify/spotifyplayback.cpp +++ b/spotify/spotifyplayback.cpp @@ -23,8 +23,6 @@ #include #include -#include - SpotifyPlayback::SpotifyPlayback(QObject *parent) : QObject(parent), m_trackEnded( false ) diff --git a/subsonic/config.ui b/subsonic/config.ui deleted file mode 100644 index c6538ab1f..000000000 --- a/subsonic/config.ui +++ /dev/null @@ -1,154 +0,0 @@ - - - Form - - - - 0 - 0 - 400 - 300 - - - - - 400 - 300 - - - - Form - - - - - - subsonic.png - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - User: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Password: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Server: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Max Songs: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - API Version: - - - - - - - - - - - - - - QLineEdit::Password - - - - - - - - - - 20 - - - 1 - - - 500 - - - - - - - false - - - - 1.5.0 - - - - - 1.6.0 - - - - - 1.7.0 - - - - - - - - - - - - - diff --git a/subsonic/content/contents/code/config.ui b/subsonic/content/contents/code/config.ui new file mode 100644 index 000000000..61bf10b55 --- /dev/null +++ b/subsonic/content/contents/code/config.ui @@ -0,0 +1,120 @@ + + + Form + + + + 0 + 0 + 331 + 250 + + + + + 331 + 250 + + + + Form + + + + + + subsonic.png + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Username: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Password: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + QLineEdit::Password + + + + + + + Server: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Max Songs: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 1 + + + 500 + + + 20 + + + + + + + + + + diff --git a/subsonic/content/contents/code/runnersid-icon.png b/subsonic/content/contents/code/runnersid-icon.png new file mode 100644 index 000000000..ef2379d45 Binary files /dev/null and b/subsonic/content/contents/code/runnersid-icon.png differ diff --git a/subsonic/subsonic-icon.png b/subsonic/content/contents/code/subsonic-icon.png similarity index 100% rename from subsonic/subsonic-icon.png rename to subsonic/content/contents/code/subsonic-icon.png diff --git a/subsonic/content/contents/code/subsonic.js b/subsonic/content/contents/code/subsonic.js new file mode 100644 index 000000000..d9e0f62b4 --- /dev/null +++ b/subsonic/content/contents/code/subsonic.js @@ -0,0 +1,434 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012, mack-t + * Copyright 2012, Peter Loron + * Copyright 2013, Teo Mrnjavac + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +var SubsonicResolver = Tomahawk.extend(TomahawkResolver, { + + getConfigUi: function () { + var uiData = Tomahawk.readBase64("config.ui"); + return { + "widget": uiData, + fields: [{ + name: "user", + widget: "user_edit", + property: "text" + }, { + name: "password", + widget: "password_edit", + property: "text" + }, { + name: "subsonic_url", + widget: "subsonic_url_edit", + property: "text" + }, { + name: "max_songs", + widget: "max_songs_spinbox", + property: "value" + }], + images: [{ + "subsonic.png" : Tomahawk.readBase64("subsonic.png") + }] + }; + }, + + newConfigSaved: function () { + var userConfig = this.getUserConfig(); + Tomahawk.log("newConfigSaved User: " + userConfig.user); + + if (this.user !== userConfig.user || + this.password !== userConfig.password || + this.subsonic_url !== userConfig.subsonic_url || + this.max_songs !== userConfig.max_songs) + { + this.init(); + } + }, + + settings: + { + name: 'Subsonic', + icon: 'subsonic-icon.png', + weight: 70, + timeout: 8 + }, + + encodePassword : function(password) + { + var hex_slice; + var hex_string = ""; + var padding = [ "", "0", "00" ]; + for (pos = 0; pos < password.length; hex_string += hex_slice) + { + hex_slice = password.charCodeAt(pos++).toString(16); + hex_slice = hex_slice.length < 2 ? (padding[2 - hex_slice.length] + hex_slice) : hex_slice; + } + return "enc:" + hex_string; + }, + + init: function() + { + var userConfig = this.getUserConfig(); + if (!userConfig.user || !userConfig.password) { + Tomahawk.log("Subsonic Resolver not properly configured!"); + return; + } + + Tomahawk.log("Doing Subsonic resolver init, got credentials from config. User: " + userConfig.user); + this.user = userConfig.user; + var enc_password = this.encodePassword(userConfig.password); + this.password = enc_password; + this.subsonic_url = userConfig.subsonic_url.replace(/\/+$/, ""); + this.max_songs = userConfig.max_songs; + + this.element = document.createElement('div'); + + // We need at least 1.6.0 for resolve operations (JSON API support) + // and 1.8.0 for scriptcollection + this.supported_api_versions = [ "1.6.0", "1.7.0", "1.8.0" ]; + this.subsonic_api = 0; + + //let's ask the server which API version it actually supports. + if (this.user === undefined || this.password === undefined || this.subsonic_url === undefined) + return; + + var that = this; + var ping_url = this.buildBaseUrl("/rest/ping.view") + "&f=json"; + Tomahawk.asyncRequest(ping_url, function(xhr) { + var doc = JSON.parse(xhr.responseText); + if ( typeof doc["subsonic-response"].version === 'undefined' ) + return; + + var versionString = doc["subsonic-response"].version; + + for ( var i = 0; i < that.supported_api_versions.length; ++i ) + { + if ( that.supported_api_versions[i] === versionString ) + { + that.subsonic_api = i; + break; + } + } + + if ( that.subsonic_api != 2 ) //version 1.8.0, scriptcollection support + Tomahawk.reportCapabilities( TomahawkResolverCapability.AccountFactory ); + else + Tomahawk.reportCapabilities( TomahawkResolverCapability.Browsable | TomahawkResolverCapability.AccountFactory ); + } ); + + }, + + getXmlAttribute: function(attrib_name, attributes) + { + for (var count = 0; count < attributes.length; ++count) + { + if (attrib_name === attributes[count].nodeName) + return attributes[count].nodeValue; + } + return null; + }, + + buildBaseUrl : function(subsonic_view) + { + + return this.subsonic_url + subsonic_view + + "?u=" + this.user + + "&p=" + this.password + + "&v=" + this.supported_api_versions[ this.subsonic_api ] + + "&c=tomahawk"; + }, + + parseSongFromXmlAttributes : function(song_attributes) + { + return { + artist: this.getXmlAttribute("artist", song_attributes), + album: this.getXmlAttribute("album", song_attributes), + track: this.getXmlAttribute("title", song_attributes), + albumpos: this.getXmlAttribute("track", song_attributes), + source: this.settings.name, + size: this.getXmlAttribute("size", song_attributes), + duration: this.getXmlAttribute("duration", song_attributes), + bitrate: this.getXmlAttribute("bitRate", song_attributes), + url: this.buildBaseUrl("/rest/stream.view") + "&id=" + this.getXmlAttribute("id", song_attributes), + extension: this.getXmlAttribute("suffix", song_attributes), + year: this.getXmlAttribute("year", song_attributes) + }; + }, + + decodeEntity : function(str) + { + this.element.innerHTML = str; + return this.element.textContent; + }, + + parseSongFromAttributes : function(song_attributes) + { + return { + artist: this.decodeEntity(song_attributes["artist"]), + album: this.decodeEntity(song_attributes["album"]), + track: this.decodeEntity(song_attributes["title"]), + albumpos: song_attributes["track"], + source: this.settings.name, + size: song_attributes["size"], + duration: song_attributes["duration"], + bitrate: song_attributes["bitRate"], + url: this.buildBaseUrl("/rest/stream.view") + "&id=" + song_attributes["id"], + extension: song_attributes["suffix"], + year: song_attributes["year"], + }; + }, + + executeSearchQuery : function(qid, search_url, song_xml_tag, limit) + { + var results = []; + var that = this; // needed so we can reference this from within the lambda + + // Important to recognize this async request is doing a get and the user / password is passed in the search url + // TODO: should most likely just use the xhr object and doing basic authentication. + Tomahawk.asyncRequest(search_url, function(xhr) { + var dom_parser = new DOMParser(); + xmlDoc = dom_parser.parseFromString(xhr.responseText, "text/xml"); + + var search_results = xmlDoc.getElementsByTagName(song_xml_tag); + Tomahawk.log(search_results.length + " results returned.") + for (var count = 0; count < Math.min(search_results.length, limit); count++) + { + results.push(that.parseSongFromXmlAttributes(search_results[count].attributes)); + } + + var return_songs = { + qid: qid, + results: results + }; + + Tomahawk.addTrackResults(return_songs); + }); + }, + + executeArtistsQuery : function(qid, artists_url) + { + var results = []; + artists_url += "&f=json"; //for large responses we surely want JSON + + // Important to recognize this async request is doing a get and the user / password is passed in the search url + // TODO: should most likely just use the xhr object and doing basic authentication. + Tomahawk.asyncRequest(artists_url, function(xhr) { + var doc = JSON.parse(xhr.responseText); + Tomahawk.log("subsonic artists query:" + artists_url); + Tomahawk.log("subsonic artists response:" + xhr.responseText); + var artists = doc["subsonic-response"].artists.index; + + for (var i = 0; i < artists.length; i++) + { + if ( artists[i].artist instanceof Array ) + { + for (var j = 0; j < artists[i].artist.length; j++) + { + results.push( artists[i].artist[j].name) + } + } + else + { + results.push( artists[i].artist.name ) + } + } + + var return_artists = { + qid: qid, + artists: results + }; + + Tomahawk.log("subsonic artists about to return: " + JSON.stringify( return_artists ) ); + Tomahawk.addArtistResults(return_artists); + }); + }, + + executeAlbumsQuery : function(qid, search_url, artist) + { + var results = []; + search_url += "&f=json"; //for large responses we surely want JSON + + // Important to recognize this async request is doing a get and the user / password is passed in the search url + // TODO: should most likely just use the xhr object and doing basic authentication. + Tomahawk.asyncRequest(search_url, function(xhr) { + var doc = JSON.parse(xhr.responseText); + Tomahawk.log("subsonic albums query:" + search_url); + Tomahawk.log("subsonic albums response:" + xhr.responseText); + var albums = doc["subsonic-response"].searchResult2.album; + + if (albums instanceof Array) + { + Tomahawk.log(albums.length + " albums returned.") + for (var i = 0; i < albums.length; i++) + { + if (albums[i].artist.toLowerCase() === artist.toLowerCase()) //search2 does partial matches + { + results.push(albums[i].title) + } + } + } + else + { + if (albums.artist.toLowerCase() === artist.toLowerCase()) + { + results.push(albums.title); + } + } + + var return_albums = { + qid: qid, + artist: artist, + albums: results + }; + + Tomahawk.log("subsonic albums about to return: " + JSON.stringify( return_albums ) ); + Tomahawk.addAlbumResults(return_albums); + }); + }, + + executeTracksQuery : function(qid, search_url, artist, album) + { + var results = []; + var that = this; + search_url += "&f=json"; //for large responses we surely want JSON + + // Important to recognize this async request is doing a get and the user / password is passed in the search url + // TODO: should most likely just use the xhr object and doing basic authentication. + Tomahawk.asyncRequest(search_url, function(xhr) { + var doc = JSON.parse(xhr.responseText); + Tomahawk.log("subsonic tracks query:" + search_url); + Tomahawk.log("subsonic tracks response:" + xhr.responseText); + var tracks = doc["subsonic-response"].searchResult.match; + + if (tracks instanceof Array) + { + Tomahawk.log(tracks.length + " tracks returned.") + for (var i = 0; i < tracks.length; i++ ) + { + Tomahawk.log("tracks[i].artist=" + tracks[i].artist); + Tomahawk.log("artist= " + artist); + Tomahawk.log("tracks[i].album =" + tracks[i].album); + Tomahawk.log("album= " + album); + + if (tracks[i].artist.toLowerCase() === artist.toLowerCase() && tracks[i].album.toLowerCase() === album.toLowerCase()) + { + results.push(that.parseSongFromAttributes(tracks[i])); + } + } + } + else + { + if (tracks.artist.toLowerCase() === artist.toLowerCase() && tracks.album.toLowerCase() === album.toLowerCase()) + { + results.push(that.parseSongFromAttributes(tracks)); + } + } + + var return_tracks = { + qid: qid, + artist: artist, + album: album, + results: results + }; + + Tomahawk.log("subsonic tracks about to return: " + JSON.stringify( return_tracks ) ); + Tomahawk.addAlbumTrackResults(return_tracks); + }); + }, + + //! Please note i am using the deprecated search method in resolve + // The reason i am doing this is because it allows me to get a little more specific with the search + // since i have the artist, album and title i want to be as specific as possible + // NOTE: I do use the newer search2.view in the search method below and it will populate each result with the + // appropriate url. + resolve: function(qid, artist, album, title) + { + if (this.user === undefined || this.password === undefined || this.subsonic_url === undefined) + return { qid: qid, results: [] }; + + var search_url = this.buildBaseUrl("/rest/search.view") + "&artist=" + artist + "&album=" + album + "&title=" + title + "&count=1"; + this.executeSearchQuery(qid, search_url, "match", 1); + }, + + search: function( qid, searchString ) + { + if (this.user === undefined || this.password === undefined || this.subsonic_url === undefined) + return { qid: qid, results: [] }; + + var search_url = this.buildBaseUrl("/rest/search2.view") + "&songCount=" + this.max_songs + "&query=\"" + encodeURIComponent(searchString) + "\""; + this.executeSearchQuery(qid, search_url, "song", this.max_songs); + }, + + artists: function( qid ) + { + if (this.user === undefined || this.password === undefined || this.subsonic_url === undefined) + return { qid: qid, artists: [] }; + + var artists_url = this.buildBaseUrl("/rest/getArtists.view"); + this.executeArtistsQuery(qid, artists_url); + }, + + albums: function( qid, artist ) + { + if (this.user === undefined || this.password === undefined || this.subsonic_url === undefined) + return { qid: qid, artist: artist, albums: [] }; + + var search_url = this.buildBaseUrl("/rest/search2.view") + "&songCount=0&artistCount=0&albumCount=900" + + "&query=\"" + encodeURIComponent(artist) + "\""; + this.executeAlbumsQuery(qid, search_url, artist); + }, + + tracks: function( qid, artist, album ) + { + if (this.user === undefined || this.password === undefined || this.subsonic_url === undefined) + return { qid: qid, artist: artist, album: album, tracks: [] }; + + // See note for resolve() about the search method + var search_url = this.buildBaseUrl("/rest/search.view") + + "&artist=\"" + encodeURIComponent(artist) + + "\"&album=\"" + encodeURIComponent(album) + "\"&count=200"; + + this.executeTracksQuery(qid, search_url, artist, album); + }, + + collection: function() + { + //strip http:// and trailing slash + var desc = this.subsonic_url.replace(/^http:\/\//,"") + .replace(/\/$/, "") + .replace(/\/remote.php\/submedia/, ""); + + var return_object = { + prettyname: "Subsonic", + description: desc, + iconfile: "subsonic-icon.png" + }; + + //Icon and text specific for Runners-ID + if (desc.indexOf("runners-id.com") !== -1 || + desc.indexOf("runners-id.org") !== -1 ) + { + return_object["prettyname"] = "Runners-ID"; + return_object["iconfile"] = "runnersid-icon.png"; + } + + return return_object; + } +}); + +Tomahawk.resolver.instance = SubsonicResolver; diff --git a/subsonic/content/contents/code/subsonic.png b/subsonic/content/contents/code/subsonic.png new file mode 100644 index 000000000..f2417286f Binary files /dev/null and b/subsonic/content/contents/code/subsonic.png differ diff --git a/subsonic/content/contents/images/icon.png b/subsonic/content/contents/images/icon.png new file mode 100644 index 000000000..f3b6553b0 Binary files /dev/null and b/subsonic/content/contents/images/icon.png differ diff --git a/subsonic/content/metadata.json b/subsonic/content/metadata.json new file mode 100644 index 000000000..3ab37f472 --- /dev/null +++ b/subsonic/content/metadata.json @@ -0,0 +1,21 @@ +{ + "name": "Subsonic", + "pluginName": "subsonic", + "author": "mack_t and Teo", + "email": "teo@kde.org", + "version": "0.5", + "website": "http://gettomahawk.com", + "description": "Searches your Subsonic server for music to play", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/subsonic.js", + "scripts": [], + "icon": "contents/images/icon.png", + "resources": [ + "contents/code/config.ui", + "contents/code/runnersid-icon.png", + "contents/code/subsonic-icon.png", + "contents/code/subsonic.png" + ] + } +} diff --git a/subsonic/subsonic.js b/subsonic/subsonic.js deleted file mode 100644 index 14764fb36..000000000 --- a/subsonic/subsonic.js +++ /dev/null @@ -1,176 +0,0 @@ - -var SubsonicResolver = Tomahawk.extend(TomahawkResolver, { - - getConfigUi: function () { - var uiData = Tomahawk.readBase64("config.ui"); - return { - "widget": uiData, - fields: [{ - name: "user", - widget: "user_edit", - property: "text" - }, { - name: "password", - widget: "password_edit", - property: "text" - }, { - name: "subsonic_url", - widget: "subsonic_url_edit", - property: "text" - }, { - name: "subsonic_api", - widget: "api_version_combo", - property: "currentIndex" - }, { - name: "max_songs", - widget: "max_songs_spinbox", - property: "value" - }], - images: [{ - "subsonic.png" : Tomahawk.readBase64("subsonic.png") - }] - }; - }, - - newConfigSaved: function () { - var userConfig = this.getUserConfig(); - Tomahawk.log("newConfigSaved User: " + userConfig.user); - - if (this.user !== userConfig.user || - this.password !== userConfig.password || - this.subsonic_url !== userConfig.subsonice_url || - this.subsonic_api !== userConfig.subsonic_api || - this.max_songs !== userConfig.max_songs) - { - this.init(); - } - }, - - settings: - { - name: 'Subsonic', - icon: 'subsonic-icon.png', - weight: 70, - timeout: 8 - }, - - encodePassword : function(password) - { - var hex_slice; - var hex_string = ""; - var padding = [ "", "0", "00" ]; - for (pos = 0; pos < password.length; hex_string += hex_slice) - { - hex_slice = password.charCodeAt(pos++).toString(16); - hex_slice = hex_slice.length < 2 ? (padding[2 - hex_slice.length] + hex_slice) : hex_slice; - } - return "enc:" + hex_string; - }, - - init: function() - { - var userConfig = this.getUserConfig(); - if (!userConfig.user || !userConfig.password) { - Tomahawk.log("Subsonic Resolver not properly configured!"); - return; - } - - Tomahawk.log("Doing Subsonic resolver init, got credentials from config. User: " + userConfig.user); - this.user = userConfig.user; - var enc_password = this.encodePassword(userConfig.password); - this.password = enc_password; - this.subsonic_url = userConfig.subsonic_url.replace(/\/+$/, ""); - this.subsonic_api = userConfig.subsonic_api; - this.max_songs = userConfig.max_songs; - }, - - getXmlAttribute: function(attrib_name, attributes) - { - for (var count = 0; count < attributes.length; ++count) - { - if (attrib_name === attributes[count].nodeName) - return attributes[count].nodeValue; - } - return null; - }, - - buildBaseUrl : function(subsonic_view) - { - var supported_api_versions = [ "1.5.0", "1.6.0", "1.7.0" ]; - - return this.subsonic_url + subsonic_view + - "?u=" + this.user + - "&p=" + this.password + - "&v=" + supported_api_versions[ this.subsonic_api ] + - "&c=tomahawk"; - }, - - parseSongFromAttributes : function(song_attributes) - { - return { - artist: this.getXmlAttribute("artist", song_attributes), - album: this.getXmlAttribute("album", song_attributes), - track: this.getXmlAttribute("title", song_attributes), - albumpos: this.getXmlAttribute("track", song_attributes), - source: this.settings.name, - size: this.getXmlAttribute("size", song_attributes), - duration: this.getXmlAttribute("duration", song_attributes), - bitrate: this.getXmlAttribute("bitRate", song_attributes), - url: this.buildBaseUrl("/rest/stream.view") + "&id=" + this.getXmlAttribute("id", song_attributes), - extension: this.getXmlAttribute("suffix", song_attributes), - year: this.getXmlAttribute("year", song_attributes) - }; - }, - - executeSearchQuery : function(qid, search_url, song_xml_tag, limit) - { - var results = []; - var that = this; // needed so we can reference this from within the lambda - - // Important to recognize this async request is doing a get and the user / password is passed in the search url - // TODO: should most likely just use the xhr object and doing basic authentication. - Tomahawk.asyncRequest(search_url, function(xhr) { - var dom_parser = new DOMParser(); - xmlDoc = dom_parser.parseFromString(xhr.responseText, "text/xml"); - - var search_results = xmlDoc.getElementsByTagName(song_xml_tag); - Tomahawk.log(search_results.length + " results returned.") - for (var count = 0; count < Math.min(search_results.length, limit); count++) - { - results.push(that.parseSongFromAttributes(search_results[count].attributes)); - } - - var return_songs = { - qid: qid, - results: results - }; - - Tomahawk.addTrackResults(return_songs); - }); - }, - - //! Please note i am using the deprecated search method in resolve - // The reason i am doing this is because it allows me to get a little more specific with the search - // since i have the artist, album and title i want to be as specific as possible - // NOTE: I do use the newer search2.view in the search method below and it will populate each result with the - // appropriate url. - resolve: function(qid, artist, album, title) - { - if (this.user === undefined || this.password === undefined || this.subsonic_url === undefined) - return { qid: qid, results: [] }; - - var search_url = this.buildBaseUrl("/rest/search.view") + "&artist=" + artist + "&album=" + album + "&title=" + title + "&count=1"; - this.executeSearchQuery(qid, search_url, "match", 1); - }, - - search: function( qid, searchString ) - { - if (this.user === undefined || this.password === undefined || this.subsonic_url === undefined) - return { qid: qid, results: [] }; - - var search_url = this.buildBaseUrl("/rest/search2.view") + "&songCount=" + this.max_songs + "&query=" + encodeURIComponent(searchString); - this.executeSearchQuery(qid, search_url, "song", this.max_songs); - } -}); - -Tomahawk.resolver.instance = SubsonicResolver; diff --git a/subsonic/subsonic.png b/subsonic/subsonic.png deleted file mode 100644 index f2cb2a600..000000000 Binary files a/subsonic/subsonic.png and /dev/null differ diff --git a/vkontakte/vkontakte-resolver.js b/vkontakte/content/contents/code/vkontakte-resolver.js similarity index 94% rename from vkontakte/vkontakte-resolver.js rename to vkontakte/content/contents/code/vkontakte-resolver.js index 4a7cd177c..a781cd7a0 100644 --- a/vkontakte/vkontakte-resolver.js +++ b/vkontakte/content/contents/code/vkontakte-resolver.js @@ -1,5 +1,19 @@ -/* - * (c) 2011 Krzysztof Klinikowski +/* === This file is part of Tomahawk Player - === + * + * Copyright 2011, Krzysztof Klinikowski + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . */ debugMode = false; diff --git a/vkontakte/content/contents/images/icon.png b/vkontakte/content/contents/images/icon.png new file mode 100644 index 000000000..ced9db3b3 Binary files /dev/null and b/vkontakte/content/contents/images/icon.png differ diff --git a/vkontakte/content/metadata.json b/vkontakte/content/metadata.json new file mode 100644 index 000000000..ae5e53a5b --- /dev/null +++ b/vkontakte/content/metadata.json @@ -0,0 +1,16 @@ +{ + "name": "VKontakte", + "pluginName": "vkontakte", + "author": "Krzysztof Klinikowski", + "email": "kkszysiu@gmail.com", + "version": "0.1", + "website": "http://gettomahawk.com", + "description": "Searches VKontakte (vk.com) for music to play", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/vkontakte-resolver.js", + "scripts": [], + "icon": "contents/images/icon.png", + "resources": [] + } +} diff --git a/youtube/config.ui b/youtube/content/contents/code/config.ui similarity index 100% rename from youtube/config.ui rename to youtube/content/contents/code/config.ui diff --git a/youtube/youtube-icon.png b/youtube/content/contents/code/youtube-icon.png similarity index 100% rename from youtube/youtube-icon.png rename to youtube/content/contents/code/youtube-icon.png diff --git a/youtube/youtube.js b/youtube/content/contents/code/youtube.js similarity index 98% rename from youtube/youtube.js rename to youtube/content/contents/code/youtube.js index f2fd5ae9a..ffbbcc3d8 100644 --- a/youtube/youtube.js +++ b/youtube/content/contents/code/youtube.js @@ -18,6 +18,7 @@ * * NOTICE: This resolver and its intent, is for demonstrational purposes only **/ + var YoutubeResolver = Tomahawk.extend(TomahawkResolver, { getConfigUi: function () @@ -184,7 +185,7 @@ var YoutubeResolver = Tomahawk.extend(TomahawkResolver, { console.log(this.settings.name + msg); } else if (this.debugMode){ - Tomahawk.log(this.settings.name + "Debug: " + msg); + Tomahawk.log(this.settings.name + " debug: " + msg); } }, @@ -271,7 +272,6 @@ var YoutubeResolver = Tomahawk.extend(TomahawkResolver, { // First, lets try and find the stream_map at top of the page // to save some time going to the end and do JSON.parse on the yt.config var streamMatch = html.match(/(url_encoded_fmt_stream_map=)(.*?)(?=(\\u0026amp))/i); - //Tomahawk.log(streamMatch); if (streamMatch && streamMatch[2] !== undefined) { var parsed = this.parseURLS(streamMatch[2]); if (parsed) { @@ -282,8 +282,8 @@ var YoutubeResolver = Tomahawk.extend(TomahawkResolver, { } } - // Now we can go further down, and check the yt.config map - streamMatch = html.match(/(yt\.playerConfig =)([^\r\n]+)/); + // Now we can go further down, and check the ytplayer.config map + streamMatch = html.match(/(ytplayer\.config =)([^\r\n]+);/); if (!streamMatch) { // Todo: Open window for user input? @@ -299,7 +299,7 @@ var YoutubeResolver = Tomahawk.extend(TomahawkResolver, { if (streamMatch && streamMatch[2] !== undefined) { try { - var jsonMap = JSON.parse(streamMatch[2].replace("};", "}")); + var jsonMap = JSON.parse(streamMatch[2]); if (jsonMap.args.url_encoded_fmt_stream_map !== undefined) { parsed = this.parseURLS(jsonMap.args.url_encoded_fmt_stream_map); if (parsed){ diff --git a/youtube/youtube.png b/youtube/content/contents/code/youtube.png similarity index 100% rename from youtube/youtube.png rename to youtube/content/contents/code/youtube.png diff --git a/youtube/content/contents/images/icon.png b/youtube/content/contents/images/icon.png new file mode 100644 index 000000000..2b9bb3a23 Binary files /dev/null and b/youtube/content/contents/images/icon.png differ diff --git a/youtube/content/metadata.json b/youtube/content/metadata.json new file mode 100644 index 000000000..d95c65777 --- /dev/null +++ b/youtube/content/metadata.json @@ -0,0 +1,20 @@ +{ + "name": "YouTube", + "pluginName": "youtube", + "author": "Hugo, Leo and Thierry", + "email": "lfranchi@kde.org", + "version": "0.9.7", + "website": "http://gettomahawk.com", + "description": "Searches YouTube for audio content", + "type": "resolver/javascript", + "manifest": { + "main": "contents/code/youtube.js", + "scripts": [], + "icon": "contents/images/icon.png", + "resources": [ + "contents/code/config.ui", + "contents/code/youtube-icon.png", + "contents/code/youtube.png" + ] + } +}