diff -Nru deejayd-0.7.2/data/htdocs/js/ajax.js deejayd-0.9.0/data/htdocs/js/ajax.js --- deejayd-0.7.2/data/htdocs/js/ajax.js 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/js/ajax.js 1970-01-01 01:00:00.000000000 +0100 @@ -1,126 +0,0 @@ -/* - * ajax.js - * class for http request - */ - -function http_request() -{ - this.url = ''; - this.busy = false; - this.xmlhttp = null; - - this.reset = function() - { - // set unassigned event handlers - this.onloading = function(){ }; - this.onloaded = function(){ }; - this.oninteractive = function(){ }; - this.oncomplete = function(){ }; - this.onabort = function(){ }; - this.onerror = function(){ }; - - this.url = ''; - this.busy = false; - this.xmlhttp = null; - }; - - this.build = function() - { - if (window.XMLHttpRequest) - this.xmlhttp = new XMLHttpRequest(); - else if (window.ActiveXObject) - this.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); - }; - - this.GET = function(url) - { - this.build(); - - if (!this.xmlhttp) { - this.onerror(this); - return false; - } - - var ref = this; - this.url = url; - this.busy = true; - - this.xmlhttp.onreadystatechange = function(){ - ref.xmlhttp_onreadystatechange(); }; - this.xmlhttp.open('GET', url); - return this.xmlhttp.send(null); - }; - - this.POST = function(url,parm) - { - this.build(); - - if (!this.xmlhttp) { - this.onerror(this); - return false; - } - - var ref = this; - this.url = url; - this.busy = true; - - - this.xmlhttp.onreadystatechange = function(){ - ref.xmlhttp_onreadystatechange(); }; - this.xmlhttp.open('POST', url); - - var toSend = ''; - if (typeof parm == 'object') { - this.xmlhttp.setRequestHeader('Content-Type', - 'application/x-www-form-urlencoded'); - - for (var i in parm) { - if (typeof parm[i] == 'object') { - var obj = parm[i]; - for (var j in obj) - toSend += (toSend?'&':'') + i + '=' + urlencode(obj[j]); - } - else - toSend += (toSend? '&' : '') + i + '=' + urlencode(parm[i]); - } - } - else - toSend = parm; - - return this.xmlhttp.send(toSend); - }; - - this.xmlhttp_onreadystatechange = function() - { - if(this.xmlhttp.readyState == 1) - this.onloading(this); - - else if(this.xmlhttp.readyState == 2) - this.onloaded(this); - - else if(this.xmlhttp.readyState == 3) - this.oninteractive(this); - - else if(this.xmlhttp.readyState == 4) { - this.responseText = this.xmlhttp.responseText; - this.responseXML = this.xmlhttp.responseXML; - - if(this.xmlhttp.status == 0) - this.onabort(this); - else if(this.xmlhttp.status == 200) - this.oncomplete(this); - else - this.onerror(this); - - this.busy = false; - } - }; - - // a best way to take http header - this.get_header = function(name) - { - return this.xmlhttp.getResponseHeader(name); - }; - - this.reset(); -} diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/js/common.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/js/common.js --- deejayd-0.7.2/data/htdocs/js/common.js 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/js/common.js 2009-09-21 08:44:20.000000000 +0100 @@ -1,10 +1,24 @@ -/* - * common.js - */ +/* Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ function formatTime(time) { - var sec = time % 60; + var sec = parseInt(time % 60); if (sec < 10) sec = "0" + sec; var min = parseInt(time/60); @@ -28,47 +42,13 @@ return output; } -function $(id) { - return document.getElementById(id); -} - -function eregReplace(search, replace, subject) { - return subject.replace(new RegExp(search,'g'), replace); -} - -function toogleNodeVisibility(node) -{ - if (typeof node == 'string') - node = $(node); - - var newState = node.style.visibility == "visible" ? "collapse" : "visible"; - node.style.visibility = newState; -} - -function removeNode(node) +function createElement(name, cls, attributes) { - if (typeof node == 'string') - node = $(node); + var div = document.createElement(name); + $(div).addClass(cls); + for (attr in attributes) + $(div).attr(attr, attributes[attr]); + return div; +}; - if (node && node.parentNode) - return node.parentNode.removeChild(node); - else - return false; -} - -function replaceNodeText(node,content) -{ - text = document.createTextNode(content); - if (typeof node == 'string') - node = $(node); - - if (node.firstChild) - node.replaceChild(text,node.firstChild) - else - node.appendChild(text); -} - -function DEBUG(msg) -{ - $('debug').value += "\n" + msg; -} +// vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/js/lib/jquery.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/js/lib/jquery.js --- deejayd-0.7.2/data/htdocs/js/lib/jquery.js 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/js/lib/jquery.js 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,19 @@ +/* + * jQuery JavaScript Library v1.3.2 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) + * Revision: 6246 + */ +(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
","
"]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); +/* + * Sizzle CSS Selector Engine - v0.9.3 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); \ No newline at end of file diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/js/lib/jquery.json.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/js/lib/jquery.json.js --- deejayd-0.7.2/data/htdocs/js/lib/jquery.json.js 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/js/lib/jquery.json.js 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,98 @@ + +(function ($) { + var m = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + s = { + 'array': function (x) { + var a = ['['], b, f, i, l = x.length, v; + for (i = 0; i < l; i += 1) { + v = x[i]; + f = s[typeof v]; + if (f) { + v = f(v); + if (typeof v == 'string') { + if (b) { + a[a.length] = ','; + } + a[a.length] = v; + b = true; + } + } + } + a[a.length] = ']'; + return a.join(''); + }, + 'boolean': function (x) { + return String(x); + }, + 'null': function (x) { + return "null"; + }, + 'number': function (x) { + return isFinite(x) ? String(x) : 'null'; + }, + 'object': function (x) { + if (x) { + if (x instanceof Array) { + return s.array(x); + } + var a = ['{'], b, f, i, v; + for (i in x) { + v = x[i]; + f = s[typeof v]; + if (f) { + v = f(v); + if (typeof v == 'string') { + if (b) { + a[a.length] = ','; + } + a.push(s.string(i), ':', v); + b = true; + } + } + } + a[a.length] = '}'; + return a.join(''); + } + return 'null'; + }, + 'string': function (x) { + if (/["\\\x00-\x1f]/.test(x)) { + x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) { + var c = m[b]; + if (c) { + return c; + } + c = b.charCodeAt(); + return '\\u00' + + Math.floor(c / 16).toString(16) + + (c % 16).toString(16); + }); + } + return '"' + x + '"'; + } + }; + + $.toJSON = function(v) { + var f = isNaN(v) ? s[typeof v] : s['number']; + if (f) return f(v); + }; + + $.parseJSON = function(v, safe) { + if (safe === undefined) safe = $.parseJSON.safe; + if (safe && !/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(v)) + return undefined; + return eval('('+v+')'); + }; + + $.parseJSON.safe = false; +})(jQuery); + +// vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/js/main.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/js/main.js --- deejayd-0.7.2/data/htdocs/js/main.js 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/js/main.js 1970-01-01 01:00:00.000000000 +0100 @@ -1,316 +0,0 @@ -/* - * main.js - */ - -var ajaxdj_ref; - -function ajaxdj() -{ - // Activate Debug - this.debug = false; - - // Internal parms - this.message_time = 4000; - this.current_msg = null; - this.localeStrings = Array(); - this.config = Array(); - this.config["refresh"] = "0"; - this.refreshEvent = null; - - ajaxdj_ref = this; - this.ref = 'ajaxdj_ref'; - - this.init = function() - { - // Init var - this.fileListObj = new FileList(); - this.fileListObj.init(); - this.playerObj = new Player(); - // Initiate debug - if (this.debug) - $("debug-button").style.display = "block"; - - // Send Init request - this.send_http_request('GET','commands?action=init','',false); - }; - - this.set_busy = function(a) - { - if (a) - $('msg-loading').style.display = "block"; - else if (!a && this.busy) - $('msg-loading').style.display = "none"; - this.busy = a; - }; - - this.display_message = function(msg,type) - { - var p = type == "error" ? 8 : 4; - var image = "./static/themes/default/images/"; - image += type == "error" ? "error.png" : "info.png"; - - var msg = $("notification-box").appendNotification(msg, 0, image, p); - if (type != 'error') { - this.hide_message(); - this.current_msg = msg; - setTimeout(this.ref+'.hide_message()', this.message_time); - } - }; - - this.hide_message = function() - { - if (this.current_msg != null) { - $("notification-box").removeNotification(this.current_msg); - this.current_msg = null; - } - }; - - this.http_sockets = new Array(); - - this.get_request_obj = function() - { - for (var n=0; n +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + +var mobileui_ref; + +function mobileUI() +{ + mobileui_ref = this; + this.ref = 'mobileui_ref'; + + this.init = function() + { + // build ui + this.ui = new UI(this); + + this.rpc = new RPC(this, './../rpc/'); + this.rpc.callbacks = { + updateMode: this.updateMode, + updateOption: function(data) { + mobileui_ref.updateMode(data); + mobileui_ref.ui.getCurrentMode().closeExtra(); + }, + player: { def: this.updateStatus, }, + playlist: { + def: this.updateMode, + addPath: function(data) { + mobileui_ref.ui.displayMessage("Files has been loaded"); + mobileui_ref.updateMode(null); + }, + }, + video: { def: this.updateMode, }, + webradio: { def: this.updateMode, }, + panel: { + def: this.updateMode, + clearFilter: function(data) {}, + clearSearch: function(data) {}, + }, + dvd: this.updateMode, + }; + this.rpc.onerror = function(request, error, exception){ + $("#fatal_error").html("Fatal Error " + error + " : " + + request.responseText).show(); + }; + this.rpc.onrequesterror = function(code, message){ + mobileui_ref.ui.displayMessage("Resquest Error " + code + " - " + + message, 'error'); + }; + + this.updateStatus(null); + } + +/* + * Callbacks + */ + this.updateStatus = function(data) + { + var success = function(data) { + mobileui_ref.ui.buildPage("now_playing", data); + }; + mobileui_ref.rpc.send("status", [], success); + }; + + this.updateMode = function(data) + { + var success = function(data) { + mobileui_ref.ui.buildPage("current_mode", data); + }; + mobileui_ref.rpc.send("status", [], success); + }; + + this.updateVolume = function(orient) + { + var current = $('#volume-handle').attr("value"); + if (orient == "up") { + var value = Math.min(parseInt(current) + 5, 100); + } + else if (orient == "down") { + var value = Math.max(parseInt(current) - 5, 0); + } + mobileui_ref.rpc.setVolume(value); + }; + +/* + * localisation + */ + this.getString = function(id, def) + { + var text = $("#i18n-"+id).html(); + if (!text) + text = def; + return text; + }; +} + +$(document).ready( function() { + var _mobileui = new mobileUI(); + _mobileui.init(); + setTimeout("window.scrollTo(0, 1);", 1000); +}); + +// vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/js/mobile/ui.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/js/mobile/ui.js --- deejayd-0.7.2/data/htdocs/js/mobile/ui.js 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/js/mobile/ui.js 2009-09-21 08:44:21.000000000 +0100 @@ -0,0 +1,968 @@ +/* Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + +var iPhone = RegExp("(iPhone|iPod)").test(navigator.userAgent); + +function UI(controller) +{ + this.def_cover = "./../static/themes/mobile/images/missing-cover.png"; + this.initialize(controller); + return this; +} + +UI.prototype = +{ + initialize: function(controller, url) { + this._controller = controller; + this.message_time = 4000; + this.current_page = {name: "now_playing", obj: null}; + + // set playing control width + $("#playing-control").width($(window).width()). + css("padding-left", "1px"); + // set main title width + var title_width = $(window).width()-115; + $("#main_title").width(title_width); + + // handler on ajax event + $("#loading").ajaxStart(function(){ + //window.scrollTo(0, 1); + setTimeout("window.scrollTo(0, 1);", 100); + $(this).show(); + }); + $("#loading").ajaxStop(function(){ $(this).hide(); }); + + // initialize pager for medialist + $(".pager-first").click(function(evt) { + var mode = mobileui_ref.ui.getCurrentMode(); + if (mode.current_page != 1) + mode.updateMedialist(mode.current_length, 1); + }); + $(".pager-previous").click(function(evt) { + var mode = mobileui_ref.ui.getCurrentMode(); + if (mode.current_page > 1) + mode.updateMedialist(mode.current_length, mode.current_page-1); + }); + $(".pager-next").click(function(evt) { + var mode = mobileui_ref.ui.getCurrentMode(); + if (mode.current_page < mode.total_page) + mode.updateMedialist(mode.current_length, mode.current_page+1); + }); + $(".pager-last").click(function(evt) { + var mode = mobileui_ref.ui.getCurrentMode(); + if (mode.current_page != mode.total_page) + mode.updateMedialist(mode.current_length, mode.total_page); + }); + + }, + + displayMessage: function(msg, type) { + if (type === undefined) + type = "confirmation"; + var cont = msg; + if (type == 'error') + cont = '' + cont; + cont = '
'+cont+'
'; + + $("#notification").html(cont).show(); + if (type != 'error') { + setTimeout('mobileui_ref.ui.hideMessage()', + mobileui_ref.ui.message_time); + } + }, + + hideMessage: function() { $("#notification").hide(); }, + + buildPage: function(page, st) { + if (this.current_page.name == page && this.current_page.obj) { + // just update the page + this.current_page.obj.update(st); + return false; + } + else if (this.current_page.name != page) { + $("#"+this.current_page.name+"_page").hide(); + // hide options + $("#mode-extra").hide(); + $("#mode-main").show(); + } + + switch (page) { + case "now_playing": + var obj = new NowPlayingPage(st); + break; + case "modelist": + window.scrollTo(0, 1); + $("#modelist_page").show(); + var obj = null; + break; + case "current_mode": + var modes = { + playlist: PlaylistMode, + video: VideoMode, + webradio: WebradioMode, + dvd: DvdMode, + panel: PanelMode, + }; + var obj = new modes[st.mode](st, this._controller); + break; + } + this.current_page = {name: page, obj: obj}; + return false; + }, + + getCurrentMode: function() { + if (this.current_page.name == "current_mode") + return this.current_page.obj; + return null; + }, + + setOptions: function() { + var mode_name = this.getCurrentMode()._mode; + mobileui_ref.rpc.setOption(mode_name, "playorder", + $("#playorder-option").val()); + + var repeat = $("#repeat-option").get(0).checked ? "1" : "0"; + mobileui_ref.rpc.setOption(mode_name, "repeat", repeat); + }, +}; + + +// +// Now Playing Page +// +function NowPlayingPage(st) { + this.initialize(st); + return this; +} + +NowPlayingPage.prototype = +{ + initialize: function(st) { + // init var + this._state = "stop"; + this._volume = -1; + this._current = -1; + + // build page + this.update(st); + $("#now_playing_page").show(); + }, + + update: function(st) { + // update play/pause button + var state = st.state; + if (state != this._state) { + $("#playpause_button").removeClass(this._state); + $("#playpause_button").addClass(state); + this._state = state; + } + + // update volume + var volume = st.volume; + if (volume != this._volume) { + var left = parseInt(volume)*2 - 12; + $("#volume-handle").attr("value", volume); + $("#volume-handle").css("left", left+"px"); + this._volume = volume; + } + + // update position + if (st.state != "stop" && st.mode != "webradio") { + // parse st.time parameter + var times = st.time.split(":"); + $("#time-control-position").html(formatTime(times[0]) + "/" + + formatTime(times[1])); + $("#playing-time-control").show(); + } + else { + $("#playing-time-control").hide(); + } + + // update current media + var current = st.current ? st.current : null; + if (current != this._current) { + $("#playing-title").empty().html(""); + if (current) { + // get informations on current media + var current_callback = function(data) { + var media = data.medias[0]; + if (media) { + var playing_text = createElement("div", "", + {id: "playing-text"}); + var title = createElement("div", "title", {}); + var desc = createElement("div", "desc", {}); + switch (media.type) { + case "song": + $(title).html(media.title + " (" + + formatTime(media.length)+ ")"); + $(desc).html(media.artist+" - "+media.album); + break; + case "video": + $(title).html(media.title + " (" + + formatTime(media.length)+ ")"); + break; + case "webradio": + $(title).html(media.title); + $(desc).html(media.url); + break; + } + $(playing_text).append(title).append(desc); + $("#playing-title").append(playing_text); + + // get cover if available + if (media.type == "song") { + var cover_callback = function(data) { + if (data.cover) + $("#playing-cover").attr("src", + "../"+data.cover); + else + $("#playing-cover").attr("src", + mobileui_ref.ui.def_cover); + }; + mobileui_ref.rpc.send("web.writecover", + [media.media_id], cover_callback); + } + else + $("#playing-cover").attr("src", + mobileui_ref.ui.def_cover); + } + else { + var str = mobileui_ref.getString("no-media", + "No Playing Media"); + $("#playing-title").html(str); + $("#playing-cover").attr("src", + mobileui_ref.ui.def_cover); + } + + }; + mobileui_ref.rpc.send("player.current", [], current_callback); + } + else { + var str = mobileui_ref.getString("no-media","No Playing Media"); + $("#playing-title").html(str); + $("#playing-cover").attr("src", mobileui_ref.ui.def_cover); + } + + this._current = current; + } + }, + +}; + +// +// Current Mode Page +// +var _ModePage = function(st) { + this.pager_support = true; +}; +_ModePage.prototype = +{ + initialize: function(st, controller) { + this._pager = null; + this._id = -1; + this._controller = controller; + // vars for pager + this.current_page = null; + this.current_length = null; + this.total_page = null; + + // set title + $("#mode-title").html(this.title); + + // build toolbar + this.buildToolbar(); + this.update(st); + $("#current_mode_page").show(); + }, + + closeExtra: function() { + $('#mode-extra').hide(); + $('#mode-main').show(); + + // update media info width + var mode = mobileui_ref.ui.getCurrentMode(); + var win_width = $("#current_mode_page").width(); + var width = mode.has_selection ? win_width-85 : win_width-55; + $(".media-info").css("width", width); + }, + + update: function(st) { + if (this.has_options) { // update options + $("#playorder-option").val(st[st.mode+"playorder"]); + var ck = st[st.mode+"repeat"] == "1" ? true : false; + $("#repeat-option").attr("checked", ck); + } + if (parseInt(st[this._mode]) != this._id) { //update medialist + this.updateMedialist(parseInt(st[this._mode+"length"])); + this._id = parseInt(st[this._mode]); + } + }, + + updateMedialist: function(length, page) { + var callback = function(data) { + var mode = mobileui_ref.ui.getCurrentMode(); + if (!mode) { return; } // page has change + + // first remove loading information + $("#mode-content-list").empty(); + + // build new medialist + for (i in data.medias) { + var infos = data.medias[i]; + var media = createElement("div", "media-item", {}); + + if (mode.has_selection) { + var select = createElement("div", "media-select", {}); + var input = document.createElement("input"); + $(input).val(infos.id).attr("type","checkbox"); + $(select).append(input); + + $(media).append(select); + } + else { + // set left padding + $(media).css("padding-left", "10px"); + } + + var media_info = createElement("div", "media-info", {}); + var media_title = createElement("div", "media-title", {}); + var media_desc = createElement("div", "media-desc", {}); + if (infos.length) + var title = infos.title + " ("+ + formatTime(infos.length)+")"; + else + var title = infos.title; + switch (data.media_type) { + case "song": + var desc = infos.artist + " - " + infos.album; + break; + case "video": + var desc = infos.videoheight+" X "+infos.videowidth; + break; + case "webradio": + var desc = infos.url; + break; + case "dvd_track": + var desc = ''; + break; + } + $(media_title).html(title); + $(media_desc).html(desc); + $(media_info).append(media_title); + $(media_info).append(media_desc); + $(media).append(media_info); + + var button = createElement("div", "media-play-btn", + {value: infos.id}); + $(button).click(function(evt) { + var id = $(evt.target).attr("value"); + mobileui_ref.rpc.goto(id); + }); + $(media).append(button); + + $("#mode-content-list").append(media); + } + + // set media info width + var win_width = $("#current_mode_page").width(); + var width = mode.has_selection ? win_width-85 : win_width-55; + $(".media-info").css("width", width); + }; + + // set pager + $(".content-list-pager").hide(); + if (this.pager_support) { + var NUMBER_PER_PAGE = 20; + this.current_page = page ? parseInt(page) : 1; + this.current_length = parseInt(length); + this.total_page = parseInt(this.current_length/NUMBER_PER_PAGE)+1; + if (this.total_page > 1) { + $(".pager-desc").html(this.current_page+"/"+this.total_page); + $(".content-list-pager").show(); + } + var parms = [(this.current_page-1)*NUMBER_PER_PAGE,NUMBER_PER_PAGE]; + } + else + var parms = []; + + var loading = createElement("div", "list-loading", {}); + var str = mobileui_ref.getString("loading","Loading ..."); + $(loading).html(str); + $("#mode-content-list").empty().append(loading); + mobileui_ref.rpc.send(this._mode+".get", parms, callback) + }, + + buildToolbar: function() { + var mode = this; + var tb = this.toolbar; + $("#mode-toolbar").empty(); + for (i in tb) { + var button = document.createElement("div"); + if (typeof tb[i].cmd == "function") + var func = tb[i].cmd; + else + var func = mode[tb[i].cmd]; + $(button).click(func).attr("id", tb[i].id). + addClass("toolbar-button"); + $("#mode-toolbar").append(button); + } + }, + + showOptions: function(evt) { + $("#mode-extra-title").html("Options"); + $("#mode-main").hide(); + $("#mode-extra-content").hide(); + $("#mode-extra-options").show(); + $("#mode-extra").show(); + }, + + setExtraLoading: function() { + var loading = createElement("div", "list-loading", {}); + var str = mobileui_ref.getString("loading","Loading ..."); + $(loading).html(str); + $("#mode-extra-content").empty().append(loading).show(); + $("#mode-main").hide(); + $("#mode-extra-options").hide(); + $("#mode-extra").show(); + }, + + getSelection: function(mode) { + if (mode.has_selection) { + // get selected items + var items = $(".media-select"); + var selections = new Array(); + for (var i = 0; item = items[i]; i++) { + var input = item.getElementsByTagName("input").item(0); + if (input.checked) + selections.push(input.value) + } + + if (selections.length > 0) { // send rpc command + return selections; + } + } + return null; + }, +}; + +/* + * Playlist + */ +function PlaylistMode(st) { + this.has_options = true; + this.has_selection = true; + this.title = mobileui_ref.getString("pls-mode","Playlist Mode"); + this._mode = "playlist"; + this.toolbar = [ + {id: "pl-opt", cmd: "showOptions"}, + {id: "pl-add", cmd: "showLibrary"}, + {id: "pl-remove", cmd: "remove"}, + {id: "pl-shuffle", cmd: "shuffle"}, + {id: "pl-clear", cmd: "clear"}, + ]; + this.initialize(st); + this.extra_pager = new Pager(function(evt, page) { + var mode = mobileui_ref.ui.getCurrentMode(); + mode.showLibrary(evt, mode.current_dir, page); + }); + + return this; +}; +PlaylistMode.prototype = new _ModePage; +PlaylistMode.prototype.showLibrary = function(evt, dir, page) { + if (!page) { page = 1; } + var mode = mobileui_ref.ui.getCurrentMode(); + mode.current_dir = dir; + + var callback = function(data) { + var buildItem = function(title, value, type) { + var item = createElement("div", "library-item", {}); + var select = createElement("div", "library-select", {}); + if (title != "..") { + var input = document.createElement("input"); + $(input).val(value).attr("type","checkbox"); + $(select).append(input); + } + $(item).append(select); + var title_div = createElement("div", type, {}); + $(title_div).html(title); + $(item).append(title_div); + + if (type == "directory") { + var button = createElement("div", "media-play-btn", {}); + $(button).attr("value", value); + $(button).click(function(evt) { + var dir = $(evt.target).attr("value"); + var mode = mobileui_ref.ui.getCurrentMode(); + if (mode) + mode.showLibrary(evt, dir); + }); + $(item).append(button); + } + return item; + }; + + $("#mode-extra-content").empty(); + var mode = mobileui_ref.ui.getCurrentMode(); + + // pager + mode.extra_pager.update(data.directories.length+data.files.length,page); + var items = data.directories.concat(data.files); + var extract = items.slice( + mode.extra_pager.NUMBER_PER_PAGE*(parseInt(page)-1), + mode.extra_pager.NUMBER_PER_PAGE*(parseInt(page))); + + + if (data.root != '') { + var idx = data.root.lastIndexOf("/"); + var root = data.root.substring(0, idx); + $("#mode-extra-content").append(buildItem("..", root, "directory")); + } + // files and directories + for (var i=0; item=extract[i]; i++) { + if (typeof(item) == "object") { // it is a file + var value = item.filename; + var type = "file"; + } + else {// it is a folder + var value = item; + var type = "directory"; + } + + var path = data.root != '' ? data.root + "/" + value : value; + $("#mode-extra-content").append(buildItem(value, path, type)); + } + + // pager + if (mode.extra_pager.total_page > 1) { + $("#mode-extra-content").prepend(mode.extra_pager.build()); + $("#mode-extra-content").append(mode.extra_pager.build()); + } + + // submit button + var str = mobileui_ref.getString("load-files","Load Files"); + var button = createElement("div", "center", {}); + var input = createElement("input", "form-submit", + {type: "submit", value: str}); + $(input).click(function(evt) { + //get selections + var items = $(".library-select"); + var selections = new Array(); + for (var i = 0; item = items[i]; i++) { + var input = item.getElementsByTagName("input").item(0); + if (input && input.checked) + selections.push(input.value) + } + + if (selections.length > 0) { // send rpc command + mobileui_ref.rpc.plsModeAddPath(selections); + } + }); + $(button).append(input); + $("#mode-extra-content").append(button); + + $(".file").css("width", $("#mode-extra-content").width() - 45); + $(".directory").css("width", $("#mode-extra-content").width() - 85); + }; + + var str = mobileui_ref.getString("audio-library","Audio Library"); + $("#mode-extra-title").html(str); + mode.setExtraLoading(); + + var parms = []; + if (dir) { parms.push(dir); } + mobileui_ref.rpc.send("audiolib.getDir", parms, callback); +}; +PlaylistMode.prototype.shuffle = function(evt) { + mobileui_ref.rpc.plsModeShuffle(); +}; +PlaylistMode.prototype.clear = function(evt) { + mobileui_ref.rpc.plsModeClear(); +}; +PlaylistMode.prototype.remove = function(evt) { + var mode = mobileui_ref.ui.getCurrentMode(); + var selections = mode.getSelection(mode); + if (selections) { // send rpc command + mobileui_ref.rpc.plsModeRemove(selections); + } +}; + +/* + * Video + */ +function VideoMode(st) { + this.has_options = true; + this.has_selection = false; + this.title = mobileui_ref.getString("video-mode","Video Mode") + this._mode = "video"; + this.toolbar = [ + {id: "video-opt", cmd: "showOptions"}, + {id: "video-set", cmd: "showLibrary"}, + {id: "video-search", cmd: "search"}, + ]; + this.initialize(st); + this.extra_pager = new Pager(function(evt, page) { + var mode = mobileui_ref.ui.getCurrentMode(); + mode.showLibrary(evt, mode.current_dir, page); + }); + + return this; +}; +VideoMode.prototype = new _ModePage; +VideoMode.prototype.showLibrary = function(evt, dir, page) { + if (!page) { page = 1; } + var mode = mobileui_ref.ui.getCurrentMode(); + mode.current_dir = dir; + + var callback = function(data) { + var buildItem = function(title, value) { + var item = createElement("div", "library-item", {}); + var select = createElement("div", "library-apply", {}); + if (title != "..") { + var input = createElement("div", "apply-btn", {value: value}); + $(input).click(function(evt) { + var dir = $(evt.target).attr("value"); + mobileui_ref.rpc.videoSet(dir, "directory"); + $("#mode-extra").hide(); + $("#mode-main").show(); + }); + $(select).append(input); + } + + $(item).append(select); + var title_div = createElement("div", "directory", {}); + $(title_div).html(title); + $(item).append(title_div); + + var button = createElement("div", "media-play-btn", {value: value}); + $(button).click(function(evt) { + var dir = $(evt.target).attr("value"); + var mode = mobileui_ref.ui.getCurrentMode(); + if (mode) + mode.showLibrary(evt, dir); + }); + $(item).append(button); + return item; + }; + + $("#mode-extra-content").empty(); + var mode = mobileui_ref.ui.getCurrentMode(); + + if (data.root != '') { + var idx = data.root.lastIndexOf("/"); + var root = data.root.substring(0, idx); + $("#mode-extra-content").append(buildItem("..", root)); + } + // directories + mode.extra_pager.update(data.directories.length, page); + var extract = data.directories.slice( + mode.extra_pager.NUMBER_PER_PAGE*(parseInt(page)-1), + mode.extra_pager.NUMBER_PER_PAGE*(parseInt(page))); + for (var i=0; dir=extract[i]; i++) { + var path = dir; + if (data.root != '') + path = data.root + "/" + path; + $("#mode-extra-content").append(buildItem(dir, path)); + } + $(".directory").css("width", $("#mode-extra-content").width() - 85); + + // pager + if (mode.extra_pager.total_page > 1) { + $("#mode-extra-content").prepend(mode.extra_pager.build()); + $("#mode-extra-content").append(mode.extra_pager.build()); + } + }; + + var str = mobileui_ref.getString("video-library","Video Library"); + $("#mode-extra-title").html(str); + mode.setExtraLoading(); + + var parms = []; + if (dir) { parms.push(dir); } + mobileui_ref.rpc.send("videolib.getDir", parms, callback); +}; +VideoMode.prototype.search = function(evt) { + var box = createElement("div", "center", {}); + var input = createElement("input", "form-text", + {id: "search-input", type: "text", size: 36, maxlength: 64}); + var submit = createElement("input", "form-input", + {value: mobileui_ref.getString("search","Search"), type: "submit"}); + $(submit).click(function(evt) { + var value = $("#search-input").val(); + if (value != '') { + mobileui_ref.rpc.videoSet(value, "search"); + $("#mode-extra").hide(); + $("#mode-main").show(); + } + return false; + }); + + $(box).append(input).append(submit); + $("#mode-extra-title").html(mobileui_ref.getString("search","Search")); + $("#mode-extra-content").empty().append(box).show(); + $("#mode-extra-options").hide(); + $("#mode-main").hide(); + $("#mode-extra").show(); + return false; +}; + +/* + * Webradio + */ +function WebradioMode(st) { + this.has_options = false; + this.has_selection = true; + this.title = mobileui_ref.getString("wb-mode","Webradio Mode") + this._mode = "webradio"; + this.toolbar = [ + {id: "wb-add", cmd: "add"}, + {id: "wb-remove", cmd: "remove"}, + {id: "wb-clear", cmd: "clear"}, + ]; + this.initialize(st); + + return this; +}; +WebradioMode.prototype = new _ModePage; +WebradioMode.prototype.clear = function(evt) { + mobileui_ref.rpc.wbModeClear(); +}; +WebradioMode.prototype.remove = function(evt) { + var mode = mobileui_ref.ui.getCurrentMode(); + var selections = mode.getSelection(mode); + if (selections) { // send rpc command + mobileui_ref.rpc.wbModeRemove(selections); + } +}; +WebradioMode.prototype.add = function(evt) { + var name_box = createElement("div", "wb-input", {}); + var title = document.createElement("span"); + $(title).html(mobileui_ref.getString("wb-name","Webradio Name")); + var input = createElement("input", "form-text", + {id: "wb-name", type:"text", size: 24, maxlength: 64}); + $(name_box).append(title).append(input); + + var url_box = createElement("div", "wb-input", {}); + var title = document.createElement("span"); + $(title).html(mobileui_ref.getString("wb-url","Webradio URL")); + var input = createElement("input", "form-text", + {id: "wb-url", type:"text", size: 24, maxlength: 128}); + $(name_box).append(title).append(input); + + var button = createElement("div", "center", {}); + var input = createElement("input", "form-submit", + {value: mobileui_ref.getString("add", "Add"), type:"submit"}); + $(input).click(function(evt) { + var name = $("#wb-name").val(); + var url = $("#wb-url").val(); + if (name != "" && url != "") { + mobileui_ref.rpc.wbModeAdd(name, url); + $("#mode-extra").hide(); + $("#mode-main").show(); + } + }); + $(button).append(input); + + $("#mode-extra-options").hide(); + $("#mode-extra-title").html( + mobileui_ref.getString("wb-add","Add a Webradio")); + $("#mode-extra-content").empty().append(name_box).append(url_box) + .append(button).show(); + $("#mode-main").hide(); + $("#mode-extra").show(); + return false; +}; + +/* + * dvd + */ +function DvdMode(st) { + this.has_options = false; + this.pager_support = false; + this.has_selection = false; + this.title = mobileui_ref.getString("dvd-mode","DVD Mode") + this._mode = "dvd"; + this.toolbar = [ + {id: "dvd-load", cmd: "reload"}, + ]; + this.initialize(st); + + return this; +}; +DvdMode.prototype = new _ModePage; +DvdMode.prototype.reload = function(evt) { mobileui_ref.rpc.dvdModeReload(); }; +DvdMode.prototype.updateMedialist = function(length, page) { + var callback = function(data) { + var mode = mobileui_ref.ui.getCurrentMode(); + if (!mode) { return; } // page has change + + // first remove loading information + $("#mode-content-list").empty(); + + // build new medialist + for (i in data.track) { + var infos = data.track[i]; + var media = createElement("div", "media-item", {}); + + // set left padding + $(media).css("padding-left", "10px"); + + var media_info = createElement("div", "media-info", {}); + var media_title = createElement("div", "media-title", {}); + var media_desc = createElement("div", "media-desc", {}); + var idx = parseInt(i)+1; + var title = "Track "+idx; + var desc = ''; + $(media_title).html(title); + $(media_desc).html(desc); + $(media_info).append(media_title); + $(media_info).append(media_desc); + $(media).append(media_info); + + var button = createElement("div", "media-play-btn", + {value: infos.ix}); + $(button).click(function(evt) { + var id = $(evt.target).attr("value"); + mobileui_ref.rpc.goto(id, "dvd_id"); + }); + $(media).append(button); + + $("#mode-content-list").append(media); + } + + // set media info width + var win_width = $("#current_mode_page").width(); + var width = mode.has_selection ? win_width-85 : win_width-55; + $(".media-info").css("width", width); + }; + + var loading = createElement("div", "list-loading", {}); + var str = mobileui_ref.getString("loading","Loading ..."); + $(loading).html(str); + $(".content-list-pager").hide(); + $("#mode-content-list").empty().append(loading); + mobileui_ref.rpc.send("dvd.get", [], callback) +}; + +/* + * Panel + */ +function PanelMode(st) { + this.has_options = true; + this.has_selection = false; + this.title = mobileui_ref.getString("panel-mode","Panel Mode") + this._mode = "panel"; + this.toolbar = [ + {id: "panel-opt", cmd: "showOptions"}, + {id: "panel-set", cmd: "showTags"}, + ]; + this.initialize(st); + + // init var to update panel filter + this.filters = null; + this.cur_filter_idx = null; + this.current_tag = null; + // get tag list + this.tags = null; + var taglist_callback = function(data) { + var mode = mobileui_ref.ui.getCurrentMode(); + mode.tags = data; + }; + mobileui_ref.rpc.send("panel.tags", [], taglist_callback); + + return this; +}; +PanelMode.prototype = new _ModePage; +PanelMode.prototype.showTags = function(evt, tag_pos) { + var mode = mobileui_ref.ui.getCurrentMode(); + var tag = tag_pos ? mode.tags[tag_pos] : mode.tags[0]; + if (tag) { + mode.current_tag = tag; + if (tag == mode.tags[0]) { // init filters + mode.filters = {type: "complex", id: "and", value: []}; + } + + var callback = function(data) { + $("#mode-extra-content").empty(); + // add a item to select all + data.unshift("__all__"); + + // build list + for (idx in data) { + var item = createElement("div", "tag-item", {}); + var text = data[idx]; + if (text == "__all__") + text = mobileui_ref.getString("all","All"); + else if (text == "__various__") + text = mobileui_ref.getString("various","Varioust Artist"); + else if (text == "") + text = mobileui_ref.getString("unknown","Unknown"); + $(item).attr("value", data[idx]).html(text).click(function(evt){ + var mode = mobileui_ref.ui.getCurrentMode(); + var pattern = $(evt.target).attr("value"); + // update filters + if (pattern != "__all__") { + var n_filter = {type: "basic", id: "equals", + value: {tag: mode.current_tag, pattern: pattern}}; + mode.filters.value.push(n_filter); + } + + + var n_idx = 1+mode.tags.indexOf(mode.current_tag); + if (n_idx < mode.tags.length) + mode.showTags(evt, n_idx); + else { + mobileui_ref.rpc.send("panel.clearAll", + [], mode.__updatePanelFilters) + } + }); + + $("#mode-extra-content").append(item); + } + }; + + var tag_title = tag; + if (tag == "various_artist") + tag_title = "artist"; + $("#mode-extra-title").html(mobileui_ref.getString(tag_title, tag)); + mode.setExtraLoading(); + mobileui_ref.rpc.send("audiolib.taglist", [tag,mode.filters], callback); + } +}; +PanelMode.prototype.__updatePanelFilters = function() { + var mode = mobileui_ref.ui.getCurrentMode(); + if (mode.cur_filter_idx == null) { + mode.cur_filter_idx = 0; + } + else if (mode.cur_filter_idx == mode.filters.value.length-1) { + mobileui_ref.rpc.pnModeSetActiveList("panel", ""); + $("#mode-extra").hide(); + $("#mode-main").show(); + mode.cur_filter_idx = null; + return; + } + else { + mode.cur_filter_idx += 1; + } + var f = mode.filters.value[mode.cur_filter_idx]; + mobileui_ref.rpc.send("panel.setFilter", + [f.value.tag, [f.value.pattern]], mode.__updatePanelFilters); +}; + +// vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/js/mobile/widget.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/js/mobile/widget.js --- deejayd-0.7.2/data/htdocs/js/mobile/widget.js 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/js/mobile/widget.js 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,78 @@ +/* Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + +function Pager(func) +{ + this.NUMBER_PER_PAGE = 20; + this.func = func; + this.current_page = null; + this.desc = null; + + return this; +} + +Pager.prototype.build = function() { + + var pager = createElement("div", "pager", {}); + var first = createElement("div", "pager-first pager-btn", {}); + $(first).click(function(evt) { + var mode = mobileui_ref.ui.getCurrentMode(); + var pager = mode.extra_pager; + if (pager.current_page != 1) + pager.func(evt, 1); + }); + var previous = createElement("div", "pager-previous pager-btn", {}); + $(previous).click(function(evt) { + var mode = mobileui_ref.ui.getCurrentMode(); + var pager = mode.extra_pager; + if (pager.current_page > 1) + pager.func(evt, pager.current_page - 1); + }); + var next = createElement("div", "pager-next pager-btn", {}); + $(next).click(function(evt) { + var mode = mobileui_ref.ui.getCurrentMode(); + var pager = mode.extra_pager; + if (pager.current_page < pager.total_page) + pager.func(evt, pager.current_page + 1); + }); + var last = createElement("div", "pager-last pager-btn", {}); + $(last).click(function(evt) { + var mode = mobileui_ref.ui.getCurrentMode(); + var pager = mode.extra_pager; + if (pager.current_page != pager.total_page) + pager.func(evt, pager.total_page); + }); + + var desc = createElement("div", "extra-pager-desc", {}); + $(desc).html(this.desc); + $(pager).append(first).append(previous).append(desc).append(next). + append(last); + return pager; +}; + +Pager.prototype.update = function(length, current_page) { + this.total_page = parseInt(parseInt(length)/this.NUMBER_PER_PAGE) + 1; + this.current_page = current_page; + this.desc = current_page+"/"+this.total_page; +}; + +Pager.prototype.registerFunction = function(func) { + this.func = func; +}; + +// vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/js/rpc.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/js/rpc.js --- deejayd-0.7.2/data/htdocs/js/rpc.js 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/js/rpc.js 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,217 @@ +/* Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + + +function dump(arr,level) { + var dumped_text = ""; + if(!level) level = 0; + + //The padding given at the beginning of the line. + var level_padding = ""; + for(var j=0;j \"" + value + "\"\n"; + } + } + } else { //Stings/Chars/Numbers etc. + dumped_text = "===>"+arr+"<===("+typeof(arr)+")"; + } + return dumped_text; +} + +function RPC(controller, url) +{ + this.initialize(controller, url); + return this; +} + +RPC.prototype = +{ + initialize: function(controller, url) { + this._controller = controller; + this._root = url; + this._id = 0; + + // init default callback + this.callbacks = {}; + this.onerror = function(request, error_string, exception) {}; + this.onrequesterror = function(code, message) {}; + }, + + send: function(method, params, success) { + var remote = this; + // build data + this._id += 1; + var data = {method: method, params: params, id: this._id}; + // build success callback + var callback = function(data) { + if (data.error != null) { // request error + remote.onrequesterror(data.error.code, data.error.message); + } + else { + if (data.result && data.result.type) + success(data.result.answer); + else + success(data.result); + } + }; + $.ajax( { + url: this._root, + type: 'POST', + contentType: 'json', + dataType: 'json', + cache: false, + data: $.toJSON(data), + error: function(request, error_string, exception){ + remote.onerror(request, error_string, exception, this) }, + success: callback + } ); + }, + +/* + * general requests + */ + setMode: function(mode) { + this.send("setmode", [mode], this.callbacks.updateMode); + }, + + setOption: function(source, name, value) { + this.send("setOption", [source, name, value], + this.callbacks.updateOption); + }, + +/* + * player requests + */ + __playerRequest: function(request, params) { + var pl_callbacks = this.callbacks.player; + if (pl_callbacks[request]) + var callback = pl_callbacks[request]; + else + var callback = pl_callbacks.def; + var p = params ? params : []; + this.send("player."+request, p, callback); + }, + + playToggle: function() { this.__playerRequest("playToggle"); }, + stop: function() { this.__playerRequest("stop"); }, + next: function() { this.__playerRequest("next"); }, + previous: function() { this.__playerRequest("previous"); }, + goto: function(id, id_type, source) { + var parms = [id]; + if (id_type) { parms.push(id_type); } + if (source) { parms.push(source); } + this.__playerRequest("goto", parms); + }, + setVolume: function(vol) { this.__playerRequest("setVolume",[vol]); }, + seek: function(pos, rel) { this.__playerRequest("seek", [pos, rel]); }, + setPlayerOption: function(opt_name, opt_value) { + this.__playerRequest("setPlayerOption",[opt_name, opt_value]); + }, + +/* + * playlist requests + */ + __plsModeRequest: function(request, params) { + var pl_callbacks = this.callbacks.playlist; + if (pl_callbacks[request]) + var callback = pl_callbacks[request]; + else + var callback = pl_callbacks.def; + var p = params ? params : []; + this.send("playlist."+request, p, callback); + }, + + plsModeShuffle: function() { this.__plsModeRequest("shuffle"); }, + plsModeClear: function() { this.__plsModeRequest("clear"); }, + plsModeRemove: function(ids) { this.__plsModeRequest("remove", [ids]); }, + plsModeAddPath: function(paths) { + this.__plsModeRequest("addPath", [paths]); + }, + +/* + * video requests + */ + __videoModeRequest: function(request, params) { + var video_callbacks = this.callbacks.video; + if (video_callbacks[request]) + var callback = video_callbacks[request]; + else + var callback = video_callbacks.def; + var p = params ? params : []; + this.send("video."+request, p, callback); + }, + + videoSet: function(val, type) { + this.__videoModeRequest("set",[val, type]); + }, + +/* + * dvd requests + */ + dvdModeReload: function() { + this.send("dvd.reload", [], this.callbacks.dvd); + }, +/* + * Webradio requests + */ + __wbModeRequest: function(request, params) { + var wb_callbacks = this.callbacks.webradio; + if (wb_callbacks[request]) + var callback = wb_callbacks[request]; + else + var callback = wb_callbacks.def; + var p = params ? params : []; + this.send("webradio."+request, p, callback); + }, + + wbModeClear: function() { this.__wbModeRequest("clear"); }, + wbModeRemove: function(val) { this.__wbModeRequest("remove",[val]); }, + wbModeAdd: function(name, url) { this.__wbModeRequest("add",[name, url]); }, + +/* + * Webradio requests + */ + __pnModeRequest: function(request, params) { + var pn_callbacks = this.callbacks.panel; + if (pn_callbacks[request]) + var callback = pn_callbacks[request]; + else + var callback = pn_callbacks.def; + var p = params ? params : []; + this.send("panel."+request, p, callback); + }, + + pnModeSetActiveList: function(type, val) { + this.__pnModeRequest("setActiveList",[type, val]); + }, + + pnModeClearFilter: function() { this.__pnModeRequest("clearFilter"); }, + pnModeClearSearch: function() { this.__pnModeRequest("clearSearch"); }, +}; + +// vim: ts=4 sw=4 expandtab Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/add.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/add.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/audio.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/audio.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/cancel.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/cancel.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/clear.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/clear.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/collapsed.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/collapsed.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/confirm.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/confirm.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/dvd.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/dvd.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/error.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/error.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/expanded.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/expanded.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/folder.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/folder.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/fullscreen.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/fullscreen.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/go.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/go.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/home.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/home.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/info.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/info.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/loading.gif and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/loading.gif differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/next.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/next.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/pause.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/pause.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/playlist.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/playlist.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/play-low.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/play-low.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/play.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/play.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/previous.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/previous.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/remove.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/remove.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/repeat.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/repeat.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/save.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/save.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/search.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/search.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/shuffle.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/shuffle.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/stop.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/stop.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/subtitle.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/subtitle.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/tree-folder.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/tree-folder.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/tree-pause.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/tree-pause.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/tree-play.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/tree-play.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/update.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/update.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/video.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/video.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/volume-max.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/volume-max.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/volume-zero.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/volume-zero.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/warning.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/warning.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/images/webradio.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/images/webradio.png differ diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/default/theme.css /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/default/theme.css --- deejayd-0.7.2/data/htdocs/themes/default/theme.css 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/themes/default/theme.css 1970-01-01 01:00:00.000000000 +0100 @@ -1,156 +0,0 @@ -/* - * An example if you want to style the tree - */ -/* -treechildren::-moz-tree-row(selected) { background-color: #ddd; } -treechildren::-moz-tree-row(focus,selected) { background-color: #aaa; } -treechildren::-moz-tree-row(odd) { background-color: #eee; } -treechildren::-moz-tree-row(odd,selected) { background-color: #ddd; } -treechildren::-moz-tree-row(focus,odd,selected) { background-color: #aaa; } -*/ - -/* - * General parms - */ -#main{margin: 10px;} - -.progressbar{height: 5px !important;} -caption{font-weight: bold;} - - -/* - * Icons - */ -.collapsed{list-style-image: url("./images/collapsed.png");} -.expanded{list-style-image: url("./images/expanded.png");} - -.audio-item{list-style-image: url("./images/audio.png");} -.directory-item{list-style-image: url("./images/folder.png");} -.video-item{list-style-image: url("./images/video.png");} -.playlist-item{list-style-image: url("./images/playlist.png");} -.webradio-item{list-style-image: url("./images/webradio.png");} -.dvd-item{list-style-image: url("./images/dvd.png");} - -.add-action{list-style-image: url("./images/add.png");} -.remove-action{list-style-image: url("./images/remove.png");} -.play-action{list-style-image: url("./images/play-low.png");} -.search-action{list-style-image: url("./images/search.png");} - -.shuffle-button{list-style-image: url("./images/update.png");} -.save-button{list-style-image: url("./images/save.png");} -.clear-button{list-style-image: url("./images/clear.png");} -.cancel-button{list-style-image: url("./images/cancel.png");} -.fullscreen-button{list-style-image: url("./images/fullscreen.png");} -.update-button{list-style-image: url("./images/update.png");} -.subtitle-button{list-style-image: url("./images/subtitle.png");} - -#random-button{list-style-image: url("./images/shuffle.png");} -#repeat-button{list-style-image: url("./images/repeat.png");} -#playlist-goCurSong{list-style-image: url("./images/go.png");} - -.home-menu{list-style-image: url("./images/home.png");} -.search-menu{list-style-image: url("./images/search.png");} -.playlist-menu{list-style-image: url("./images/playlist.png");} - -/* - * Example to change the style of a checked button - */ -button[checked="true"] { font-weight: bold; } -toolbarbutton[checked="true"] { font-weight: bold; } - -/* - * Playlist - */ -treechildren::-moz-tree-row(dragged) { border-top: 2px solid #333; } - -/* - * video directoey - */ -treechildren::-moz-tree-image(folder) { -list-style-image: url("./images/tree-folder.png"); -padding-right: 6px; -} -treechildren::-moz-tree-image(videodir-select) { -list-style-image: url("./images/go.png"); -} - -/* - * Command - */ -#command-panel{ - margin: 5px 10px 2px 20px; - padding: 5px; - } -#seekbar-button{max-height: 26px !important; } -#volseek-toolbar{margin: 5px 0px 5px 0px;} - -/* - * Message - */ -#message-box{ - padding: 3px; - height:20px;} -.error{ - background-color: #EF9398; - border: 1px solid #DC5757; - } -.confirmation{ - background-color: #A6EF7B; - border: 1px solid #76C83F; - } - -/* - * Player - */ -.play-button {list-style-image: url("./images/play.png");} -.pause-button {list-style-image: url("./images/pause.png");} -.stop-button {list-style-image: url("./images/stop.png");} -.previous-button {list-style-image: url("./images/previous.png");} -.next-button {list-style-image: url("./images/next.png");} - -#current-media{margin: 10px 5px 0px 5px;} -#media-info .cursong {margin: 0px;} -#current-artist,#current-url,#current-album{ - font-size: 11px; - padding-left: 10px; - } -#current-artist,#current-url{font-weight: bold;} -#current-album{font-style: italic;} - -#seekbar{margin-top: 5px;} - -treechildren::-moz-tree-row(play) {background-color: #ccc;} -treechildren::-moz-tree-image(play) { -list-style-image: url("./images/tree-play.png"); -} -treechildren::-moz-tree-row(pause) {background-color: #ccc;} -treechildren::-moz-tree-image(pause) { -list-style-image: url("./images/tree-pause.png"); -} - -/* - * Search - */ -#search-box{height: 11px;} - -/* - * Playlist - */ -#navigation-path {margin: 1px 10px 1px 10px;} -#navigation-path toolbarbutton{ - font-size: 10px; - max-width:60px; - margin: 2px; - background-color: #ddd; - border-left: 1px solid #ddd; - border-right: 1px solid #ddd; - } - -/* - * Queue - */ -#queue-menu{ -height:30px; -min-height:30px; -max-height:30px; -} Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/deejayd.jpg and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/deejayd.jpg differ diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/default.css /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/default.css --- deejayd-0.7.2/data/htdocs/themes/mobile/default.css 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/themes/mobile/default.css 2009-09-21 08:44:21.000000000 +0100 @@ -0,0 +1,350 @@ +/* + * General parms + */ +.center { text-align: center; vertical-align: middle; } +.hide { display: none; } +body { + margin: 0px; padding: 0px; + background-color: #121212; + color: #fff; + min-height: 416px; +} +input { padding: 2px; } + +div.page, div.header { width: 100%; } + +/* + * notification + */ +#notification { + position: fixed; top: 0px; left: 0px; + width: 100%; + z-index: 10; +} +#notification div { + padding: 5px; + width: 100%; + color: #000; +} +#notification div.error { + background-color: #EF9398; + border: 1px solid #DC5757; +} +#notification div.confirmation { + background-color: #A6EF7B; + border: 1px solid #76C83F; +} +#notification div input { + float: right; + background: #eee; + color: #000; +} + +/* + * Header + */ +div.header { + margin: 0px; padding: 0px; + background-image: url(images/control-bg.png); + background-repeat: repeat-x; + height: 45px; + text-align: center; +} +div.header div { + display: inline-block; + text-align: center; +} +div.header div.title { + padding-top: 4px; + font-size: 14px; font-weight: bold; +} +#playing-text div { + display: block; + text-align: center; +} +#playing-text div.title { + font-size: 13px; font-weight: bold; + white-space: nowrap; +} +#playing-text div.desc{ + font-size: 12px; font-style: italic; + white-space: nowrap; +} +div.header div.button { + position: fixed; top: 3px; + height: 35px; width: 50px; + font-size: 11px; + background-image: url(images/headerbtn-bg.png); + background-repeat: repeat-x; + border: 1px solid #ababab; + padding: 2px 8px 0px 8px; + overflow: hidden; +} +div.left { left: 4px; } +div.right { right: 4px; } +#loading { + width: 30px; height: 30px; + position: fixed; top: 0px; right: 2px; + z-index: 10; + background-image: url(images/loading.gif); + background-repeat: no-repeat; +} + +/* + * playing info / control + */ +#playing-block { + width: 100%; + height:306px; + background-color: #121212; + text-align: center; +} +#playing-block #playing-cover { + z-index: 0; + height: 311px; + width: 311px; +} +#playing-control { + position: fixed; left: 0px; top: 301px; + z-index: 1; opacity: 0.8; + width: 100%; height: 55px; + text-align: center; + background-image: url(images/control-bg.png); + background-repeat: repeat-x; +} +#playing-control div.control-button, +#playing-time-control div.time-control-button { + padding: 0px; + width: 48px; + height: 48px; + background-repeat: no-repeat; + display: inline-block; + text-align: center; + margin-right: auto; margin-left: auto; +} +#previous_button { background-image: url(images/previous.png); } +#next_button { background-image: url(images/next.png); } +#stop_button { background-image: url('images/stop.png'); } +div.play { background-image: url('images/pause.png'); } +div.pause, div.stop { background-image: url('images/play.png'); } +#playing-time-control { + position: fixed; left: 0px; top: 45px; + z-index: 1; opacity: 0.7; + width: 100%; height: 50px; + text-align: center; vertical-align: middle; + background-image: url(images/control-bg.png); + background-repeat: repeat-x; +} +#playing-time-control #time-control-position { + height: 50px; + font-size: 12px; + line-height: 12px; + text-align: center; vertical-align: middle; + white-space: nowrap; + overflow: hidden; + display: inline-block; +} +#time-fast-back { background-image: url(images/go-first.png); } +#time-back { background-image: url(images/go-previous.png); } +#time-forward { background-image: url(images/go-next.png); } +#time-fast-forward { background-image: url(images/go-last.png); } + +/* + * volume widget + */ +#volume-widget { + width: 100%; height: 60px; + background-image: url(images/volume-bg.png); + background-repeat: repeat-x; +} +#volume-widget div { display: inline-block; } +#volume-slider { + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + position: relative; top: 2px; + width: 200px; height: 15px; + background-color: #fff; +} +#volume-handle { + position: relative; top: -5px; + width: 25px; height: 25px; + background-image: url(images/vol-handle.png); +} +.volume-button { + position: relative; top: 2px; + width: 40px; height: 40px; +} +#volume-up { background-image: url(images/vol-up.png); } +#volume-down { background-image: url('images/vol-down.png'); } + +/* + * Mode List + */ +#modelist-content { + text-align: center; + width: 100%; + background-color: #121212; + padding: 10px 0px; +} +#modelist-content div.mode-button { + text-align: center; + height: 40px; + font-size: 20px; font-weight: bold; + margin: 0px 20px 10px 20px; + background-color: #454545; + padding-top: 20px; + + -webkit-border-radius: 10px; + -moz-border-radius: 10px; +} + +/* + * current mode - toolbar + */ +#mode-toolbar { + background-color: #232323; + width: 100%; height: 30px; +} +#mode-toolbar div.toolbar-button { + display: inline-block; + width: 30px; height: 30px; + padding: 0px 5px; + background-repeat: no-repeat; +} +#pl-add, #wb-add, #video-set, #panel-set { + background-image: url(images/add-btn.png); +} +#pl-opt, #panel-opt, #video-opt { background-image: url(images/options.png); } +#video-search { background-image: url(images/search-btn.png); } +#pl-clear, #wb-clear { background-image: url(images/clear-btn.png); } +#pl-remove, #wb-remove { background-image: url(images/remove-btn.png); } +#pl-shuffle,#dvd-load { background-image: url(images/refresh.png); } + +/* + * current mode - media list + */ +#mode-content div.media-item { + max-width: 100%; min-height: 40px; + border-bottom: 1px solid #676767; +} +#mode-content div.media-item div { + min-height: 40px; + display: inline-block; +} +#mode-content div.media-item div.media-info div { + min-height: 40px; + display: block; +} +#mode-content div.media-item div.media-info .media-title, +#mode-content div.media-item div.media-info .media-desc { + min-height: 16px; + overflow:visible; + white-space: normal; +} +#mode-content div.media-item div.media-info .media-title { + font-weight: bold; font-size: 15px; +} +#mode-content div.media-item div.media-info .media-desc { + font-style: italic; font-size: 14px; + padding-left: 15px; +} +#mode-content div.media-item div.media-play-btn { + height: 40px; width: 40px; + background-image: url(images/go-last.png); + background-repeat: no-repeat; +} +#mode-content div.media-item div.media-select { + height: 40px; width: 40px; + text-align: center; vertical-align: middle; +} + +/* + * current mode - medialist and library pager + */ +#mode-content div.content-list-pager, +#mode-extra-content div.pager { text-align: center; vertical-align: middle; } +#mode-extra-content div.pager div { min-height: 40px; display: inline-block;} +.pager-btn{ height: 40px; width: 40px;} +.pager-previous { background-image: url(images/go-previous.png); } +.pager-next { background-image: url(images/go-next.png); } +.pager-first { background-image: url(images/go-first.png); } +.pager-last { background-image: url(images/go-last.png); } +.pager-desc, .extra-pager-desc { + font-size: 30px; vertical-align: middle; + line-height: 10px; +} + +/* + * current mode - extra content + */ +#mode-extra-header { + height: 30px; width: 100%; + vertical-align: middle; text-align: center; + background-color: #343434; +} +#mode-extra-header div { + display: inline-block; +} +#mode-extra-title { height: 30px; font-style: italic; line-height: 30px;} +#mode-extra-close { + height: 25px; position: fixed; right: 5px; top: 45px; + padding: 2px; + background-color: #565656; +} + +/* + * current mode - library list + */ +#mode-extra-content div.library-item { + max-width: 100%; height: 40px; + border-bottom: 1px solid #676767; +} +#mode-extra-content div.library-item div { + height: 40px; + display: inline-block; +} +#mode-extra-content div.library-item div.media-play-btn { + height: 40px; width: 40px; + background-image: url(images/go-last.png); + background-repeat: no-repeat; +} +#mode-extra-content div.library-item div.library-select { + height: 40px; width: 40px; + text-align: center; vertical-align: middle; +} +#mode-extra-content div.library-apply div.apply-btn { + height: 40px; width: 40px; + background-image: url(images/apply.png); + background-repeat: no-repeat; +} +#mode-extra-content div.library-item div.directory, +#mode-extra-content div.library-item div.file { + font-size: 15px; line-height: 40px; + overflow: hidden; + white-space: normal; +} + +/* + * current mode - tag list + */ +#mode-extra-content div.tag-item { + max-width: 100%; height: 40px; + border-bottom: 1px solid #676767; + padding-left: 10px; +} + +/* current mode - option */ +#playorder-option, #repeat-option { + padding: 10px 10px 10px 100px; +} +#mode-extra-content .form-submit { + padding: 15px 10px; +} + +/* current mode - webradio form */ +.wb-input { margin-left: 5px; padding: 10px; } + +/* current mode - video search */ +.videosearch-box { + margin-top: 5px; +} Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/add-btn.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/add-btn.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/add.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/add.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/apply.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/apply.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/clear-btn.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/clear-btn.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/control-bg.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/control-bg.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/delete.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/delete.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/go-first.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/go-first.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/go-last.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/go-last.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/go-next.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/go-next.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/go-previous.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/go-previous.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/headerbtn-bg.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/headerbtn-bg.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/loading.gif and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/loading.gif differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/missing-cover.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/missing-cover.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/next.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/next.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/options.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/options.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/pause.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/pause.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/play.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/play.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/previous.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/previous.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/refresh.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/refresh.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/remove-btn.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/remove-btn.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/remove.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/remove.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/search-btn.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/search-btn.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/stop.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/stop.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/vol-down.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/vol-down.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/vol-handle.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/vol-handle.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/volume-bg.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/volume-bg.png differ Binary files /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/images/vol-up.png and /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/images/vol-up.png differ diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/themes/mobile/webkit.css /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/themes/mobile/webkit.css --- deejayd-0.7.2/data/htdocs/themes/mobile/webkit.css 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/themes/mobile/webkit.css 2009-09-21 08:44:21.000000000 +0100 @@ -0,0 +1,13 @@ +#playing-control div.control-button { + padding: 0px 8px; +} + +#volume-slider, #volume-widget div.volume-button { + position: relative; + left: 18px; +} +#volume-widget div.volume-button { top: 12px; } + +#mode-extra-content div.library-item div.directory { + position: relative; top: -10px; +} diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/xul/bindings/bindings.css /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/xul/bindings/bindings.css --- deejayd-0.7.2/data/htdocs/xul/bindings/bindings.css 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/xul/bindings/bindings.css 1970-01-01 01:00:00.000000000 +0100 @@ -1,11 +0,0 @@ -progress_slider { - -moz-binding: url("./progress_slider.xml#progress_slider") !important; -} - -player_volume { - -moz-binding: url("./player_controls.xml#volume") !important; -} - -player_seekbar { - -moz-binding: url("./player_controls.xml#seekbar") !important; -} diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/xul/bindings/player_controls.xml /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/xul/bindings/player_controls.xml --- deejayd-0.7.2/data/htdocs/xul/bindings/player_controls.xml 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/xul/bindings/player_controls.xml 1970-01-01 01:00:00.000000000 +0100 @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/xul/bindings/progress_slider.xml /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/xul/bindings/progress_slider.xml --- deejayd-0.7.2/data/htdocs/xul/bindings/progress_slider.xml 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/xul/bindings/progress_slider.xml 1970-01-01 01:00:00.000000000 +0100 @@ -1,223 +0,0 @@ - - - - - - - - - - - - - - - - - - - document.addEventListener("mouseup", this.globalMouseUp, true); - - - document.removeEventListener("mouseup", this.globalMouseUp, true); - - - 0 - document.getAnonymousElementByAttribute(this, 'sbid', 'seekbar_slider'); - document.getAnonymousElementByAttribute(this, 'sbid', 'seekbar_thumb'); - document.getAnonymousElementByAttribute(this, 'sbid', 'seekbar_progress'); - - - - return this.slider.getAttribute('curpos'); - - - this.progress_value = val; - return this.slider.setAttribute('curpos', val); - - - - - - return ( this.progress.getAttribute('value') * this.maxpos ) / 100; - - - 0 ) - { - // Stupid fun to keep progress under the thumb. - if ( ( val > 0 ) && ( prog < 50 ) ) - { - prog += 2; - } - } - this.progress.setAttribute('value', prog); - ]]> - - - - - - return this.slider.getAttribute('maxpos'); - - - this.slider.setAttribute('maxpos', val); - this.progress_value = this.value; - return this.maxpos; - - - - - - return this.getAttribute('data_tracking'); - - - // nope - - - - - - var data_str = this.getAttribute('data_tracking'); - if ( data_str.length ) - { - return SBDataGetBoolValue( data_str ); - } - return ""; - - - var data_str = this.getAttribute('data_tracking'); - if ( data_str.length ) - { - return SBDataSetBoolValue( data_str, val ); - } - return ""; - - - - - - var e = document.createEvent("Events"); - e.initEvent("progressSliderChange", false, true); - this.dispatchEvent(e); - - - - - - var e = document.createEvent("Events"); - e.initEvent("progressSliderRelease", false, true); - this.dispatchEvent(e); - - - - - - - - - - - - - - - = this.thumb.boxObject.screenX && x <= this.thumb.boxObject.screenX + this.thumb.boxObject.width && - y >= this.thumb.boxObject.screenY && y <= this.thumb.boxObject.screenY + this.thumb.boxObject.height) { - return true; - } - return false; - ]]> - - - - - - - 0 ) - { - // Report if requested - this.is_tracking_data = true; - } - this.sendChangeEvent(); - ]]> - - - - - - - if (this.tracking == 0) return; - this.tracking = 0; - this.is_tracking_data = false; - this.sendReleaseEvent(); - - - - - - - if (this.tracking == 2) { - this.progress_value = this.value; - } else if (this.tracking == 0) { - return; - } else { - this.handlemouse(event); - } - this.sendChangeEvent(); - - - - - - - m) v = m; - this.slider.setAttribute('curpos', ''+v); - this.progress_value = v; - ]]> - - - - - - - - diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/xul/scripts/commonList.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/xul/scripts/commonList.js --- deejayd-0.7.2/data/htdocs/xul/scripts/commonList.js 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/xul/scripts/commonList.js 1970-01-01 01:00:00.000000000 +0100 @@ -1,161 +0,0 @@ -// commonList.js - -// listbox.selectAll function does not work correctly. -// this (awful) hack is needed -var slAll = function(content) { - var list = $(content); - var nb = list.getNumberOfVisibleRows(); - - list.clearSelection(); - var i = 0; - while (i< list.getRowCount()) { - list.ensureIndexIsVisible(i); - i += nb; - } - list.ensureIndexIsVisible(list.getRowCount()-2); - list.selectAll(); -}; - - -var CommonList = function() -{ - this.curDir = ""; - this.menuId = ""; - this.menuCommand = ""; - this.contextMenuId = ""; - this.listType = ""; - - this.getSelectedItems = function(content) - { - var navList = $(content); - var items = navList.selectedItems; - var argArray = new Array(); - for (var id in items) { - var item = items[id]; - argArray.push(item.value); - } - - return argArray; - }; - - this.constructItemList = function(itemList,content) - { - var listObj = $(content); - var currentItem = listObj.firstChild; - - var Items = itemList.getElementsByTagName("item"); - for(var i=0;item=Items.item(i);i++) { - var type = item.getAttribute("type"); - switch (type) { - case "song": - var row = this.constructFileRow(item); - break; - case "directory": - var row = this.constructDirRow(item); - break; - case "playlist": - var row = this.constructPlaylistRow(item); - break; - case "video": - var row = this.constructVideoRow(item); - break; - } - - if (currentItem) { - listObj.replaceChild(row,currentItem); - currentItem = row.nextSibling; - } - else - listObj.appendChild(row); - } - - while(currentItem) { - var nextItem = currentItem.nextSibling; - listObj.removeChild(currentItem); - currentItem = nextItem; - } - }; - - this.constructMenu = function(path) - { - var navMenu = $(this.menuId); - this.eraseMenu(navMenu); - if (path == '' || !path) { - navMenu.style.display="none"; - return; - } - - var tmp = path.split("/"); - for (var i=-1; i < (tmp.length); i++) { - var tmp_path = ''; - for (var j=0; j<(i); j++) - tmp_path += tmp[j]+"/"; - tmp_path += (i == -1 ? '' : tmp[i]); - - var button = document.createElement('toolbarbutton'); - var name = tmp[i] ? tmp[i] : 'Root'; - button.setAttribute("crop","end"); - button.setAttribute("label",name); - button.setAttribute("tooltiptext",name); - button.id = tmp_path ? tmp_path : 'root_link'; - - button.addEventListener('command', this.getDir, true); - - navMenu.appendChild(button); - } - navMenu.style.display="block"; - - }; - - this.getDir = function(e) - { - var args = {}; - if (this.id !='root_link') - args['dir'] = this.id; - ajaxdj_ref.send_post_command(this.menuCommand,args); - }; - - this.eraseMenu = function(navMenu) - { - while (navMenu.hasChildNodes()) - navMenu.removeChild(navMenu.firstChild); - }; - - this.constructDirRow = function(dir) - { - var dirName = dir.firstChild.data; - var dirItem = document.createElement("listitem"); - dirItem.setAttribute("label",dirName); - dirItem.setAttribute("type","directory"); - var path = this.curDir != "" ? this.curDir+"/" - +dirName : dirName; - dirItem.setAttribute("value",path); - dirItem.setAttribute("context",this.contextMenuId); - dirItem.setAttribute("type","directory"); - dirItem.className = "listitem-iconic directory-item"; - - this.customConstructDirRow(dirItem) - return dirItem; - }; - - this.updateDatabase = function(upObj) - { - var progress = upObj.getAttribute("p"); - var upId = upObj.firstChild.data; - if (progress == "1") { - $(this.updateBox).selectedIndex = 1 - $('audio-update-progressbar').mode = "undetermined"; - setTimeout( - "ajaxdj_ref.send_command('"+this.listType+ - "_update_check',{id:"+upId+ - "},false)",1000); - } - else { - $(this.updateBox).selectedIndex = 0 - $('audio-update-progressbar').mode = "determined"; - } - }; - -}; - -// vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/xul/scripts/commonTree.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/xul/scripts/commonTree.js --- deejayd-0.7.2/data/htdocs/xul/scripts/commonTree.js 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/data/htdocs/xul/scripts/commonTree.js 1970-01-01 01:00:00.000000000 +0100 @@ -1,278 +0,0 @@ -// commonTree.js - -var CommonTreeManagement = function() -{ - this.treeId = -1; - this.canDrop = false; - this.mediaDragged = false; - this.playing = null; - - this.init = function() - { - try{ - netscape.security.PrivilegeManager. - enablePrivilege("UniversalXPConnect"); - } - catch(ex){return} - - if (typeof this.treeController == 'object') - this.tree.controllers.appendController(this.treeController); - }; - - this.update = function(obj) - { - var id = parseInt(obj.getAttribute("id")); - if (id != this.treeId) { - this.treeId = id; - // clear selection - if (this.tree.view) { this.tree.view.selection.clearSelection(); } - // Update datasources - this.tree.setAttribute("datasources",window.location.href+"rdf/"+ - this.module+"-"+id+".rdf"); - // do custom update - this.customUpdate(obj); - // update playing - if (this.playing != null) - this.setPlaying(this.playing["pos"], this.playing["id"], - this.playing["state"]); - } - }; - - this.removeItems = function() - { - var items = this.getTreeSelection(); - ajaxdj_ref.send_post_command(this.module+"Remove", {ids: items}); - }; - - this.getTreeSelection = function() - { - var start = new Object(); - var end = new Object(); - var numRanges = this.tree.view.selection.getRangeCount(); - var selectedItems = Array(); - - for (var t=0; t= 0) { - var item = this.tree.contentView.getItemAtIndex(row); - this.row.obj = item.firstChild; - this.row.pos = row; - this.row.obj.setAttribute("properties","dragged"); - if (oldRow && oldRow != this.row.obj){ - oldRow.setAttribute("properties",""); - } - } - else { - this.row.pos = -1; - if (oldRow) { - try{ oldRow.setAttribute("properties","");} - catch(ex){} - } - } - - var dragService = Components - .classes["@mozilla.org/widget/dragservice;1"]. - getService().QueryInterface(Components.interfaces.nsIDragService); - if (dragService) { - var dragSession = dragService.getCurrentSession(); - if (dragSession && (this.mediaDragged || fileList_ref.dragItemType)) - dragSession.canDrop = true; - } - }; - - this.dragEnter = function(evt) - { - evt.stopPropagation(); - try{ - netscape.security.PrivilegeManager. - enablePrivilege("UniversalXPConnect"); - } - catch(ex){return} // drag and drop not allowed - - var dragService=Components.classes["@mozilla.org/widget/dragservice;1"]. - getService().QueryInterface(Components.interfaces.nsIDragService); - if (dragService) { - var dragSession = dragService.getCurrentSession(); - if (dragSession && (this.mediaDragged || fileList_ref.dragItemType)) - dragSession.canDrop = true; - } - }; - - this.dragExit = function(evt) - { - evt.stopPropagation(); - try{ - netscape.security.PrivilegeManager. - enablePrivilege("UniversalXPConnect"); - } - catch(ex){return} // drag and drop not allowed - - if (this.row.obj) { - this.row.pos = -1; - try{ this.row.obj.setAttribute("properties","");} - catch(ex){} - } - - var dragService = Components - .classes["@mozilla.org/widget/dragservice;1"]. - getService().QueryInterface(Components.interfaces.nsIDragService); - if (dragService) { - var dragSession = dragService.getCurrentSession(); - if (dragSession) - dragSession.canDrop = false; - } - }; - - this.drop = function(evt) - { - evt.stopPropagation(); - try{ - netscape.security.PrivilegeManager. - enablePrivilege("UniversalXPConnect"); - } - catch(ex){return} // drag and drop not allowed - - if (this.row.obj) { - try{ this.row.obj.setAttribute("properties","");} - catch(ex){} - } - - this.dropAction(this.row.pos); - }; -}; diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/xul/scripts/dvd.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/xul/scripts/dvd.js --- deejayd-0.7.2/data/htdocs/xul/scripts/dvd.js 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/xul/scripts/dvd.js 1970-01-01 01:00:00.000000000 +0100 @@ -1,52 +0,0 @@ -// webradio.js - -var dvd_ref; - -var Dvd = function() -{ - dvd_ref = this; - this.ref = "dvd_ref"; - - this.module = "dvd"; - this.tree = $("dvd-tree"); - // Activate this mode - $("dvd-source").disabled = false; - - this.init = function() { }; - - this.customUpdate = function(webradio) - { - $("dvdinfo-box").setAttribute("datasources",window.location.href+"rdf/"+ - this.module+"-"+this.treeId+".rdf"); - return true; - }; - - /**************************************************************/ - // Event handler - /**************************************************************/ - this.play = function() - { - if (this.tree.contentView) { - var item = this.tree.contentView.getItemAtIndex( - this.tree.currentIndex); - var str = item.id; - str = str.split("/") - - var id_value = str[3]; - if (str.length > 4) - id_value += "."+str[4] - - ajaxdj_ref.send_command("goto",{id:id_value,id_type:"dvd_id"},true); - } - return false; - }; - - this.update_langinfo = function() - { - } -}; - -// heritage by prototype -Dvd.prototype = new CommonTreeManagement; - -// vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/xul/scripts/fileList.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/xul/scripts/fileList.js --- deejayd-0.7.2/data/htdocs/xul/scripts/fileList.js 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/xul/scripts/fileList.js 1970-01-01 01:00:00.000000000 +0100 @@ -1,263 +0,0 @@ -// fileList.js - -var fileList_ref; - -var FileList = function() -{ - fileList_ref = this; - this.menuId = "navigation-path"; - this.menuCommand = "getdir"; - this.contextMenuId = "fileList-menu"; - this.dragItemType = null; - this.playlistList = new Array(); - this.updateBox = "audio-update"; - this.listType = "audio"; - - this.init = function() - { - try{ - netscape.security.PrivilegeManager. - enablePrivilege("UniversalXPConnect"); - } - catch(ex){return} - var fileList = $('file-content'); - var fileListController = { - supportsCommand : function(cmd){ return (cmd == "cmd_selectAll"); }, - isCommandEnabled : function(cmd){return true; }, - doCommand : function(cmd) { slAll('file-content'); }, - onEvent : function(evt){ } - }; - - var playlistList = $('playlistList-content'); - var playlistListController = { - supportsCommand : function(cmd){ - return (cmd == "cmd_selectAll" || cmd == "cmd_delete"); }, - isCommandEnabled : function(cmd){return true;}, - doCommand : function(cmd){ - if (cmd == "cmd_selectAll") - slAll('playlistList-content'); - else if (cmd == "cmd_delete") - fileList_ref.erasePlaylist(); - }, - onEvent : function(evt){ } - }; - - playlistList.controllers.appendController(playlistListController); - fileList.controllers.appendController(fileListController); - }; - - this.getDir = function(e) - { - var args = {}; - if (this.id !='root_link') - args['dir'] = this.id; - ajaxdj_ref.send_post_command("getdir",args); - }; - - this.updateFileList = function(fileList,dir) - { - this.curDir = dir; - this.constructMenu(dir); - this.constructItemList(fileList,"file-content"); - }; - - this.loadCommand = function() - { - if ($('file-content').selectedCount > 0) - this.loadFiles(-1); - else if ($('playlistList-content').selectedCount > 0) - this.loadPlaylist(-1); - } - - this.loadItems = function(pos) - { - if (this.dragItemType == "playlist") - this.loadPlaylist(pos); - else - this.loadFiles(pos); - }; - - this.loadItemsInQueue = function(pos) - { - if (this.dragItemType == "playlist") - this.loadPlaylistInQueue(pos); - else - this.loadFilesInQueue(pos); - }; - - this.loadFiles = function(pos) - { - this.dragItemType = null; - ajaxdj_ref.send_post_command('playlistAdd&pos='+pos, - {path: this.getSelectedItems("file-content")}); - }; - - this.loadFilesInQueue = function(pos) - { - this.dragItemType = null; - ajaxdj_ref.send_post_command('queueAdd&pos='+pos, - {path: this.getSelectedItems("file-content")}); - }; - - this.addToPlaylist = function(name) - { - ajaxdj_ref.send_post_command('playlistAdd&name='+urlencode(name), - {path: this.getSelectedItems("file-content")}); - }; - - /*************************************/ - /******** Playlist Part ****************/ - /*************************************/ - this.updatePlaylistList = function(playlistList) - { - var Items = playlistList.getElementsByTagName("item"); - this.playlistList = new Array(); - for (var i=0;item=Items[i];i++) - this.playlistList.push(item.firstChild.data); - - // Update fileList Menu - var menu = $('fileaddplaylist-menu'); - while(menu.hasChildNodes()) - menu.removeChild(menu.firstChild); - this.contructPlsListMenu(menu,true); - - // Update playlist-save Menu - var menu = $('playlist-save-plslist'); - while(menu.hasChildNodes()) - menu.removeChild(menu.firstChild); - this.contructPlsListMenu(menu,false); - - this.constructItemList(playlistList,"playlistList-content"); - }; - - this.loadPlaylist = function(pos) - { - this.dragItemType = null; - ajaxdj_ref.send_post_command('playlistLoad&pos='+pos, - {pls_name: this.getSelectedItems("playlistList-content")}); - }; - - this.loadPlaylistInQueue = function(pos) - { - this.dragItemType = null; - ajaxdj_ref.send_post_command('queueLoad&pos='+pos, - {pls_name: this.getSelectedItems("playlistList-content")}); - }; - - this.erasePlaylist = function() - { - var rs = window.confirm(ajaxdj_ref.getString("confirm")); - - if (rs) - ajaxdj_ref.send_post_command('playlistErase', - {name: this.getSelectedItems("playlistList-content")}); - }; - - /*************************************/ - /******** Search Part ****************/ - /*************************************/ - this.searchFile = function() - { - var text = $('search-text').value; - var typ = $('search-type').selectedItem.value; - ajaxdj_ref.send_post_command("search",{type:typ,txt:text}); - }; - - this.searchClear = function() - { - $('search-text').value = ""; - ajaxdj_ref.send_post_command('getdir',{dir:this.curDir}); - }; - - -/*********************************************************/ -/************* INTERNAL FUNCTIONS ***********************/ -/*********************************************************/ - this.contructPlsListMenu = function(menu,cmd) - { - for(var i in this.playlistList) { - var item = document.createElement("menuitem"); - item.setAttribute("label",this.playlistList[i]); - if (cmd) { - item.setAttribute("oncommand", - "fileList_ref.addToPlaylist('"+this.playlistList[i]+"');"); - } - menu.appendChild(item); - } - }; - - this.customConstructDirRow = function(dirItem) - { - dirItem.ondblclick = function(e) { - ajaxdj_ref.send_post_command("getdir", - { dir:e.target.value }); }; - dirItem.addEventListener('draggesture', FileObserver.dragStart, true); - }; - - this.constructFileRow = function(file) - { - var fileName = file.firstChild.data; - var fileItem = document.createElement("listitem"); - fileItem.setAttribute("label",fileName); - fileItem.setAttribute("context","fileList-menu"); - fileItem.className = "audio-item listitem-iconic"; - var path = file.getAttribute("path"); - fileItem.setAttribute("value",path); - fileItem.setAttribute("type","audio-file"); - fileItem.addEventListener('draggesture', FileObserver.dragStart, true); - - return fileItem; - }; - - this.constructPlaylistRow = function(playlist) - { - var plsItem = document.createElement("listitem"); - plsItem.setAttribute("label",playlist.firstChild.data); - plsItem.setAttribute("context","playlistList-menu"); - plsItem.setAttribute("value",playlist.firstChild.data); - plsItem.setAttribute("type","playlist"); - plsItem.className = "playlist-item listitem-iconic"; - plsItem.addEventListener('draggesture', FileObserver.dragStart, true); - - return plsItem; - }; -}; - -// heritage by prototype -FileList.prototype = new CommonList; - - -var FileObserver = { - dragStart: function (evt) - { - evt.stopPropagation(); - - try{ - netscape.security.PrivilegeManager. - enablePrivilege("UniversalXPConnect"); - } - catch(ex){return} - - fileList_ref.dragItemType = evt.target.getAttribute("type"); - var plainText=evt.target.value; - - var ds = Components.classes["@mozilla.org/widget/dragservice;1"]. - getService(Components.interfaces.nsIDragService); - var trans = Components.classes["@mozilla.org/widget/transferable;1"]. - createInstance(Components.interfaces.nsITransferable); - trans.addDataFlavor("text/plain"); - var textWrapper = Components.classes["@mozilla.org/supports-string;1"]. - createInstance(Components.interfaces.nsISupportsString); - textWrapper.data = plainText; - trans.setTransferData("text/plain",textWrapper,textWrapper.data.length); - // create an array for our drag items, though we only have one this time - var transArray = Components.classes["@mozilla.org/supports-array;1"]. - createInstance(Components.interfaces.nsISupportsArray); - transArray.AppendElement(trans); - // Actually start dragging - ds.invokeDragSession(evt.target, transArray, null, - ds.DRAGDROP_ACTION_COPY + ds.DRAGDROP_ACTION_MOVE); - } - - }; -// vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/xul/scripts/player.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/xul/scripts/player.js --- deejayd-0.7.2/data/htdocs/xul/scripts/player.js 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/data/htdocs/xul/scripts/player.js 1970-01-01 01:00:00.000000000 +0100 @@ -1,321 +0,0 @@ -// player.js - -var playerStatus = { - volume: "", - update_volume: function() { - $('player-volume').updateVolume(this.volume); - $('volume-image').src = "./static/themes/default/images/volume-max.png"; - }, - - random: "", - update_random: function() { - $("random-button").checked = this.random == "1" ? true : false; - }, - - qrandom: "", - update_qrandom: function() { - $("qrandom-button").checked = this.qrandom == "1" ? true : false; - }, - - repeat: "", - update_repeat: function() { - $("repeat-button").checked = this.repeat == "1" ? true : false; - }, - - state: "", - update_state: function() { - $("playtoggle-button").className = this.state == "play" ? - "pause-button" : "play-button"; - }, - - /********************************************************************/ - /********************************************************************/ - time: "", - media_length:"", - update_time: function(t) { - this.time = t; - var seekbarValue = 0; - var time = this.time.split(":"); - this.media_length = time[1]; - value = formatTime(time[0]); - seekbarValue = (time[0] / time[1]) * 100; - $('seekbar-button').label = value + " -->"; - $('player-seekbar').updateSeekbar(seekbarValue); - }, - - init_mode: function() { - this.modes = {}; - this.modes["playlist"] = playlist_ref; - this.modes["webradio"] = webradio_ref; - this.modes["video"] = videolist_ref; - this.modes["dvd"] = dvd_ref; - }, - current: "", - current_mode: null, - current_in_queue: false, - reset_current: function() { - var media_info = $("media-info"); - while (media_info.hasChildNodes()) - removeNode(media_info.firstChild); - // hide option block - var rows = Array("audio-row", "subtitle-row", "av_offset-row", - "sub_offset-row", "player-seekbar", "current-media", - "playeroption-button"); - for (ix in rows) { - $(rows[ix]).style.visibility = "collapse"; - } - - $("queue-status-button").className = "stop-button"; - this.current_in_queue = false; - if (this.current_mode) { - this.modes[this.current_mode].resetPlaying(); - this.current_mode = null; - } - }, - __update_current_state: function() { - var current = this.current.split(":"); - if (current[2] == "queue") { - $("queue-status-button").className = this.state+"-button"; - this.current_in_queue = true; - } - else { - this.modes[current[2]].setPlaying(current[0],current[1],this.state); - this.current_mode = current[2]; - } - }, - update_current: function(cur_song) { - this.reset_current(); - this.__update_current_state(); - - switch(cur_song.getAttribute("type")) { - case "song": - $("playeroption-button").style.visibility = "collapse"; - // title - this.__build_title(cur_song); - // Artist - var artist = cur_song.getElementsByTagName("artist").item(0); - if (artist && artist.firstChild) - this.__build_label_item("artist",artist.firstChild.data); - // Album - var album = cur_song.getElementsByTagName("album").item(0); - if (album && album.firstChild) - this.__build_label_item("album",album.firstChild.data); - break; - - case "webradio": - $("playeroption-button").style.visibility = "collapse"; - // title - var title = cur_song.getElementsByTagName("title").item(0). - firstChild.data; - var song = cur_song.getElementsByTagName("song-title").item(0); - if (song && song.firstChild) - title += " : " + song.firstChild.data; - this.__build_label_item("title", title); - // Url - var url = cur_song.getElementsByTagName("url").item(0); - if (url) - this.__build_label_item("url",url.firstChild.data); - break; - - case "video": - // title - this.__build_title(cur_song); - - var a = Array("audio","subtitle"); - for (ix in a) { - var obj = cur_song.getElementsByTagName(a[ix]).item(0); - var cur_ch = cur_song.getElementsByTagName(a[ix]+ - "_idx").item(0); - if (obj && cur_ch) { - this.__build_menu(a[ix],obj,cur_ch.firstChild.data); - } - } - - a = Array("av_offset","sub_offset"); - for (ix in a) { - var value = cur_song.getElementsByTagName(a[ix]).item(0); - if (value && value.firstChild) { - $(a[ix]+"-row").style.visibility = "visible"; - $(a[ix]+"-value").value = value.firstChild.data; - } - } - $("playeroption-button").style.visibility = "visible"; - break; - } - $("current-media").style.visibility = "visible"; - - }, - update_current_option: function(current_obj) { - if (this.current_in_queue) - $("queue-status-button").className = this.state+"-button"; - else if (this.current_mode) - this.modes[this.current_mode].updatePlaying(this.state); - }, - - // - // Internal Functions - // - __build_menu: function(type, obj, current_channel) { - var menu = $(type+"-menu"); - // first clear old menu - while (menu.hasChildNodes()) - removeNode(menu.firstChild); - - var current_item = null; - var items = obj.getElementsByTagName("dictparm"); - for(var i = 0;item = items[i];i++) { - var it = document.createElement("menuitem"); - it.setAttribute("label",item.getAttribute('lang')); - it.setAttribute("value",item.getAttribute('ix')); - menu.appendChild(it); - - if (item.getAttribute('ix') == current_channel) - current_item = it; - } - - if (current_item) - menu.parentNode.selectedItem = current_item; - $(type+"-row").style.visibility = "visible"; - }, - - __build_title: function(current_song) { - var title = current_song.getElementsByTagName("title").item(0); - if (!title || !title.firstChild) - title = current_song.getElementsByTagName("filename").item(0); - title = title.firstChild.data; - - var length = current_song.getElementsByTagName("length").item(0); - if (length) - title += " (" + formatTime(length.firstChild.data) + ")"; - - this.__build_label_item("title",title); - }, - - __build_label_item: function(type,val) { - var desc = document.createElement("description"); - desc.id = "current-"+type; - desc.className = "cursong"; - desc.appendChild(document.createTextNode(val)); - $("media-info").appendChild(desc); - }, - }; - - -var player_ref; -var Player = function() -{ - player_ref = this; - - this.updatePlayerInfo = function(playerObj) - { - // extract status infos - parm_objs = playerObj.getElementsByTagName("parm"); - obj = ""; - st_obj = new Object(); - for(var i=0; obj = parm_objs.item(i); i++) - st_obj[obj.getAttribute("key")] = obj.getAttribute("value"); - - var list = Array("state","volume","random","qrandom","repeat"); - for (var i in list) { - var key = list[i]; - if (st_obj[key] != playerStatus[key]) { - playerStatus[key] = st_obj[key]; - playerStatus["update_"+key](); - } - } - var time = "time" in st_obj ? st_obj["time"] : "0:0"; - playerStatus["update_time"](time); - - // update current song - if ("current" in st_obj) { - var cur_song = playerObj.getElementsByTagName("cursong").item(0); - if (st_obj["current"] != playerStatus.current) { - playerStatus.current = st_obj["current"]; - playerStatus.update_current(cur_song); - } - else { playerStatus.update_current_option(cur_song); } - } - else if (playerStatus.current != "") { - playerStatus.reset_current(); - playerStatus.current = ""; - } - }; - - this.goToCurSong = function() - { - if (playerStatus.current != "") { - var cur = playerStatus.current.split(":"); - var tree = $("playlist-tree"); - var boxobject = tree.treeBoxObject; - boxobject.ensureRowIsVisible(cur[0]); - } - }; - - this.set_alang = function(idx) - { - ajaxdj_ref.send_command('setPlayerOption',{option_name:"audio_lang", - option_value: idx},true); - } - - this.set_slang = function(idx) - { - ajaxdj_ref.send_command('setPlayerOption',{option_name:"sub_lang", - option_value: idx},true); - } - - this.set_avoffset = function() - { - ajaxdj_ref.send_command('setPlayerOption',{option_name:"av_offset", - option_value: $("av_offset-value").value},true); - } - - this.set_suboffset = function() - { - ajaxdj_ref.send_command('setPlayerOption',{option_name:"sub_offset", - option_value: $("sub_offset-value").value},true); - } -}; - - -var PlayerObserver = -{ - volume_timeout: null, - - onTrackVolume: function(obj) - { - if (this.volume_timeout) - clearTimeout(this.volume_timeout); - - obj.trackingVolume = true; - this.volume_timeout = setTimeout( - "ajaxdj_ref.send_command('setVol',{volume:"+obj.value+ - "},true)", 200); - }, - - onReleaseVolume: function(obj) - { - if (this.volume_timeout) - clearTimeout(this.volume_timeout); - - obj.trackingVolume = false; - ajaxdj_ref.send_command('setVol',{volume: obj.value},true); - }, - - onTrackSeekbar: function(obj) - { - obj.trackingPosition = true; - - var timeVal = parseInt( (Number(obj.value)* - Number(playerStatus.media_length)) / 100 ); - $('seekbar-button').label = formatTime(timeVal) + " -->"; - }, - - onReleaseSeekbar: function(obj) - { - obj.trackingPosition = false; - var timeVal = parseInt( (Number(obj.value)* - Number(playerStatus.media_length)) / 100 ); - ajaxdj_ref.send_command('setTime',{time: timeVal},true); - } - -}; diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/xul/scripts/playlist.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/xul/scripts/playlist.js --- deejayd-0.7.2/data/htdocs/xul/scripts/playlist.js 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/xul/scripts/playlist.js 1970-01-01 01:00:00.000000000 +0100 @@ -1,91 +0,0 @@ -// playlist.js - -var playlist_ref; - -var Playlist = function() -{ - playlist_ref = this; - this.ref = "playlist_ref"; - - this.module = "playlist"; - this.tree = $("playlist-tree"); - this.canDrop = true; - // Activate this mode - $("playlist-source").disabled = false; - - this.treeController = { - supportsCommand : function(cmd){ - return (cmd == "cmd_delete"); }, - isCommandEnabled : function(cmd){return true;}, - doCommand : function(cmd){ - if (cmd == "cmd_delete") - playlist_ref.removeItems(); - }, - onEvent : function(evt){ } - }; - - this.customUpdate = function(playlist) - { - // Update Playlist Description - $("playlist-description").value = - playlist.getAttribute("description"); - var saveDisabled = playlist.getAttribute("length") == "0" ? - true : false; - $("playlist-savebtn").disabled = saveDisabled; - }; - - this.togglePlaylistForm = function(print) - { - var plsForm = $('playlist-save-form'); - if (print == 1) { - plsForm.style.display = "block"; - $('playlist-save-btn').selectedIndex = 1; - $('playlist-save-list').focus(); - } - else if (print == 0) { - plsForm.style.display = "none"; - $('playlist-save-btn').selectedIndex = 0; - $('playlist-save-list').value = ""; - } - }; - - this.savePlaylist = function() - { - var enable = true; - var plsName = $('playlist-save-list').value; - - for (id in fileList_ref.playlistList) { - if (plsName == fileList_ref.playlistList[id]) { - enable = window.confirm(ajaxdj_ref.getString("replacePls")); - break; - } - } - - if (enable) { - ajaxdj_ref.send_post_command("playlistSave",{name:plsName}); - this.togglePlaylistForm(0); - } - }; - - /********************************************************************/ - // custom drag and drop actions - /********************************************************************/ - this.dropAction = function(pos) - { - if (this.mediaDragged) { - // move song at the new position - var s_ids = this.getTreeSelection(); - ajaxdj_ref.send_post_command("playlistMove", - {ids:s_ids, new_pos:pos}); - } - else - fileList_ref.loadItems(pos); - this.mediaDragged = false; - fileList_ref.dragItemType = null; - }; -}; - -// heritage by prototype -Playlist.prototype = new CommonTreeManagement; - -// vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/xul/scripts/queue.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/xul/scripts/queue.js --- deejayd-0.7.2/data/htdocs/xul/scripts/queue.js 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/xul/scripts/queue.js 1970-01-01 01:00:00.000000000 +0100 @@ -1,66 +0,0 @@ -// queue.js - -var queue_ref; - -var Queue = function() -{ - queue_ref = this; - this.module = "queue"; - this.tree = $("queue-tree"); - - var queueController = { - supportsCommand : function(cmd){ - return (cmd == "cmd_delete"); }, - isCommandEnabled : function(cmd){ return true; }, - doCommand : function(cmd){ - if (cmd == "cmd_delete") - queue_ref.remove(); - }, - onEvent : function(evt){ } - }; - - this.customUpdate = function(queue) - { - $("queue-description").value = queue.getAttribute("description"); - return true; - }; - - this.dropAction = function(pos) - { - fileList_ref.loadItemsInQueue(pos); - fileList_ref.dragItemType = null; - }; - - this.toogleQueue = function() - { - var currentState = $('queue-splitter').getAttribute("state"); - if (currentState == "collapsed") { - $('queue-splitter').setAttribute("state","open"); - $('queue-splitter').style.visibility = "visible"; - $('queue-button').className = "expanded"; - $('queue-actions').style.visibility = "visible"; - } - else { - $('queue-splitter').setAttribute("state","collapsed"); - $('queue-button').className = "collapsed"; - $('queue-actions').style.visibility = "hidden"; - $('queue-splitter').style.visibility = "collapse"; - } - }; - - this.play = function() - { - if (this.tree.contentView) { - var item = this.tree.contentView.getItemAtIndex( - this.tree.currentIndex); - var str = item.id; - ajaxdj_ref.send_command("goto",{ source:"queue", - id:str.split("/")[3] },true); - } - } -}; - -// heritage by prototype -Queue.prototype = new CommonTreeManagement; - -// vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/xul/scripts/video.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/xul/scripts/video.js --- deejayd-0.7.2/data/htdocs/xul/scripts/video.js 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/xul/scripts/video.js 1970-01-01 01:00:00.000000000 +0100 @@ -1,92 +0,0 @@ -// fileList.js - -var videolist_ref; -var videolib_ref; - -var VideoLibrary = function() -{ - videolib_ref = this; - this.tree = $("videodir-tree"); - this.updateBox = "video-update"; - this.treeId = -1 - - this.updateDir = function(obj) - { - var id = parseInt(obj.getAttribute("id")); - if (id != this.treeId) { - this.treeId = id; - // clear selection - if (this.tree.view) - this.tree.view.selection.clearSelection(); - // Update datasources - this.tree.setAttribute("datasources",window.location.href+"rdf/"+ - "videodir-"+id+".rdf"); - } - }; - - this.setDirectory = function(evt) - { - var childElement = {}, rowObject = {}, columnObject = {}; - this.tree.treeBoxObject.getCellAt(evt.clientX, evt.clientY, rowObject, - columnObject, childElement); - - if (columnObject.value && rowObject.value != -1) { - if (columnObject.value.index != 1) - return true; - var dir_item = this.tree.contentView.getItemAtIndex( - rowObject.value); - var dir = dir_item.id.replace("http://videodir/root/", ""); - ajaxdj_ref.send_post_command("videoset", - {type:"directory", value:dir}); - } - return true; - }; - - this.search = function() - { - var text = $('videosearch-text').value; - if (text != "") - ajaxdj_ref.send_post_command("videoset",{type:"search",value:text}); - }; - - this.updateDatabase = function(upObj) - { - var progress = upObj.getAttribute("p"); - var upId = upObj.firstChild.data; - if (progress == "1") { - $(this.updateBox).selectedIndex = 1 - $('video-update-progressbar').mode = "undetermined"; - setTimeout( - "ajaxdj_ref.send_command('video_update_check',{id:"+upId+ - "},false)",1000); - } - else { - $(this.updateBox).selectedIndex = 0 - $('video-update-progressbar').mode = "determined"; - } - }; -} - - -var VideoList = function() -{ - videolist_ref = this; - this.ref = "videolist_ref"; - - this.mediaDragged = true; - this.module = "video"; - this.tree = $("video-tree"); - // Activate this mode - $("video-source").disabled = false; - - this.treeController = false; - this.customUpdate = function(video) - { - $("videolist-description").value = video.getAttribute("description"); - return true; - }; -}; -// heritage by prototype -VideoList.prototype = new CommonTreeManagement; - -// vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/data/htdocs/xul/scripts/webradio.js /tmp/LOo8qwLBV9/deejayd-0.9.0/data/htdocs/xul/scripts/webradio.js --- deejayd-0.7.2/data/htdocs/xul/scripts/webradio.js 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/data/htdocs/xul/scripts/webradio.js 1970-01-01 01:00:00.000000000 +0100 @@ -1,54 +0,0 @@ -// webradio.js - -var webradio_ref; - -var Webradio = function() -{ - webradio_ref = this; - this.ref = "webradio_ref"; - - this.module = "webradio"; - this.tree = $("webradio-tree"); - // Activate this mode - $("webradio-source").disabled = false; - - this.treeController = { - supportsCommand : function(cmd){ - return (cmd == "cmd_delete"); }, - isCommandEnabled : function(cmd){return true;}, - doCommand : function(cmd){ - if (cmd == "cmd_delete") - webradio_ref.removeItems(); - }, - onEvent : function(evt){ } - }; - - this.customUpdate = function(webradio) - { - $("webradio-description").value = - webradio.getAttribute("description"); - return true; - }; - - this.add = function() - { - var nameParm = $('webradio-name').value; - var urlParm = $('webradio-url').value; - - if (!nameParm || !urlParm) { - alert(ajaxdj_ref.getString('missParm')); - return; - } - ajaxdj_ref.send_post_command('webradioAdd', - {name: nameParm, url: urlParm},true); - - // Clear form - $('webradio-name').value = ""; - $('webradio-url').value = ""; - }; -}; - -// heritage by prototype -Webradio.prototype = new CommonTreeManagement; - -// vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/50deejayd_steal-session /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/50deejayd_steal-session --- deejayd-0.7.2/debian/50deejayd_steal-session 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/debian/50deejayd_steal-session 2009-11-15 05:48:11.000000000 +0000 @@ -0,0 +1,35 @@ +# This file is sourced by Xsession(5), not executed. + +# Steal X session for the deejayd user if that is configured to do so + +if [ -r /etc/default/deejayd ]; then + . /etc/default/deejayd + if [ "$DEEJAYD_XSERVER_METHOD" = "reuse" ]; then + # If we have an auth cookie matching the Deejayd configured DISPLAY, + # add it to the Deejayd auth cookie file. Also cleanup old auth cookies + # matching the same display. + DEEJAYD_DISPLAYNAME=${DEEJAYD_DISPLAYNAME:=:0.0} + if [ ! -z "$XAUTHORITY" ] && [ -r "$XAUTHORITY" ]; then + authcookie=`xauth list "$DEEJAYD_DISPLAYNAME" 2> /dev/null\ + | sed -n "s/.*$DEEJAYD_DISPLAYNAME[[:space:]*].*[[:space:]*]//p"` 2>/dev/null; + if [ -r "$DEEJAYD_XAUTHORITY" ]; then + # To change the $DEEJAYD_XAUTHORITY file, we must use a + # temporary file because xauth uses one of its own in the + # same directory, and we do not have write permission in this + # directory. + tmpauthfile=`mktemp -t deejayd-reuse.XXXXXX` + cp $DEEJAYD_XAUTHORITY $tmpauthfile + xauth -f $tmpauthfile remove $DEEJAYD_DISPLAYNAME + if [ "z${authcookie}" != "z" ]; then + xauth -f $tmpauthfile << EOF +add $DEEJAYD_DISPLAYNAME . $authcookie +EOF + fi + cp $tmpauthfile $DEEJAYD_XAUTHORITY + rm $tmpauthfile + fi + fi + fi +fi + +# vim:set ai et sts=2 sw=2 tw=80: diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/changelog /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/changelog --- deejayd-0.7.2/debian/changelog 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/debian/changelog 2009-11-15 05:48:11.000000000 +0000 @@ -1,9 +1,74 @@ -deejayd (0.7.2-2ubuntu1) karmic; urgency=low +deejayd (0.9.0-2ubuntu1) lucid; urgency=low - * Pass --install-layout=deb to setup.py install. - * Replace site-packages with *-packages in deejayd*.install + * Build test. - -- Michael Bienia Sun, 13 Sep 2009 16:11:36 +0200 + -- Bhavani Shankar Sun, 15 Nov 2009 11:12:12 +0530 + +deejayd (0.9.0-2) unstable; urgency=low + + * Xorg related scripts : fix auth files created in / by passing -t to mktemp. + * Fix another bashism in /etc/init.d/deejayd. + * Really fix build with python2.6 by applying patch from Kumar Appaiah + (Closes: #547815). + + -- Alexandre Rossi Tue, 13 Oct 2009 20:48:47 +0200 + +deejayd (0.9.0-1) unstable; urgency=low + + * New upstream version. + + -- Alexandre Rossi Tue, 22 Sep 2009 07:58:33 +0200 + +deejayd (0.8.3-2) unstable; urgency=low + + * Xorg connection scripts : + - do not force mktemp to use /tmp. + - fix possible bashisms in related scripts. + - ensure required variables have default values. + - change [ -r $filepath ] to [ -r "$filepath" ] in scripts to ensure proper + behaviour. + - when using the provided Xorg initscript, ensure the X server is ready to + accept connections before trying to launch the X clients. + * Update to policy 3.8.3 (nothing to do). + + -- Alexandre Rossi Wed, 26 Aug 2009 22:05:34 +0200 + +deejayd (0.8.3-1) UNRELEASED; urgency=low + + * New upstream release ensuring compatility with Firefox/Iceweasel >= 3.5 . + * Improve how Deejayd can connect to Xorg, see README.Debian and comments + in /etc/default/deejayd . + * Update to policy 3.8.2 (nothing to do). + + -- Alexandre Rossi Mon, 06 Jul 2009 07:25:10 +0200 + +deejayd (0.8.2-1) UNRELEASED; urgency=low + + * New upstream version fixing playback of albums lacking album art. + + -- Alexandre Rossi Sun, 24 May 2009 10:30:52 +0200 + +deejayd (0.8.1-1) UNRELEASED; urgency=low + + * New upstream version. + * Add new deejayd-webui-extension binary package. + * Remove the djc binary package and include the djc script in the + deejayd-client binary package. + * Improve debian/rules clean target by making it remove all .pyc files. + * Bump standards version to 3.8.1 : + - Ensure the start action of the init script does not start the daemon + again. + * Fixed default file (/etc/default/deejayd). + * Do not process the deejayd-webui-extension packages with dh_pycentral. + * Added NEWS to installed doc. + * Update Vcs-Browser field. + * Simplify clean rule thanks to upstream improvements (Closes: #535374). + * Add upstream tarball embbedded JQuery copy licence information to + debian/copyright. + * Fix build with Python 2.6 as distutils file locations change from + site-packages to dist-packages, for public modules (LP: #351626). + + -- Alexandre Rossi Thu, 21 May 2009 15:09:11 +0200 deejayd (0.7.2-2) unstable; urgency=low diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/control /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/control --- deejayd-0.7.2/debian/control 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/debian/control 2009-11-15 05:48:11.000000000 +0000 @@ -1,15 +1,14 @@ Source: deejayd Section: sound Priority: extra -Maintainer: Ubuntu Developers -XSBC-Original-Maintainer: Alexandre Rossi +Maintainer: Alexandre Rossi Build-Depends: debhelper (>= 5.0.38), python Build-Depends-Indep: python-central (>= 0.5.6), gettext, xsltproc, docbook-xsl -Standards-Version: 3.8.0 +Standards-Version: 3.8.3 XS-Python-Version: current, >= 2.4 Homepage: http://mroy31.dyndns.org/~roy/projects/deejayd Vcs-Darcs: http://mroy31.dyndns.org/~roy/repository/deejayd -Vcs-Browser: http://mroy31.dyndns.org/~roy/darcsweb/darcsweb.cgi?r=deejayd;a=tree;f=/src +Vcs-Browser: http://mroy31.dyndns.org/~roy/darcsweb/darcsweb.cgi?r=deejayd;a=tree Package: deejayd Architecture: all @@ -18,9 +17,10 @@ deejayd-xine (= ${binary:Version}) | deejayd-gstreamer (= ${binary:Version}), python-twisted, python-pysqlite2 | python-mysqldb, - python-mutagen + python-lxml, python-mutagen, python-kaa-metadata (>= 0.7.3) Recommends: python-pyinotify (>= 0.6.0) -Suggests: deejayd-webui, djc +Suggests: deejayd-webui, xserver-xorg, xauth, x11-utils +Breaks: deejayd-client (<< 0.8.4) XB-Python-Version: ${python:Versions} Description: Network controllable media player daemon Deejayd is a multi purpose media player that can be completely controlled @@ -31,35 +31,34 @@ Package: deejayd-webui Architecture: all -Depends: ${python:Depends}, deejayd (= ${binary:Version}), logrotate, python-twisted-web +Depends: ${python:Depends}, deejayd (= ${binary:Version}), logrotate, + python-twisted-web, libjs-jquery XB-Python-Version: ${python:Versions} Description: Web interface for deejayd - This package provides a XUL web interface to control your deejayd server. The - required webserver is embedded in the deejayd daemon. + This package provides, in order to control your deejayd server: + - a XUL web interface and, + - a pure HTML and AJAX web interface optimized for small screens. + . + The required webserver is embedded in the deejayd daemon. Package: deejayd-client Architecture: all -Depends: ${python:Depends}, python-celementtree | python (>= 2.5) +Depends: ${python:Depends}, python-simplejson | python (>= 2.6) +Conflicts: djc XB-Python-Version: ${python:Versions} -Description: Client library to access the deejayd server +Description: Client library and command line tool to access the deejayd server This package provides easy to use classes to manipulate the deejayd server, abstracting XML message processing and network operations. It fully implements the set of features exploitable from the server. - -Package: djc -Architecture: all -Depends: ${python:Depends}, deejayd-client (= ${binary:Version}) -XB-Python-Version: ${python:Versions} -Description: Command line basic client to the deejayd server - djc provides a subset of deejayd commands for your deejayd server to be - reachable from the command line. + . + It also includes djc, which provides a subset of deejayd commands for your + deejayd server to be reachable from the command line. Package: deejayd-xine Architecture: all Depends: ${python:Depends}, python-ctypes (>= 1.0.0) | python (>= 2.5), libxine1, libx11-6, libxext6 -Recommends: lsdvd XB-Python-Version: ${python:Versions} Description: Deejayd XINE backend The deejayd media backend using the XINE library. @@ -71,3 +70,10 @@ XB-Python-Version: ${python:Versions} Description: Deejayd GStreamer backend The deejayd media backend using the GStreamer library. + +Package: deejayd-webui-extension +Architecture: all +Depends: iceweasel +Description: Deejayd web user interface Iceweasel extension + The Deejayd Iceweasel browser extension provides a richer user interface to + use as a client to the Deejayd server. diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/copyright /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/copyright --- deejayd-0.7.2/debian/copyright 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/debian/copyright 2009-11-15 05:48:11.000000000 +0000 @@ -9,7 +9,7 @@ Copyright: - Copyright (C) 2006-2008 Mickaël Royer + Copyright © 2006-2009 Mickaël Royer License: @@ -30,7 +30,7 @@ On Debian systems, the complete text of the GNU General Public License can be found in `/usr/share/common-licenses/GPL'. -The Debian packaging is (C) 2007-2008, +The Debian packaging is © 2007-2009, Alexandre Rossi and is licensed under the GPL, see above. @@ -38,3 +38,6 @@ the GPL v2. This consists of the files located under the gentoo/ directory. On Debian systems, the complete text of the GNU General Public License can be found in `/usr/share/common-licenses/GPL-2'. + +The upstream tarball contains an embedded copy of JQuery, which is © 2008 John +Resig (jquery.com), and is dual licensed under the MIT and GPL v2 licenses. diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/deejayd-client.install /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/deejayd-client.install --- deejayd-0.7.2/debian/deejayd-client.install 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/debian/deejayd-client.install 2009-11-15 05:48:11.000000000 +0000 @@ -1,5 +1,9 @@ usr/lib/python*/*-packages/deejayd/__init__.py usr/lib/python*/*-packages/deejayd/interfaces.py -usr/lib/python*/*-packages/deejayd/net/xmlbuilders.py +usr/lib/python*/*-packages/deejayd/mediafilters.py +usr/lib/python*/*-packages/deejayd/rpc/__init__.py +usr/lib/python*/*-packages/deejayd/rpc/jsonbuilders.py +usr/lib/python*/*-packages/deejayd/rpc/jsonparsers.py usr/lib/python*/*-packages/deejayd/net/client.py usr/lib/python*/*-packages/deejayd/net/__init__.py +usr/bin/djc diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/deejayd-client.manpages /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/deejayd-client.manpages --- deejayd-0.7.2/debian/deejayd-client.manpages 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/debian/deejayd-client.manpages 2009-11-15 05:48:11.000000000 +0000 @@ -0,0 +1 @@ +man/djc.1 diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/deejayd.deejayd-xserver.init /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/deejayd.deejayd-xserver.init --- deejayd-0.7.2/debian/deejayd.deejayd-xserver.init 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/debian/deejayd.deejayd-xserver.init 2009-11-15 05:48:11.000000000 +0000 @@ -0,0 +1,260 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: deejayd-xserver +# Required-Start: $local_fs $remote_fs +# Required-Stop: $local_fs $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Launch the deejayd dedicated X server. +# Description: Prepare environement and launch a minimalistic X session. +### END INIT INFO + +# Author: Alexandre Rossi + +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="Deejayd media player daemon dedicated X server" +NAME=deejayd-xserver +DAEMON=/usr/bin/X +DAEMON_USER=deejayd +HOME=/var/lib/$DAEMON_USER +PIDFILE=/var/run/$NAME.pid +AUTHFILE=/var/run/$NAME.authfile +XCLIENTS_PIDFILE=/var/run/$NAME-xclients.pid +LOGFILE=/var/log/$NAME.log +SCRIPTNAME=/etc/init.d/$NAME + + +# Exit if xorg is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/deejayd ] && . /etc/default/deejayd + +# Exit if this method of starting Xorg for deejayd is disabled +[ "$DEEJAYD_XSERVER_METHOD" = "standalone" ] || exit 0 + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.0-6) to ensure that this file is present. +. /lib/lsb/init-functions + +DEEJAYD_DISPLAYNAME=${DEEJAYD_DISPLAYNAME:=:0.0} +DEEJAYD_XAUTHORITY=${DEEJAYD_XAUTHORITY:=/var/lib/deejayd/Xauthority} + +XAUTHORITY=$DEEJAYD_XAUTHORITY +DISPLAY=$DEEJAYD_DISPLAYNAME +export DISPLAY XAUTHORITY + +# check for GNU hostname +if hostname --version > /dev/null 2>&1; then + if [ -z "`hostname --version 2>&1 | grep GNU`" ]; then + hostname=`hostname -f` + fi +fi +if [ -z "$hostname" ]; then + hostname=`hostname` +fi +authdisplay=${DEEJAYD_DISPLAYNAME:-:0} +authdisplaynet=$hostname$authdisplay + +# Generate a MIT MAGIC COOKIE for authentification with the X server. This +# is stolen from /usr/bin/startx . +gen_auth_cookie() +{ + # set up default Xauth info for this machine + + mcookie=`/usr/bin/mcookie` + + if test x"$mcookie" = x; then + echo "Couldn't create cookie" + exit 1 + fi + dummy=0 + + # create a file with auth information for the server. ':0' is a dummy. + xserverauthfile=`mktemp -t serverauth.XXXXXXXXXX` + + xauth -q -f $xserverauthfile << EOF +add :$dummy . $mcookie +EOF + echo $xserverauthfile > $AUTHFILE + + # now add the same credentials to the client authority file + # if '$displayname' already exists do not overwrite it as another + # server man need it. Add them to the '$xserverauthfile' instead. + for displayname in $authdisplay $authdisplaynet; do + authcookie=`xauth list "$displayname" 2> /dev/null\ + | sed -n "s/.*$displayname[[:space:]*].*[[:space:]*]//p"` 2>/dev/null; + if [ "z${authcookie}" = "z" ] ; then + xauth -q 2> /dev/null << EOF +add $displayname . $mcookie +EOF + else + dummy=$(($dummy+1)); + xauth -q -f $xserverauthfile 2> /dev/null << EOF +add :$dummy . $authcookie +EOF + fi + done + + for authfile in $DEEJAYD_XAUTHORITY $xserverauthfile; do + chgrp video $authfile && chmod g+r $authfile + done +} + +do_start() +{ + gen_auth_cookie + DAEMON_ARGS="$DEEJAYD_DISPLAYNAME $DEEJAYD_XORG_DAEMON_OPTS -auth $xserverauthfile" + # Return + # 0 if daemon has been started + # 2 if daemon could not be started + is_alive && return 0 + start-stop-daemon --start --pidfile $PIDFILE --make-pidfile\ + --exec $DAEMON\ + -b -c $DAEMON_USER -- $DAEMON_ARGS \ + || return 2 + + # Wait for the X server to accept X connections. + maxtries=40 + tries=0 + while [ "`xorg_ping`" != "pong" ] && [ $tries -lt $maxtries ] + do + tries=`expr $tries + 1` + sleep 0.5 + done +} + +do_start_xclients() +{ + # Start the X init script also as a daemon. + start-stop-daemon --start --pidfile $XCLIENTS_PIDFILE --make-pidfile\ + --exec /bin/sh\ + -b -c $DAEMON_USER -- $DEEJAYD_XINITRC\ + || return 2 +} + +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + # Xorg is suid so no user filter for start-stop-daemon + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + + rm -f $PIDFILE + + xauth remove $authdisplay $authdisplaynet + if [ -e $AUTHFILE ]; then + xserverauthfile=`cat $AUTHFILE` + rm -f $xserverauthfile + rm -f $AUTHFILE + fi + + return "$RETVAL" +} + +do_stop_xclients() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --user $DAEMON_USER --stop --quiet --retry=TERM/30/KILL/5 --pidfile $XCLIENTS_PIDFILE + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + + rm -f $XCLIENTS_PIDFILE + + return "$RETVAL" +} + +is_alive () { + ret=1 + if [ -r "$PIDFILE" ] ; then + pid=`cat $PIDFILE` + if [ -e /proc/$pid ] ; then + procname=`/bin/ps h -p $pid -C $NAME` + [ -n "$procname" ] && ret=0 + fi + fi + return $ret +} + +xorg_ping () { + # This function checks if a basic X client can probe the X server. + /usr/bin/xprop -root -display $authdisplay > /dev/null 2> /dev/null + case "$?" in + 0) echo "pong" && return 0;; + *) echo "fail" && return 1;; + esac +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + [ "$VERBOSE" != no ] && log_daemon_msg "xclients" + do_start_xclients + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop_xclients + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + [ "$VERBOSE" != no ] && log_daemon_msg "xclients" + ;; + restart|force-reload) + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop_xclients + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + do_start_xclients + ;; + status) + echo -n "Status of $DESC: " + if is_alive ; then + echo "alive." + else + echo "dead." + exit 1 + fi + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload|status}" >&2 + exit 3 + ;; +esac + +: +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/deejayd.default /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/deejayd.default --- deejayd-0.7.2/debian/deejayd.default 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/debian/deejayd.default 2009-11-15 05:48:11.000000000 +0000 @@ -0,0 +1,49 @@ +# Defaults for deejayd initscripts + +# Additional options that are passed to the Daemon. +DEEJAYD_DAEMON_OPTS="" + +# Because Deejayd supports video, it makes sense for it to connect to an X +# server, even when it runs as a daemon with the privileges of a dedicated +# user. The following methods are available : +# +# - "off" (default) +# Do not try to connect deejayd to a X server. +# +# - "standalone" +# A dedicated X server is launched for the deejayd user. +# +# Please note that in order to setup this mode, you : +# - can edit /etc/deejayd.xinitrc in order to setup X clients to hold +# the X session. +# - must allow users not logged into a console to start the X server. This +# is done using the following command : +# +# # dpkg-reconfigure x11-common +# +# Also note that in this mode, a X cookie is available for users that +# are in the 'video' group in $DEEJAYD_XAUTHORITY (see below for path). +# Therefore, accessing the Deejayd dedicated X server for other users +# than the deejayd user is just a matter of adding the following lines to +# your shell startup script (e.g. ~/.bashrc) : +# +# XAUTHORITY=/var/lib/deejayd/Xauthority +# export XAUTHORITY +# +# - "reuse" +# An existing X session is reused by making the X Cookie available to +# the Deejayd daemon at X session startup. This is done in +# /etc/X11/Xsession.d/50deejayd_steal-session and therefore your session +# needs to be restarted for this setting to be taken into account. +# +#DEEJAYD_XSERVER_METHOD=off +# +# "standalone" and "reuse" mode specific options +# +DEEJAYD_DISPLAYNAME=:0 +DEEJAYD_XAUTHORITY=/var/lib/deejayd/Xauthority +# +# "standalone" mode specific options +# +DEEJAYD_XINITRC=/etc/deejayd.xinitrc +DEEJAYD_XORG_DAEMON_OPTS="-nolisten tcp" diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/deejayd.defaults /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/deejayd.defaults --- deejayd-0.7.2/debian/deejayd.defaults 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/debian/deejayd.defaults 1970-01-01 01:00:00.000000000 +0100 @@ -1,4 +0,0 @@ -# Defaults for deejayd initscript - -# Additional options that are passed to the Daemon. -DAEMON_OPTS="" diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/deejayd.docs /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/deejayd.docs --- deejayd-0.7.2/debian/deejayd.docs 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/debian/deejayd.docs 2009-11-15 05:48:11.000000000 +0000 @@ -1 +1,2 @@ debian/tmp/usr/share/doc/deejayd/* +NEWS diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/deejayd.init /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/deejayd.init --- deejayd-0.7.2/debian/deejayd.init 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/debian/deejayd.init 2009-11-15 05:48:11.000000000 +0000 @@ -26,7 +26,6 @@ PIDFILE=/var/run/$NAME.pid LOGFILE=/var/log/$NAME.log WEBUI_LOGFILE=/var/log/$NAME-webui.log -DAEMON_ARGS="-u $DAEMON_USER -g audio,cdrom -p $PIDFILE -l $LOGFILE -w $WEBUI_LOGFILE $DAEMON_OPTS" SCRIPTNAME=/etc/init.d/$NAME @@ -36,6 +35,8 @@ # Read configuration variable file if it is present [ -r /etc/default/$NAME ] && . /etc/default/$NAME +DAEMON_ARGS="-u $DAEMON_USER -g audio,cdrom,video -p $PIDFILE -l $LOGFILE -w $WEBUI_LOGFILE $DEEJAYD_DAEMON_OPTS" + # Create logfiles if they do not exist for FILE in "$LOGFILE" "$WEBUI_LOGFILE" do @@ -46,6 +47,26 @@ fi done +# If X method is managed by Deejayd scripts (not "off"), create, if it does not +# exist, a dummy auth file with appropriate permissions: +# - for the user that starts X to be able to load auth cookies in it in +# "reuse" mode, +# - and for users in the 'video' group to be able to contact the X server +# in "standalone" mode. +if [ ! -z "$DEEJAYD_XSERVER_METHOD" ] &&\ + [ "$DEEJAYD_XSERVER_METHOD" != "off" ]; then + export XAUTHORITY=$DEEJAYD_XAUTHORITY + if [ ! -e "$DEEJAYD_XAUTHORITY" ]; then + # Just create a dummy auth file to make it have to appropriate file + # permissions. + touch $DEEJAYD_XAUTHORITY + chgrp video $DEEJAYD_XAUTHORITY && chmod 640 $DEEJAYD_XAUTHORITY + if [ "$DEEJAYD_XSERVER_METHOD" = "reuse" ]; then + chmod g+w $DEEJAYD_XAUTHORITY + fi + fi +fi + # Load the VERBOSE setting and other rcS variables . /lib/init/vars.sh @@ -53,67 +74,43 @@ # Depend on lsb-base (>= 3.0-6) to ensure that this file is present. . /lib/lsb/init-functions -# -# Function that starts the daemon/service -# do_start() { - # Return - # 0 if daemon has been started - # 1 if daemon was already running - # 2 if daemon could not be started - start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ - || return 1 - start-stop-daemon --start --pidfile $PIDFILE --exec $DAEMON -- \ - $DAEMON_ARGS \ - || return 2 - # Add code here, if necessary, that waits for the process to be ready - # to handle requests from services started subsequently which depend - # on this one. As a last resort, sleep for some time. + # Return + # 0 if daemon has been started + # 2 if daemon could not be started + is_alive && return 0 + start-stop-daemon --start --pidfile $PIDFILE --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 } -# -# Function that stops the daemon/service -# do_stop() { - # Return - # 0 if daemon has been stopped - # 1 if daemon was already stopped - # 2 if daemon could not be stopped - # other if a failure occurred - start-stop-daemon --user $DAEMON_USER --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME - RETVAL="$?" - [ "$RETVAL" = 2 ] && return 2 - # Wait for children to finish too if this is a daemon that forks - # and if the daemon is only ever run from this initscript. - # If the above conditions are not satisfied then add some other code - # that waits for the process to drop all resources that could be - # needed by services started subsequently. A last resort is to - # sleep for some time. - start-stop-daemon --user $DAEMON_USER --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON - [ "$?" = 2 ] && return 2 - # Many daemons don't delete their pidfiles when they exit. - rm -f $PIDFILE - return "$RETVAL" + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --user $DAEMON_USER --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + start-stop-daemon --user $DAEMON_USER --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + rm -f $PIDFILE + return "$RETVAL" } -# -# Function that sends a SIGHUP to the daemon/service -# do_reload() { - # - # If the daemon can reload its configuration without - # restarting (for example, when it is sent a SIGHUP), - # then implement that here. - # - start-stop-daemon --user $DAEMON_USER --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME - return 0 + # + # If the daemon can reload its configuration without + # restarting (for example, when it is sent a SIGHUP), + # then implement that here. + # + start-stop-daemon --user $DAEMON_USER --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME + return 0 } -# -# Function used to determine if the program is alive -# is_alive () { ret=1 if [ -r $PIDFILE ] ; then @@ -128,66 +125,59 @@ case "$1" in - start) - [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" - do_start - case "$?" in - 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; - esac - ;; - stop) - [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" - do_stop - case "$?" in - 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; - esac - ;; - reload|force-reload) - # - # If do_reload() is not implemented then leave this commented out - # and leave 'force-reload' as an alias for 'restart'. - # - log_daemon_msg "Reloading $DESC" "$NAME" - do_reload - log_end_msg $? - ;; - restart|force-reload) - # - # If the "reload" option is implemented then remove the - # 'force-reload' alias - # - log_daemon_msg "Restarting $DESC" "$NAME" - do_stop - case "$?" in - 0|1) - do_start - case "$?" in - 0) log_end_msg 0 ;; - 1) log_end_msg 1 ;; # Old process is still running - *) log_end_msg 1 ;; # Failed to start - esac - ;; - *) - # Failed to stop - log_end_msg 1 - ;; - esac - ;; - status) - echo -n "Status of $DESC: " - if is_alive ; then - echo "alive." - else - echo "dead." - exit 1 - fi - ;; - *) - echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload|status}" >&2 - exit 3 - ;; + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + reload|force-reload) + log_daemon_msg "Reloading $DESC" "$NAME" + do_reload + log_end_msg $? + ;; + restart) + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + status) + echo -n "Status of $DESC: " + if is_alive ; then + echo "alive." + else + echo "dead." + exit 1 + fi + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload|status}" >&2 + exit 3 + ;; esac : +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/deejayd.install /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/deejayd.install --- deejayd-0.7.2/debian/deejayd.install 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/debian/deejayd.install 2009-11-15 05:48:11.000000000 +0000 @@ -1,15 +1,19 @@ usr/bin/deejayd usr/lib/python*/*-packages/deejayd/core.py usr/lib/python*/*-packages/deejayd/component.py -usr/lib/python*/*-packages/deejayd/net/commandsXML.py -usr/lib/python*/*-packages/deejayd/net/deejaydProtocol.py -usr/lib/python*/*-packages/deejayd/mediadb/*.py -usr/lib/python*/*-packages/deejayd/mediadb/formats/*.py +usr/lib/python*/*-packages/deejayd/utils.py +usr/lib/python*/*-packages/deejayd/xmlobject.py +usr/lib/python*/*-packages/deejayd/rpc/protocol.py +usr/lib/python*/*-packages/deejayd/rpc/rdfbuilder.py +usr/lib/python*/*-packages/deejayd/rpc/jsonrpc.py +usr/lib/python*/*-packages/deejayd/net/protocol.py +usr/lib/python*/*-packages/deejayd/mediadb/* usr/lib/python*/*-packages/deejayd/player/__init__.py usr/lib/python*/*-packages/deejayd/player/_base.py usr/lib/python*/*-packages/deejayd/sources/*.py usr/lib/python*/*-packages/deejayd/ui/* usr/lib/python*/*-packages/deejayd/database/* -usr/lib/python*/*-packages/deejayd/database/upgrade/* usr/share/locale/*/LC_MESSAGES/deejayd.mo etc/deejayd.conf +etc/deejayd.xinitrc +etc/X11/Xsession.d/50deejayd_steal-session diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/deejayd.postinst /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/deejayd.postinst --- deejayd-0.7.2/debian/deejayd.postinst 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/debian/deejayd.postinst 2009-11-15 05:48:11.000000000 +0000 @@ -4,9 +4,15 @@ if ! getent passwd $DEEJAYD_USER >/dev/null; then adduser --quiet --ingroup audio --system --no-create-home \ --home /var/lib/deejayd $DEEJAYD_USER - adduser $DEEJAYD_USER cdrom fi +# add deejayd to required groups +for group in audio video cdrom; do + if groups $DEEJAYD_USER | grep -w -q -v $group; then + adduser $DEEJAYD_USER $group + fi +done + for i in /var/log/deejayd.log /var/lib/deejayd; do if ! dpkg-statoverride --list --quiet "$i" >/dev/null; then diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/deejayd-webui-extension.install /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/deejayd-webui-extension.install --- deejayd-0.7.2/debian/deejayd-webui-extension.install 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/debian/deejayd-webui-extension.install 2009-11-15 05:48:11.000000000 +0000 @@ -0,0 +1,4 @@ +../../extensions/deejayd-webui/chrome /usr/share/mozilla-extensions/deejayd-webui/ +../../extensions/deejayd-webui/chrome.manifest /usr/share/mozilla-extensions/deejayd-webui/ +../../extensions/deejayd-webui/defaults /usr/share/mozilla-extensions/deejayd-webui/ +../../extensions/deejayd-webui/install.rdf /usr/share/mozilla-extensions/deejayd-webui/ diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/deejayd-webui-extension.links /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/deejayd-webui-extension.links --- deejayd-0.7.2/debian/deejayd-webui-extension.links 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/debian/deejayd-webui-extension.links 2009-11-15 05:48:11.000000000 +0000 @@ -0,0 +1 @@ +/usr/share/mozilla-extensions/deejayd-webui /usr/lib/iceweasel/extensions/webui@deejayd.net diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/deejayd-webui.install /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/deejayd-webui.install --- deejayd-0.7.2/debian/deejayd-webui.install 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/debian/deejayd-webui.install 2009-11-15 05:48:11.000000000 +0000 @@ -1,7 +1,2 @@ usr/lib/python*/*-packages/deejayd/webui/*.py -usr/lib/python*/*-packages/deejayd/webui/templates/*.xml -usr/share/deejayd/htdocs/js/*.js -usr/share/deejayd/htdocs/xul/scripts/*.js -usr/share/deejayd/htdocs/xul/bindings/*.* -usr/share/deejayd/htdocs/themes/default/*.css -usr/share/deejayd/htdocs/themes/default/images/*.png +usr/share/deejayd/htdocs/* diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/deejayd-webui.links /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/deejayd-webui.links --- deejayd-0.7.2/debian/deejayd-webui.links 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/debian/deejayd-webui.links 2009-11-15 05:48:11.000000000 +0000 @@ -0,0 +1 @@ +/usr/share/javascript/jquery/jquery.js /usr/share/deejayd/htdocs/js/lib/jquery.js diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/deejayd-xine.install /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/deejayd-xine.install --- deejayd-0.7.2/debian/deejayd-xine.install 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/debian/deejayd-xine.install 2009-11-15 05:48:11.000000000 +0000 @@ -1,3 +1,2 @@ usr/lib/python*/*-packages/deejayd/player/xine.py -usr/lib/python*/*-packages/deejayd/player/_xine.py -usr/lib/python*/*-packages/deejayd/player/display/*.py +usr/lib/python*/*-packages/pytyxi/*.py diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/deejayd.xinitrc /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/deejayd.xinitrc --- deejayd-0.7.2/debian/deejayd.xinitrc 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/debian/deejayd.xinitrc 2009-11-15 05:48:11.000000000 +0000 @@ -0,0 +1,15 @@ +# The following lines require the appropiate tool to be installed. + +# Change the default resolution. +#xrandr -s 1920x1200 + +# Set a background picture. +#xloadimage -onroot -quiet -fullscreen /path/to/bg.jpg + +# Allocate some keyboard shortcuts. +#xbindkeys -f /etc/deejayd.xbindkeys + +# The last line can contain an X client that will hold the session. It +# ususally is a window manager, but in our case, as we do not need window +# borders for deejayd with a standalone X server, it can be a simple clock. +#exec xdaliclock -24 -noseconds -transparent -fg black -geometry +1400+850 diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/djc.install /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/djc.install --- deejayd-0.7.2/debian/djc.install 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/debian/djc.install 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -usr/bin/djc diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/djc.manpages /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/djc.manpages --- deejayd-0.7.2/debian/djc.manpages 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/debian/djc.manpages 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -man/djc.1 diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/README.Debian /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/README.Debian --- deejayd-0.7.2/debian/README.Debian 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/debian/README.Debian 2009-11-15 05:48:11.000000000 +0000 @@ -11,7 +11,7 @@ want to change the following items from their default value : - in the 'general' section, maybe enable more modes using the activated_modes variable. The default does not enable video related modes. - activated_modes = playlist,webradio,video,dvd + activated_modes = playlist,webradio,video,dvd,panel (this is new in version 0.7.0) - in the 'webui' section, enable it using the 'enabled' configuration variable. - you may want the daemon to be controllable from machines other than the one it @@ -28,14 +28,10 @@ Deejayd supports video. But it will not try to establish a connection to a X server until it is asked to play a video and if the 'video' mode is enabled. -Connection to an existing user launched X server should be doable if deejayd -is not launched using its initscript. On the contrary, if it is, the daemon -must know both the display number (which does not change on most setups) and, -and this is where the problems begin, the XAUTHORITY file, which is, on debian -systems, generated whenever the X server is started with startx. When using a -display manager, the same seams to happen. +The Debian packaging provides two modes of operation in that regard: +- 'standalone': Deejayd launches its own dedicated X server, +- 'reuse': Deejayd connects to an existing X session (e.g. launched by + (x|g|k)dm). -Some people use an initscript to launch the X server on a machine dedicated to -deejayd. This method will be integrated in the deejayd packaging in the near -future. More info here : -http://sousmonlit.dyndns.org/~niol/playa/oss:debhowto:display-server +Configuration should be easy enough, and details are available in the comments +in /etc/default/deejayd . diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/debian/rules /tmp/LOo8qwLBV9/deejayd-0.9.0/debian/rules --- deejayd-0.7.2/debian/rules 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/debian/rules 2009-11-15 05:48:11.000000000 +0000 @@ -20,27 +20,32 @@ dh_clean -k -i dh_clean -k -s python$* setup.py install --no-compile\ - --root $(CURDIR)/debian/tmp \ - --install-layout=deb + --root $(CURDIR)/debian/tmp --install-layout=deb # Fixing example conffile location - mkdir $(CURDIR)/debian/tmp/etc - mv $(CURDIR)/debian/tmp/usr/share/doc/deejayd/deejayd.conf.example\ - $(CURDIR)/debian/tmp/etc/deejayd.conf + mkdir -p $(CURDIR)/debian/tmp/etc/X11/Xsession.d + cp -a $(CURDIR)/deejayd/ui/defaults.conf\ + $(CURDIR)/debian/tmp/etc/deejayd.conf + cp -a $(CURDIR)/debian/deejayd.xinitrc\ + $(CURDIR)/debian/tmp/etc/deejayd.xinitrc + cp -a $(CURDIR)/debian/50deejayd_steal-session\ + $(CURDIR)/debian/tmp/etc/X11/Xsession.d + + # Removing jquery embedded copy, see debian/deejayd-webui.links + rm $(CURDIR)/debian/tmp/usr/share/deejayd/htdocs/js/lib/jquery.js dh_install --sourcedir=$(CURDIR)/debian/tmp dh_installdirs -clean: +clean: $(PYVERS:%=clean-python%) + +clean-python%: dh_testdir dh_testroot rm -f build-python* - rm -rf $(CURDIR)/build/mo - python setup.py clean --all - rm -f $(CURDIR)/deejayd/__init__.pyc - rm -f $(CURDIR)/MANIFEST - rm -f $(CURDIR)/man/*.{1,5} + python$* setup.py clean --all + find $(CURDIR) -name '*pyc' -exec rm -f {} \; dh_clean @@ -49,11 +54,14 @@ dh_testroot dh_installchangelogs dh_installdocs - dh_pycentral + dh_pycentral -Ndeejayd-webui-extension dh_installlogrotate # Inspired by mpd packaging, so that ALSA scripts can run first dh_installinit -u"defaults 30 15" + # Inspired by gdm packaging + dh_installinit --name=deejayd-xserver -u"defaults 30 01" dh_installman + dh_link dh_compress dh_fixperms dh_installdeb diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/component.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/component.py --- deejayd-0.7.2/deejayd/component.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/component.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -17,21 +17,25 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -class SignalingComponent: +class SignalingComponent(object): + SUBSCRIPTIONS = {} def __init__(self): self.__dispatcher = None def register_dispatcher(self, dispatcher): self.__dispatcher = dispatcher + # set internal subscription + for signame in self.SUBSCRIPTIONS.keys(): + self.__dispatcher.subscribe(signame,\ + getattr(self, self.SUBSCRIPTIONS[signame])) def dispatch_signal(self, signal): if self.__dispatcher: self.__dispatcher._dispatch_signal(signal) - def dispatch_signame(self, signal_name): + def dispatch_signame(self, signal_name, attrs = {}): if self.__dispatcher: - self.__dispatcher._dispatch_signame(signal_name) - + self.__dispatcher._dispatch_signame(signal_name, attrs) # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/core.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/core.py --- deejayd-0.7.2/deejayd/core.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/core.py 2009-09-21 08:44:21.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -18,10 +18,12 @@ import deejayd.interfaces from deejayd.interfaces import DeejaydError,\ - DeejaydAnswer, DeejaydKeyValue, DeejaydFileList,\ + DeejaydAnswer,\ + DeejaydKeyValue, DeejaydList,\ + DeejaydFileList,\ DeejaydMediaList, DeejaydDvdInfo from deejayd.ui.config import DeejaydConfig -from deejayd import player, sources, mediadb, database +from deejayd import mediafilters, player, sources, mediadb, database # Exception imports import deejayd.sources.webradio @@ -71,6 +73,10 @@ if res == None: ans.contents = True elif answer_class == DeejaydMediaList: + if isinstance(res, tuple): + ans.set_filter(res[1]) + ans.set_sort(res[2]) + res = res[0] ans.set_medias(res) elif answer_class == DeejaydFileList: root_dir, dirs, files = res @@ -92,6 +98,105 @@ return returns_deejaydanswer_instance +class DeejaydStaticPlaylist(deejayd.interfaces.DeejaydStaticPlaylist): + + def __init__(self, deejaydcore, pl_id, name): + self.deejaydcore = deejaydcore + self.db, self.library = deejaydcore.db, deejaydcore.audio_library + self.name = name + self.pl_id = pl_id + + @returns_deejaydanswer(DeejaydMediaList) + def get(self, first=0, length=-1): + songs = self.db.get_static_medialist(self.pl_id,\ + infos = self.library.media_attr) + last = length == -1 and len(songs) or int(first) + int(length) + return songs[int(first):last] + + @returns_deejaydanswer(DeejaydAnswer) + def add_paths(self, paths): + ids = [] + for path in paths: + try: medias = self.library.get_all_files(path) + except deejayd.mediadb.library.NotFoundException: + try: medias = self.library.get_file(path) + except NotFoundException: + raise DeejaydError(_('Path %s not found in library') % path) + for m in medias: ids.append(m["media_id"]) + self.add_songs(ids) + + @returns_deejaydanswer(DeejaydAnswer) + def add_songs(self, song_ids): + self.db.add_to_static_medialist(self.pl_id, song_ids) + self.db.connection.commit() + self.deejaydcore._dispatch_signame('playlist.update',\ + {"pl_id": self.pl_id}) + + +class DeejaydMagicPlaylist(deejayd.interfaces.DeejaydMagicPlaylist): + """ Magic playlist object """ + + def __init__(self, deejaydcore, pl_id, name): + self.deejaydcore = deejaydcore + self.db, self.library = deejaydcore.db, deejaydcore.audio_library + self.name = name + self.pl_id = pl_id + + @returns_deejaydanswer(DeejaydMediaList) + def get(self, first=0, length=-1): + properties = dict(self.db.get_magic_medialist_properties(self.pl_id)) + if properties["use-or-filter"] == "1": + filter = mediafilters.Or() + else: + filter = mediafilters.And() + if properties["use-limit"] == "1": + sort = [(properties["limit-sort-value"],\ + properties["limit-sort-direction"])] + limit = int(properties["limit-value"]) + else: + sort, limit = [], None + filter.filterlist = self.db.get_magic_medialist_filters(self.pl_id) + songs = self.library.search(filter, sort, limit) + last = length == -1 and len(songs) or int(first) + int(length) + return (songs[int(first):last], filter, None) + + @returns_deejaydanswer(DeejaydAnswer) + def add_filter(self, filter): + if filter.type != "basic": + raise DeejaydError(\ + _("Only basic filters are allowed for magic playlist")) + self.db.add_magic_medialist_filters(self.pl_id, [filter]) + self.deejaydcore._dispatch_signame('playlist.update',\ + {"pl_id": self.pl_id}) + + @returns_deejaydanswer(DeejaydAnswer) + def remove_filter(self, filter): + record_filters = self.db.get_magic_medialist_filters(self.pl_id) + new_filters = [] + for record_filter in record_filters: + if not filter.equals(record_filter): + new_filters.append(record_filter) + self.db.set_magic_medialist_filters(self.name, new_filters) + self.deejaydcore._dispatch_signame('playlist.update',\ + {"pl_id": self.pl_id}) + + @returns_deejaydanswer(DeejaydAnswer) + def clear_filters(self): + self.db.set_magic_medialist_filters(self.name, []) + self.deejaydcore._dispatch_signame('playlist.update',\ + {"pl_id": self.pl_id}) + + @returns_deejaydanswer(DeejaydKeyValue) + def get_properties(self): + return dict(self.db.get_magic_medialist_properties(self.pl_id)) + + @returns_deejaydanswer(DeejaydAnswer) + def set_property(self, key, value): + self.db.set_magic_medialist_property(self.pl_id, key, value) + self.deejaydcore._dispatch_signame('playlist.update',\ + {"pl_id": self.pl_id}) + + class DeejaydWebradioList(deejayd.interfaces.DeejaydWebradioList): def __init__(self, deejaydcore): @@ -115,11 +220,10 @@ @returns_deejaydanswer(DeejaydAnswer) def delete_webradios(self, wr_ids): - for id in wr_ids: - try: - self.source.delete(int(id)) - except sources._base.MediaNotFoundError: - raise DeejaydError(_('Webradio with id %d not found') % int(id)) + ids = map(int, wr_ids) + try: self.source.delete(ids) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) @returns_deejaydanswer(DeejaydAnswer) def clear(self): @@ -139,20 +243,32 @@ return songs[int(first):last] @returns_deejaydanswer(DeejaydAnswer) - def add_medias(self, paths, pos = None): - position = pos and int(pos) or None - try: - self.source.add_path(paths, position) - except sources._base.MediaNotFoundError: - raise DeejaydError(_('%s not found') % (paths,)) + def add_songs(self, song_ids, pos = None): + p = pos and int(pos) or None + try: self.source.add_song(song_ids, pos = p) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) @returns_deejaydanswer(DeejaydAnswer) - def load_playlists(self, names, pos=None): + def add_paths(self, paths, pos = None): + p = pos and int(pos) or None + try: self.source.add_path(paths, p) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) + + @returns_deejaydanswer(DeejaydAnswer) + def load_playlists(self, pl_ids, pos=None): pos = pos and int(pos) or None - try: - self.source.load_playlist(names, pos) - except sources._base.PlaylistNotFoundError: - raise DeejaydError(_('Playlist %s does not exist.') % str(names)) + try: self.source.load_playlist(pl_ids, pos) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) + + @returns_deejaydanswer(DeejaydAnswer) + def move(self, ids, new_pos): + ids = [int(id) for id in ids] + try: self.source.move(ids, new_pos) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) @returns_deejaydanswer(DeejaydAnswer) def clear(self): @@ -160,93 +276,141 @@ @returns_deejaydanswer(DeejaydAnswer) def del_songs(self, ids): - for id in ids: - try: - self.source.delete(int(id)) - except sources._base.MediaNotFoundError: - raise DeejaydError(_('Song with id %d not found') % int(id)) + ids = [int(id) for id in ids] + try: self.source.delete(ids) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) -class DeejaydPlaylist(deejayd.interfaces.DeejaydPlaylist): +class DeejaydPlaylistMode(deejayd.interfaces.DeejaydPlaylistMode): + """Audio playlist mode.""" - def __init__(self, deejaydcore, name=None): + def __init__(self, deejaydcore): self.deejaydcore = deejaydcore self.source = self.deejaydcore.sources.get_source("playlist") - self.name = name @returns_deejaydanswer(DeejaydMediaList) def get(self, first=0, length=-1): - try: - songs = self.source.get_content(playlist = self.name) - except sources._base.PlaylistNotFoundError: - raise DeejaydError(_('Playlist %s not found') % self.name) - else: - last = length == -1 and len(songs) or int(first) + int(length) - return songs[int(first):last] + songs = self.source.get_content() + last = length == -1 and len(songs) or int(first) + int(length) + return songs[int(first):last] - @returns_deejaydanswer(DeejaydAnswer) + @returns_deejaydanswer(DeejaydKeyValue) def save(self, name): - self.source.save(name) + if name == "": + raise DeejaydError(_("Set a playlist name")) + return self.source.save(name) @returns_deejaydanswer(DeejaydAnswer) - def add_songs(self, paths, position=None): - p = position and int(position) or None - try: - self.source.add_path(paths, playlist = self.name, pos = p) - except sources._base.MediaNotFoundError: - raise DeejaydError(_('%s not found') % (paths,)) - except sources._base.PlaylistNotFoundError: - raise DeejaydError(_('Playlist %s not found') % self.name) + def add_paths(self, paths, pos=None): + p = pos and int(pos) or None + try: self.source.add_path(paths, pos = p) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) + + @returns_deejaydanswer(DeejaydAnswer) + def add_songs(self, song_ids, pos=None): + p = pos and int(pos) or None + try: self.source.add_song(song_ids, pos = p) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) @returns_deejaydanswer(DeejaydAnswer) - def loads(self, names, pos=None): - if self.name != None: - raise DeejaydError(_('Unable to load pls in a saved pls.')) + def loads(self, pl_ids, pos=None): pos = pos and int(pos) or None - try: - self.source.load_playlist(names, pos) - except sources._base.PlaylistNotFoundError: - raise DeejaydError(_('Playlist %s does not exist.') % str(names)) + try: self.source.load_playlist(pl_ids, pos) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) @returns_deejaydanswer(DeejaydAnswer) def move(self, ids, new_pos): - if self.name != None: - raise DeejaydError(_('Unable to load pls in a saved pls.')) ids = [int(id) for id in ids] - try: - self.source.move(ids, int(new_pos)) - except sources._base.MediaNotFoundError: - raise DeejaydError(_('song with ids %s not found') % (str(ids),)) + try: self.source.move(ids, int(new_pos)) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) @returns_deejaydanswer(DeejaydAnswer) def shuffle(self): - try: - self.source.shuffle(playlist = self.name) - except sources._base.PlaylistNotFoundError: - raise DeejaydError(_('Playlist %s does not exist.') % self.name) + try: self.source.shuffle() + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) @returns_deejaydanswer(DeejaydAnswer) def clear(self): - try: - self.source.clear(playlist = self.name) - except sources._base.PlaylistNotFoundError: - raise DeejaydError(_('Playlist %s does not exist.') % self.name) + try: self.source.clear() + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) @returns_deejaydanswer(DeejaydAnswer) def del_songs(self, ids): - name = self.name - for nb in ids: - try: - self.source.delete(int(nb), "id", playlist = name) - except sources._base.MediaNotFoundError: - raise DeejaydError(\ - _('Playlist %s does not have a song of id %d') % (name, nb)) - except sources._base.PlaylistNotFoundError: - raise DeejaydError(_('Playlist %s does not exist.') % name) + ids = [int(id) for id in ids] + try: self.source.delete(ids) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) + + +class DeejaydPanel(deejayd.interfaces.DeejaydPanel): + + def __init__(self, deejaydcore): + self.deejaydcore = deejaydcore + self.source = self.deejaydcore.sources.get_source("panel") + + @returns_deejaydanswer(DeejaydMediaList) + def get(self, first=0, length=-1): + songs, filters, sort = self.source.get_content() + last = length == -1 and len(songs) or int(first) + int(length) + return (songs[int(first):last], filters, sort) + + @returns_deejaydanswer(DeejaydList) + def get_panel_tags(self): + return self.source.get_panel_tags() + + @returns_deejaydanswer(DeejaydKeyValue) + def get_active_list(self): + return self.source.get_active_list() + @returns_deejaydanswer(DeejaydAnswer) + def set_active_list(self, type, pl_id=""): + try: self.source.set_active_list(type, pl_id) + except TypeError: + raise DeejaydError(_("Not supported type")) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) + + @returns_deejaydanswer(DeejaydAnswer) + def set_panel_filters(self, tag, values): + try: self.source.set_panel_filters(tag, values) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) + + @returns_deejaydanswer(DeejaydAnswer) + def remove_panel_filters(self, tag): + try: self.source.remove_panel_filters(tag) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) + + @returns_deejaydanswer(DeejaydAnswer) + def clear_panel_filters(self): + self.source.clear_panel_filters() + + @returns_deejaydanswer(DeejaydAnswer) + def set_search_filter(self, tag, value): + try: self.source.set_search_filter(tag, value) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) + + @returns_deejaydanswer(DeejaydAnswer) + def clear_search_filter(self): + self.source.clear_search_filter() + + @returns_deejaydanswer(DeejaydAnswer) + def set_sorts(self, sorts): + try: self.source.set_sorts(sorts) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) -class DeejaydVideo: - """Video management.""" +class DeejaydVideo(deejayd.interfaces.DeejaydVideo): + """Video mode.""" def __init__(self, deejaydcore): self.deejaydcore = deejaydcore @@ -254,16 +418,21 @@ @returns_deejaydanswer(DeejaydMediaList) def get(self, first = 0, length = -1): - videos = self.source.get_content() + videos, filters, sort = self.source.get_content() last = length == -1 and len(videos) or int(first) + int(length) - return videos[int(first):last] + return (videos[int(first):last], filters, sort) @returns_deejaydanswer(DeejaydAnswer) def set(self, value, type = "directory"): try: self.source.set(type, value) - except deejayd.sources._base.MediaNotFoundError: - raise DeejaydError(_('Directory %s not found in database') % value) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) + @returns_deejaydanswer(DeejaydAnswer) + def set_sorts(self, sorts): + try: self.source.set_sorts(sorts) + except deejayd.sources._base.SourceError, ex: + raise DeejaydError(str(ex)) class DeejayDaemonCore(deejayd.interfaces.DeejaydCore): @@ -274,7 +443,6 @@ config = DeejaydConfig() self.db = database.init(config) - self.db.connect() self.player = player.init(self.db, config) self.player.register_dispatcher(self) @@ -308,31 +476,39 @@ @returns_deejaydanswer(DeejaydAnswer) def play_toggle(self): if self.player.get_state() == player._base.PLAYER_PLAY: - self.player.pause() + current_media = self.player.get_playing() + if current_media['type'] == 'webradio': + # There is no point in pausing radio streams. + try: self.player.stop() + except player.PlayerError, err: raise DeejaydError(err) + else: + self.player.pause() else: try: self.player.play() except player.PlayerError, err: - raise DeejaydError(str(err)) + raise DeejaydError(err) @returns_deejaydanswer(DeejaydAnswer) def stop(self): - self.player.stop() + try: self.player.stop() + except player.PlayerError, err: + raise DeejaydError(err) @returns_deejaydanswer(DeejaydAnswer) def previous(self): try: self.player.previous() except player.PlayerError, err: - raise DeejaydError(str(err)) + raise DeejaydError(err) @returns_deejaydanswer(DeejaydAnswer) def next(self): try: self.player.next() except player.PlayerError, err: - raise DeejaydError(str(err)) + raise DeejaydError(err) @returns_deejaydanswer(DeejaydAnswer) - def seek(self, pos): - self.player.set_position(int(pos)) + def seek(self, pos, relative = False): + self.player.set_position(int(pos), relative) @returns_deejaydanswer(DeejaydMediaList) def get_current(self): @@ -343,10 +519,12 @@ return medias @require_mode("playlist") - def get_playlist(self, name=None): - pls = DeejaydPlaylist(self, name) - pls.get() - return pls + def get_playlist(self): + return DeejaydPlaylistMode(self) + + @require_mode("panel") + def get_panel(self): + return DeejaydPanel(self) @require_mode("webradio") def get_webradios(self): @@ -361,6 +539,8 @@ @returns_deejaydanswer(DeejaydAnswer) def go_to(self, id, id_type = "id", source = None): + if id_type not in ("dvd_id","track","chapter","id","pos"): + raise DeejaydError(_("Bad value for id_type parm")) if id_type != "dvd_id": try: id = int(id) except ValueError: @@ -368,36 +548,42 @@ try: self.player.go_to(id, id_type, source) except player.PlayerError, err: - raise DeejaydError(str(err)) + raise DeejaydError(err) @returns_deejaydanswer(DeejaydAnswer) def set_volume(self, volume_value): self.player.set_volume(int(volume_value)) @returns_deejaydanswer(DeejaydAnswer) - def set_option(self, option_name, option_value): - try: self.sources.set_option(option_name, int(option_value)) - except KeyError: - raise DeejaydError(_('Option %s does not exist') % option_name) + def set_option(self, source, option_name, option_value): + try: self.sources.set_option(source, option_name, option_value) + except sources.UnknownSourceException: + raise DeejaydError(_('Mode %s not supported') % source) + except sources._base.SourceError, ex: + raise DeejaydError(str(ex)) @returns_deejaydanswer(DeejaydAnswer) def set_mode(self, mode_name): - try: - self.sources.set_source(mode_name) + try: self.sources.set_source(mode_name) except sources.UnknownSourceException: - raise DeejaydError(_('Unknown mode: %s') % mode_name) + raise DeejaydError(_('Mode %s not supported') % mode_name) @returns_deejaydanswer(DeejaydKeyValue) def get_mode(self): av_sources = self.sources.get_available_sources() modes = {} - for s in self.sources.sources_list: + for s in self.sources.get_all_sources(): modes[s] = s in av_sources or 1 and 0 return modes @returns_deejaydanswer(DeejaydAnswer) def set_player_option(self, name, value): - try: self.player.set_option(name, int(value)) + if name != "aspect_ratio": + try: value = int(value) + except (ValueError,TypeError): + raise DeejaydError(_("Param value is not an int")) + + try: self.player.set_option(name, value) except KeyError: raise DeejaydError(_("Option %s does not exist") % name) except NotImplementedError: @@ -421,29 +607,79 @@ return dict(ans) @returns_deejaydanswer(DeejaydKeyValue) - def update_audio_library(self, sync = False): - return {'audio_updating_db': self.audio_library.update(sync)} + def update_audio_library(self, force = False, sync = False): + return {'audio_updating_db': self.audio_library.update(force, sync)} @require_mode("video") @returns_deejaydanswer(DeejaydKeyValue) - def update_video_library(self, sync = False): + def update_video_library(self, force = False, sync = False): if not self.video_library: raise DeejaydError(_("Video mode disabled")) - return {'video_updating_db': self.video_library.update(sync)} + return {'video_updating_db': self.video_library.update(force, sync)} + + @returns_deejaydanswer(DeejaydKeyValue) + def create_recorded_playlist(self, name, type): + if name == "": + raise DeejaydError(_("Set a playlist name")) + # first search if this pls already exist + try: self.db.get_medialist_id(name, type) + except ValueError: pass + else: # pls already exists + raise DeejaydError(_("This playlist already exists")) + + if type == "static": + pl_id = self.db.set_static_medialist(name, []) + elif type == "magic": + pl_id = self.db.set_magic_medialist_filters(name, []) + pl = DeejaydMagicPlaylist(self, pl_id, name) + # set default properties for this playlist + default = { + "use-or-filter": "0", + "use-limit": "0", + "limit-value": "50", + "limit-sort-value": "title", + "limit-sort-direction": "ascending" + } + for (k, v) in default.items(): + pl.set_property(k, v) + self._dispatch_signame('playlist.listupdate') + return {"pl_id": pl_id, "name": name, "type": type} + + def get_recorded_playlist(self, id, name = "", type = "static"): + try: pl_id, name, type = self.db.is_medialist_exists(id) + except TypeError: + raise DeejaydError(_("Playlist with id %s not found.") % str(id)) + if type == "static": + return DeejaydStaticPlaylist(self, pl_id, name) + elif type == "magic": + return DeejaydMagicPlaylist(self, pl_id, name) - @require_mode("playlist") @returns_deejaydanswer(DeejaydAnswer) - def erase_playlist(self, names): - for name in names: - self.sources.get_source("playlist").rm(name) + def erase_playlist(self, ids): + for id in ids: + self.db.delete_medialist(id) + self._dispatch_signame('playlist.listupdate') - @require_mode("playlist") @returns_deejaydanswer(DeejaydMediaList) def get_playlist_list(self): - plname_list = [] - for plname in self.sources.get_source('playlist').get_list(): - plname_list.append({'name': plname}) - return plname_list + return [{"name": pl, "id":id, "type":type}\ + for (id, pl, type) in self.db.get_medialist_list() if not \ + pl.startswith("__") or not pl.endswith("__")] + + @returns_deejaydanswer(DeejaydAnswer) + def set_media_rating(self, media_ids, rating, type = "audio"): + if int(rating) not in range(0, 5): + raise DeejaydError(_("Bad rating value")) + + try: library = getattr(self, type+"_library") + except AttributeError: + raise DeejaydError(_('Type %s is not supported') % (type,)) + for id in media_ids: + try: library.set_file_info(int(id), "rating", rating) + except TypeError: + raise DeejaydError(_("%s library not activated") % type) + except deejayd.mediadb.library.NotFoundException: + raise DeejaydError(_("File with id %s not found") % str(id)) @returns_deejaydanswer(DeejaydFileList) def get_audio_dir(self,dir = None): @@ -454,13 +690,27 @@ return dir, contents['dirs'], contents['files'] - @returns_deejaydanswer(DeejaydFileList) - def audio_search(self, search_txt, type = 'all'): - try: list = self.audio_library.search(type, search_txt) + @returns_deejaydanswer(DeejaydKeyValue) + def get_audio_cover(self,media_id): + try: cover = self.audio_library.get_cover(media_id) except deejayd.mediadb.library.NotFoundException: + raise DeejaydError(_('Cover not found')) + return cover + + @returns_deejaydanswer(DeejaydMediaList) + def audio_search(self, pattern, type = 'all'): + if type not in ('all','title','genre','filename','artist','album'): raise DeejaydError(_('Type %s is not supported') % (type,)) + if type == "all": + filter = mediafilters.Or() + for tag in ('title','genre','artist','album'): + filter.combine(mediafilters.Contains(tag, pattern)) + else: + filter = mediafilters.Contains(type, pattern) + songs = self.audio_library.search(filter,\ + mediafilters.DEFAULT_AUDIO_SORT) - return None, [], list + return songs @require_mode("video") @returns_deejaydanswer(DeejaydFileList) @@ -487,5 +737,9 @@ def get_dvd_content(self): return self.sources.get_source("dvd").get_content() + @returns_deejaydanswer(DeejaydList) + def mediadb_list(self, tag, filter): + return [x[0] for x in self.db.list_tags(tag, filter)] + # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/backends/_base.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/backends/_base.py --- deejayd-0.7.2/deejayd/database/backends/_base.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/backends/_base.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,42 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +class DatabaseError(Exception): + pass + +class DatabaseWrapper: + + def cursor(self): + raise NotImplementedError + + def commit(self): + raise NotImplementedError + + def rollback(self): + raise NotImplementedError + + def close(self): + raise NotImplementedError + + def get_last_insert_id(self): + raise NotImplementedError + + def to_sql(self, table): + raise NotImplementedError + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/backends/__init__.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/backends/__init__.py --- deejayd-0.7.2/deejayd/database/backends/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/backends/__init__.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,19 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/backends/mysql.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/backends/mysql.py --- deejayd-0.7.2/deejayd/database/backends/mysql.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/backends/mysql.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,148 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import time +from threading import local +from deejayd.ui import log +import MySQLdb as mysql + +# We want version (1, 2, 1, 'final', 2) or later. We can't just use +# lexicographic ordering in this check because then (1, 2, 1, 'gamma') +# inadvertently passes the version test. +version = mysql.version_info +if (version < (1,2,1) or (version[:3] == (1, 2, 1) and + (len(version) < 5 or version[3] != 'final' or version[4] < 2))): + raise ImportError, "MySQLdb-1.2.1p2 or newer is required; you have %s" % mysql.__version__ + +DatabaseError = mysql.DatabaseError + +class DatabaseWrapper(local): + __slots__ = "globalcommit" + + def __init__(self, db_name, db_user, db_password, db_host, db_port): + self.connection = None + self.last_commit = 0 + + self.db_name = db_name + self.db_user = db_user + self.db_password = db_password + self.db_host = db_host + self.db_port = db_port + + def _valid_connection(self): + if self.connection is not None: + try: + self.connection.ping() + try: globalcommit = self.globalcommit + except AttributeError: + globalcommit = 0 + if self.last_commit < globalcommit: + self.connection.commit() + self.connection.close() + self.connection = None + self.last_commit = globalcommit + else: return True + except DatabaseError: + self.connection.close() + self.connection = None + return False + + def cursor(self): + if not self._valid_connection(): + try: self.connection = mysql.connect(db=self.db_name,\ + user=self.db_user, passwd=self.db_password,\ + host=self.db_host, port=self.db_port, charset="utf8",\ + use_unicode=True) + except DatabaseError, err: + error = _("Could not connect to MySQL server %s." % err) + log.err(error, fatal = True) + cursor = self.connection.cursor() + return cursor + + def commit(self): + if self.connection is not None: + self.connection.commit() + timestamp = time.time() + self.globalcommit = timestamp + self.lastcommit = timestamp + + def rollback(self): + if self.connection is not None: + try: + self.connection.rollback() + except mysql.NotSupportedError: + pass + + def get_last_insert_id(self, cursor): + return cursor.lastrowid + + def close(self): + if self.connection is not None: + self.connection.close() + self.connection = None + + +def to_sql(table): + + def __collist(table, columns): + cols = [] + limit = 333 / len(columns) + if limit > 255: + limit = 255 + for c in columns: + name = '`%s`' % c + table_col = filter((lambda x: x.name == c), table.columns) + if len(table_col) == 1 and table_col[0].type.lower() == 'text': + name += '(%s)' % limit + # For non-text columns, we simply throw away the extra bytes. + # That could certainly be optimized better, but for now let's KISS. + cols.append(name) + return ','.join(cols) + + sql = ['CREATE TABLE %s (' % table.name] + coldefs = [] + for column in table.columns: + ctype = column.type + if ctype == "blob": ctype = "mediumblob" + if column.auto_increment: + ctype = 'INT UNSIGNED NOT NULL AUTO_INCREMENT' + # Override the column type, as a text field cannot + # use auto_increment. + column.type = 'int' + coldefs.append(' `%s` %s' % (column.name, ctype)) + if len(table.key) > 0: + coldefs.append(' PRIMARY KEY (%s)' % + __collist(table, table.key)) + sql.append(',\n'.join(coldefs) + '\n) ENGINE=InnoDB') + #sql.append(',\n'.join(coldefs) + '\n)') + yield '\n'.join(sql) + + for index in table.indices: + unique = index.unique and "UNIQUE" or "" + yield 'CREATE %s INDEX %s_%s_idx ON %s (%s);' % (unique,table.name, + '_'.join(index.columns), table.name, + __collist(table, index.columns)) + +custom_queries = [ + "CREATE UNIQUE INDEX id_key_value_idx ON media_info\ + (id, ikey(64), value(64));", + "CREATE INDEX key_value_idx ON media_info\ + (ikey(64), value(64));", + ] + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/backends/sqlite.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/backends/sqlite.py --- deejayd-0.7.2/deejayd/database/backends/sqlite.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/backends/sqlite.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,147 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from os import path +from threading import local +from deejayd.ui import log + +try: from sqlite3 import dbapi2 as sqlite # python 2.5 +except ImportError: + from pysqlite2 import dbapi2 as sqlite + # Check pysqlite version + pysqlite_min_version = [2, 2] + pysqlite_version = map(int, sqlite.version.split('.')) + if pysqlite_version < pysqlite_min_version: + sqlite_error=_('This program requires pysqlite version %s or later.')\ + % '.'.join(map(str, pysqlite_min_version)) + log.err(sqlite_error, fatal = True) + +LOCK_TIMEOUT = 600 +DatabaseError = sqlite.DatabaseError + +def str_encode(data): + if isinstance(data, unicode): + return data.encode("utf-8") + return data + + +class DatabaseWrapper(local): + + def __init__(self, db_file): + self._file = db_file + self.connection = None + + def cursor(self): + if self.connection is None: + try: self.connection = sqlite.connect(self._file,\ + timeout=LOCK_TIMEOUT) + except sqlite.Error: + error = _("Could not connect to sqlite database %s.")%self._file + log.err(error, fatal = True) + # configure connection + sqlite.register_adapter(str,str_encode) + + return self.connection.cursor(factory = SQLiteCursorWrapper) + + def commit(self): + if self.connection is not None: + self.connection.commit() + + def rollback(self): + if self.connection is not None: + self.connection.rollback() + + def get_last_insert_id(self, cursor): + return cursor.lastrowid + + def close(self): + if self.connection is not None: + self.connection.close() + self.connection = None + + +class SQLiteCursorWrapper(sqlite.Cursor): + + def execute(self, query, params=()): + query = self.convert_query(query, len(params)) + return sqlite.Cursor.execute(self, query, params) + + def executemany(self, query, param_list): + if len(param_list) == 0: return + query = self.convert_query(query, len(param_list[0])) + return sqlite.Cursor.executemany(self, query, param_list) + + def convert_query(self, query, num_params): + return query % tuple("?" * num_params) + + +def to_sql(table): + sql = ["CREATE TABLE %s (" % table.name] + coldefs = [] + for column in table.columns: + ctype = column.type.lower() + if column.auto_increment: + ctype = "integer PRIMARY KEY" + elif len(table.key) == 1 and column.name in table.key: + ctype += " PRIMARY KEY" + elif ctype == "int": + ctype = "integer" + coldefs.append(" %s %s" % (column.name, ctype)) + if len(table.key) > 1: + coldefs.append(" UNIQUE (%s)" % ','.join(table.key)) + sql.append(',\n'.join(coldefs) + '\n);') + yield '\n'.join(sql) + for index in table.indices: + unique = index.unique and "UNIQUE" or "" + yield "CREATE %s INDEX %s_%s_idx ON %s (%s);" % (unique,table.name, + '_'.join(index.columns), table.name, ','.join(index.columns)) + +custom_queries = [ + # custom indexes + "CREATE INDEX id_key_value_1x ON media_info(id,ikey,value COLLATE BINARY);", + "CREATE INDEX id_key_value_2x ON media_info(id,ikey,value COLLATE NOCASE);", + "CREATE INDEX key_value_1x ON media_info (ikey, value COLLATE BINARY);", + "CREATE INDEX key_value_2x ON media_info (ikey, value COLLATE NOCASE);", + # extract from ANALYZE request + "ANALYZE;", + "INSERT INTO sqlite_stat1 VALUES('cover', 'cover_source_idx','208 1');", + "INSERT INTO sqlite_stat1 VALUES('stats',\ + 'sqlite_autoindex_stats_1','7 1');", + "INSERT INTO sqlite_stat1 VALUES('variables',\ + 'sqlite_autoindex_variables_1','18 1');", + "INSERT INTO sqlite_stat1 VALUES('media_info',\ + 'key_value_2x','70538 3713 6');", + "INSERT INTO sqlite_stat1 VALUES('media_info',\ + 'key_value_1x','70538 3713 6');", + "INSERT INTO sqlite_stat1 VALUES('media_info',\ + 'id_key_value_2x','70538 16 1 1');", + "INSERT INTO sqlite_stat1 VALUES('media_info',\ + 'id_key_value_1x','70538 16 1 1');", + "INSERT INTO sqlite_stat1 VALUES('media_info',\ + 'sqlite_autoindex_media_info_1','70538 16 1');", + "INSERT INTO sqlite_stat1 VALUES('media_info',\ + 'sqlite_autoindex_media_info_1','70538 16 1');", + "INSERT INTO sqlite_stat1 VALUES('library',\ + 'library_directory_idx','4421 18');", + "INSERT INTO sqlite_stat1 VALUES('library',\ + 'library_name_directory_idx','4421 2 1');", + "INSERT INTO sqlite_stat1 VALUES('library_dir',\ + 'library_dir_name_lib_type_idx','377 2 1');", + ] + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/_base.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/_base.py --- deejayd-0.7.2/deejayd/database/_base.py 2008-05-14 22:50:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/_base.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,378 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -""" - Class and methods to manage database -""" - -from deejayd.ui import log -from deejayd.database import schema -from os import path -import sys - -class OperationalError(Exception): pass - -class UnknownDatabase: - connection = None - cursor = None - - def connect(self): - raise NotImplementedError - - def execute(self,cur,query,parm = None): - raise NotImplementedError - - def executemany(self,cur,query,parm): - raise NotImplementedError - - def get_new_connection(self): - raise NotImplementedError - - def close(self): - if self.cursor: - self.cursor.close() - if self.connection: - self.connection.close() - - -class Database(UnknownDatabase): - - def __init__(self): - self.structure_created = False - - def init_db(self): - for table in schema.db_schema: - for stmt in self.to_sql(table): - self.execute(stmt) - log.info(_("Database structure successfully created.")) - for query in schema.db_init_cmds: - self.execute(query) - log.info(_("Initial entries correctly inserted.")) - self.structure_created = True - - def verify_database_version(self): - try: - self.execute("SELECT value FROM variables\ - WHERE name = 'database_version'", raise_exception = True) - (db_version,) = self.cursor.fetchone() - db_version = int(db_version) - except OperationalError: - self.init_db() - else: - if schema.db_schema_version > db_version: - log.info(_("The database structure needs to be updated...")) - - base = path.dirname(__file__) - base_import = "deejayd.database.upgrade" - i = db_version+1 - while i < schema.db_schema_version+1: - db_file = "db_%d" % i - try: up = __import__(base_import+"."+db_file, {}, {}, base) - except ImportError: - err = _("Unable to upgrade database, have to quit") - log.err(err, True) - up.upgrade(self.cursor) - i += 1 - - self.connection.commit() - log.msg(_("The database structure has been updated")) - - # - # Common MediaDB requests - # - def remove_file(self,dir,f,table = "audio_library"): - query = "DELETE FROM "+table+" WHERE filename = %s AND dir = %s" - self.execute(query, (f,dir)) - - def erase_empty_dir(self, table = "audio_library"): - # get list of dir - query = "SELECT dir,filename FROM "+table+" WHERE type='directory'" - self.execute(query) - - for (dir,filename) in self.cursor.fetchall(): - query = "SELECT COUNT(*) FROM "+table+" WHERE type='file' AND dir\ - LIKE %s" - self.execute(query,(path.join(dir,filename)+'%%',)) - rs = self.cursor.fetchone() - if rs == (0,): # remove directory - query = "DELETE FROM "+table+" WHERE dir = %s AND filename = %s" - self.execute(query, (dir,filename)) - - return True - - def insert_dir(self,new_dir,table = "audio_library"): - query = "INSERT INTO "+table+" (dir,filename,type)\ - VALUES(%s,%s,'directory')" - self.execute(query, new_dir) - - def remove_dir(self,root,dir,table = "audio_library"): - query = "DELETE FROM "+table+" WHERE filename = %s AND dir = %s" - self.execute(query, (dir,root)) - # We also need to erase the content of this directory - query = "DELETE FROM "+table+" WHERE dir LIKE %s" - self.execute(query, (path.join(root,dir)+"%%",)) - - def insert_dirlink(self, new_dirlink, table="audio_library"): - query = "INSERT INTO "+table+" (dir,filename,type)\ - VALUES(%s,%s,'dirlink')" - self.execute(query, new_dirlink) - - def remove_dirlink(self, root, dirlink, table="audio_library"): - query = "DELETE FROM "+table+" WHERE filename = %s AND dir = %s\ - AND type = %s" - self.execute(query, (dirlink, root, 'dirlink', )) - - def is_dir_exist(self, root, dir, table = "audio_library"): - query = "SELECT * FROM "+table+" WHERE filename = %s AND dir = %s AND\ - type = 'directory'" - self.execute(query, (dir,root)) - - return len(self.cursor.fetchall()) - - def get_dir_info(self,dir,table = "audio_library"): - query = "SELECT * FROM "+table+" WHERE dir = %s\ - ORDER BY type,dir,filename" - self.execute(query,(dir,)) - - return self.cursor.fetchall() - - def get_file_info(self,file,table = "audio_library"): - query = "SELECT * FROM "+table+" WHERE dir = %s AND filename = %s" - self.execute(query,path.split(file)) - - return self.cursor.fetchall() - - def get_files(self,dir,table = "audio_library"): - query = "SELECT * FROM "+table+" WHERE dir = %s AND type = 'file'\ - ORDER BY dir,filename" - self.execute(query,(dir,)) - - return self.cursor.fetchall() - - def get_all_files(self,dir,table = "audio_library"): - query = "SELECT * FROM "+table+" WHERE dir LIKE %s AND type = 'file'\ - ORDER BY dir,filename" - self.execute(query,(dir+"%%",)) - - return self.cursor.fetchall() - - def get_all_dirs(self,dir,table = "audio_library"): - query = "SELECT * FROM "+table+\ - " WHERE dir LIKE %s AND type='directory' ORDER BY dir,filename" - self.execute(query,(dir+"%%",)) - - return self.cursor.fetchall() - - def get_all_dirlinks(self, dir, table='audio_library'): - query = "SELECT * FROM "+table+\ - " WHERE dir LIKE %s AND type='dirlink' ORDER BY dir,filename" - self.execute(query,(dir+"%%",)) - return self.cursor.fetchall() - - # - # Specific audio library - # - def search_audio_library(self,type,content): - if type != "all": - query = "SELECT * FROM audio_library WHERE type = 'file' AND "+\ - type+" LIKE %s ORDER BY dir, filename" - self.execute(query,('%%'+content+'%%',)) - else: - query = "SELECT * FROM audio_library WHERE type = 'file' AND \ - (genre LIKE %s OR title LIKE %s OR album LIKE %s OR \ - artist LIKE %s) ORDER BY dir, filename" - self.execute(query,('%%'+content+'%%','%%'+content+'%%', - '%%'+content+'%%','%%'+content+'%%')) - - return self.cursor.fetchall() - - def find_audio_library(self,type,content): - query = "SELECT * FROM audio_library WHERE type = 'file' AND "+\ - type+"= %s" - self.execute(query,(content,)) - - return self.cursor.fetchall() - - def insert_audio_file(self,dir,filename,fileInfo): - query = "INSERT INTO audio_library(type,dir,filename,title,artist,\ - album,genre,date,tracknumber,length,bitrate,replaygain_track_gain,\ - replaygain_track_peak)VALUES \ - ('file',%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)" - self.execute(query, (dir,filename,fileInfo["title"],\ - fileInfo["artist"],fileInfo["album"],fileInfo["genre"],\ - fileInfo["date"], fileInfo["tracknumber"],fileInfo["length"],\ - fileInfo["bitrate"],fileInfo["replaygain_track_gain"],\ - fileInfo["replaygain_track_peak"])) - - def update_audio_file(self,dir,filename,fileInfo): - query = "UPDATE audio_library SET title=%s,artist=%s,album=%s,genre=%s,\ - date=%s,tracknumber=%s,length=%s,bitrate=%s,\ - replaygain_track_gain=%s,replaygain_track_peak=%s\ - WHERE dir=%s AND filename=%s" - self.execute(query,(fileInfo["title"],fileInfo["artist"],\ - fileInfo["album"],fileInfo["genre"],fileInfo["date"],\ - fileInfo["tracknumber"],fileInfo["length"],fileInfo["bitrate"],\ - fileInfo["replaygain_track_gain"],\ - fileInfo["replaygain_track_peak"],dir,filename)) - - # - # Video MediaDB specific requests - # - def insert_video_file(self,dir,filename,fileInfo): - query = "INSERT INTO video_library(type,dir,filename,title,length,\ - videowidth,videoheight,subtitle) VALUES ('file',%s,%s,%s,%s,%s,%s,%s)" - self.execute(query, (dir,filename,\ - fileInfo["title"],fileInfo["length"],fileInfo["videowidth"],\ - fileInfo["videoheight"],fileInfo["subtitle"])) - - def update_video_file(self,dir,filename,fileInfo): - query = "UPDATE video_library SET title=%s,length=%s,videowidth=%s,\ - videoheight=%s,subtitle=%s WHERE dir=%s AND filename=%s" - self.execute(query,(fileInfo["title"],fileInfo["length"],\ - fileInfo["videowidth"],fileInfo["videoheight"],\ - fileInfo["subtitle"],dir,filename)) - - def update_video_subtitle(self,dir,filename,file_info): - query = "UPDATE video_library SET subtitle=%s \ - WHERE dir=%s AND filename=%s" - self.execute(query,(file_info["subtitle"],dir,filename)) - - def search_video_library(self,value): - query = "SELECT * FROM video_library WHERE type = 'file' AND title\ - LIKE %s ORDER BY dir,filename" - self.execute(query,('%%'+value+'%%',)) - - return self.cursor.fetchall() - # - # videolist requests - # - def get_videolist(self,name): - query = "SELECT p.position, l.dir, l.filename,\ - l.title, l.length, l.videowidth, l.videoheight, l.subtitle, l.id \ - FROM medialist p INNER JOIN video_library l \ - ON p.media_id = l.id WHERE p.name = %s ORDER BY p.position" - self.execute(query,(name,)) - return self.cursor.fetchall() - - # - # audiolist requests - # - def get_audiolist(self,name): - query = "SELECT l.id, l.dir, l.filename, l.type, l.title, l.artist,\ - l.album, l.genre, l.tracknumber, l.date, l.length, l.bitrate,\ - l.replaygain_track_gain, replaygain_track_peak, p.position\ - FROM medialist p INNER JOIN audio_library l\ - ON p.media_id = l.id WHERE p.name = %s ORDER BY p.position" - self.execute(query,(name,)) - return self.cursor.fetchall() - - # - # medialist requests - # - def delete_medialist(self,name): - self.execute("DELETE FROM medialist WHERE name = %s",(name,)) - self.connection.commit() - - def save_medialist(self,content,name): - values = [(name,s["pos"],s["media_id"]) for s in content] - query = "INSERT INTO medialist(name,position,media_id)\ - VALUES(%s,%s,%s)" - self.executemany(query,values) - self.connection.commit() - - def get_medialist_list(self): - self.execute("SELECT DISTINCT name FROM medialist ORDER BY name") - return self.cursor.fetchall() - - # - # Webradio requests - # - def get_webradios(self): - self.execute("SELECT wid, name, url FROM webradio ORDER BY wid") - return self.cursor.fetchall() - - def add_webradios(self,values): - query = "INSERT INTO webradio(wid,name,url)VALUES(%s,%s,%s)" - self.executemany(query,values) - self.connection.commit() - - def clear_webradios(self): - self.execute("DELETE FROM webradio") - self.connection.commit() - - # - # Stat requests - # - def record_mediadb_stats(self, type): - if type == "audio": - # Get the number of songs - self.execute("SELECT filename FROM audio_library\ - WHERE type = 'file'") - songs = len(self.cursor.fetchall()) - # Get the number of artist - self.execute("SELECT DISTINCT artist FROM audio_library\ - WHERE type = 'file'") - artists = len(self.cursor.fetchall()) - # Get the number of album - self.execute("SELECT DISTINCT album FROM audio_library\ - WHERE type = 'file'") - albums = len(self.cursor.fetchall()) - # Get the number of genre - self.execute("SELECT DISTINCT genre FROM audio_library\ - WHERE type = 'file'") - genres = len(self.cursor.fetchall()) - values = [(songs,"songs"),(artists,"artists"),(albums,"albums"),\ - (genres,"genres")] - elif type == "video": - # Get the number of video - self.execute("SELECT DISTINCT filename FROM video_library\ - WHERE type = 'file'") - values = [(len(self.cursor.fetchall()), "videos")] - - self.executemany("UPDATE stats SET value = %s WHERE name = %s",values) - - def set_update_time(self,type): - import time - self.execute("UPDATE stats SET value = %s WHERE name = %s",\ - (time.time(),type+"_library_update")) - - def get_update_time(self,type): - self.execute("SELECT value FROM stats WHERE name = %s",\ - (type+"_library_update",)) - (rs,) = self.cursor.fetchone() - return rs - - def get_stats(self): - self.execute("SELECT * FROM stats") - return self.cursor.fetchall() - - # - # State requests - # - def set_state(self,values): - self.executemany("UPDATE variables SET value = %s WHERE name = %s" \ - ,values) - self.connection.commit() - - def get_state(self,type): - self.execute("SELECT value FROM variables WHERE name = %s",(type,)) - (rs,) = self.cursor.fetchone() - - return rs - -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/dbobjects.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/dbobjects.py --- deejayd-0.7.2/deejayd/database/dbobjects.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/dbobjects.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,230 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +import re +from deejayd import mediafilters +from deejayd.database.querybuilders import EditRecordQuery + + +class _DBObject(object): + + def __init__(self): + self.id = None + + def save(self, db): + raise NotImplementedError + + +class NoneFilter(_DBObject): + + CLASS_TABLE = 'filters' + PRIMARY_KEY = 'filter_id' + TYPE = 'none' + + def __init__(self, none_filter = None): + super(NoneFilter, self).__init__() + self.id = None + + def restrict(self, query): + pass + + def save(self, db): + cursor = db.connection.cursor() + + query = EditRecordQuery(self.CLASS_TABLE) + query.add_value('type', self.TYPE) + if self.id: + query.set_update_id(self.PRIMARY_KEY, self.id) + cursor.execute(query.to_sql(), query.get_args()) + if not self.id: + self.id = db.connection.get_last_insert_id(cursor) + + cursor.close() + + return self.id + + +class _BasicFilter(mediafilters.BasicFilter, NoneFilter): + + TABLE = 'filters_basicfilters' + TYPE = 'basic' + + def __init__(self, basic_filter): + NoneFilter.__init__(self) + mediafilters.BasicFilter.__init__(self, basic_filter.tag, + basic_filter.pattern) + + def restrict(self, query): + query.join_on_tag(self.tag) + where_str, arg = self._match_tag() + query.append_where(where_str, (arg,)) + + def _match_tag(self, match_value): + raise NotImplementedError + + def save(self, db): + if not self.id: + new = True + else: + new = False + + super(_BasicFilter, self).save(db) + + cursor = db.connection.cursor() + + query = EditRecordQuery(self.TABLE) + if new: + query.add_value(self.PRIMARY_KEY, self.id) + else: + query.set_update_id(self.PRIMARY_KEY, self.id) + query.add_value('tag', self.tag) + query.add_value('operator', self.get_name()) + query.add_value('pattern', self.pattern) + cursor.execute(query.to_sql(), query.get_args()) + + cursor.close() + + return self.id + + +class Equals(mediafilters.Equals, _BasicFilter): + + def _match_tag(self): + return "(%s.value = " % (self.tag,) + "%s)", self.pattern + + +class NotEquals(mediafilters.NotEquals, _BasicFilter): + + def _match_tag(self): + return "(%s.value != " % (self.tag,) + "%s)", self.pattern + + +class Contains(mediafilters.Contains, _BasicFilter): + + def _match_tag(self): + return "(%s.value LIKE " % (self.tag,) + "%s)", "%%"+self.pattern+"%%" + + +class NotContains(mediafilters.NotContains, _BasicFilter): + + def _match_tag(self): + return "(%s.value NOT LIKE " % (self.tag,)+"%s)", "%%"+self.pattern+"%%" + + +class Higher(mediafilters.Higher, _BasicFilter): + + def _match_tag(self): + return "(%s.value >= " % (self.tag,)+"%s)", self.pattern + + +class Lower(mediafilters.Lower, _BasicFilter): + + def _match_tag(self): + return "(%s.value <= " % (self.tag,)+"%s)", self.pattern + + +class Regexi(mediafilters.Regexi, _BasicFilter): + # FIXME : to implement some day + pass + + +class _ComplexFilter(mediafilters.ComplexFilter, NoneFilter): + + TABLE = 'filters_complexfilters' + TYPE = 'complex' + + def __init__(self, complex_filter): + self.sqlfilterlist = map(SQLizer().translate, complex_filter.filterlist) + arglist = [self] + self.sqlfilterlist + mediafilters.ComplexFilter.__init__(*arglist) + + def restrict(self, query): + if self.filterlist: + where_str, args = self._build_wheres(query) + query.append_where(where_str, args) + + def _build_wheres(self, query): + if not self.filterlist: return "(1)", [] + wheres, wheres_args = [], [] + for filter in self.filterlist: + if filter.type == "basic": + query.join_on_tag(filter.tag) + where_query, arg = filter._match_tag() + wheres.append(where_query) + wheres_args.append(arg) + else: # complex filter + where_query, args = filter._build_wheres(query) + wheres.append(where_query) + wheres_args.extend(args) + return "(%s)" % (self.combinator.join(wheres),), wheres_args + + def save(self, db): + if not self.id: + new = True + else: + new = False + super(_ComplexFilter, self).save(db) + + cursor = db.connection.cursor() + + query = EditRecordQuery(_ComplexFilter.TABLE) + if new: + query.add_value(self.PRIMARY_KEY, self.id) + else: + query.set_update_id(self.PRIMARY_KEY, self.id) + query.add_value('combinator', self.get_name()) + cursor.execute(query.to_sql(), query.get_args()) + + for subfilter in self.sqlfilterlist: + subfilter_id = subfilter.save(db) + query = EditRecordQuery('filters_complexfilters_subfilters') + query.add_value('complexfilter_id', self.id) + query.add_value('filter_id', subfilter_id) + cursor.execute(query.to_sql(), query.get_args()) + + cursor.close() + + return self.id + + +class And(mediafilters.And, _ComplexFilter): combinator = ' AND ' +class Or(mediafilters.Or, _ComplexFilter): combinator = ' OR ' + + +class SQLizer(object): + + translations = { + mediafilters.Equals : Equals, + mediafilters.NotEquals : NotEquals, + mediafilters.Contains : Contains, + mediafilters.NotContains : NotContains, + mediafilters.Regexi : Regexi, + mediafilters.Higher : Higher, + mediafilters.Lower : Lower, + mediafilters.And : And, + mediafilters.Or : Or, + } + + def translate(self, object): + if object == None: + return NoneFilter(object) + object_class = SQLizer.translations[object.__class__] + return object_class(object) + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/__init__.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/__init__.py --- deejayd-0.7.2/deejayd/database/__init__.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/__init__.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -16,19 +16,30 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from os import path from ConfigParser import NoOptionError from deejayd.ui import log +from deejayd.database.queries import DatabaseQueries +from deejayd.database import schema + +DatabaseError = None def init(config): + global DatabaseError + db_type = config.get("database","db_type") + db_name = config.get("database","db_name") + try: backend = __import__('deejayd.database.backends.%s' % db_type,\ + {}, {}, ['']) + except ImportError, ex: + log.err(_(\ + "You chose a database which is not supported. see config file. %s")\ + % str(ex), fatal = True) + DatabaseError = backend.DatabaseError if db_type == "sqlite": - db_file = config.get("database","db_name") - - from deejayd.database.sqlite import SqliteDatabase - return SqliteDatabase(db_file) + connection = backend.DatabaseWrapper(db_name) elif db_type == "mysql": - db_name = config.get("database","db_name") db_user = config.get("database","db_user") db_password = config.get("database","db_password") try: db_host = config.get("database","db_host") @@ -38,12 +49,47 @@ except (NoOptionError, ValueError): db_port = 3306 - from deejayd.database.mysql import MysqlDatabase - return MysqlDatabase(db_name, db_user, db_password, db_host, db_port) + connection = backend.DatabaseWrapper(db_name, db_user, db_password,\ + db_host, db_port) + + # verify database version + cursor = connection.cursor() + try: + cursor.execute("SELECT value FROM variables\ + WHERE name = 'database_version'") + (db_version,) = cursor.fetchone() + db_version = int(db_version) + except DatabaseError: # initailise db + for table in schema.db_schema: + for stmt in backend.to_sql(table): + cursor.execute(stmt) + for query in backend.custom_queries: + cursor.execute(query) + log.info(_("Database structure successfully created.")) + for query in schema.db_init_cmds: + cursor.execute(query) + log.info(_("Initial entries correctly inserted.")) + + DatabaseQueries.structure_created = True + connection.commit() else: - log.err(_("You chose a database which is not supported.\ - Verify your config file."), fatal = True) + if schema.db_schema_version > db_version: + log.info(_("The database structure needs to be updated...")) + + base = path.dirname(__file__) + base_import = "deejayd.database.upgrade" + i = db_version+1 + while i < schema.db_schema_version+1: + db_file = "db_%d" % i + try: up = __import__(base_import+"."+db_file, {}, {}, base) + except ImportError: + err = _("Unable to upgrade database, have to quit") + log.err(err, True) + up.upgrade(cursor, backend, config) + i += 1 + connection.commit() - return database + cursor.close() + return DatabaseQueries(connection) # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/mysql.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/mysql.py --- deejayd-0.7.2/deejayd/database/mysql.py 2008-05-14 22:50:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/mysql.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,127 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -from deejayd.ui import log -from deejayd.database._base import Database, OperationalError -import MySQLdb as mysql - - -class MysqlDatabase(Database): - - def __init__(self, db_name, db_user, db_password, db_host, db_port): - Database.__init__(self) - self.db_name = db_name - self.db_user = db_user - self.db_password = db_password - self.db_host = db_host - self.db_port = db_port - - self.connection = None - self.cursor = None - - def __connect(self): - if self.connection is None: - try: - self.connection = mysql.connect(db=self.db_name,\ - user=self.db_user, passwd=self.db_password,\ - host=self.db_host, port=self.db_port, use_unicode=False) - self.cursor = self.connection.cursor() - except mysql.DatabaseError, err: - error = _("Could not connect to MySQL server %s." % err) - log.err(error, fatal = True) - - def connect(self): - self.__connect() - self.verify_database_version() - - def get_new_connection(self): - return MysqlDatabase(self.db_name, self.db_user, self.db_password, \ - self.db_host, self.db_port) - - def __valid_connection(self): - try: self.connection.ping() - except mysql.DatabaseError: - self.close() - log.info(_("Try Mysql reconnection")) - self.__connect() - return False - return True - - def __execute(self, func_name, query, parm, raise_exception = False): - func = getattr(self.cursor, func_name) - try: func(query,parm) - except mysql.DatabaseError, err: - if self.__valid_connection(): - log.err(_("Unable to execute database request '%s': %s") \ - % (query, err)) - if raise_exception: raise OperationalError - self.__execute(func_name, query, parm, raise_exception) - - def execute(self, query, parm = None, raise_exception = False): - self.__execute("execute", query, parm, raise_exception) - - def executemany(self, query, parm = []): - if parm == []: return # no request to execute - self.__execute("executemany", query, parm) - - def __collist(self, table, columns): - cols = [] - limit = 333 / len(columns) - if limit > 255: - limit = 255 - for c in columns: - name = '`%s`' % c - table_col = filter((lambda x: x.name == c), table.columns) - if len(table_col) == 1 and table_col[0].type.lower() == 'text': - name += '(%s)' % limit - # For non-text columns, we simply throw away the extra bytes. - # That could certainly be optimized better, but for now let's KISS. - cols.append(name) - return ','.join(cols) - - def to_sql(self, table): - sql = ['CREATE TABLE %s (' % table.name] - coldefs = [] - for column in table.columns: - ctype = column.type - if column.auto_increment: - ctype = 'INT UNSIGNED NOT NULL AUTO_INCREMENT' - # Override the column type, as a text field cannot - # use auto_increment. - column.type = 'int' - coldefs.append(' `%s` %s' % (column.name, ctype)) - if len(table.key) > 0: - coldefs.append(' PRIMARY KEY (%s)' % - self.__collist(table, table.key)) - sql.append(',\n'.join(coldefs) + '\n)') - yield '\n'.join(sql) - - for index in table.indices: - yield 'CREATE INDEX %s_%s_idx ON %s (%s);' % (table.name, - '_'.join(index.columns), table.name, - self.__collist(table, index.columns)) - - def close(self): - if self.cursor is not None: - self.cursor.close() - self.cursor = None - if self.connection is not None: - self.connection.close() - self.connection = None - -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/queries.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/queries.py --- deejayd-0.7.2/deejayd/database/queries.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/queries.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,687 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os, sys, time, base64 +from deejayd.mediafilters import * +from deejayd.ui import log +from deejayd.database.querybuilders import * +from deejayd.database.dbobjects import SQLizer + + +############################################################################ +class MediaFile(dict): + + def __init__(self, db, file_id): + self.db = db + self["media_id"] = file_id + + def set_info(self, key, value): + self.set_infos({key: value}) + + def set_infos(self, infos): + self.db.set_media_infos(self["media_id"], infos) + self.db.connection.commit() + self.update(infos) + + def played(self): + played = int(self["playcount"]) + 1 + timestamp = int(time.time()) + self.set_infos({"playcount":str(played), "lastplayed":str(timestamp)}) + + def skip(self): + skip = int(self["skipcount"]) + 1 + self.set_info("skipcount", str(skip)) + + def get_cover(self): + if self["type"] != "song": + raise AttributeError + try: (id, mime, cover) = self.db.get_file_cover(self["media_id"]) + except TypeError: + raise AttributeError + return {"cover": base64.b64decode(cover), "id":id, "mime": mime} + + def replay_gain(self): + """Return the recommended Replay Gain scale factor.""" + try: + db = float(self["replaygain_track_gain"].split()[0]) + peak = self["replaygain_track_peak"] and\ + float(self["replaygain_track_peak"]) or 1.0 + except (KeyError, ValueError, IndexError): + return 1.0 + else: + scale = 10.**(db / 20) + if scale * peak > 1: + scale = 1.0 / peak # don't clip + return min(15, scale) + +############################################################################ + + +class DatabaseQueries(object): + structure_created = False + + def __init__(self, connection): + self.connection = connection + self.sqlizer = SQLizer() + + # + # MediaDB requests + # + @query_decorator("lastid") + def insert_file(self, cursor, dir, filename, lastmodified): + query = "INSERT INTO library \ + (directory,name,lastmodified)VALUES(%s, %s, %s)" + cursor.execute(query, (dir, filename, lastmodified)) + + @query_decorator("none") + def update_file(self, cursor, id, lastmodified): + query = "UPDATE library SET lastmodified = %s WHERE id = %s" + cursor.execute(query, (lastmodified, id)) + + @query_decorator("rowcount") + def set_media_infos(self, cursor, file_id, infos, allow_create = True): + if allow_create: + query = "REPLACE INTO media_info (id,ikey,value)VALUES(%s,%s,%s)" + entries = [(file_id, k, v) for k, v in infos.items()] + else: + query = "UPDATE media_info SET value=%s WHERE id=%s and ikey=%s" + entries = [(v, file_id, k) for k, v in infos.items()] + cursor.executemany(query, entries) + + @query_decorator("none") + def remove_file(self, cursor, id): + queries = [ + "DELETE FROM library WHERE id = %s", + "DELETE FROM media_info WHERE id = %s", + "DELETE FROM medialist_libraryitem WHERE libraryitem_id = %s", + ] + for q in queries: cursor.execute(q, (id,)) + + @query_decorator("fetchone") + def is_file_exist(self, cursor, dirname, filename, type = "audio"): + query = "SELECT d.id, l.id \ + FROM library l JOIN library_dir d ON d.id=l.directory\ + WHERE l.name = %s AND d.name = %s AND d.lib_type = %s" + cursor.execute(query,(filename, dirname, type)) + + @query_decorator("lastid") + def insert_dir(self, cursor, new_dir, type="audio"): + query = "INSERT INTO library_dir (name,type,lib_type)VALUES(%s,%s,%s)" + cursor.execute(query, (new_dir, 'directory', type)) + + @query_decorator("none") + def remove_dir(self, cursor, id): + cursor.execute("SELECT id FROM library WHERE directory = %s", (id,)) + for (id,) in cursor.fetchall(): + self.remove_file(id) + cursor.execute("DELETE FROM library_dir WHERE id = %s", (id,)) + + @query_decorator("none") + def remove_recursive_dir(self, cursor, dir, type="audio"): + files = self.get_all_files(dir, type) + for file in files: self.remove_file(file[2]) + cursor.execute("DELETE FROM library_dir\ + WHERE name LIKE %s AND lib_type = %s", (dir+u"%%", type)) + return [f[2] for f in files] + + @query_decorator("custom") + def is_dir_exist(self, cursor, dirname, type): + cursor.execute("SELECT id FROM library_dir\ + WHERE name=%s AND lib_type=%s", (dirname, type)) + rs = cursor.fetchone() + return rs and rs[0] + + @query_decorator("none") + def insert_dirlink(self, cursor, new_dirlink, type="audio"): + query = "INSERT INTO library_dir (name,type,lib_type)VALUES(%s,%s,%s)" + cursor.execute(query, (new_dirlink, "dirlink", type)) + + @query_decorator("none") + def remove_dirlink(self, cursor, dirlink, type="audio"): + query = "DELETE FROM library_dir\ + WHERE name = %s AND\ + type = 'dirlink' AND\ + lib_type = %s" + cursor.execute(query, (dirlink, type)) + + @query_decorator("fetchall") + def get_dir_list(self, cursor, dir, t = "audio"): + query = "SELECT DISTINCT id, name FROM library_dir\ + WHERE name LIKE %s AND\ + lib_type = %s AND\ + type = 'directory'\ + ORDER BY name" + term = dir == unicode("") and u"%%" or dir+unicode("/%%") + cursor.execute(query, (term, t)) + + @query_decorator("fetchone") + def get_file_info(self, cursor, file_id, info_type): + query = "SELECT value FROM media_info WHERE id = %s AND ikey = %s" + cursor.execute(query, (file_id, info_type)) + + @query_decorator("fetchall") + def get_all_files(self, cursor, dir, type = "audio"): + query = "SELECT DISTINCT d.id, d.name, l.id, l.name, l.lastmodified\ + FROM library l JOIN library_dir d ON d.id=l.directory\ + WHERE d.name LIKE %s AND d.lib_type = %s ORDER BY d.name,l.name" + cursor.execute(query,(dir+u"%%", type)) + + @query_decorator("fetchall") + def get_all_dirs(self, cursor, dir, type = "audio"): + query = "SELECT DISTINCT id,name FROM library_dir\ + WHERE name LIKE %s AND type='directory' AND lib_type = %s\ + ORDER BY name" + cursor.execute(query,(dir+u"%%", type)) + + @query_decorator("fetchall") + def get_all_dirlinks(self, cursor, dirlink, type = 'audio'): + query = "SELECT DISTINCT id,name FROM library_dir\ + WHERE name LIKE %s AND type='dirlink' AND lib_type = %s\ + ORDER BY name" + cursor.execute(query,(dirlink+u"%%", type)) + + def _medialist_answer(self, answer, infos = []): + files = [] + for m in answer: + current = MediaFile(self, m[0]) + for index, attr in enumerate(infos): + current[attr] = m[index+1] + files.append(current) + + return files + + def _build_media_query(self, infos_list): + selectquery = "i.id" + joinquery = "" + for index, key in enumerate(infos_list): + selectquery += ",i%d.value" % index + joinquery += " JOIN media_info i%d ON i%d.id=i.id AND\ + i%d.ikey='%s'" % (index, index, index, key) + return selectquery, joinquery + + @query_decorator("medialist") + def get_dir_content(self, cursor, dir, infos = [], type = "audio"): + selectquery, joinquery = self._build_media_query(infos) + query = "SELECT DISTINCT "+ selectquery +\ + " FROM library l JOIN library_dir d ON d.id=l.directory\ + JOIN media_info i ON i.id=l.id"\ + + joinquery+\ + " WHERE d.name = %s AND d.lib_type = %s ORDER BY d.name,l.name" + cursor.execute(query,(dir, type)) + + @query_decorator("medialist") + def get_file(self, cursor, dir, file, infos = [], type = "audio"): + selectquery, joinquery = self._build_media_query(infos) + query = "SELECT DISTINCT "+ selectquery +\ + " FROM library l JOIN library_dir d ON d.id=l.directory\ + JOIN media_info i ON i.id=l.id"\ + + joinquery+\ + " WHERE d.name = %s AND l.name = %s AND d.lib_type = %s" + cursor.execute(query, (dir, file, type)) + + @query_decorator("medialist") + def get_file_withids(self, cursor, file_ids, infos=[], type="audio"): + selectquery, joinquery = self._build_media_query(infos) + query = "SELECT DISTINCT "+ selectquery +\ + " FROM library l JOIN library_dir d ON d.id=l.directory\ + JOIN media_info i ON i.id=l.id"\ + + joinquery+\ + " WHERE l.id IN (%s) AND d.lib_type = '%s'" % \ + (",".join(map(str,file_ids)), type) + cursor.execute(query) + + @query_decorator("medialist") + def get_alldir_files(self, cursor, dir, infos = [], type = "audio"): + selectquery, joinquery = self._build_media_query(infos) + query = "SELECT DISTINCT "+ selectquery +\ + " FROM library l JOIN library_dir d ON d.id=l.directory\ + JOIN media_info i ON i.id=l.id"\ + + joinquery+\ + " WHERE d.name LIKE %s AND d.lib_type = %s ORDER BY d.name,l.name" + cursor.execute(query,(dir+u"%%", type)) + + @query_decorator("fetchall") + def get_dircontent_id(self, cursor, dir, type): + query = "SELECT l.id\ + FROM library l JOIN library_dir d ON l.directory = d.id\ + WHERE d.lib_type = %s AND d.name = %s" + cursor.execute(query,(type, dir)) + + @query_decorator("fetchall") + def search_id(self, cursor, key, value): + query = "SELECT DISTINCT id FROM media_info WHERE ikey=%s AND value=%s" + cursor.execute(query,(key, value)) + + @query_decorator("medialist") + def search(self, cursor, filter, infos = [], orders = [], limit = None): + filter = self.sqlizer.translate(filter) + query = MediaSelectQuery() + query.select_id() + for tag in infos: + query.select_tag(tag) + filter.restrict(query) + for (tag, direction) in orders: + query.order_by_tag(tag, direction == "descending") + query.set_limit(limit) + cursor.execute(query.to_sql(), query.get_args()) + + @query_decorator("fetchall") + def list_tags(self, cursor, tag, filter): + filter = self.sqlizer.translate(filter) + query = MediaSelectQuery() + query.select_tag(tag) + filter.restrict(query) + query.order_by_tag(tag) + cursor.execute(query.to_sql(), query.get_args()) + + @query_decorator("fetchall") + def get_media_keys(self, cursor, type): + query = "SELECT DISTINCT m.ikey FROM media_info m\ + JOIN media_info m2 ON m.id = m2.id\ + WHERE m2.ikey='type' AND m2.value=%s" + cursor.execute(query, (type,)) +# +# Post update action +# + @query_decorator("none") + def set_variousartist_tag(self, cursor, fid, file_info): + query = "SELECT DISTINCT m.id,m.value,m3.value FROM media_info m\ + JOIN media_info m2 ON m.id = m2.id\ + JOIN media_info m3 ON m.id = m3.id\ + WHERE m.ikey='various_artist' AND m2.ikey='album'\ + AND m2.value=%s AND m3.ikey='artist'" + cursor.execute(query, (file_info["album"],)) + try: (id, various_artist, artist) = cursor.fetchone() + except TypeError: # first song of this album + return + else: + need_update = False + if various_artist == "__various__": + need_update, ids = True, [(fid,)] + elif artist != file_info["artist"]: + need_update = True + cursor.execute("SELECT id FROM media_info\ + WHERE ikey='album' AND value=%s", (file_info["album"],)) + ids = cursor.fetchall() + + if need_update: + cursor.executemany("UPDATE media_info SET value = '__various__'\ + WHERE ikey='various_artist' AND id = %s", ids) + + @query_decorator("none") + def erase_empty_dir(self, cursor, type = "audio"): + cursor.execute("SELECT DISTINCT name FROM library_dir\ + WHERE lib_type=%s", (type,)) + for (dirname,) in cursor.fetchall(): + rs = self.get_all_files(dirname, type) + if len(rs) == 0: # remove directory + cursor.execute("DELETE FROM library_dir WHERE name = %s",\ + (dirname,)) + + @query_decorator("none") + def update_stats(self, cursor, type = "audio"): + # record mediadb stats + query = "UPDATE stats SET value = \ + (SELECT COUNT(DISTINCT m.value) FROM media_info m JOIN media_info m2\ + ON m.id = m2.id WHERE m.ikey=%s AND m2.ikey='type' AND m2.value=%s)\ + WHERE name = %s" + if type == "audio": + values = [("uri", "song", "songs"), + ("artist", "song", "artists"), + ("genre", "song", "genres"), + ("album", "song", "albums")] + elif type == "video": + values = [("uri", "video", "videos")] + cursor.executemany(query, values) + + # update last updated stats + cursor.execute("UPDATE stats SET value = %s WHERE name = %s",\ + (time.time(),type+"_library_update")) + + # + # cover requests + # + @query_decorator("fetchone") + def get_file_cover(self, cursor, file_id, source = False): + var = source and "source" or "image" + query = "SELECT c.id, c.mime_type, c." + var +\ + " FROM media_info m JOIN cover c\ + ON m.ikey = 'cover' AND m.value = c.id\ + WHERE m.id = %s" + cursor.execute(query, (file_id,)) + + @query_decorator("fetchone") + def is_cover_exist(self, cursor, source): + query = "SELECT id,lmod FROM cover WHERE source=%s" + cursor.execute(query, (source,)) + + @query_decorator("lastid") + def add_cover(self, cursor, source, mime, image): + query = "INSERT INTO cover (source,mime_type,lmod,image)\ + VALUES(%s,%s,%s,%s)" + cursor.execute(query, (source, mime, time.time(), image)) + + @query_decorator("none") + def update_cover(self, cursor, id, mime, new_image): + query = "UPDATE cover SET mime_type = %s lmod = %s, image = %s\ + WHERE id=%s" + cursor.execute(query, (mime, time.time(), new_image, id)) + + @query_decorator("none") + def remove_cover(self, cursor, id): + query = "DELETE FROM cover WHERE id=%s" + cursor.execute(query, (id,)) + + @query_decorator("none") + def remove_unused_cover(self, cursor): + query = "DELETE FROM cover WHERE id NOT IN \ + (SELECT DISTINCT value FROM media_info WHERE ikey = 'cover')" + cursor.execute(query) + + # + # common medialist requests + # + @query_decorator("fetchall") + def get_medialist_list(self, cursor): + query = SimpleSelect('medialist') + query.select_column('id', 'name', 'type') + query.order_by('name') + cursor.execute(query.to_sql(), query.get_args()) + + @query_decorator("custom") + def get_medialist_id(self, cursor, pl_name, pl_type = 'static'): + query = SimpleSelect('medialist') + query.select_column('id') + query.append_where("name = %s", (pl_name, )) + query.append_where("type = %s", (pl_type, )) + cursor.execute(query.to_sql(), query.get_args()) + + ans = cursor.fetchone() + if ans is None: raise ValueError + return ans[0] + + @query_decorator("fetchone") + def is_medialist_exists(self, cursor, pl_id): + query = SimpleSelect('medialist') + query.select_column('id', 'name', 'type') + query.append_where("id = %s", (pl_id, )) + cursor.execute(query.to_sql(), query.get_args()) + + @query_decorator("none") + def delete_medialist(self, cursor, ml_id): + try: ml_id, name, type = self.is_medialist_exists(ml_id) + except TypeError: + return + if type == "static": + query = "DELETE FROM medialist_libraryitem WHERE medialist_id = %s" + cursor.execute(query, (ml_id,)) + elif type == "magic": + for (filter_id,) in self.__get_medialist_filterids(cursor, ml_id): + self.delete_filter(cursor, filter_id) + cursor.execute(\ + "DELETE FROM medialist_filters WHERE medialist_id=%s", (ml_id,)) + # delete medialist properties + cursor.execute(\ + "DELETE FROM medialist_property WHERE medialist_id=%s", (ml_id,)) + # delete medialist sort + cursor.execute(\ + "DELETE FROM medialist_sorts WHERE medialist_id=%s", (ml_id,)) + cursor.execute("DELETE FROM medialist WHERE id = %s", (ml_id,)) + self.connection.commit() + + def get_filter(self, cursor, id): + try: filter_type = self.__get_filter_type(cursor, id) + except ValueError: + return None + if filter_type == 'basic': + return self.__get_basic_filter(cursor, id) + elif filter_type == 'complex': + return self.__get_complex_filter(cursor, id) + + def delete_filter(self, cursor, filter_id): + try: filter_type = self.__get_filter_type(cursor, filter_id) + except ValueError: + return None + + if filter_type == 'basic': + cursor.execute("DELETE FROM filters_basicfilters\ + WHERE filter_id = %s", (filter_id,)) + elif filter_type == 'complex': + # get filters id associated with this filter + query = SimpleSelect('filters_complexfilters_subfilters') + query.select_column('filter_id') + query.append_where("complexfilter_id = %s", (filter_id, )) + cursor.execute(query.to_sql(), query.get_args()) + + for (id,) in cursor.fetchall(): + self.delete_filter(cursor, id) + cursor.execute("DELETE FROM filters_complexfilters_subfilters \ + WHERE complexfilter_id = %s AND filter_id = %s",\ + (filter_id, id)) + cursor.execute("DELETE FROM filters_complexfilters \ + WHERE filter_id = %s",(filter_id,)) + + cursor.execute("DELETE FROM filters WHERE filter_id = %s",(filter_id,)) + + def __get_filter_type(self, cursor, filter_id): + query = SimpleSelect('filters') + query.select_column('type') + query.append_where("filter_id = %s", (filter_id, )) + cursor.execute(query.to_sql(), query.get_args()) + record = cursor.fetchone() + + if not record: raise ValueError + return record[0] + + def __get_basic_filter(self, cursor, id): + query = SimpleSelect('filters_basicfilters') + query.select_column('tag', 'operator', 'pattern') + query.append_where("filter_id = %s", (id, )) + cursor.execute(query.to_sql(), query.get_args()) + record = cursor.fetchone() + + if record: + bfilter_class = NAME2BASIC[record[1]] + f = bfilter_class(record[0], record[2]) + return f + + def __get_complex_filter(self, cursor, id): + query = SimpleSelect('filters_complexfilters') + query.select_column('combinator') + query.append_where("filter_id = %s", (id, )) + cursor.execute(query.to_sql(), query.get_args()) + record = cursor.fetchone() + + if record: + cfilter_class = NAME2COMPLEX[record[0]] + query = SimpleSelect('filters_complexfilters_subfilters') + query.select_column('filter_id') + query.append_where("complexfilter_id = %s", (id, )) + cursor.execute(query.to_sql(), query.get_args()) + sf_records = cursor.fetchall() + filterlist = [] + for sf_record in sf_records: + sf_id = sf_record[0] + filterlist.append(self.get_filter(cursor, sf_id)) + cfilter = cfilter_class(*filterlist) + return cfilter + + def __get_medialist_filterids(self, cursor, ml_id): + query = SimpleSelect('medialist_filters') + query.select_column('filter_id') + query.append_where("medialist_id = %s", (ml_id, )) + cursor.execute(query.to_sql(), query.get_args()) + + return cursor.fetchall() + + def __add_medialist_filters(self, cursor, pl_id, filters): + filter_ids = [] + for filter in filters: + filter_id = self.sqlizer.translate(filter).save(self) + if filter_id: filter_ids.append((pl_id, filter_id)) + cursor.executemany("INSERT INTO medialist_filters\ + (medialist_id,filter_id)VALUES(%s,%s)", filter_ids) + + @query_decorator("custom") + def get_magic_medialist_filters(self, cursor, ml_id): + rs = self.__get_medialist_filterids(cursor, ml_id) + if not rs: return [] + filters = [] + for (filter_id,) in rs: + filter = self.get_filter(cursor, filter_id) + if filter: filters.append(filter) + return filters + + @query_decorator("custom") + def set_magic_medialist_filters(self, cursor, pl_name, filters): + slt_query = "SELECT id FROM medialist WHERE name=%s AND type = 'magic'" + cursor.execute(slt_query, (pl_name,)) + rs = cursor.fetchone() + if not rs: + query = "INSERT INTO medialist (name,type)VALUES(%s,'magic')" + cursor.execute(query, (pl_name,)) + id = self.connection.get_last_insert_id(cursor) + else: (id,) = rs + + for (filter_id,) in self.__get_medialist_filterids(cursor, id): + self.delete_filter(cursor, filter_id) + cursor.execute(\ + "DELETE FROM medialist_filters WHERE medialist_id=%s", (id,)) + + self.__add_medialist_filters(cursor, id, filters) + self.connection.commit() + return id + + @query_decorator("none") + def add_magic_medialist_filters(self, cursor, pl_id, filters): + self.__add_medialist_filters(cursor, pl_id, filters) + self.connection.commit() + + @query_decorator("fetchall") + def get_magic_medialist_sorts(self, cursor, ml_id): + query = "SELECT tag,direction FROM medialist_sorts\ + WHERE medialist_id = %s" + cursor.execute(query, (ml_id,)) + + @query_decorator("none") + def set_magic_medialist_sorts(self, cursor, ml_id, sorts): + # first, delete all previous sort for this medialist + cursor.execute("DELETE FROM medialist_sorts WHERE medialist_id=%s",\ + (ml_id,)) + cursor.executemany("INSERT INTO medialist_sorts\ + (medialist_id,tag,direction)VALUES(%s,%s,%s)",\ + [ (ml_id, tag, direction) for (tag, direction) in sorts]) + self.connection.commit() + + @query_decorator("fetchall") + def get_magic_medialist_properties(self, cursor, ml_id): + query = "SELECT ikey,value FROM medialist_property\ + WHERE medialist_id = %s" + cursor.execute(query, (ml_id,)) + + @query_decorator("none") + def set_magic_medialist_property(self, cursor, ml_id, key, value): + cursor.execute("REPLACE INTO medialist_property\ + (medialist_id,ikey,value)VALUES(%s,%s,%s)", (ml_id, key, value)) + self.connection.commit() + +###################################### +###### Static medialist queries ###### +###################################### + @query_decorator("none") + def add_to_static_medialist(self, cursor, ml_id, media_ids): + query = "INSERT INTO medialist_libraryitem\ + (medialist_id, libraryitem_id) VALUES(%s,%s)" + cursor.executemany(query, [(ml_id, mid) for mid in media_ids]) + + @query_decorator("medialist") + def get_static_medialist(self, cursor, ml_id, infos = []): + selectquery, joinquery = self._build_media_query(infos) + query = "SELECT DISTINCT "+ selectquery + ", mi.position " +\ + " FROM medialist m JOIN medialist_libraryitem mi\ + ON m.id = mi.medialist_id\ + JOIN media_info i ON i.id=mi.libraryitem_id"\ + + joinquery+\ + " WHERE m.id = %s AND m.type = 'static' ORDER BY mi.position" + cursor.execute(query,(ml_id,)) + + @query_decorator("custom") + def set_static_medialist(self, cursor, name, content): + slt_query = "SELECT id FROM medialist WHERE name=%s AND type = 'static'" + cursor.execute(slt_query, (name,)) + rs = cursor.fetchone() + if not rs: + query = "INSERT INTO medialist (name,type)VALUES(%s,'static')" + cursor.execute(query, (name,)) + id = self.connection.get_last_insert_id(cursor) + else: (id,) = rs + + cursor.execute(\ + "DELETE FROM medialist_libraryitem WHERE medialist_id = %s",(id,)) + values = [(id, s["media_id"]) for s in content] + query = "INSERT INTO medialist_libraryitem(medialist_id,libraryitem_id)\ + VALUES(%s,%s)" + cursor.executemany(query,values) + self.connection.commit() + # return id of the playlist + return id + + # + # Webradio requests + # + @query_decorator("fetchall") + def get_webradios(self, cursor): + cursor.execute("SELECT wid, name, url FROM webradio ORDER BY wid") + + @query_decorator("none") + def add_webradios(self, cursor, values): + query = "INSERT INTO webradio(wid,name,url)VALUES(%s,%s,%s)" + cursor.executemany(query,values) + self.connection.commit() + + @query_decorator("none") + def clear_webradios(self, cursor): + cursor.execute("DELETE FROM webradio") + self.connection.commit() + + # + # Stat requests + # + @query_decorator("fetchall") + def get_stats(self, cursor): + cursor.execute("SELECT * FROM stats") + + # + # State requests + # + @query_decorator("none") + def set_state(self, cursor, values): + cursor.executemany("UPDATE variables SET value = %s WHERE name = %s" \ + ,values) + self.connection.commit() + + @query_decorator("custom") + def get_state(self, cursor, type): + cursor.execute("SELECT value FROM variables WHERE name = %s",(type,)) + (rs,) = cursor.fetchone() + return rs + + def close(self): + self.connection.close() + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/querybuilders.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/querybuilders.py --- deejayd-0.7.2/deejayd/database/querybuilders.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/querybuilders.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,179 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +class _DBQuery(object): + + def __init__(self, table_name): + self.table_name = table_name + + def get_args(self): + raise NotImplementedError + + def to_sql(self): + return str(self) + + +class SimpleSelect(_DBQuery): + + def __init__(self, table_name): + super(SimpleSelect, self).__init__(table_name) + self.selects = [] + self.orders = [] + self.wheres, self.wheres_args = [], [] + + def select_column(self, *__args, **__kw): + for col in __args: + self.selects.append("%s.%s" % (self.table_name, col)) + + def order_by(self, column, desc = False): + self.orders.append("%s.%s" % (self.table_name, column)) + + def append_where(self, where_query, args): + self.wheres.append(where_query) + self.wheres_args.extend(args) + + def get_args(self): + return self.wheres_args + + def __str__(self): + return "SELECT DISTINCT %s FROM %s WHERE %s"\ + % ( ', '.join(self.selects), + self.table_name, + ' AND '.join(self.wheres) or 1, + ) + +class MediaSelectQuery(SimpleSelect): + + def __init__(self): + super(MediaSelectQuery, self).__init__('library') + self.joins = [] + self.limit = None + self.__joined_tags = [] + self.id = False + + def select_id(self): + self.id = True + + def select_column(self, column_name, table_name=None): + if not table_name: + table_name = self.table_name + self.selects.append("%s.%s" % (table_name, column_name)) + + def select_tag(self, tagname): + self.select_column('value', tagname) + self.join_on_tag(tagname) + + def order_by_tag(self, tagname, desc = False): + order = "%s.value" % tagname + if desc: order = "%s DESC" % order + self.orders.append(order) + self.join_on_tag(tagname) + + def join_on_tag(self, tagname): + if tagname not in self.__joined_tags: + self.__joined_tags.append(tagname) + j_st = "JOIN media_info %(tag)s ON %(tag)s.id = library.id\ + AND %(tag)s.ikey = '%(tag)s'"\ + % { 'tag' : tagname } + self.joins.append(j_st) + + def set_limit(self, limit): + self.limit = limit + + def __str__(self): + orders, limit = None, None + if len(self.orders) >= 1: + orders = 'ORDER BY ' + ', '.join(self.orders) + if self.limit is not None: + limit = "LIMIT %s" % str(self.limit) + + + return "SELECT DISTINCT %s %s FROM %s %s WHERE %s %s %s"\ + % (self.id and 'library.id,' or '', + ', '.join(self.selects), + self.table_name, + ' '.join(self.joins), + ' AND '.join(self.wheres) or 1, + orders or '', + limit or '') + + +class EditRecordQuery(_DBQuery): + + def __init__(self, table_name): + super(EditRecordQuery, self).__init__(table_name) + self.dbvalues = {} + self.update_id = None + + def add_value(self, column_name, column_value): + self.dbvalues[column_name] = column_value + + def set_update_id(self, key, id): + self.update_key = key + self.update_id = id + + def get_args(self): + args = self.dbvalues.values() + if self.update_id: + args.append(self.update_id) + return args + + def __str__(self): + if self.update_id: + sets_st = ["%s = %%s" % x for x in self.dbvalues.keys()] + query = "UPDATE %s SET %s WHERE %s"\ + % ( self.table_name, + ', '.join(sets_st), + "%s.%s = %%s" % (self.table_name, self.update_key), + ) + else: + query = "INSERT INTO %s(%s) VALUES(%s)"\ + % ( self.table_name, + ', '.join(self.dbvalues.keys()), + ', '.join(["%s" for x in self.get_args()]), + ) + return query + + +def query_decorator(answer_type): + def query_decorator_instance(func): + + def query_func(self, *__args, **__kw): + cursor = self.connection.cursor() + rs = func(self, cursor, *__args, **__kw) + if answer_type == "lastid": + rs = self.connection.get_last_insert_id(cursor) + elif answer_type == "rowcount": + rs = cursor.rowcount + elif answer_type == "fetchall": + rs = list(cursor.fetchall()) + elif answer_type == "fetchone": + rs = cursor.fetchone() + elif answer_type == "medialist": + rs = self._medialist_answer(cursor.fetchall(),__kw['infos']) + + cursor.close() + return rs + + return query_func + + return query_decorator_instance + + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/schema.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/schema.py --- deejayd-0.7.2/deejayd/database/schema.py 2008-05-14 22:50:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/schema.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -46,51 +46,96 @@ class Index(object): """Declare an index for a database schema.""" - def __init__(self, columns): + def __init__(self, columns, unique = True): + self.unique = unique self.columns = columns -db_schema_version=7 +db_schema_version=12 db_schema = [ - Table('audio_library', key='id')[ + Table('library_dir', key='id')[ Column('id', auto_increment=True), - Column('dir'), - Column('filename'), - Column('type'), - Column('title'), - Column('artist'), - Column('album'), - Column('genre'), - Column('tracknumber'), - Column('date'), - Column('length', type='int'), - Column('bitrate', type='int'), - Column('replaygain_track_gain'), - Column('replaygain_track_peak')], - Table('video_library', key='id')[ + Column('name'), + Column('lib_type'), # audio or video + Column('type'), # directory or dirlink + Index(('name', 'lib_type', 'type'))], + Table('library', key='id')[ + Column('id', auto_increment=True), + Column('directory', type='int'), + Column('name'), + Column('lastmodified', type='int'), + Index(('name', 'directory')), + Index(('directory',), unique = False)], + Table('media_info', key=('id','ikey'))[ + Column('id', type="int"), + Column('ikey'), + Column('value')], + Table('cover', key='id')[ + Column('id', auto_increment=True), + # path to the cover file or + # hash of the picture for cover inside audio file + Column('source'), + Column('mime_type'), # mime type of the cover, ex image/jpeg + Column('lmod', type="int"), # last modified + Column('image', type="blob"), + Index(('source',))], + Table('medialist', key='id')[ Column('id', auto_increment=True), - Column('dir'), - Column('filename'), - Column('type'), - Column('title'), - Column('videowidth'), - Column('videoheight'), - Column('subtitle'), - Column('length', type='int')], + Column('name'), + Column('type'), # magic or static + Index(('name','type'))], + Table('medialist_libraryitem', key=('position'))[ + Column('position', auto_increment=True), + Column('medialist_id', type='int'), + Column('libraryitem_id', type='int')], + Table('medialist_property', key=('medialist_id','ikey'))[ + Column('medialist_id', type='int'), + Column('ikey'), + Column('value')], + Table('medialist_sorts', key=('position'))[ + Column('position', auto_increment=True), + Column('medialist_id', type='int'), + Column('tag'), + Column('direction')], + Table('medialist_filters', key=('medialist_id', 'filter_id'))[ + Column('medialist_id', type='int'), + Column('filter_id', type='int')], + Table('filters', key=('filter_id'))[ + Column('filter_id', auto_increment=True), + Column('type')], # complex or basic + Table('filters_basicfilters', key=('filter_id'))[ + Column('filter_id', type='int'), + Column('tag'), # criterion + Column('operator'), # equal, not equal, regex, regexi, etc. + Column('pattern')], # matched value + Table('filters_complexfilters', key=('filter_id'))[ + Column('filter_id', type='int'), + Column('combinator')], # AND, OR, XOR + Table('filters_complexfilters_subfilters', + key=('complexfilter_id', 'filter_id'))[ + Column('complexfilter_id', type='int'), + Column('filter_id', type='int')], Table('webradio', key='wid')[ Column('wid', type='int'), Column('name'), Column('url')], - Table('medialist', key=('name','position'))[ - Column('name'), - Column('position', type='int'), - Column('media_id', type='int')], Table('stats', key='name')[ Column('name'), Column('value', type='int')], Table('variables', key='name')[ Column('name'), Column('value')], + + # for future use + # Table('webradio', key='id')[ + # Column('id', auto_increment=True), + # Column('name'), + # Index(('name',))], + # Table('webradio_entries', key='pos')[ + # Column('pos', auto_increment=True), + # Column('url'), + # Column('webradio_id', type='int'), + # Index(('url', 'webradio_id'))], ] db_init_cmds = [ # stats @@ -108,16 +153,22 @@ "INSERT INTO variables VALUES('current_pos','0');", "INSERT INTO variables VALUES('state','stop');", "INSERT INTO variables VALUES('source','playlist');", - "INSERT INTO variables VALUES('random','0');", - "INSERT INTO variables VALUES('qrandom','0');", - "INSERT INTO variables VALUES('repeat','0');", + "INSERT INTO variables VALUES('playlist-playorder','inorder');", + "INSERT INTO variables VALUES('video-playorder','inorder');", + "INSERT INTO variables VALUES('panel-playorder','inorder');", + "INSERT INTO variables VALUES('queue-playorder','inorder');", + "INSERT INTO variables VALUES('playlist-repeat','0');", + "INSERT INTO variables VALUES('panel-repeat','0');", + "INSERT INTO variables VALUES('video-repeat','0');", "INSERT INTO variables VALUES('queueid','1');", "INSERT INTO variables VALUES('playlistid','1');", - "INSERT INTO variables VALUES('songlistid','1');", + "INSERT INTO variables VALUES('panelid','1');", "INSERT INTO variables VALUES('webradioid','1');", "INSERT INTO variables VALUES('dvdid','1');", "INSERT INTO variables VALUES('videoid','1');", - "INSERT INTO variables VALUES('database_version','7');", + "INSERT INTO variables VALUES('panel-type','panel');", + "INSERT INTO variables VALUES('panel-value','');", + "INSERT INTO variables VALUES('database_version','12');", ] # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/sqlite.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/sqlite.py --- deejayd-0.7.2/deejayd/database/sqlite.py 2008-05-14 22:50:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/sqlite.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,183 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -from deejayd.ui import log -from deejayd.database._base import Database, OperationalError -from pysqlite2 import dbapi2 as sqlite -from os import path - - -LOCK_TIMEOUT = 600 - - -def str_encode(data): - if isinstance(data, unicode): - return data.encode("utf-8") - return data - - -class SqliteDatabase(Database): - - def __init__(self, db_file): - Database.__init__(self) - self.db_file = path.expanduser(db_file) - - def connect(self): - # Check pysqlite version - pysqlite_min_version = [2, 2] - pysqlite_version = map(int, sqlite.version.split('.')) - if pysqlite_version < pysqlite_min_version: - sqlite_error=_(\ - 'This program requires pysqlite version %s or later.')\ - % '.'.join(map(str, pysqlite_min_version)) - log.err(sqlite_error, fatal = True) - - self.__set_connection() - self.verify_database_version() - - def __set_connection(self): - try: - self.connection = sqlite.connect(self.db_file, timeout=LOCK_TIMEOUT) - except sqlite.Error: - error = _("Could not connect to sqlite database %s." % self.db_file) - log.err(error, fatal = True) - else: - self.cursor = self.connection.cursor() - - # configure connection - self.connection.text_factory = str - self.connection.row_factory = sqlite.Row - sqlite.register_adapter(str,str_encode) - - def get_new_connection(self): - return SqliteDatabase(self.db_file) - - def reset_connection(self): - self.cursor.close() - self.connection.close() - self.__set_connection() - - def execute(self, query, parm = None, raise_exception = False): - if parm: query = query % (('?',) * len(parm)) - args = parm or () - try: self.cursor.execute(query,args) - except sqlite.DatabaseError, err: - self.reset_connection() - log.err(_("Unable to execute database request '%s': %s") \ - % (query, err)) - if raise_exception: - raise OperationalError - - def executemany(self, query, parm = []): - if parm == []: return # no request to execute - query = query % (('?',) * len(parm[0])) - try: self.cursor.executemany(query, parm) - except sqlite.DatabaseError, err: - self.reset_connection() - log.err(_("Unable to execute database request '%s': %s") \ - % (query, err)) - - def to_sql(self, table): - sql = ["CREATE TABLE %s (" % table.name] - coldefs = [] - for column in table.columns: - ctype = column.type.lower() - if column.auto_increment: - ctype = "integer PRIMARY KEY" - elif len(table.key) == 1 and column.name in table.key: - ctype += " PRIMARY KEY" - elif ctype == "int": - ctype = "integer" - coldefs.append(" %s %s" % (column.name, ctype)) - if len(table.key) > 1: - coldefs.append(" UNIQUE (%s)" % ','.join(table.key)) - sql.append(',\n'.join(coldefs) + '\n);') - yield '\n'.join(sql) - for index in table.indices: - yield "CREATE INDEX %s_%s_idx ON %s (%s);" % (table.name, - '_'.join(index.columns), table.name, ','.join(index.columns)) - - def init_db(self): - Database.init_db(self) - self.connection.commit() - - #################################################################### - # workaround to migrate old database schema - # remove it when it becomes useless - #################################################################### - try: - self.execute("SELECT value FROM rj_variables\ - WHERE name = 'database_version'", raise_exception = True) - except OperationalError: - pass - else: # old schema exists - log.msg("Migrate from old database schema") - # migrate audio library - self.execute("SELECT * FROM rj_audio_library") - library = self.cursor.fetchall() - query = "INSERT INTO audio_library(dir,filename,type,title,\ - artist,album,genre,tracknumber,date,length,bitrate)VALUES \ - (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)" - self.executemany(query, library) - - # migrate video library - self.execute("SELECT * FROM rj_video_library") - library = self.cursor.fetchall() - query = "INSERT INTO video_library(dir,filename,type,title,\ - length,videowidth,videoheight,subtitle) \ - VALUES (%s,%s,%s,%s,%s,%s,%s,%s)" - self.executemany(query, library) - - # migrate medialist - self.execute("SELECT * FROM rj_medialist") - medialist = self.cursor.fetchall() - for (n, pos, dir, fn) in medialist: - self.execute("SELECT id FROM audio_library WHERE \ - dir = %s AND filename = %s", (dir, fn)) - song = self.cursor.fetchone() - try: id = song[0] - except (IndexError, TypeError): - continue - self.execute("INSERT INTO medialist(name,position,media_id)\ - VALUES(%s,%s,%s)", (n, pos, id)) - - # migrate webradio - self.execute("SELECT * FROM rj_webradio") - webradios = self.cursor.fetchall() - self.executemany("INSERT INTO webradio(wid,name,url)\ - VALUES(%s,%s,%s)", webradios) - - # migrate stats - self.execute("SELECT * FROM rj_stats") - stats = self.cursor.fetchall() - for (k, v) in stats: - self.execute("UPDATE stats SET value = %s WHERE name = %s",\ - (v,k)) - - # remove old table - self.execute("DROP TABLE rj_audio_library") - self.execute("DROP TABLE rj_video_library") - self.execute("DROP TABLE rj_webradio") - self.execute("DROP TABLE rj_medialist") - self.execute("DROP TABLE rj_variables") - self.execute("DROP TABLE rj_stats") - - self.connection.commit() - #################################################################### - -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/upgrade/db_10.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/upgrade/db_10.py --- deejayd-0.7.2/deejayd/database/upgrade/db_10.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/upgrade/db_10.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,54 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from deejayd.database import schema + +def upgrade(cursor, backend, config): + # create new table medialist_sorts + for table in schema.db_schema: + if table.name == "medialist_sorts": + for stmt in backend.to_sql(table): + cursor.execute(stmt) + break + + # remove compilation tag and add various_artist tag + query = "SELECT id FROM media_info WHERE ikey='compilation' and value='1'" + cursor.execute(query) + cursor.executemany("INSERT INTO media_info (id,ikey,value)\ + VALUES(%s,%s,%s)", [(id,"various_artist","__various__")\ + for (id,) in cursor.fetchall()]) + + query = "SELECT m.id, m.value FROM media_info m\ + JOIN media_info m1 ON m.id = m1.id\ + WHERE m1.ikey='compilation' and m1.value='0' and m.ikey='artist'" + cursor.execute(query) + cursor.executemany("INSERT INTO media_info (id,ikey,value)\ + VALUES(%s,%s,%s)", [(id,"various_artist",artist)\ + for (id,artist) in cursor.fetchall()]) + + query = "DELETE FROM media_info WHERE ikey='compilation'" + cursor.execute(query) + + # update db version + sql = [ + "UPDATE variables SET value = '10' WHERE name = 'database_version';", + ] + for s in sql: + cursor.execute(s) + +# vim ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/upgrade/db_11.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/upgrade/db_11.py --- deejayd-0.7.2/deejayd/database/upgrade/db_11.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/upgrade/db_11.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,36 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from deejayd.database import schema + +def upgrade(cursor, backend, config): + # create new table medialist_property + for table in schema.db_schema: + if table.name == "medialist_property": + for stmt in backend.to_sql(table): + cursor.execute(stmt) + break + + # update db version + sql = [ + "UPDATE variables SET value = '11' WHERE name = 'database_version';", + ] + for s in sql: + cursor.execute(s) + +# vim ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/upgrade/db_12.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/upgrade/db_12.py --- deejayd-0.7.2/deejayd/database/upgrade/db_12.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/upgrade/db_12.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,37 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from deejayd.database import schema + +def upgrade(cursor, backend, config): + # drop wrong table medialist_sorts and create the new one + cursor.execute("DROP TABLE medialist_sorts") + for table in schema.db_schema: + if table.name == "medialist_sorts": + for stmt in backend.to_sql(table): + cursor.execute(stmt) + break + + # update db version + sql = [ + "UPDATE variables SET value = '12' WHERE name = 'database_version';", + ] + for s in sql: + cursor.execute(s) + +# vim ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/upgrade/db_6.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/upgrade/db_6.py --- deejayd-0.7.2/deejayd/database/upgrade/db_6.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/upgrade/db_6.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -16,7 +16,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -def upgrade(cursor): +def upgrade(cursor, backend, config): sql = [ "DELETE FROM variables WHERE name = 'currentPos';", "INSERT INTO variables VALUES('current','-1');", diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/upgrade/db_7.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/upgrade/db_7.py --- deejayd-0.7.2/deejayd/database/upgrade/db_7.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/upgrade/db_7.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -16,7 +16,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -def upgrade(cursor): +def upgrade(cursor, backend, config): sql = [ "INSERT INTO variables VALUES('qrandom','0');", "UPDATE variables SET value = '7' WHERE name = 'database_version';", diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/upgrade/db_8.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/upgrade/db_8.py --- deejayd-0.7.2/deejayd/database/upgrade/db_8.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/upgrade/db_8.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,222 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os +from deejayd.utils import quote_uri +from deejayd.database import schema + +def format_tracknumber(tckn): + numbers = tckn.split("/") + try: numbers = ["%02d" % int(num) for num in numbers] + except (TypeError, ValueError): + return tckn + + return "/".join(numbers) + +def upgrade(cursor, backend, config): + # get audio/video library + cursor.execute("SELECT * FROM audio_library;") + audio_library = cursor.fetchall() + cursor.execute("SELECT * FROM video_library;") + video_library = cursor.fetchall() + + # get medialist + cursor.execute("SELECT DISTINCT name FROM medialist ORDER BY name") + medialist = {} + for (name,) in cursor.fetchall(): + if name == "__videocurrent__": + query = "SELECT l.dir, l.filename\ + FROM medialist p JOIN video_library l \ + ON p.media_id = l.id WHERE p.name = %s ORDER BY p.position" + else: + query = "SELECT l.dir, l.filename\ + FROM medialist p JOIN audio_library l\ + ON p.media_id = l.id WHERE p.name = %s ORDER BY p.position" + cursor.execute(query, (name,)) + medialist[name] = cursor.fetchall() + + # erase old table + cursor.execute("DROP TABLE audio_library") + cursor.execute("DROP TABLE video_library") + cursor.execute("DROP TABLE medialist") + + # create new table/indexes + new_tables = ( + "library_dir", + "library", + "media_info", + "cover", + "medialist", + "medialist_libraryitem", + "medialist_filters", + "filters", + "filters_basicfilters", + "filters_complexfilters", + "filters_complexfilters_subfilters", + ) + for table in schema.db_schema: + if table.name in new_tables: + for stmt in backend.to_sql(table): + cursor.execute(stmt) + for query in backend.custom_queries: + cursor.execute(query) + + # set library directory + audio_dirs, video_dirs = {}, {} + query = "INSERT INTO library_dir (name,lib_type,type)VALUES(%s,%s,%s)" + + path = config.get("mediadb","music_directory") + cursor.execute(query, (path.rstrip("/"), "audio", "directory")) # root + audio_dirs[path.rstrip("/")] = cursor.lastrowid + for id,dir,fn,type,tit,art,alb,gn,tk,date,len,bt,rpg,rpp in audio_library: + if type != "file": + cursor.execute(query, (os.path.join(path,dir,fn), "audio", type)) + audio_dirs[os.path.join(path,dir,fn).rstrip("/")] = cursor.lastrowid + + path = config.get("mediadb","video_directory") + cursor.execute(query, (path.rstrip("/"), "video", "directory")) # root + video_dirs[path.rstrip("/")] = cursor.lastrowid + for id,dir,fn,type,tit,width,height,sub,len in video_library: + if type != "file": + cursor.execute(query, (os.path.join(path,dir,fn), "video", type)) + video_dirs[os.path.join(path,dir,fn).rstrip("/")] = cursor.lastrowid + + # set library files + library_files = {} + query = "INSERT INTO library (directory,name,lastmodified)VALUES(%s,%s,%s)" + for id,dir,fn,type,tit,art,alb,gn,tk,date,len,bt,rpg,rpp in audio_library: + path = config.get("mediadb","music_directory") + if type == "file": + try: dir_id = audio_dirs[os.path.join(path,dir).rstrip("/")] + except KeyError: + continue + file_path = os.path.join(path,dir,fn) + if not os.path.isfile(file_path): continue + cursor.execute(query, (dir_id,fn,os.stat(file_path).st_mtime)) + file_id = cursor.lastrowid + infos = { + "type": "song", + "filename": fn, + "uri": quote_uri(file_path), + "rating": "2", + "lastplayed": "0", + "skipcount": "0", + "playcount": "0", + "compilation": "0", + "tracknumber": format_tracknumber(tk), + "title": tit, + "genre": gn, + "artist": art, + "album": alb, + "date": date, + "replaygain_track_gain":rpg, + "replaygain_track_peak":rpp, + "bitrate": bt, + "length": len, + "cover": "", + } + entries = [(file_id, k, v) for k, v in infos.items()] + cursor.executemany("INSERT INTO media_info\ + (id,ikey,value)VALUES(%s,%s,%s)", entries) + library_files[os.path.join(path,dir,fn)] = file_id + + for id,dir,fn,type,tit,width,height,sub,len in video_library: + path = config.get("mediadb","video_directory") + if type == "file": + try: dir_id = video_dirs[os.path.join(path,dir).rstrip("/")] + except KeyError: + continue + file_path = os.path.join(path,dir,fn) + if not os.path.isfile(file_path): continue + cursor.execute(query, (dir_id,fn,os.stat(file_path).st_mtime)) + file_id = cursor.lastrowid + infos = { + "type": "video", + "filename": fn, + "uri": quote_uri(file_path), + "rating": "2", + "lastplayed": "0", + "skipcount": "0", + "playcount": "0", + "title": tit, + "length": len, + "videowidth": width, + "videoheight": height, + "external_subtitle": sub, + } + entries = [(file_id, k, v) for k, v in infos.items()] + cursor.executemany("INSERT INTO media_info\ + (id,ikey,value)VALUES(%s,%s,%s)", entries) + library_files[os.path.join(path,dir,fn)] = file_id + + # set compilation tag + query = "SELECT DISTINCT value FROM media_info WHERE ikey='album'" + cursor.execute(query) + for (album,) in cursor.fetchall(): + if album == '': continue # do not set compilation tag + query = "SELECT COUNT(DISTINCT m.value) FROM media_info m \ + JOIN media_info m2 ON m.id = m2.id\ + WHERE m.ikey='artist' AND m2.ikey='album'\ + AND m2.value=%s" + cursor.execute(query, (album,)) + (value,) = cursor.fetchone() + + if int(value) > 1: + cursor.execute("SELECT id FROM media_info\ + WHERE ikey='album' AND value=%s", (album,)) + cursor.executemany("UPDATE media_info SET value = '1'\ + WHERE ikey='compilation' AND id = %s",cursor.fetchall()) + + # set medialist + for pl_name, items in medialist.items(): + cursor.execute("INSERT INTO medialist (name,type)VALUES(%s,%s)",\ + (pl_name, "static")) + pl_id = cursor.lastrowid + path = config.get("mediadb","music_directory") + if pl_name == "__videocurrent__": + path = config.get("mediadb","video_directory") + for item in items: + try: file_id = library_files[os.path.join(path,item[0],item[1])] + except KeyError: + continue + cursor.execute("INSERT INTO medialist_libraryitem\ + (medialist_id,libraryitem_id)VALUES(%s,%s)", (pl_id, file_id)) + + # other updates + sql = [ + "DELETE FROM variables WHERE name='repeat'", + "DELETE FROM variables WHERE name='songlistid'", + "DELETE FROM variables WHERE name='repeat'", + "DELETE FROM variables WHERE name='random'", + "DELETE FROM variables WHERE name='qrandom'", + "INSERT INTO variables VALUES('playlist-playorder','inorder');", + "INSERT INTO variables VALUES('panel-playorder','inorder');", + "INSERT INTO variables VALUES('video-playorder','inorder');", + "INSERT INTO variables VALUES('queue-playorder','inorder');", + "INSERT INTO variables VALUES('playlist-repeat','0');", + "INSERT INTO variables VALUES('panel-repeat','0');", + "INSERT INTO variables VALUES('video-repeat','0');", + "INSERT INTO variables VALUES('panelid','1');", + "INSERT INTO variables VALUES('panel-type','panel');", + "INSERT INTO variables VALUES('panel-value','');", + "UPDATE variables SET value = '8' WHERE name = 'database_version';", + ] + for s in sql: + cursor.execute(s) + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/upgrade/db_9.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/upgrade/db_9.py --- deejayd-0.7.2/deejayd/database/upgrade/db_9.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/upgrade/db_9.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,46 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from deejayd.database import schema + +def upgrade(cursor, backend, config): + # get covers + cursor.execute("SELECT * FROM cover;") + covers = cursor.fetchall() + + # drop old table + cursor.execute("DROP TABLE cover") + # create new table + for table in schema.db_schema: + if table.name != "cover": continue + for stmt in backend.to_sql(table): + cursor.execute(stmt) + + query = "INSERT INTO cover (id,source,mime_type,lmod,image)\ + VALUES(%s,%s,%s,%s,%s)" + for (id, source, lmod, img) in covers: + mime = "image/jpeg" + cursor.execute(query, (id, source, mime, lmod, img)) + + sql = [ + "UPDATE variables SET value = '9' WHERE name = 'database_version';", + ] + for s in sql: + cursor.execute(s) + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/database/upgrade/__init__.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/database/upgrade/__init__.py --- deejayd-0.7.2/deejayd/database/upgrade/__init__.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/database/upgrade/__init__.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/__init__.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/__init__.py --- deejayd-0.7.2/deejayd/__init__.py 2008-05-14 22:55:52.000000000 +0100 +++ deejayd-0.9.0/deejayd/__init__.py 2009-09-21 08:44:21.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -16,6 +16,6 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -__version__ = "0.7.2" +__version__ = "0.9.0" # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/interfaces.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/interfaces.py --- deejayd-0.7.2/deejayd/interfaces.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/interfaces.py 2009-09-21 08:44:21.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -16,12 +16,28 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import locale + + class DeejaydError(Exception): """General purpose error structure.""" - pass + # Handle unicode messages, what Exceptions cannot. See Python issue at + # http://bugs.python.org/issue2517 + def __str__(self): + if type(self.message) is unicode: + return str(self.message.encode(locale.getpreferredencoding())) + else: + return str(self.message) + + def __unicode__(self): + if type(self.message) is unicode: + return self.message + else: + return unicode(self.message) -class DeejaydAnswer: +class DeejaydAnswer(object): """General purpose core answer container.""" def __init__(self): @@ -54,6 +70,18 @@ return self.contents.items() +class DeejaydList(DeejaydAnswer): + """List answer.""" + + def __len__(self): + self.get_contents() + return len(self.contents) + + def __iter__(self): + self.get_contents() + return self.contents.__iter__() + + class DeejaydFileList(DeejaydAnswer): """File list answer.""" @@ -93,13 +121,9 @@ def __init__(self): DeejaydAnswer.__init__(self) self.medias = [] - self.total_length = None - - def set_total_length(self, length): - self.total_length = length - - def get_total_length(self): - return self.total_length + self.filter = None + self.sort = None + self.media_type = None def add_media(self, media): self.medias.append(media) @@ -111,6 +135,32 @@ def set_medias(self, medias): self.medias = medias + def is_magic(self): + self.get_contents() + return self.filter != None + + def set_media_type(self, media_type): + self.media_type = media_type + + def get_media_type(self): + self.get_contents() + return self.media_type + + def set_filter(self, filter): + self.filter = filter + + def get_filter(self): + self.get_contents() + return self.filter + + def set_sort(self, sort): + self.sort = sort + + def get_sort(self): + self.get_contents() + return self.sort + + class DeejaydDvdInfo(DeejaydAnswer): """Dvd information answer.""" @@ -119,14 +169,16 @@ self.dvd_content = {} def set_dvd_content(self, infos): - for (k, v) in infos.items(): - self.dvd_content[k] = v + self.dvd_content.update(infos) def add_track(self, track): if "track" not in self.dvd_content.keys(): self.dvd_content['track'] = [] self.dvd_content['track'].append(track) + def set_tracks(self, tracks): + self.dvd_content['track'] = tracks + def get_dvd_contents(self): self.get_contents() if "track" not in self.dvd_content.keys(): @@ -134,7 +186,50 @@ return self.dvd_content -class DeejaydWebradioList: +class DeejaydStaticPlaylist(object): + """ Static playlist object """ + type = "static" + + def get(self, first=0, length=-1): + raise NotImplementedError + + def add_path(self, path): + return self.add_paths([path]) + + def add_paths(self, paths): + raise NotImplementedError + + def add_song(self, song_id): + return self.add_songs([song_id]) + + def add_songs(self, song_ids): + raise NotImplementedError + + +class DeejaydMagicPlaylist(object): + """ Magic playlist object """ + type = "magic" + + def get(self, first=0, length=-1): + raise NotImplementedError + + def add_filter(self, filter): + raise NotImplementedError + + def remove_filter(self, filter): + raise NotImplementedError + + def clear_filters(self): + raise NotImplementedError + + def get_properties(self): + raise NotImplementedError + + def set_property(self, key, value): + raise NotImplementedError + + +class DeejaydWebradioList(object): """Webradio list management.""" def get(self, first = 0, length = None): @@ -153,22 +248,31 @@ raise NotImplementedError -class DeejaydQueue: +class DeejaydQueue(object): """Queue management.""" def get(self, first = 0, length = None): raise NotImplementedError - def add_media(self, path, position=None): - return self.add_medias([path], position) + def add_path(self, path, pos = None): + return self.add_paths([path], pos) - def add_medias(self, paths, pos = None): + def add_paths(self, paths, pos = None): raise NotImplementedError - def load_playlist(self, name, pos = None): - return self.load_playlists([name], pos) + def add_song(self, song_id, pos = None): + return self.add_songs([song_id], pos) - def load_playlists(self, names, pos = None): + def add_songs(self, song_ids, pos = None): + raise NotImplementedError + + def load_playlist(self, pl_id, pos = None): + return self.load_playlists([pl_id], pos) + + def load_playlists(self, pl_ids, pos = None): + raise NotImplementedError + + def move(self, ids, new_pos): raise NotImplementedError def clear(self): @@ -181,11 +285,40 @@ raise NotImplementedError -class DeejaydPlaylist: - """Playlist management.""" +class DeejaydPanel(object): + + def get(self, first = 0, length = None): + raise NotImplementedError + + def get_active_list(self): + raise NotImplementedError + + def get_panel_tags(self): + raise NotImplementedError + + def set_active_list(self, type, pl_id=""): + raise NotImplementedError + + def set_panel_filters(self, tag, values): + raise NotImplementedError + + def remove_panel_filters(self, tag): + raise NotImplementedError + + def clear_panel_filters(self): + raise NotImplementedError + + def set_search_filter(self, tag, value): + raise NotImplementedError + + def clear_search_filter(self): + raise NotImplementedError + + def set_sorts(self, sort): + raise NotImplementedError + - def __init__(self, pl_name = None): - self.__pl_name = pl_name +class DeejaydPlaylistMode(object): def get(self, first = 0, length = None): raise NotImplementedError @@ -193,16 +326,22 @@ def save(self, name): raise NotImplementedError - def add_song(self, path, position = None): - return self.add_songs([path], position) + def add_path(self, path, pos = None): + return self.add_paths([path], pos) + + def add_paths(self, paths, pos = None): + raise NotImplementedError + + def add_song(self, song_id, pos = None): + return self.add_songs([song_id], pos) - def add_songs(self, paths, position = None): + def add_songs(self, song_ids, pos = None): raise NotImplementedError - def load(self, name, pos = None): - return self.loads([name], pos) + def load(self, pl_id, pos = None): + return self.loads([pl_id], pos) - def loads(self, names, pos = None): + def loads(self, pl_ids, pos = None): raise NotImplementedError def move(self, ids, new_pos): @@ -221,7 +360,7 @@ raise NotImplementedError -class DeejaydVideo: +class DeejaydVideo(object): """Video management.""" def get(self, first = 0, length = None): @@ -230,27 +369,38 @@ def set(self, value, type = "directory"): raise NotImplementedError + def set_sorts(self, sorts): + raise NotImplementedError + -class DeejaydSignal: +class DeejaydSignal(object): SIGNALS = ('player.status', # Player status change (play/pause/stop/ # random/repeat/volume/manseek) - 'player.current', # Currently played song + 'player.current', # Currently played song change 'player.plupdate', # The current playlist has changed - 'playlist.update', # The stored playlist list has changed + 'playlist.listupdate', # The stored playlist list has changed # (either a saved playlist has been saved # or deleted). + 'playlist.update', # A recorded playlist (static or magic) + # has changed + # set id of playlist as attribute 'webradio.listupdate', + 'panel.update', 'queue.update', 'video.update', 'dvd.update', 'mode', # Mode change 'mediadb.aupdate', # Media library audio update 'mediadb.vupdate', # Media library video update + 'mediadb.mupdate', # a media has been updated + # set id and type of media as attribute + # set type of update as attribute ) - def __init__(self, name=None): + def __init__(self, name=None, attrs = {}): self.name = name + self.attrs = attrs def set_name(self, name): self.name = name @@ -258,8 +408,17 @@ def get_name(self): return self.name + def get_attrs(self): + return self.attrs + + def get_attr(self, key): + return self.attrs[key] + + def set_attr(self, key, value): + self.attrs[key] = value + -class DeejaydCore: +class DeejaydCore(object): """Abstract class for a deejayd core.""" def __init__(self): @@ -280,7 +439,7 @@ def next(self): raise NotImplementedError - def seek(self, pos): + def seek(self, pos, relative = False): raise NotImplementedError def get_current(self): @@ -292,7 +451,7 @@ def set_volume(self, volume_value): raise NotImplementedError - def set_option(self, option_name, option_value): + def set_option(self, source, option_name, option_value): raise NotImplementedError def set_mode(self, mode_name): @@ -310,19 +469,25 @@ def get_stats(self): raise NotImplementedError - def update_audio_library(self): + def update_audio_library(self, force = False, sync = False): raise NotImplementedError - def update_video_library(self): + def update_video_library(self, force = False, sync = False): raise NotImplementedError - def erase_playlist(self, names): + def create_recorded_playlist(self, name, type): + raise NotImplementedError + + def get_recorded_playlist(self, pl_id): + raise NotImplementedError + + def erase_playlist(self, pl_ids): raise NotImplementedError def get_playlist_list(self): raise NotImplementedError - def get_playlist(self, name=None): + def get_playlist(self): raise NotImplementedError def get_webradios(self): @@ -331,12 +496,21 @@ def get_queue(self): raise NotImplementedError + def get_panel(self): + raise NotImplementedError + def get_video(self): raise NotImplementedError + def set_media_rating(self, media_ids, rating, type = "audio"): + raise NotImplementedError + def get_audio_dir(self, dir=None): raise NotImplementedError + def get_audio_cover(self, media_id): + raise NotImplementedError + def audio_search(self, search_txt, type = 'all'): raise NotImplementedError @@ -346,6 +520,9 @@ def dvd_reload(self): raise NotImplementedError + def mediadb_list(self, taglist, filter): + raise NotImplementedError + def get_dvd_content(self): raise NotImplementedError @@ -384,8 +561,8 @@ if sub[0] == signal.get_name()]: cb(signal) - def _dispatch_signame(self, signal_name): - self._dispatch_signal(DeejaydSignal(signal_name)) + def _dispatch_signame(self, signal_name, attrs = {}): + self._dispatch_signal(DeejaydSignal(signal_name, attrs)) # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/formats/audio/flac.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/formats/audio/flac.py --- deejayd-0.7.2/deejayd/mediadb/formats/audio/flac.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/formats/audio/flac.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,37 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from deejayd.mediadb.formats._base import _AudioFile + +extensions = [".flac"] +try: from mutagen.flac import FLAC +except ImportError: + extensions = [] + +class FlacFile(_AudioFile): + _tagclass_ = FLAC + + def get_cover(self, tag_info): + for picture in tag_info.pictures: + if picture.type == 3: # album front cover + return {"data": picture.data, "mime": picture.mime} + return None + +object = FlacFile + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/formats/audio/__init__.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/formats/audio/__init__.py --- deejayd-0.7.2/deejayd/mediadb/formats/audio/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/formats/audio/__init__.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,19 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/formats/audio/mp3.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/formats/audio/mp3.py --- deejayd-0.7.2/deejayd/mediadb/formats/audio/mp3.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/formats/audio/mp3.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,101 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os +from deejayd.mediadb.formats._base import _AudioFile + +extensions = [".mp3",".mp2",".aac"] +try: from mutagen.mp3 import MP3 +except ImportError: + extensions = [] + + +class Mp3File(_AudioFile): + IDS = { "TIT2": "title", + "TPE1": "artist", + "TALB": "album", + } + replaygain_process = False + + def parse(self, file): + infos = _AudioFile.parse(self, file) + mp3_info = MP3(file) + + infos.update([ + ("title", ""), + ("artist", ""), + ("album", ""), + ("tracknumber", ""), + ("discnumber", ""), + ("date", ""), + ("genre", ""), + ("replaygain_track_gain", ""), + ("replaygain_track_peak", ""), + ("bitrate", int(mp3_info.info.bitrate)), + ("length", int(mp3_info.info.length)), + ]) + + tag = mp3_info.tags + if not tag: + infos["title"] = os.path.split(file)[1] + return infos + + for frame in tag.values(): + if frame.FrameID == "TXXX": + if frame.desc in ("replaygain_track_peak",\ + "replaygain_track_gain"): + # Some versions of Foobar2000 write broken Replay Gain + # tags in this format. + infos[frame.desc] = frame.text[0] + self.replaygain_process = True + else: continue + elif frame.FrameID == "RVA2": # replaygain + self.__process_rg(frame, infos) + continue + elif frame.FrameID == "TCON": # genre + infos["genre"] = frame.genres[0] + continue + elif frame.FrameID == "TDRC": # date + list = [stamp.text for stamp in frame.text] + infos["date"] = list[0] + continue + elif frame.FrameID == "TRCK": # tracknumber + infos["tracknumber"] = self._format_number(frame.text[0]) + elif frame.FrameID == "TPOS": # discnumber + infos["discnumber"] = self._format_number(frame.text[0]) + elif frame.FrameID in self.IDS.keys(): + infos[self.IDS[frame.FrameID]] = frame.text[0] + elif frame.FrameID == "APIC": # picture + if frame.type == 3: # album front cover + infos["cover"] = {"data": frame.data, "mime": frame.mime} + else: continue + + infos["various_artist"] = infos["artist"] + return infos + + def __process_rg(self, frame, infos): + if frame.channel == 1: + if frame.desc == "album": return # not supported + elif frame.desc == "track" or not self.replaygain_process: + infos["replaygain_track_gain"] = "%+f dB" % frame.gain + infos["replaygain_track_peak"] = str(frame.peak) + self.replaygain_process = True + +object = Mp3File + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/formats/audio/mp4.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/formats/audio/mp4.py --- deejayd-0.7.2/deejayd/mediadb/formats/audio/mp4.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/formats/audio/mp4.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,73 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from deejayd.mediadb.formats._base import _AudioFile + +extensions = ['.mp4', '.m4a'] +try: from mutagen.mp4 import MP4 +except ImportError: + extensions = [] + +class Mp4File(_AudioFile): + __translate = { + "\xa9nam": "title", + "\xa9alb": "album", + "\xa9ART": "artist", + "\xa9day": "date", + "\xa9gen": "genre", + "----:com.apple.iTunes:replaygain_track_gain": "replaygain_track_gain", + "----:com.apple.iTunes:replaygain_track_peak": "replaygain_track_peak", + } + __tupletranslate = { + "trkn": "tracknumber", + "disk": "discnumber", + } + + def parse(self, file): + infos = _AudioFile.parse(self, file) + mp4_info = MP4(file) + infos["bitrate"] = int(mp4_info.info.bitrate) + infos["length"] = int(mp4_info.info.length) + + for tag, name in self.__translate.iteritems(): + try: infos[name] = mp4_info[tag][0] + except: infos[name] = ''; + + for tag, name in self.__tupletranslate.iteritems(): + try: + cur, total = mp4_info[tag][0] + if total: self[name] = "%02d/%02d" % (cur, total) + else: infos[name] = "%02d" % cur + except: infos[name] = ''; + + # extract cover + try: cover = mp4_info["covr"][0] + except (KeyError, ValueError): + pass + else: + mime = "image/jpeg" + if cover.format == cover.FORMAT_PNG: + mime = "image/png" + infos["cover"] = {"mime": mime, "data": cover} + + infos["various_artist"] = infos["artist"] + return infos + +object = Mp4File + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/formats/audio/ogg.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/formats/audio/ogg.py --- deejayd-0.7.2/deejayd/mediadb/formats/audio/ogg.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/formats/audio/ogg.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,44 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from deejayd.mediadb.formats._base import _AudioFile + +extensions = [".ogg"] +try: from mutagen.oggvorbis import OggVorbis +except ImportError: + extensions = [] + +class OggFile(_AudioFile): + _tagclass_ = OggVorbis + + def get_cover(self, tag_info): + return None + # disable for now + # not work correctly + #if 'coverarttype' in tag_info.keys() and\ + # int(tag_info['coverarttype'][0])==3: + # try: + # return {"data": tag_info['coverart'][0],\ + # "mime": tag_info['coverartmime'][0]} + # except KeyError: + # return None + #return None + +object = OggFile + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/formats/audio_flac.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/formats/audio_flac.py --- deejayd-0.7.2/deejayd/mediadb/formats/audio_flac.py 2008-05-14 22:50:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/formats/audio_flac.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,43 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -extensions = [".flac"] -try: from mutagen.flac import FLAC -except ImportError: - extensions = [] - -class FlacFile: - supported_tag = ("tracknumber","title","genre","artist","album","date",\ - "replaygain_track_gain", "replaygain_track_peak") - - def parse(self, file): - infos = {} - flac_info = FLAC(file) - try: infos["bitrate"] = int(flac_info.info.bitrate) - except AttributeError: infos["bitrate"] = 0 - infos["length"] = int(flac_info.info.length) - - for t in self.supported_tag: - try: infos[t] = flac_info[t][0] - except: infos[t] = ''; - - return infos - -object = FlacFile - -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/formats/audio_mp3.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/formats/audio_mp3.py --- deejayd-0.7.2/deejayd/mediadb/formats/audio_mp3.py 2008-05-14 22:50:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/formats/audio_mp3.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,88 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -extensions = [".mp3",".mp2"] -try: from mutagen.mp3 import MP3 -except ImportError: - extensions = [] - - -class Mp3File: - IDS = { "TIT2": "title", - "TPE1": "artist", - "TALB": "album", - "TRCK": "tracknumber", - } - replaygain_process = False - - def parse(self, file): - mp3_info = MP3(file) - - infos = { - "title": "", - "artist": "", - "album": "", - "tracknumber": "", - "date": "", - "genre": "", - "replaygain_track_gain": "", - "replaygain_track_peak": "", - } - infos["bitrate"] = int(mp3_info.info.bitrate) - infos["length"] = int(mp3_info.info.length) - - tag = mp3_info.tags - if not tag: - return infos - - for frame in tag.values(): - if frame.FrameID == "TXXX": - if frame.desc in ("replaygain_track_peak",\ - "replaygain_track_gain"): - # Some versions of Foobar2000 write broken Replay Gain - # tags in this format. - infos[frame.desc] = frame.text[0] - self.replaygain_process = True - else: continue - elif frame.FrameID == "RVA2": - self.__process_rg(frame, infos) - continue - elif frame.FrameID == "TCON": # genre - infos["genre"] = frame.genres[0] - continue - elif frame.FrameID == "TDRC": # date - list = [stamp.text for stamp in frame.text] - infos["date"] = list[0] - continue - elif frame.FrameID in self.IDS.keys(): - infos[self.IDS[frame.FrameID]] = frame.text[0] - else: continue - - return infos - - def __process_rg(self, frame, infos): - if frame.channel == 1: - if frame.desc == "album": return # not supported - elif frame.desc == "track" or not self.replaygain_process: - infos["replaygain_track_gain"] = "%+f dB" % frame.gain - infos["replaygain_track_peak"] = str(frame.peak) - self.replaygain_process = True - -object = Mp3File - -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/formats/audio_mp4.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/formats/audio_mp4.py --- deejayd-0.7.2/deejayd/mediadb/formats/audio_mp4.py 2008-05-14 22:50:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/formats/audio_mp4.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,60 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -extensions = ['.mp4', '.m4a'] - -try: from mutagen.mp4 import MP4 -except ImportError: - extensions = [] - -class Mp4File: - __translate = { - "\xa9nam": "title", - "\xa9alb": "album", - "\xa9ART": "artist", - "\xa9day": "date", - "\xa9gen": "genre", - "----:com.apple.iTunes:replaygain_track_gain": "replaygain_track_gain", - "----:com.apple.iTunes:replaygain_track_peak": "replaygain_track_peak", - } - __tupletranslate = { - "trkn": "tracknumber", - } - - def parse(self, file): - infos = {} - mp4_info = MP4(file) - infos["bitrate"] = int(mp4_info.info.bitrate) - infos["length"] = int(mp4_info.info.length) - - for tag, name in self.__translate.iteritems(): - try: infos[name] = mp4_info[tag][0] - except: infos[name] = ''; - - for tag, name in self.__tupletranslate.iteritems(): - try: - cur, total = mp4_info[tag][0] - if total: infos[name] = "%d/%d" % (cur, total) - else: infos[name] = "%d" % cur - except: infos[name] = ''; - - return infos - -object = Mp4File - -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/formats/audio_ogg.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/formats/audio_ogg.py --- deejayd-0.7.2/deejayd/mediadb/formats/audio_ogg.py 2008-05-14 22:50:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/formats/audio_ogg.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,42 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -extensions = [".ogg"] -try: from mutagen.oggvorbis import OggVorbis -except ImportError: - extensions = [] - -class OggFile: - supported_tag = ("tracknumber","title","genre","artist","album","date",\ - "replaygain_track_gain", "replaygain_track_peak") - - def parse(self, file): - infos = {} - ogg_info = OggVorbis(file) - infos["bitrate"] = int(ogg_info.info.bitrate) - infos["length"] = int(ogg_info.info.length) - - for t in self.supported_tag: - try: infos[t] = ogg_info[t][0] - except: infos[t] = ''; - - return infos - -object = OggFile - -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/formats/_base.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/formats/_base.py --- deejayd-0.7.2/deejayd/mediadb/formats/_base.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/formats/_base.py 2009-09-21 08:44:21.000000000 +0100 @@ -0,0 +1,113 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os +from deejayd.utils import quote_uri +import kaa.metadata + + +class _MediaFile(object): + type = "unknown" + + def parse(self, file_path): + return { + "filename": os.path.basename(file_path), + "uri": quote_uri(file_path), + "type": self.type, + "rating": "2", # [0-4] + "lastplayed": "0", + "skipcount": "0", + "playcount": "0", + } + + +class _AudioFile(_MediaFile): + _tagclass_ = None + type = "song" + supported_tag = ("tracknumber","title","genre","artist","album",\ + "discnumber","date","replaygain_track_gain",\ + "replaygain_track_peak") + + def _format_number(self, nb): + numbers = nb.split("/") + try: numbers = ["%02d" % int(num) for num in numbers] + except (TypeError, ValueError): + return nb + + return "/".join(numbers) + + def parse(self, file): + infos = _MediaFile.parse(self, file) + if self._tagclass_: + tag_info = self._tagclass_(file) + for i in ("bitrate", "length"): + try: infos[i] = int(getattr(tag_info.info, i)) + except AttributeError: + infos[i] = 0 + for t in self.supported_tag: + try: info = tag_info[t][0] + except: + info = '' + if t in ("tracknumber", "discnumber"): + info = self._format_number(info) + infos[t] = info + # get front cover album if available + infos["various_artist"] = infos["artist"] + cover = self.get_cover(tag_info) + if cover: infos["cover"] = cover + + return infos + +class _VideoFile(_MediaFile): + type = "video" + mime_type = None + + def _format_title(self, f): + (filename,ext) = os.path.splitext(f) + title = filename.replace(".", " ") + title = title.replace("_", " ") + return title.title() + + def parse(self, file): + infos = _MediaFile.parse(self, file) + infos.update({ + "audio_channels": "0", + "subtitle_channels": "0", + "length": "0", + "videoheight": "0", + "videowidth": "0", + }) + (path,filename) = os.path.split(file) + infos["title"] = self._format_title(filename) + + # parse video file with kaa + kaa_infos = kaa.metadata.parse(file) + if kaa_infos is None: + raise TypeError(_("Video media %s not supported by kaa parser") \ + % file) + if len(kaa_infos["video"]) == 0: + raise TypeError(_("This file is not a video")) + infos["length"] = int(kaa_infos["length"]) + infos["videowidth"] = kaa_infos["video"][0]["width"] + infos["videoheight"] = kaa_infos["video"][0]["height"] + infos["audio_channels"] = len(kaa_infos["audio"]) + infos["subtitle_channels"] = len(kaa_infos["subtitles"]) + + return infos + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/formats/__init__.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/formats/__init__.py --- deejayd-0.7.2/deejayd/mediadb/formats/__init__.py 2008-05-14 22:50:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/formats/__init__.py 2009-09-21 08:44:21.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -18,31 +18,19 @@ import os, glob -def get_audio_extensions(player): - base = os.path.dirname(__file__) - base_import = "deejayd.mediadb.formats" +def get_extensions(player, type = "audio"): + base = os.path.join(os.path.dirname(__file__), type) + base_import = "deejayd.mediadb.formats.%s" % type ext_dict = {} modules = [os.path.basename(f[:-3]) \ - for f in glob.glob(os.path.join(base, "[!_]*.py"))\ - if os.path.basename(f).startswith("audio")] + for f in glob.glob(os.path.join(base, "[!_]*.py"))] for m in modules: mod = __import__(base_import+"."+m, {}, {}, base) - inst = mod.object() + filetype_class = mod.object for ext in mod.extensions: if player.is_supported_format(ext): - ext_dict[ext] = inst - - return ext_dict - -def get_video_extensions(player): - ext_dict = {} - - from deejayd.mediadb.formats import video - inst = video.object(player) - for ext in video.extensions: - if player.is_supported_format(ext): - ext_dict[ext] = inst + ext_dict[ext] = filetype_class return ext_dict diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/formats/video/default.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/formats/video/default.py --- deejayd-0.7.2/deejayd/mediadb/formats/video/default.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/formats/video/default.py 2009-09-21 08:44:21.000000000 +0100 @@ -0,0 +1,31 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os, datetime + +from deejayd.mediadb.formats._base import _VideoFile +extensions = (".avi", ".asf", ".wmv", ".ogm", ".mkv", ".mp4", ".mov", ".m4v") + +class VideoFile(_VideoFile): + mime_type = (u"video/x-msvideo", u"video/x-ms-asf", u"video/x-ms-wmv",\ + u"video/x-ogg", u"video/x-theora",u"application/ogg",\ + u"video/x-matroska", u'video/quicktime', u'video/mp4') + +object = VideoFile + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/formats/video/__init__.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/formats/video/__init__.py --- deejayd-0.7.2/deejayd/mediadb/formats/video/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/formats/video/__init__.py 2009-09-21 08:44:21.000000000 +0100 @@ -0,0 +1,19 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/formats/video.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/formats/video.py --- deejayd-0.7.2/deejayd/mediadb/formats/video.py 2008-05-14 22:50:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/formats/video.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,59 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import os -extensions = (".avi", ".mpeg", ".mpg", ".mkv", ".asf", ) - -class VideoFile: - supported_tag = ("videowidth","length","videoheight") - - def __init__(self, player): - self.player = player - - def parse(self, file): - (path,filename) = os.path.split(file) - def format_title(f): - (filename,ext) = os.path.splitext(f) - title = filename.replace(".", " ") - title = title.replace("_", " ") - - return title.title() - - infos = self.parse_sub(file) - infos["title"] = format_title(filename) - video_info = self.player.get_video_file_info(file) - for t in self.supported_tag: - infos[t] = video_info[t] - - return infos - - def parse_sub(self, file): - infos = {} - # Try to find a subtitle (same name with a srt/sub extension) - (base_path,ext) = os.path.splitext(file) - sub = "" - for ext_type in (".srt",".sub"): - if os.path.isfile(base_path + ext_type): - sub = "file://" + base_path + ext_type - break - infos["subtitle"] = sub - return infos - -object = VideoFile - -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/__init__.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/__init__.py --- deejayd-0.7.2/deejayd/mediadb/__init__.py 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/deejayd/mediadb/__init__.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -24,9 +24,7 @@ try: from deejayd.mediadb import inotify except ImportError: - HAVE_INOTIFY = False -else: - HAVE_INOTIFY = True + inotify = False def init(db, player, config): @@ -45,8 +43,8 @@ except library.NotFoundException,msg: log.err(_("Unable to init video library : %s") % msg, fatal=True) - if HAVE_INOTIFY: - lib_watcher = inotify.DeejaydInotify(db, audio_library, video_library) + if inotify: + lib_watcher = inotify.get_watcher(db, audio_library, video_library) else: log.info(_("Inotify support disabled")) return audio_library,video_library,lib_watcher diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/inotify.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/inotify.py --- deejayd-0.7.2/deejayd/mediadb/inotify.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/inotify.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -16,9 +16,10 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import os, threading +import os, threading, traceback, Queue from deejayd.ui import log -from pyinotify import WatchManager, Notifier, EventsCodes, ProcessEvent +from deejayd.utils import str_encode +import pyinotify ############################################################################# ##### Events Watcher @@ -26,66 +27,42 @@ def log_event(func): def log_event_func(self, event): - log.info(_("Inotify event %s: %s") % \ - (event.event_name,os.path.join(event.path, event.name))) + path = os.path.join(event.path.decode("utf-8"),\ + event.name.decode("utf-8")) + try: + event.maskname + except AttributeError: + event.maskname = event.event_name + log.info(_("Inotify event %s: %s") % (event.maskname, path)) func(self,event) return log_event_func -class LibraryWatcher(ProcessEvent): +class InotifyWatcher(pyinotify.ProcessEvent): - def __init__(self,library): + def __init__(self, library, queue): self.__library = library - self.__created_files = [] - - def __occured_on_dirlink(self, event): - if not event.name: - return False - file_path = os.path.join(event.path, event.name) - if os.path.exists(file_path): - return os.path.islink(file_path) and os.path.isdir(file_path) - else: - # File seems to have been deleted, so we lookup for a dirlink - # in the library. - db = self.__library.inotify_db - return file_path in self.__library.get_root_paths(db) + self.__queue = queue @log_event def process_IN_CREATE(self, event): - if self.__occured_on_dirlink(event): - self.__library.add_directory(event.path, event.name, True) - elif not event.is_dir: - self.__created_files.append((event.path, event.name)) + self.__queue.put(("create", self.__library, event)) @log_event def process_IN_DELETE(self, event): - if self.__occured_on_dirlink(event): - self.__library.remove_directory(event.path, event.name, True) - elif not event.is_dir: - self.__library.remove_file(event.path, event.name) + self.__queue.put(("delete", self.__library, event)) @log_event def process_IN_MOVED_FROM(self, event): - if not event.is_dir: - self.__library.remove_file(event.path, event.name) - else: - self.__library.remove_directory(event.path, event.name) + self.__queue.put(("move_from", self.__library, event)) @log_event def process_IN_MOVED_TO(self, event): - if not event.is_dir: - self.__library.add_file(event.path, event.name) - else: - self.__library.add_directory(event.path, event.name) + self.__queue.put(("move_to", self.__library, event)) @log_event def process_IN_CLOSE_WRITE(self, event): - if (event.path, event.name) in self.__created_files: - self.__library.add_file(event.path, event.name) - del self.__created_files[\ - self.__created_files.index((event.path, event.name))] - else: - self.__library.update_file(event.path, event.name) + self.__queue.put(("close_write", self.__library, event)) def process_IN_IGNORED(self, event): # This is said to be useless in the documentation, and is @@ -94,14 +71,102 @@ pass -############################################################################# +class _LibraryWatcher(threading.Thread): + + def __init__(self, db, queue): + threading.Thread.__init__(self) + self.should_stop = threading.Event() + self.__db = db + self.__queue = queue + self.__created_files = [] + self.__record = False + + def run(self): + while not self.should_stop.isSet(): + try: type, library, event = self.__queue.get(True, 0.1) + except Queue.Empty: + continue + try: self.__record = self.__execute(type,library,event)\ + or self.__record + except Exception, ex: + path = str_encode(os.path.join(event.path, event.name)) + log.err(_("Inotify problem for '%s', see traceback") % path) + log.err("------------------Traceback lines--------------------") + log.err(traceback.format_exc()) + log.err("-----------------------------------------------------") + if self.__record and self.__queue.empty(): # record changes + library.inotify_record_changes() + self.__db.close() + + def __occured_on_dirlink(self, library, event): + if not event.name: + return False + file_path = os.path.join(event.path, event.name) + if os.path.exists(file_path): + return os.path.islink(file_path) and os.path.isdir(file_path) + else: + # File seems to have been deleted, so we lookup for a dirlink + # in the library. + return file_path in library.get_root_paths() -class DeejaydInotify(threading.Thread): + def __execute(self, type, library, event): + # first be sure that path are correct + try: + path = library._encode(event.path) + name = library._encode(event.name) + except UnicodeError: # skip this event + return False - # watched events - EVENT_MASK = EventsCodes.IN_DELETE | EventsCodes.IN_CREATE |\ - EventsCodes.IN_MOVED_FROM | EventsCodes.IN_MOVED_TO |\ - EventsCodes.IN_CLOSE_WRITE + if type == "create": + if self.__occured_on_dirlink(library, event): + return library.add_directory(path, name, True) + elif not self.is_on_dir(event): + self.__created_files.append((path, name)) + elif type == "delete": + if self.__occured_on_dirlink(library, event): + return library.remove_directory(path, name, True) + elif not self.is_on_dir(event): + return library.remove_file(path, name) + elif type == "move_from": + if not self.is_on_dir(event): + return library.remove_file(path, name) + else: + return library.remove_directory(path, name) + elif type == "move_to": + if not self.is_on_dir(event): + return library.add_file(path, name) + else: + return library.add_directory(path, name) + elif type == "close_write": + if (path, name) in self.__created_files: + del self.__created_files[\ + self.__created_files.index((path, name))] + return library.add_file(path, name) + else: + return library.update_file(path, name) + + return False + + def close(self): + self.should_stop.set() + threading.Thread.join(self) + + +class LibraryWatcher(_LibraryWatcher): + + def is_on_dir(self, event): + return event.dir + + +class LibraryWatcherOLD(_LibraryWatcher): + + def is_on_dir(self, event): + return event.is_dir + + +############################################################################# + +class _DeejaydInotify(threading.Thread): def __init__(self, db, audio_library, video_library): threading.Thread.__init__(self) @@ -110,9 +175,11 @@ self.__audio_library = audio_library self.__video_library = video_library self.__db = db + self.__queue = Queue.Queue(1000) - self.__wm = WatchManager() + self.__wm = pyinotify.WatchManager() self.__watched_dirs = {} + self.EVENT_MASK = self.watched_events_mask() def is_watched (self, dir_path): return dir_path in self.__watched_dirs.keys() @@ -120,9 +187,9 @@ def watch_dir(self, dir_path, library): if self.is_watched(dir_path): raise ValueError('dir %s is already watched' % dir_path) - wdd = self.__wm.add_watch(dir_path, DeejaydInotify.EVENT_MASK, - proc_fun=LibraryWatcher(library), rec=True, - auto_add=True) + wdd = self.__wm.add_watch(dir_path, self.EVENT_MASK, + proc_fun=InotifyWatcher(library,self.__queue), rec=True, + auto_add=True) self.__watched_dirs[dir_path] = wdd def stop_watching_dir(self, dir_path): @@ -132,32 +199,73 @@ self.__wm.rm_watch(wdd[dir_path], rec=True) def run(self): - # open a new database connection for this thread - threaded_db = self.__db.get_new_connection() - threaded_db.connect() - - notifier = Notifier(self.__wm) + notifier = self.notifier(self.__wm) for library in (self.__audio_library, self.__video_library): if library: - library.set_inotify_connection(threaded_db) library.watcher = self - for dir_path in library.get_root_paths(threaded_db): + for dir_path in library.get_root_paths(): self.watch_dir(dir_path, library) + # start library watcher thread + lib_watcher = self.watcher(self.__db, self.__queue) + lib_watcher.start() while not self.should_stop.isSet(): # process the queue of events as explained above notifier.process_events() if notifier.check_events(): # read notified events and enqeue them notifier.read_events() - + lib_watcher.close() notifier.stop() - # close database connection - threaded_db.close() def close(self): self.should_stop.set() threading.Thread.join(self) + +class DeejaydInotify(_DeejaydInotify): + + def watched_events_mask(self): + return pyinotify.IN_DELETE |\ + pyinotify.IN_CREATE |\ + pyinotify.IN_MOVED_FROM |\ + pyinotify.IN_MOVED_TO |\ + pyinotify.IN_CLOSE_WRITE + + def watcher(self, db, queue): + return LibraryWatcher(db, queue) + + def notifier(self, watch_manager): + return pyinotify.Notifier(watch_manager, timeout=1000) + + +class DeejaydInotifyOLD(_DeejaydInotify): + + def watched_events_mask(self): + return pyinotify.EventsCodes.IN_DELETE |\ + pyinotify.EventsCodes.IN_CREATE |\ + pyinotify.EventsCodes.IN_MOVED_FROM |\ + pyinotify.EventsCodes.IN_MOVED_TO |\ + pyinotify.EventsCodes.IN_CLOSE_WRITE + + def watcher(self, db, queue): + return LibraryWatcherOLD(db, queue) + + def notifier(self, watch_manager): + return pyinotify.Notifier(watch_manager) + + +def get_watcher(db, audio_library, video_library): + try: + pyinotify_version = map(int, pyinotify.__version__.split('.')) + except AttributeError: + pyinotify_version = [0, 7] + + if pyinotify_version >= [0, 8]: + return DeejaydInotify(db, audio_library, video_library) + else: + return DeejaydInotifyOLD(db, audio_library, video_library) + + # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/library.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/library.py --- deejayd-0.7.2/deejayd/mediadb/library.py 2008-05-14 22:50:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/library.py 2009-09-21 08:44:21.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -17,87 +17,21 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -*- coding: utf-8 -*- -import os, sys, urllib, threading, traceback -from twisted.internet import threads +import os, sys, threading, traceback, base64, locale, hashlib +from twisted.internet import threads, reactor +import kaa.metadata + +from deejayd.interfaces import DeejaydError from deejayd.component import SignalingComponent -from deejayd.mediadb import formats, _media -from deejayd import database +from deejayd.mediadb import formats +from deejayd.utils import quote_uri, str_encode +from deejayd import database, mediafilters from deejayd.ui import log -class NotFoundException(Exception):pass -class NotSupportedFormat(Exception):pass - -def log_traceback(func): - def log_traceback_func(self, *__args,**__kw): - try: func(self, *__args,**__kw) - except Exception, ex: - log.err(_("Unable to get video metadata from %s, see traceback\ -for more information.") % self.file) - print "---------------------Traceback lines-----------------------" - print traceback.format_exc() - print "-----------------------------------------------------------" - return False - return True - - return log_traceback_func - -class _DeejaydFile: - table = "unknown" - - def __init__(self, db_con, dir, filename, path, info): - self.file = path - self.db_con = db_con - self.dir = dir - self.filename = filename - self.info = info - - def remove(self): - self.db_con.remove_file(self.dir, self.file, self.table) - - def insert(self): - raise NotImplementedError - - def update(self): - raise NotImplementedError - - def force_update(self): - raise NotImplementedError - - -class DeejaydAudioFile(_DeejaydFile): - table = "audio_library" +class NotFoundException(DeejaydError):pass +class NotSupportedFormat(DeejaydError):pass - @log_traceback - def insert(self): - file_info = self.info.parse(self.file) - self.db_con.insert_audio_file(self.dir,self.filename,file_info) - - @log_traceback - def update(self): - file_info = self.info.parse(self.file) - self.db_con.update_audio_file(self.dir,self.filename,file_info) - - def force_update(self):pass - - -class DeejaydVideoFile(_DeejaydFile): - table = "video_library" - - @log_traceback - def insert(self): - file_info = self.info.parse(self.file) - self.db_con.insert_video_file(self.dir,self.filename,file_info) - - @log_traceback - def update(self): - file_info = self.info.parse(self.file) - self.db_con.update_video_file(self.dir,self.filename,file_info) - - def force_update(self): - # Update external subtitle - file_info = self.info.parse_sub(self.file) - self.db_con.update_video_subtitle(self.dir,self.filename,file_info) ########################################################################## ########################################################################## @@ -110,38 +44,36 @@ except UnicodeError: return - self.mutex.acquire() - - rs = func(*__args, **__kw) - if rs: # commit change - self.inotify_db.record_mediadb_stats(self.type) - self.inotify_db.set_update_time(self.type) - self.inotify_db.connection.commit() - self.dispatch_signame(self.update_signal_name) - - self.mutex.release() + return func(*__args, **__kw) return inotify_action_func + class _Library(SignalingComponent): - ext_dict = {} - table = None + common_attr = ("filename","uri","type","title","length") + persistent_attr = ("rating","skipcount","playcount","lastplayed") type = None - file_class = None def __init__(self, db_connection, player, path, fs_charset="utf-8"): SignalingComponent.__init__(self) # init Parms + self.media_attr = [] + for i in self.__class__.common_attr: self.media_attr.append(i) + for j in self.__class__.custom_attr: self.media_attr.append(j) + for k in self.__class__.persistent_attr: self.media_attr.append(k) self._fs_charset = fs_charset self._update_id = 0 self._update_end = True self._update_error = None + self._changes_cb = {} + self._changes_cb_id = 0 + self._path = os.path.abspath(path) # test library path if not os.path.isdir(self._path): - msg = _("Unable to find directory %s") % self._path + msg = _("Unable to find directory %s") % self._encode(self._path) raise NotFoundException(msg) # Connection to the database @@ -151,56 +83,87 @@ self.mutex = threading.Lock() # build supported extension list - self._build_supported_extension(player) + self.ext_dict = formats.get_extensions(player, self.type) self.watcher = None def _encode(self, data): - try: rs = data.decode(self._fs_charset, "strict").encode("utf-8") - except UnicodeError: - log.err(_("%s has wrong character") %\ - data.decode(self._fs_charset, "replace").encode("utf-8")) - raise UnicodeError - return rs + return str_encode(data, self._fs_charset) def _build_supported_extension(self, player): raise NotImplementedError - def get_dir_content(self,dir): - rs = self.db_con.get_dir_info(dir,self.table) - if len(rs) == 0 and dir != "": + def set_file_info(self, file_id, key, value, allow_create = False): + ans = self.db_con.set_media_infos(file_id, {key: value}, allow_create) + if not ans: + raise NotFoundException + self.dispatch_signame('mediadb.mupdate',\ + attrs = {"type": "update", "id": file_id}) + self.db_con.connection.commit() + + def get_dir_content(self, dir): + dir = os.path.join(self._path, dir).rstrip("/") + files_rsp = self.db_con.get_dir_content(dir,\ + infos = self.media_attr, type = self.type) + dirs_rsp = self.db_con.get_dir_list(dir, self.type) + if len(files_rsp) == 0 and len(dirs_rsp) == 0 and dir != self._path: # nothing found for this directory raise NotFoundException - return self._format_db_rsp(rs) + dirs = [] + for dir_id, dir_path in dirs_rsp: + root, d = os.path.split(dir_path.rstrip("/")) + if d != "" and root == self._encode(dir): + dirs.append(d) + return {'files': files_rsp, 'dirs': dirs} def get_dir_files(self,dir): - rs = self.db_con.get_files(dir, self.table) - if len(rs) == 0 and dir != "": raise NotFoundException - return self._format_db_rsp(rs)["files"] + dir = os.path.join(self._path, dir).rstrip("/") + files_rsp = self.db_con.get_dir_content(dir,\ + infos = self.media_attr, type = self.type) + if len(files_rsp) == 0 and dir != self._path: raise NotFoundException + return files_rsp def get_all_files(self,dir): - rs = self.db_con.get_all_files(dir, self.table) - if len(rs) == 0 and dir != "": raise NotFoundException - return self._format_db_rsp(rs)["files"] + dir = os.path.join(self._path, dir).rstrip("/") + files_rsp = self.db_con.get_alldir_files(dir,\ + infos = self.media_attr, type = self.type) + if len(files_rsp) == 0 and dir != self._path: raise NotFoundException + return files_rsp def get_file(self,file): - rs = self.db_con.get_file_info(file,self.table) - if len(rs) == 0: + file = os.path.join(self._path, file) + d, f = os.path.split(file) + files_rsp = self.db_con.get_file(d, f,\ + infos = self.media_attr, type = self.type) + if len(files_rsp) == 0: # this file is not found raise NotFoundException - return self._format_db_rsp(rs)["files"] + return files_rsp + + def get_file_withids(self,file_ids): + files_rsp = self.db_con.get_file_withids(file_ids,\ + infos = self.media_attr, type = self.type) + if len(files_rsp) != len(file_ids): + raise NotFoundException + return files_rsp + + def search(self, filter, ords = [], limit = None): + ft = mediafilters.And() + ft.combine(mediafilters.Equals("type", self.__class__.search_type)) + if filter is not None: + ft.combine(filter) + + return self.db_con.search(ft, infos = self.media_attr, orders=ords,\ + limit = limit) def get_root_path(self): return self._path - def get_root_paths(self, db_con=None): - if not db_con: - db_con = self.db_con + def get_root_paths(self): root_paths = [self.get_root_path()] - for dirlink_record in db_con.get_all_dirlinks('', self.table): - dirlink = os.path.join(self.get_root_path(), - dirlink_record[1], dirlink_record[2]) + for id, dirlink_record in self.db_con.get_all_dirlinks('', self.type): + dirlink = os.path.join(self.get_root_path(), dirlink_record) root_paths.append(dirlink) return root_paths @@ -220,14 +183,14 @@ # # Update process # - def update(self, sync = False): + def update(self, force = False, sync = False): if self._update_end: self._update_id += 1 if sync: # synchrone update - self._update() + self._update(force) self._update_end = True else: # asynchrone update - self.defered = threads.deferToThread(self._update) + self.defered = threads.deferToThread(self._update, force) self.defered.pause() # Add callback functions @@ -248,15 +211,6 @@ return 0 - def strip_root(self, path): - abs_path = os.path.abspath(path) - rel_path = os.path.normpath(abs_path[len(self.get_root_path()):]) - - if rel_path != '.': rel_path = rel_path.strip("/") - else: rel_path = '' - - return rel_path - def is_in_root(self, path, root=None): """Checks if a directory is physically in the supplied root (the library root by default).""" if not root: @@ -280,50 +234,64 @@ return True return False - def _update(self): - conn = self.db_con.get_new_connection() - conn.connect() + def _update_dir(self, dir, force = False): + # dirname/filename : (id, lastmodified) + library_files = dict([(os.path.join(it[1],it[3]), (it[2],it[4]))\ + for it in self.db_con.get_all_files(dir,self.type)]) + # name : id + library_dirs = dict([(item[1],item[0]) for item \ + in self.db_con.get_all_dirs(dir,self.type)]) + # name + library_dirlinks = [item[1] for item\ + in self.db_con.get_all_dirlinks(dir, self.type)] + + self.walk_directory(dir or self.get_root_path(), + library_dirs, library_files, library_dirlinks, force=force) + + # Remove unexistent files and directories from library + for (id, lastmodified) in library_files.values(): + self.db_con.remove_file(id) + reactor.callFromThread(self.dispatch_signame,'mediadb.mupdate',\ + attrs = {"type": "remove", "id": id}) + for id in library_dirs.values(): self.db_con.remove_dir(id) + for dirlinkname in library_dirlinks: + self.db_con.remove_dirlink(dirlinkname, self.type) + if self.watcher: + self.watcher.stop_watching_dir(dirlinkname) + + def _update(self, force = False): self._update_end = False try: - self.last_update_time = conn.get_update_time(self.type) - library_files = [(item[1],item[2]) for item \ - in conn.get_all_files('',self.table)] - library_dirs = [(item[1],item[2]) for item \ - in conn.get_all_dirs('',self.table)] - library_dirlinks = [(item[1],item[2]) for item\ - in conn.get_all_dirlinks('', self.table)] - - self.walk_directory(conn, self.get_root_path(), - library_dirs, library_files, library_dirlinks) + # compare keys recorded in the database with needed key + # if there is a difference, force update + keys = self.db_con.get_media_keys(self.search_type) + # remove cover because it is not used + try: keys.remove(("cover",)) + except ValueError: + pass + if len(keys) != len(self.media_attr): + log.msg(\ + _("%s library has to be updated, this can take a while.")%\ + (self.type,)) + force = True + self._update_dir('', force) self.mutex.acquire() - # Remove unexistent files and directories from library - for (dir,filename) in library_files: - conn.remove_file(dir, filename, self.table) - for (root,dirname) in library_dirs: - conn.remove_dir(root, dirname, self.table) - for (root, dirlinkname) in library_dirlinks: - conn.remove_dirlink(root, dirlinkname, self.table) - if self.watcher: - self.watcher.stop_watching_dir(os.path.join(root, - dirlinkname)) - # Remove empty dir - conn.erase_empty_dir(self.table) - # update stat values - conn.record_mediadb_stats(self.type) - conn.set_update_time(self.type) + self.db_con.erase_empty_dir(self.type) + self.db_con.update_stats(self.type) # commit changes - conn.connection.commit() + self.db_con.connection.commit() self.mutex.release() finally: # close the connection - conn.close() + self.db_con.close() - def walk_directory(self, db_con, walk_root, + def walk_directory(self, walk_root, library_dirs, library_files, library_dirlinks, - forbidden_roots=None): - """Walk a directory for files to update. Called recursively to carefully handle symlinks.""" + force = False, forbidden_roots=None): + """Walk a directory for files to update. + Called recursively to carefully handle symlinks.""" if not forbidden_roots: forbidden_roots = [self.get_root_path()] @@ -332,18 +300,17 @@ except UnicodeError: # skip this directory continue - # first update directory + try: dir_id = library_dirs[root] + except KeyError: + dir_id = self.db_con.insert_dir(root, self.type) + else: + del library_dirs[root] + + # search symlinks for dir in dirs: try: dir = self._encode(dir) except UnicodeError: # skip this directory continue - - tuple = (self.strip_root(root), dir) - if tuple in library_dirs: - library_dirs.remove(tuple) - else: - db_con.insert_dir(tuple,self.table) - # Walk only symlinks that aren't in library root or in one of # the additional known root paths which consist in already # crawled and out-of-main-root directories @@ -351,43 +318,19 @@ dir_path = os.path.join(root, dir) if os.path.islink(dir_path): if not self.is_in_a_root(dir_path, forbidden_roots): - forbidden_roots.append(dir_path) - if tuple in library_dirlinks: - library_dirlinks.remove(tuple) + forbidden_roots.append(os.path.realpath(dir_path)) + if dir_path in library_dirlinks: + library_dirlinks.remove(dir_path) else: - db_con.insert_dirlink(tuple, self.table) + self.db_con.insert_dirlink(dir_path, self.type) if self.watcher: self.watcher.watch_dir(dir_path, self) - self.walk_directory(db_con, dir_path, + self.walk_directory(dir_path, library_dirs, library_files, library_dirlinks, - forbidden_roots) + force, forbidden_roots) # else update files - for file in files: - try: file = self._encode(file) - except UnicodeError: # skip this file - continue - - try: obj_cls = self._get_file_info(file) - except NotSupportedFormat: - log.info(_("File %s not supported") % file) - continue - - path = os.path.join(self._path, root, file) - dir, fn = self.strip_root(root), file - file_object = self.file_class(db_con, dir, fn, path, obj_cls) - - tuple = (dir, fn) - if tuple in library_files: - library_files.remove(tuple) - if os.stat(os.path.join(root,file)).st_mtime >= \ - int(self.last_update_time): - file_object.update() - # Even if the media has not been modified, we may need - # to update some information (like external subtitle) - # it is the aim of this function - else: file_object.force_update() - else: file_object.insert() + self.update_files(root, dir_id, files, library_files, force) def end_update(self, result = True): self._update_end = True @@ -398,164 +341,406 @@ self._update_error = msg return True - def _get_file_info(self, filename): - (base, ext) = os.path.splitext(filename) - ext = ext.lower() - if ext in self.ext_dict.keys(): - return self.ext_dict[ext] + def update_files(self, root, dir_id, files, library_files, force = False): + for file in files: + try: file = self._encode(file) + except UnicodeError: # skip this file + continue + + file_path = os.path.join(root, file) + try: + fid, lastmodified = library_files[file_path] + need_update = force or os.stat(file_path).st_mtime>lastmodified + changes_type = "update" + except KeyError: + need_update, fid = True, None + changes_type = "add" + else: del library_files[file_path] + if need_update: + file_info = self._get_file_info(file_path) + if file_info is not None: # file supported + fid = self.set_media(dir_id, file_path, file_info, fid) + if fid: self.set_extra_infos(root, file, fid) + if need_update and fid: + reactor.callFromThread(self.dispatch_signame,'mediadb.mupdate',\ + attrs = {"type": changes_type, "id": fid}) + + def set_media(self, dir_id, file_path, file_info, file_id): + if file_info is None: return file_id # not supported + lastmodified = os.stat(file_path).st_mtime + if file_id: # do not update persistent attribute + for attr in self.__class__.persistent_attr: del file_info[attr] + fid = file_id + self.db_con.update_file(fid, lastmodified) else: - raise NotSupportedFormat + filename = os.path.basename(file_path) + fid = self.db_con.insert_file(dir_id, filename, lastmodified) + self.db_con.set_media_infos(fid, file_info) + return fid + + def set_extra_infos(self, dir, file, file_id): + pass + + def _get_file_info(self, file_path): + (base, ext) = os.path.splitext(file_path) + # try to get infos from this file + try: file_info = self.ext_dict[ext.lower()]().parse(file_path) + except KeyError: + log.info(_("File %s not supported") % file_path) + return None + except Exception, ex: + log.err(_("Unable to get infos from %s, see traceback")%file_path) + log.err("------------------Traceback lines--------------------") + log.err(self._encode(traceback.format_exc())) + log.err("-----------------------------------------------------") + return None + return file_info ####################################################################### ## Inotify actions ####################################################################### - def set_inotify_connection(self, db): - self.inotify_db = db - @inotify_action - def add_file(self, path, name): - try: obj_cls = self._get_file_info(name) - except NotSupportedFormat: - return False - - file_path = os.path.join(path, name) - dir = self.strip_root(path) - f_obj = self.file_class(self.inotify_db, dir, name, file_path, obj_cls) - if not f_obj.insert(): # insert failed - return False - self._add_missing_dir(path) + def add_file(self, path, file): + file_path = os.path.join(path, file) + file_info = self._get_file_info(file_path) + if not file_info: return self._inotify_add_info(path, file) + + try: dir_id, file_id = self.db_con.is_file_exist(path, file, self.type) + except TypeError: + dir_id = self.db_con.is_dir_exist(path, self.type) or\ + self.db_con.insert_dir(path, self.type) + file_id = None + + fid = self.set_media(dir_id, file_path, file_info, file_id) + if fid: + self.set_extra_infos(path, file, fid) + self._add_missing_dir(os.path.dirname(path)) + reactor.callFromThread(self.dispatch_signame, 'mediadb.mupdate',\ + attrs = {"type": "add", "id": fid}) return True @inotify_action def update_file(self, path, name): - try: obj_cls = self._get_file_info(name) - except NotSupportedFormat: - return False - - file_path = os.path.join(path, name) - dir = self.strip_root(path) - f_obj = self.file_class(self.inotify_db, dir, name, file_path, obj_cls) - if not f_obj.update(): # update failed - return False - return True + try: dir_id, file_id = self.db_con.is_file_exist(path, name, self.type) + except TypeError: + return self._inotify_update_info(path, name) + else: + file_path = os.path.join(path, name) + file_info = self._get_file_info(file_path) + if not file_info: + return self._inotify_update_info(path, name) + self.set_media(dir_id, file_path, file_info, file_id) + reactor.callFromThread(self.dispatch_signame, 'mediadb.mupdate',\ + attrs = {"type": "update", "id": file_id}) + return True @inotify_action def remove_file(self, path, name): - self.inotify_db.remove_file(self.strip_root(path), name, self.table) - self._remove_empty_dir(path) - return True + file = self.db_con.is_file_exist(path, name, self.type) + if file: + dir_id, file_id = file + self.db_con.remove_file(file_id) + self._remove_empty_dir(path) + reactor.callFromThread(self.dispatch_signame, 'mediadb.mupdate',\ + attrs = {"type": "remove", "id": file_id}) + return True + else: return self._inotify_remove_info(path, name) @inotify_action def add_directory(self, path, name, dirlink=False): dir_path = os.path.join(path, name) - if dirlink: - tuple = (self.strip_root(path), name) - self.inotify_db.insert_dirlink(tuple, self.table) + self.db_con.insert_dirlink(dir_path, self.type) self.watcher.watch_dir(dir_path, self) - self.walk_directory(self.inotify_db, dir_path, - [], [], self.get_root_paths(self.inotify_db)) - self._add_missing_dir(dir_path) + self._update_dir(dir_path.rstrip("/")) + self._add_missing_dir(os.path.dirname(dir_path)) self._remove_empty_dir(path) return True @inotify_action def remove_directory(self, path, name, dirlink=False): - rel_path = self.strip_root(path) + dir_path = os.path.join(path, name) + dir_id = self.db_con.is_dir_exist(dir_path, self.type) + if not dir_id: return False if dirlink: - self.inotify_db.remove_dirlink(rel_path, name, self.table) - self.watcher.stop_watching_dir(os.path.join(path, name)) + self.db_con.remove_dirlink(dir_path, self.type) + self.watcher.stop_watching_dir(dir_path) - self.inotify_db.remove_dir(rel_path, name, self.table) + ids = self.db_con.remove_recursive_dir(dir_path) + for id in ids: + reactor.callFromThread(self.dispatch_signame, 'mediadb.mupdate',\ + attrs = {"type": "remove", "id": id}) self._remove_empty_dir(path) return True + def inotify_record_changes(self): + self.mutex.acquire() + self.db_con.update_stats(self.type) + self.db_con.connection.commit() + self.dispatch_signame(self.update_signal_name) + self.mutex.release() + def _remove_empty_dir(self, path): - path = self.strip_root(path) while path != "": - if len(self.inotify_db.get_all_files(path, self.table)) > 0: + if len(self.db_con.get_all_files(path, self.type)) > 0: break - (path, dirname) = os.path.split(path) - self.inotify_db.remove_dir(path, dirname, self.table) + dir_id = self.db_con.is_dir_exist(path, self.type) + if dir_id: self.db_con.remove_dir(dir_id) + path = os.path.dirname(path) def _add_missing_dir(self, path): """ add missing dir in the mediadb """ - path = self.strip_root(path) while path != "": - (path, dirname) = os.path.split(path) - if self.inotify_db.is_dir_exist(path, dirname, self.table): - break - self.inotify_db.insert_dir((path, dirname), self.table) + dir_id = self.db_con.is_dir_exist(path, self.type) + if dir_id: break + self.db_con.insert_dir(path, self.type) + path = os.path.dirname(path) class AudioLibrary(_Library): - table = "audio_library" type = "audio" - file_class = DeejaydAudioFile + search_type = "song" update_signal_name = 'mediadb.aupdate' + custom_attr = ("artist","album","genre","tracknumber","date","bitrate",\ + "replaygain_track_gain","replaygain_track_peak",\ + "various_artist","discnumber") + cover_name = ("cover.jpg", "folder.jpg", ".folder.jpg",\ + "cover.png", "folder.png", ".folder.png") + + def get_cover(self, file_id): + try: (cover_id, mime, image) = self.db_con.get_file_cover(file_id) + except TypeError: + raise NotFoundException + return {"mime": mime, "cover": base64.b64decode(image), "id": cover_id} - def _build_supported_extension(self, player): - self.ext_dict = formats.get_audio_extensions(player) + def __extract_cover(self, cover_path): + if os.path.getsize(cover_path) > 512*1024: + return None # file too large (> 512k) + + # parse video file with kaa + cover_infos = kaa.metadata.parse(cover_path) + if cover_infos is None: + raise TypeError(_("cover %s not supported by kaa parser") % \ + cover_path) + # get mime type of this picture + mime_type = cover_infos["mime"] + if unicode(mime_type) not in (u"image/jpeg", u"image/png"): + log.info(_("cover %s : wrong mime type") % cover_path) + return None - def search(self,type,content): - accepted_type = ('all','title','genre','filename','artist','album') - if type not in accepted_type: - raise NotFoundException + try: fd = open(cover_path) + except Exception, ex: + log.info(_("Unable to open cover file %s") % cover_path) + return None + rs = fd.read() + fd.close() + return mime_type, base64.b64encode(rs) + + def __find_cover(self, dir): + cover = None + for name in self.cover_name: + cover_path = os.path.join(dir, name) + if os.path.isfile(cover_path): + try: (cover, lmod) = self.db_con.is_cover_exist(cover_path) + except TypeError: + try: mime, image = self.__extract_cover(cover_path) + except TypeError: + return None + cover = self.db_con.add_cover(cover_path, mime, image) + else: + if int(lmod)lastmodified + changes_type = "update" + except KeyError: + need_update, fid = True, None + changes_type = "add" + else: del library_files[file_path] + if need_update: + file_info = self._get_file_info(file_path) + if file_info is not None: # file supported + fid = self.set_media(dir_id,file_path,file_info,fid,cover) + elif fid and cover: + self.__update_cover(fid, cover) + if need_update and fid: + reactor.callFromThread(self.dispatch_signame,'mediadb.mupdate',\ + attrs = {"type": changes_type, "id": fid}) + + def set_media(self, dir_id, file_path, file_info, file_id, cover = None): + if file_info is not None and "cover" in file_info: + # find a cover in the file + image = base64.b64encode(file_info["cover"]["data"]) + mime = file_info["cover"]["mime"] + # use hash to identify cover in the db and avoid duplication + img_hash = self.__get_digest(image) + try: (cover, lmod) = self.db_con.is_cover_exist(img_hash) + except TypeError: + cover = self.db_con.add_cover(img_hash, mime, image) + file_info["cover"] = cover + elif cover: # use the cover available in this directory + file_info["cover"] = cover + fid = super(AudioLibrary, self).set_media(dir_id, file_path, \ + file_info, file_id) + # update compilation tag if necessary + if fid and "album" in file_info.keys() and file_info["album"] != '': + self.db_con.set_variousartist_tag(fid, file_info) + return fid - def _format_db_rsp(self,rs): - # format correctly database result - files = [] - dirs = [] - for song in rs: - if song[3] == 'directory': dirs.append(song[2]) - else: - file_info = _media.SongMedia(self.db_con, song) - file_info["uri"] = "file://"+urllib.quote(\ - os.path.join(self._path,song[1],song[2])) - files.append(file_info) - return {'files':files, 'dirs': dirs} + # + # custom inotify actions + # + @inotify_action + def add_file(self, path, file): + file_path = os.path.join(path, file) + file_info = self._get_file_info(file_path) + if not file_info and file in self.cover_name: # it is a cover + files = self.db_con.get_dircontent_id(path, self.type) + if len(files) > 0: + file_path = os.path.join(path, file) + try: mime, image = self.__extract_cover(file_path) + except TypeError: # image not supported + return False + if image: + cover = self.db_con.add_cover(file_path, mime, image) + for (id,) in files: + if self.__update_cover(id, cover): + reactor.callFromThread(self.dispatch_signame,\ + 'mediadb.mupdate',\ + attrs = {"type": "update", "id": id}) + return True + return False + + try: dir_id, file_id = self.db_con.is_file_exist(path, file, self.type) + except TypeError: + dir_id = self.db_con.is_dir_exist(path, self.type) or\ + self.db_con.insert_dir(path, self.type) + file_id = None + + fid = self.set_media(dir_id, file_path, file_info, file_id) + if fid: + self._add_missing_dir(os.path.dirname(path)) + reactor.callFromThread(self.dispatch_signame,\ + 'mediadb.mupdate', attrs = {"type": "add", "id": fid}) + return True + + def _inotify_remove_info(self, path, file): + rs = self.db_con.is_cover_exist(os.path.join(path, file)) + try: (cover, lmod) = rs + except TypeError: + return False + ids = self.db_con.search_id("cover", cover) + for (id,) in ids: + self.db_con.set_media_infos(id, {"cover": ""}) + reactor.callFromThread(self.dispatch_signame,\ + 'mediadb.mupdate', attrs = {"type": "update", "id": id}) + self.db_con.remove_cover(cover) + return True + + def _inotify_update_info(self, path, file): + file_path = os.path.join(path, file) + rs = self.db_con.is_cover_exist(file_path) + try: (cover, lmod) = rs + except TypeError: + return False + image = self.__extract_cover(file_path) + if image: self.db_con.update_cover(cover, image) + return True + ########################################################### class VideoLibrary(_Library): - table = "video_library" type = "video" - file_class = DeejaydVideoFile + search_type = "video" update_signal_name = 'mediadb.vupdate' + custom_attr = ("videoheight", "videowidth","external_subtitle",\ + "audio_channels", "subtitle_channels") + subtitle_ext = (".srt",) + + def set_extra_infos(self, dir, file, file_id): + file_path = os.path.join(dir, file) + (base_path,ext) = os.path.splitext(file_path) + sub = "" + for ext_type in self.subtitle_ext: + if os.path.isfile(os.path.join(base_path + ext_type)): + sub = quote_uri(base_path + ext_type) + break + try: (recorded_sub,) = self.db_con.get_file_info(file_id,\ + "external_subtitle") + except TypeError: + recorded_sub = None + if recorded_sub != sub: + self.db_con.set_media_infos(file_id,{"external_subtitle": sub}) - def search(self, content): - rs = self.db_con.search_video_library(content) - return self._format_db_rsp(rs)["files"] + # + # custom inotify actions + # + def _inotify_add_info(self, path, file): + (base_file, ext) = os.path.splitext(file) + if ext in self.subtitle_ext: + for video_ext in self.ext_dict.keys(): + try: (dir_id,fid,) = self.db_con.is_file_exist(path,\ + base_file+video_ext, self.type) + except TypeError: pass + else: + uri = quote_uri(os.path.join(path, file)) + self.db_con.set_media_infos(fid, {"external_subtitle": uri}) + reactor.callFromThread(self.dispatch_signame,\ + 'mediadb.mupdate',\ + attrs = {"type": "update", "id": fid}) + return True + return False - def _build_supported_extension(self, player): - self.ext_dict = formats.get_video_extensions(player) + def _inotify_remove_info(self, path, file): + (base_file, ext) = os.path.splitext(file) + if ext in self.subtitle_ext: + ids = self.db_con.search_id("external_subtitle",\ + quote_uri(os.path.join(path, file))) + for (id,) in ids: + self.db_con.set_media_infos(id, {"external_subtitle": ""}) + reactor.callFromThread(self.dispatch_signame,\ + 'mediadb.mupdate', attrs = {"type": "update", "id": id}) + return True + return False - def _format_db_rsp(self,rs): - # format correctly database result - files = [] - dirs = [] - for (id,dir,fn,t,ti,videow,videoh,sub,len) in rs: - if t == 'directory': dirs.append(fn) - else: - file_info = {"path":os.path.join(dir,fn),"length":len, - "media_id":id,"filename":fn,"dir":dir, - "title":ti, - "videowidth":videow,"videoheight":videoh, - "uri":"file://"+os.path.join(self._path,dir,fn), - "external_subtitle":sub,"type":"video"} - files.append(file_info) - return {'files':files, 'dirs': dirs} + def _inotify_update_info(self, path, file): + return False + ########################################################### # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediadb/_media.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediadb/_media.py --- deejayd-0.7.2/deejayd/mediadb/_media.py 2008-05-14 22:50:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediadb/_media.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,56 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import os - -class SongMedia(dict): - - def __init__(self, db, infos): - self.__db = db - #infos : (id,dir,fn,t,ti,ar,al,gn,tn,dt,lg,bt,rg_gain,rg_peak) - self.__replaygain = {"track_gain":infos[12] , "track_peak":infos[13]} - - self["path"] = os.path.join(infos[1],infos[2]) - self["length"] = infos[10] - self["media_id"] = infos[0] - self["filename"] = infos[2] - self["dir"] = infos[1] - self["title"] = infos[4] - self["artist"] = infos[5] - self["album"] = infos[6] - self["genre"] = infos[7] - self["track"] = infos[8] - self["date"] = infos[9] - self["bitrate"] = infos[11] - self["type"] = "song" - - def replay_gain(self): - """Return the recommended Replay Gain scale factor.""" - try: - db = float(self.__replaygain["track_gain"].split()[0]) - peak = self.__replaygain["track_peak"] and\ - float(self.__replaygain["track_peak"]) or 1.0 - except (KeyError, ValueError, IndexError): - return 1.0 - else: - scale = 10.**(db / 20) - if scale * peak > 1: - scale = 1.0 / peak # don't clip - return min(15, scale) - -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/mediafilters.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/mediafilters.py --- deejayd-0.7.2/deejayd/mediafilters.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/mediafilters.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,147 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +__all__ = ( + 'BASIC_FILTERS', 'NAME2BASIC', + 'Equals', 'NotEquals', 'Contains', 'NotContains', 'Regexi', + 'Higher', 'Lower', + 'COMPLEX_FILTERS', 'NAME2COMPLEX', + 'And', 'Or', + "DEFAULT_AUDIO_SORT", "DEFAULT_VIDEO_SORT" + ) + + +class MediaFilter(object): + + def get_identifier(self): + return self.__class__.__name__.lower() + + def get_name(self): + return self.__class__.__name__.lower() + + def __str__(self): + return NotImplementedError + + +class BasicFilter(MediaFilter): + type = 'basic' + + def __init__(self, tag, pattern): + super(BasicFilter, self).__init__() + self.tag = tag + self.pattern = pattern + + def equals(self, filter): + if filter.type == 'basic' and filter.get_name() == self.get_name(): + return filter.tag == self.tag and filter.pattern == self.pattern + return False + + def __str__(self): + return self.repr_str % (self.tag, self.pattern) + + +class Equals(BasicFilter): repr_str = "(%s == '%s')" +class NotEquals(BasicFilter): repr_str = "(%s != '%s')" +class Contains(BasicFilter): repr_str = "(%s == '%%%s%%')" +class NotContains(BasicFilter): repr_str = "(%s != '%%%s%%')" +class Regexi(BasicFilter): repr_str = "(%s ~ /%s/)" +class Higher(BasicFilter): repr_str = "(%s >= %s)" +class Lower(BasicFilter): repr_str = "(%s <= %s)" + + +BASIC_FILTERS = ( + Equals, + NotEquals, + Contains, + NotContains, + Regexi, + Higher, + Lower, + ) + +NAME2BASIC = dict([(x(None, None).get_identifier(), x) for x in BASIC_FILTERS]) + + +class ComplexFilter(MediaFilter): + type = 'complex' + + def __init__(*__args, **__kwargs): + self = __args[0] + super(ComplexFilter, self).__init__() + + self.filterlist = [] + for filter in __args[1:]: + self.combine(filter) + + def combine(self, filter): + self.filterlist.append(filter) + + def equals(self, filter): + if filter.type == 'complex' and len(filter) == len(self.filterlist): + for ft in self.filterlist: + if not filter.find_filter(ft): return False + return True + return False + + def find_filter_by_tag(self, tag): + return [ft for ft in self.filterlist\ + if ft.type == 'basic' and ft.tag == tag] + + def find_filter_by_name(self, name): + return [ft for ft in self.filterlist if ft.get_name() == name] + + def find_filter(self, filter): + return [ft for ft in self.filterlist if ft.equals(filter)] + + def remove_filter(self, filter): + filters = self.find_filter(filter) + if not filters: + raise ValueError + for ft in filters: self.filterlist.remove(ft) + + def __iter__(self): + return iter(self.filterlist) + + def __getitem__(self, idx): + return self.filterlist[idx] + + def __len__(self): + return len(self.filterlist) + + def __str__(self): + filters_str = map(str, self.filterlist) + return "(%s)" % self.repr_joiner.join(filters_str) + + +class And(ComplexFilter): repr_joiner = " && " +class Or(ComplexFilter): repr_joiner = " || " + + +COMPLEX_FILTERS = ( + And, + Or, + ) + +NAME2COMPLEX = dict([(x().get_identifier(), x) for x in COMPLEX_FILTERS]) + +DEFAULT_AUDIO_SORT = [("album", "ascending"), ("discnumber", "ascending"),\ + ("tracknumber", "ascending")] +DEFAULT_VIDEO_SORT = [("title", "ascending")] + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/net/client.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/net/client.py --- deejayd-0.7.2/deejayd/net/client.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/net/client.py 2009-09-21 08:44:21.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -20,19 +20,18 @@ import socket, asyncore, threading import sys,time -from cStringIO import StringIO -try: from xml.etree import cElementTree as ET # python 2.5 -except ImportError: # python 2.4 - import cElementTree as ET from Queue import Queue, Empty import deejayd.interfaces from deejayd.interfaces import DeejaydError, DeejaydKeyValue, DeejaydSignal -from deejayd.net.xmlbuilders import DeejaydXMLCommand +from deejayd.rpc import Fault, DEEJAYD_PROTOCOL_VERSION +from deejayd.rpc.jsonbuilders import JSONRPCRequest, Get_json_filter +from deejayd.rpc.jsonparsers import loads_response, Parse_json_filter -MSG_DELIMITER = 'ENDXML\n' +MSG_DELIMITER = 'ENDJSON\n' +MAX_BANNER_LENGTH = 50 class DeejaydAnswer(deejayd.interfaces.DeejaydAnswer): @@ -42,7 +41,13 @@ self.answer_received = threading.Event() self.callbacks = [] self.server = server - self.originating_command = 'unknown' + self.id = None + + def set_id(self, id): + self.id = id + + def get_id(self): + return self.id def wait(self): self.answer_received.wait() @@ -72,14 +77,14 @@ for cb in self.callbacks: cb(self) - def set_originating_command(self, cmd): - self.originating_command = cmd - def get_originating_command(self): - return self.originating_command +class DeejaydKeyValue(DeejaydAnswer, deejayd.interfaces.DeejaydKeyValue): + + def __init__(self, server=None): + DeejaydAnswer.__init__(self, server) -class DeejaydKeyValue(DeejaydAnswer, deejayd.interfaces.DeejaydKeyValue): +class DeejaydList(DeejaydAnswer, deejayd.interfaces.DeejaydList): def __init__(self, server=None): DeejaydAnswer.__init__(self, server) @@ -106,35 +111,98 @@ DeejaydAnswer.__init__(self, server) +class DeejaydStaticPlaylist(deejayd.interfaces.DeejaydStaticPlaylist): + + def __init__(self, server, pl_id, name): + self.server = server + self.__pl_id = pl_id + self.__name = name + + def get(self, first=0, length=-1): + params = [self.__pl_id, first] + if length != -1: + params.append(length) + cmd = JSONRPCRequest('recpls.get', params) + ans = DeejaydMediaList(self.server) + return self.server._send_command(cmd, ans) + + def __add(self, values, type): + cmd = JSONRPCRequest('recpls.staticAdd', [self.__pl_id, values, type]) + return self.server._send_command(cmd) + + def add_songs(self, song_ids): + return self.__add(song_ids, "id") + + def add_paths(self, paths): + return self.__add(paths, "path") + + +class DeejaydMagicPlaylist(deejayd.interfaces.DeejaydMagicPlaylist): + + def __init__(self, server, pl_id, name): + self.server = server + self.__pl_id = pl_id + self.__name = name + + def get(self, first=0, length=-1): + params = [self.__pl_id, first] + if length != -1: + params.append(length) + cmd = JSONRPCRequest('recpls.get', params) + ans = DeejaydMediaList(self.server) + return self.server._send_command(cmd, ans) + + def add_filter(self, filter): + jfilter = Get_json_filter(filter).dump() + cmd = JSONRPCRequest('recpls.magicAddFilter', [self.__pl_id, jfilter]) + return self.server._send_command(cmd) + + def remove_filter(self, filter): + jfilter = Get_json_filter(filter).dump() + cmd = JSONRPCRequest('recpls.magicRemoveFilter',\ + [self.__pl_id, jfilter]) + return self.server._send_command(cmd) + + def clear_filters(self): + cmd = JSONRPCRequest('recpls.magicClearFilter', [self.__pl_id]) + return self.server._send_command(cmd) + + def get_properties(self): + cmd = JSONRPCRequest('recpls.magicGetProperties', [self.__pl_id]) + return self.server._send_command(cmd, DeejaydKeyValue()) + + def set_property(self, key, value): + cmd = JSONRPCRequest('recpls.magicSetProperty',\ + [self.__pl_id, key, value]) + return self.server._send_command(cmd) + + class DeejaydWebradioList(deejayd.interfaces.DeejaydWebradioList): def __init__(self, server): self.server = server def get(self, first = 0, length = None): - cmd = DeejaydXMLCommand('webradioList') - cmd.add_simple_arg('first', first) + params = [first] if length != None: - cmd.add_simple_arg('length', length) + params.append(length) + cmd = JSONRPCRequest('webradio.get', params) ans = DeejaydMediaList(self) return self.server._send_command(cmd, ans) def add_webradio(self, name, urls): - cmd = DeejaydXMLCommand('webradioAdd') - cmd.add_simple_arg('name', name) # FIXME : Provision for the future where one webradio may have multiple # urls. # cmd.add_multiple_arg('url', urls) - cmd.add_simple_arg('url', urls) + cmd = JSONRPCRequest('webradio.add', [name, urls]) return self.server._send_command(cmd) def delete_webradios(self, wr_ids): - cmd = DeejaydXMLCommand('webradioRemove') - cmd.add_multiple_arg('id', wr_ids) + cmd = JSONRPCRequest('webradio.remove', [wr_ids]) return self.server._send_command(cmd) def clear(self): - cmd = DeejaydXMLCommand('webradioClear') + cmd = JSONRPCRequest('webradio.clear', []) return self.server._send_command(cmd) @@ -144,228 +212,235 @@ self.server = server def get(self, first = 0, length = None): - cmd = DeejaydXMLCommand('queueInfo') - cmd.add_simple_arg('first', first) + params = [first] if length != None: - cmd.add_simple_arg('length', length) + params.append(length) + cmd = JSONRPCRequest('queue.get', params) ans = DeejaydMediaList(self) return self.server._send_command(cmd, ans) - def add_medias(self, paths, pos = None): - cmd = DeejaydXMLCommand('queueAdd') - cmd.add_multiple_arg('path', paths) - if pos!= None: - cmd.add_simple_arg('pos', pos) + def add_songs(self, song_ids, pos = None): + params = [song_ids] + if pos != None: + params.append(pos) + cmd = JSONRPCRequest('queue.addIds', params) return self.server._send_command(cmd) - def load_playlists(self, names, pos = None): - cmd = DeejaydXMLCommand('queueLoadPlaylist') - cmd.add_multiple_arg('name', names) + def add_paths(self, paths, pos = None): + params = [paths] if pos != None: - cmd.add_simple_arg('pos', pos) + params.append(pos) + cmd = JSONRPCRequest('queue.addPath', params) + return self.server._send_command(cmd) + + def load_playlists(self, pl_ids, pos = None): + params = [pl_ids] + if pos != None: + params.append(pos) + cmd = JSONRPCRequest('queue.loads', params) return self.server._send_command(cmd) def clear(self): - cmd = DeejaydXMLCommand('queueClear') + cmd = JSONRPCRequest('queue.clear', []) + return self.server._send_command(cmd) + + def move(self, ids, new_pos): + cmd = JSONRPCRequest('queue.move', [ids, new_pos]) return self.server._send_command(cmd) def del_songs(self, ids): - cmd = DeejaydXMLCommand('queueRemove') - cmd.add_multiple_arg('id', ids) + cmd = JSONRPCRequest('queue.remove', [ids]) return self.server._send_command(cmd) -class DeejaydPlaylist(deejayd.interfaces.DeejaydPlaylist): +class DeejaydPlaylistMode(deejayd.interfaces.DeejaydPlaylistMode): - def __init__(self, server, pl_name = None): - deejayd.interfaces.DeejaydPlaylist.__init__(self, pl_name) + def __init__(self, server): self.server = server def get(self, first = 0, length = None): - cmd = DeejaydXMLCommand('playlistInfo') - if self.__pl_name != None: - cmd.add_simple_arg('name', self.__pl_name) - cmd.add_simple_arg('first', first) + params = [first] if length != None: - cmd.add_simple_arg('length', length) + params.append(length) + cmd = JSONRPCRequest('playlist.get', params) ans = DeejaydMediaList(self) return self.server._send_command(cmd, ans) def save(self, name): - cmd = DeejaydXMLCommand('playlistSave') - cmd.add_simple_arg('name', name or self.__pl_name) + cmd = JSONRPCRequest('playlist.save', [name]) + return self.server._send_command(cmd, DeejaydKeyValue()) + + def add_songs(self, song_ids, pos = None): + params = [song_ids] + if pos != None: + params.append(pos) + cmd = JSONRPCRequest('playlist.addIds', params) + return self.server._send_command(cmd) + + def add_paths(self, paths, pos = None): + params = [paths] + if pos != None: + params.append(pos) + cmd = JSONRPCRequest('playlist.addPath', params) return self.server._send_command(cmd) - def add_songs(self, paths, position = None): - cmd = DeejaydXMLCommand('playlistAdd') - cmd.add_multiple_arg('path', paths) - if position != None: - cmd.add_simple_arg('pos', position) - if self.__pl_name != None: - cmd.add_simple_arg('name', self.__pl_name) - return self.server._send_command(cmd) - - def loads(self, names, pos = None): - cmd = DeejaydXMLCommand('playlistLoad') - cmd.add_multiple_arg('name', names) + def loads(self, pl_ids, pos = None): + params = [pl_ids] if pos != None: - cmd.add_simple_arg('pos', pos) + params.append(pos) + cmd = JSONRPCRequest('playlist.loads', params) return self.server._send_command(cmd) def move(self, ids, new_pos): - cmd = DeejaydXMLCommand('playlistMove') - cmd.add_multiple_arg('ids', ids) - cmd.add_simple_arg('new_pos', new_pos) + cmd = JSONRPCRequest('playlist.move', [ids, new_pos]) return self.server._send_command(cmd) def shuffle(self): - cmd = DeejaydXMLCommand('playlistShuffle') - if self.__pl_name != None: - cmd.add_simple_arg('name', self.__pl_name) + cmd = JSONRPCRequest('playlist.shuffle', []) return self.server._send_command(cmd) def clear(self): - cmd = DeejaydXMLCommand('playlistClear') - if self.__pl_name != None: - cmd.add_simple_arg('name', self.__pl_name) + cmd = JSONRPCRequest('playlist.clear', []) return self.server._send_command(cmd) def del_songs(self, ids): - cmd = DeejaydXMLCommand('playlistRemove') - cmd.add_multiple_arg('id', ids) - if self.__pl_name != None: - cmd.add_simple_arg('name', self.__pl_name) + cmd = JSONRPCRequest('playlist.remove', [ids]) return self.server._send_command(cmd) -class DeejaydVideo(deejayd.interfaces.DeejaydVideo): +class DeejaydPanel(deejayd.interfaces.DeejaydPanel): def __init__(self, server): self.server = server def get(self, first = 0, length = None): - cmd = DeejaydXMLCommand('videoInfo') - cmd.add_simple_arg('first', first) + params = [first] if length != None: - cmd.add_simple_arg('length', length) + params.append(length) + cmd = JSONRPCRequest('panel.get', params) ans = DeejaydMediaList(self) return self.server._send_command(cmd, ans) - def set(self, value, type = "directory"): - cmd = DeejaydXMLCommand('setvideo') - cmd.add_simple_arg('value', value) - cmd.add_simple_arg('type', type) + def get_panel_tags(self): + cmd = JSONRPCRequest('panel.tags', []) + return self.server._send_command(cmd, DeejaydList()) + + def get_active_list(self): + cmd = JSONRPCRequest('panel.activeList', []) + return self.server._send_command(cmd, DeejaydKeyValue()) + + def set_active_list(self, type, pl_id=""): + cmd = JSONRPCRequest('panel.setActiveList', [type, pl_id]) return self.server._send_command(cmd) + def set_panel_filters(self, tag, values): + if not isinstance(values, list): + values = [values] + cmd = JSONRPCRequest('panel.setFilter', [tag, values]) + return self.server._send_command(cmd) -class ConnectError(Exception): - pass + def remove_panel_filters(self, tag): + cmd = JSONRPCRequest('panel.removeFilter', [tag]) + return self.server._send_command(cmd) + def clear_panel_filters(self): + cmd = JSONRPCRequest('panel.clearFilter', []) + return self.server._send_command(cmd) -class _DeejayDaemon(deejayd.interfaces.DeejaydCore): - """Abstract class for a deejay daemon client.""" + def set_search_filter(self, tag, value): + cmd = JSONRPCRequest('panel.setSearch', [tag, value]) + return self.server._send_command(cmd) - def __init__(self): - deejayd.interfaces.DeejaydCore.__init__(self) + def clear_search_filter(self): + cmd = JSONRPCRequest('panel.clearSearch', []) + return self.server._send_command(cmd) - self.__timeout = 2 - self.expected_answers_queue = Queue() - self.next_msg = "" + def set_sorts(self, sort): + cmd = JSONRPCRequest('panel.setSort', [sort]) + return self.server._send_command(cmd) - # Socket setup - self.socket_to_server = socket.socket(socket.AF_INET, - socket.SOCK_STREAM) - self.socket_to_server.settimeout(self.__timeout) - self.connected = False - def connect(self, host, port): - if self.connected: - self.disconnect() +class DeejaydVideo(deejayd.interfaces.DeejaydVideo): - self.host = host - self.port = port + def __init__(self, server): + self.server = server - try: self.socket_to_server.connect((self.host, self.port)) - except socket.timeout, msg: - # reset connection - self._reset_socket() - raise ConnectError('Connection timeout') - except socket.error, msg: - raise ConnectError('Connection with server failed : %s' % msg) + def get(self, first = 0, length = None): + params = [first] + if length != None: + params.append(length) + cmd = JSONRPCRequest('video.get', params) + ans = DeejaydMediaList(self) + return self.server._send_command(cmd, ans) - self.socket_to_server.settimeout(None) - socketFile = self.socket_to_server.makefile() + def set(self, value, type = "directory"): + cmd = JSONRPCRequest('video.set', [value, type]) + return self.server._send_command(cmd) - # Catch version - self.version = socketFile.readline() - self.connected = True - if self.version.startswith("OK DEEJAYD"): - # FIXME extract version number - self.connected = True - else: - self.disconnect() - raise ConnectError('Connection with server failed') + def set_sorts(self, sort): + cmd = JSONRPCRequest('video.sort', [sort]) + return self.server._send_command(cmd) - def is_connected(self): - return self.connected - def disconnect(self): - if not self.connected: - return +class ConnectError(deejayd.interfaces.DeejaydError): + pass - self.socket_to_server.settimeout(self.__timeout) - try: self._send_simple_command('close').get_contents() - except socket.timeout: pass - self._reset_socket() - self.connected = False - self.host = None - self.port = None +class _DeejayDaemon(deejayd.interfaces.DeejaydCore): + """Abstract class for a deejay daemon client.""" - def _reset_socket(self): - self.socket_to_server.close() - # New Socket setup - self.socket_to_server = socket.socket(socket.AF_INET, - socket.SOCK_STREAM) - self.socket_to_server.settimeout(self.__timeout) + def __init__(self): + deejayd.interfaces.DeejaydCore.__init__(self) - def _sendmsg(self, buf): - self.socket_to_server.send(buf + MSG_DELIMITER) + self.expected_answers_queue = Queue() + self.connected = False - def _readmsg(self): - msg_chunks = '' - msg_chunk = '' - def split_msg(msg,index): - return (msg[0:index], msg[index+len(MSG_DELIMITER):len(msg)]) + def connect(self, host, port, ignore_version=False): + raise NotImplementedError - while 1: - try: index = self.next_msg.index(MSG_DELIMITER) - except ValueError: pass - else: - (rs,self.next_msg) = split_msg(self.next_msg, index) - break + def is_connected(self): + return self.connected - msg_chunk = self.socket_to_server.recv(4096) - # socket.recv returns an empty string if the socket is closed, so - # catch this. - if msg_chunk == '': - raise socket.error() + def disconnect(self): + raise NotImplementedError - msg_chunks += msg_chunk - try: index = msg_chunks.index(MSG_DELIMITER) - except ValueError: pass + def _version_from_banner(self, banner_line): + tokenized_banner = banner_line.split(' ') + try: + version = tokenized_banner[2] + except IndexError: + raise ValueError + else: + numerical_version = map(int, version.split('.')) + try: + if tokenized_banner[3] == 'protocol': + protocol_version = tokenized_banner[4] + else: + raise IndexError + except IndexError: + # Assume protocol is first one + protocol_version = 1 else: - (rs,self.next_msg) = split_msg(msg_chunks, index) - break + protocol_version = int(protocol_version) + return numerical_version, protocol_version - return rs + def _version_is_supported(self, versions): + numerical_version = versions[0] + protocol_version = versions[1] + return protocol_version == DEEJAYD_PROTOCOL_VERSION def _send_simple_command(self, cmd_name): - cmd = DeejaydXMLCommand(cmd_name) + cmd = JSONRPCRequest(cmd_name, []) return self._send_command(cmd) - def get_playlist(self, name = None): - return DeejaydPlaylist(self,name) + def get_recorded_playlist(self, pl_id): + return DeejaydStaticPlaylist(self, pl_id) + + def get_playlist(self): + return DeejaydPlaylistMode(self) + + def get_panel(self): + return DeejaydPanel(self) def get_webradios(self): return DeejaydWebradioList(self) @@ -379,206 +454,159 @@ def ping(self): return self._send_simple_command('ping') + def set_mode(self, mode): + cmd = JSONRPCRequest('setmode', [mode]) + return self._send_command(cmd) + + def get_mode(self): + cmd = JSONRPCRequest('availablemodes', []) + return self._send_command(cmd, DeejaydKeyValue()) + + def set_option(self, source, option_name, option_value): + cmd = JSONRPCRequest('setOption', [source, option_name, option_value]) + return self._send_command(cmd) + + def get_status(self): + cmd = JSONRPCRequest('status', []) + return self._send_command(cmd, DeejaydKeyValue()) + + def get_stats(self): + cmd = JSONRPCRequest('stats', []) + return self._send_command(cmd, DeejaydKeyValue()) + def play_toggle(self): - return self._send_simple_command('playToggle') + return self._send_simple_command('player.playToggle') def stop(self): - return self._send_simple_command('stop') + return self._send_simple_command('player.stop') def previous(self): - return self._send_simple_command('previous') + return self._send_simple_command('player.previous') def next(self): - return self._send_simple_command('next') + return self._send_simple_command('player.next') - def seek(self, pos): - cmd = DeejaydXMLCommand('seek') - cmd.add_simple_arg('time', pos) + def seek(self, pos, relative = False): + cmd = JSONRPCRequest('player.seek', [pos, relative]) return self._send_command(cmd) def get_current(self): - cmd = DeejaydXMLCommand('current') + cmd = JSONRPCRequest('player.current', []) return self._send_command(cmd,DeejaydMediaList()) - def go_to(self, id, id_type = None, source = None): - cmd = DeejaydXMLCommand('goto') - cmd.add_simple_arg('id', id) - if id_type: - cmd.add_simple_arg('id_type', id_type) + def go_to(self, id, id_type = "id", source = None): + params = [id, id_type] if source: - cmd.add_simple_arg('source', source) + params.append(source) + cmd = JSONRPCRequest('player.goto', params) return self._send_command(cmd) - def set_volume(self, volume_value): - cmd = DeejaydXMLCommand('setVolume') - cmd.add_simple_arg('volume', volume_value) + def set_volume(self, volume): + cmd = JSONRPCRequest('player.setVolume', [volume]) return self._send_command(cmd) - def set_option(self, option_name, option_value): - cmd = DeejaydXMLCommand('setOption') - cmd.add_simple_arg('option_name', option_name) - cmd.add_simple_arg('option_value', option_value) + def set_player_option(self, name, value): + cmd = JSONRPCRequest('player.setPlayerOption', [name, value]) return self._send_command(cmd) - def set_mode(self, mode_name): - cmd = DeejaydXMLCommand('setMode') - cmd.add_simple_arg('mode', mode_name) - return self._send_command(cmd) + def update_audio_library(self, force = False): + cmd = JSONRPCRequest('audiolib.update', [force]) + return self._send_command(cmd, DeejaydKeyValue()) - def get_mode(self): - cmd = DeejaydXMLCommand('getMode') + def update_video_library(self, force = False): + cmd = JSONRPCRequest('videolib.update', [force]) return self._send_command(cmd, DeejaydKeyValue()) - def set_player_option(self, name, value): - cmd = DeejaydXMLCommand('setPlayerOption') - cmd.add_simple_arg('option_name', name) - cmd.add_simple_arg('option_value', value) - return self._send_command(cmd) + def get_audio_dir(self, dir = ""): + ans = DeejaydFileList(self) + cmd = JSONRPCRequest('audiolib.getDir', [dir]) + return self._send_command(cmd, ans) - def get_status(self): - cmd = DeejaydXMLCommand('status') - return self._send_command(cmd, DeejaydKeyValue()) + def audio_search(self, pattern, type = 'all'): + ans = DeejaydMediaList(self) + cmd = JSONRPCRequest('audiolib.search', [pattern, type]) + return self._send_command(cmd, ans) - def get_stats(self): - cmd = DeejaydXMLCommand('stats') - return self._send_command(cmd, DeejaydKeyValue()) + def mediadb_list(self, tag, filter): + params = [tag] + if filter is not None: + params.append(Get_json_filter(filter).dump()) + ans = DeejaydList(self) + cmd = JSONRPCRequest('audiolib.taglist', params) + return self._send_command(cmd, ans) - def update_audio_library(self): - cmd = DeejaydXMLCommand('audioUpdate') - return self._send_command(cmd, DeejaydKeyValue()) + def get_video_dir(self, dir = ""): + ans = DeejaydFileList(self) + cmd = JSONRPCRequest('videolib.getDir', [dir]) + return self._send_command(cmd, ans) - def update_video_library(self): - cmd = DeejaydXMLCommand('videoUpdate') + def create_recorded_playlist(self, name, type): + cmd = JSONRPCRequest('recpls.create', [name, type]) return self._send_command(cmd, DeejaydKeyValue()) - def erase_playlist(self, names): - cmd = DeejaydXMLCommand('playlistErase') - cmd.add_multiple_arg('name', names) + def get_recorded_playlist(self, pl_id, name, type): + if type == "static": + return DeejaydStaticPlaylist(self, pl_id, name) + elif type == "magic": + return DeejaydMagicPlaylist(self, pl_id, name) + + def erase_playlist(self, pl_ids): + cmd = JSONRPCRequest('recpls.erase', [pl_ids]) return self._send_command(cmd) def get_playlist_list(self): - cmd = DeejaydXMLCommand('playlistList') + cmd = JSONRPCRequest('recpls.list', []) return self._send_command(cmd,DeejaydMediaList()) - def get_audio_dir(self,dir = None): - cmd = DeejaydXMLCommand('getdir') - if dir != None: - cmd.add_simple_arg('directory', dir) - ans = DeejaydFileList(self) - return self._send_command(cmd, ans) - - def audio_search(self, search_txt, type = 'all'): - cmd = DeejaydXMLCommand('search') - cmd.add_simple_arg('type', type) - cmd.add_simple_arg('txt', search_txt) - ans = DeejaydFileList(self) - return self._send_command(cmd, ans) - - def get_video_dir(self,dir = None): - cmd = DeejaydXMLCommand('getvideodir') - if dir != None: - cmd.add_simple_arg('directory', dir) - ans = DeejaydFileList(self) - return self._send_command(cmd, ans) + def set_media_rating(self, media_ids, rating, type = "audio"): + cmd = JSONRPCRequest('setRating', [media_ids, rating, type]) + return self._send_command(cmd) def dvd_reload(self): - cmd = DeejaydXMLCommand('dvdLoad') - return self._send_command(cmd) + return self._send_simple_command('dvd.reload') def get_dvd_content(self): - cmd = DeejaydXMLCommand('dvdInfo') + cmd = JSONRPCRequest('dvd.get', []) ans = DeejaydDvdInfo(self) return self._send_command(cmd, ans) - def _build_answer(self, string_io): - xmlpath = [] - originating_command = '' - parms = {} - answer = True - signal = None - for event, elem in ET.iterparse(string_io,events=("start","end")): - if event == "start": - xmlpath.append(elem.tag) - if len(xmlpath) == 2: - if elem.tag in ('error', 'response'): - expected_answer = self.expected_answers_queue.get() - elif elem.tag == 'signal': - signal = DeejaydSignal() - elif elem.tag in ("directory","file","media"): - assert xmlpath == ['deejayd', 'response',elem.tag] - elif elem.tag == "track": - assert xmlpath == ['deejayd','response','dvd','track'] - track = {"audio":[],"subtitle":[],"chapter":[]} - elif elem.tag in ("audio","subtitle","chapter"): - assert xmlpath == ['deejayd','response','dvd','track',\ - elem.tag] - elif elem.tag == "listparm": - list_parms = [] - elif elem.tag == "dictparm": - dict_parms = {} - else: # event = "end" - xmlpath.pop() - - if elem.tag in ('error','response'): - expected_answer.set_originating_command(elem.attrib['name']) - elif elem.tag == 'signal': - signal.set_name(elem.attrib['name']) - self._dispatch_signal(signal) - signal = None - - if elem.tag == "error": - expected_answer.set_error(elem.text) - elif elem.tag == "response": - rsp_type = elem.attrib['type'] - if rsp_type == "KeyValue": - answer = parms - elif rsp_type == "FileAndDirList": - if 'directory' in elem.attrib.keys(): - expected_answer.set_rootdir(elem.\ - attrib['directory']) - elif rsp_type == "MediaList": - if 'total_length' in elem.attrib.keys(): - expected_answer.set_total_length(elem.\ - attrib['total_length']) - expected_answer._received(answer) - expected_answer = None - elif elem.tag == "listparm": - parms[elem.attrib["name"]] = list_parms - elif elem.tag == "listvalue": - list_parms.append(elem.attrib["value"]) - elif elem.tag == "dictparm": - list_parms.append(dict_parms) - elif elem.tag == "dictitem": - dict_parms[elem.attrib["name"]] = elem.attrib["value"] - elif elem.tag == "parm": - value = elem.attrib["value"] - try: value = int(value) - except ValueError: pass - parms[elem.attrib["name"]] = value - elif elem.tag == "media": - expected_answer.add_media(parms) - elif elem.tag == "directory": - expected_answer.add_dir(elem.attrib['name']) - elif elem.tag == "file": - expected_answer.add_file(parms) - elif elem.tag in ("audio","subtitle"): - track[elem.tag].append({"ix": elem.attrib['ix'],\ - "lang": elem.attrib['lang']}) - elif elem.tag == "chapter": - track["chapter"].append({"ix": elem.attrib['ix'],\ - "length": elem.attrib['length']}) - elif elem.tag == "track": - track["ix"] = elem.attrib["ix"] - track["length"] = elem.attrib["length"] - expected_answer.add_track(track) - elif elem.tag == "dvd": - infos = {"title": elem.attrib['title'], \ - "longest_track": elem.attrib['longest_track']} - expected_answer.set_dvd_content(infos) - parms = elem.tag in ("parm","listparm","dictparm","listvalue",\ - "dictitem") and parms or {} - - elem.clear() + def _build_answer(self, msg): + try: msg = loads_response(msg) + except Fault, f: + raise DeejaydError("JSONRPC error - %s - %s" % (f.code, f.message)) + else: + if msg["id"] is None: # it is a notification + result = msg["result"]["answer"] + type = msg["result"]["type"] + if type == 'signal': + signal = DeejaydSignal(result["name"], result["attrs"]) + return self._dispatch_signal(signal) + else: + expected_answer = self.expected_answers_queue.get() + if expected_answer.id != msg["id"]: + raise DeejaydError("Bad id for JSON server answer") + + if msg["error"] is not None: # an error is returned + expected_answer.set_error("Deejayd Server Error - %s - %s"\ + % (msg["error"]["code"], msg["error"]["message"])) + else: + result = msg["result"]["answer"] + type = msg["result"]["type"] + if type == 'fileAndDirList': + expected_answer.set_rootdir(result["root"]) + expected_answer.set_files(result["files"]) + expected_answer.set_directories(result["directories"]) + elif type == 'mediaList': + expected_answer.set_medias(result["medias"]) + expected_answer.set_media_type(result["media_type"]) + if "filter" in result and result["filter"] is not None: + expected_answer.set_filter(\ + Parse_json_filter(result["filter"])) + if "sort" in result: + expected_answer.set_sort(result["sort"]) + elif type == 'dvdInfo': + expected_answer.set_dvd_content(result) + expected_answer._received(result) class DeejayDaemonSync(_DeejayDaemon): @@ -587,20 +615,110 @@ def __init__(self): _DeejayDaemon.__init__(self) + self.socket_to_server = socket.socket(socket.AF_INET, + socket.SOCK_STREAM) + self.__timeout = 2 + self.socket_to_server.settimeout(self.__timeout) + self.next_msg = "" + + def connect(self, host, port, ignore_version=False): + if self.connected: + self.disconnect() + + self.host = host + self.port = port + + try: self.socket_to_server.connect((self.host, self.port)) + except socket.timeout, msg: + # reset connection + self._reset_socket() + raise ConnectError('Connection timeout') + except socket.error, msg: + raise ConnectError('Connection with server failed : %s' % msg) + + self.socket_to_server.settimeout(None) + socketFile = self.socket_to_server.makefile() + + # Catch version + banner_line = socketFile.readline() + self.connected = True + if not banner_line.startswith("OK DEEJAYD")\ + or len(banner_line) > MAX_BANNER_LENGTH: + self.disconnect() + raise ConnectError('Connection with server failed') + try: + versions = self._version_from_banner(banner_line) + except ValueError: + self.disconnect() + raise ConnectError('Initial version dialog with server failed') + if not ignore_version and not self._version_is_supported(versions): + self.disconnect() + raise ConnectError('This server version protocol is not handled by this client version') + def _send_command(self, cmd, expected_answer = None): # Set a default answer by default if expected_answer == None: expected_answer = DeejaydAnswer(self) + expected_answer.set_id(cmd.get_id()) self.expected_answers_queue.put(expected_answer) - self._sendmsg(cmd.to_xml()) + self._sendmsg(cmd.to_json()) rawmsg = self._readmsg() - try: self._build_answer(StringIO(rawmsg)) - except SyntaxError: - raise DeejaydError("Unable to parse server answer : %s" % rawmsg) + self._build_answer(rawmsg) return expected_answer + def disconnect(self): + if not self.connected: + return + + self.socket_to_server.settimeout(self.__timeout) + try: self._send_simple_command('close').get_contents() + except socket.timeout: pass + + self._reset_socket() + self.connected = False + self.host = None + self.port = None + + def _reset_socket(self): + self.socket_to_server.close() + # New Socket setup + self.socket_to_server = socket.socket(socket.AF_INET, + socket.SOCK_STREAM) + self.socket_to_server.settimeout(self.__timeout) + + def _sendmsg(self, buf): + self.socket_to_server.send(buf + MSG_DELIMITER) + + def _readmsg(self): + msg_chunks = '' + msg_chunk = '' + def split_msg(msg,index): + return (msg[0:index], msg[index+len(MSG_DELIMITER):len(msg)]) + + while 1: + try: index = self.next_msg.index(MSG_DELIMITER) + except ValueError: pass + else: + (rs,self.next_msg) = split_msg(self.next_msg, index) + break + + msg_chunk = self.socket_to_server.recv(4096) + # socket.recv returns an empty string if the socket is closed, so + # catch this. + if msg_chunk == '': + raise socket.error() + + msg_chunks += msg_chunk + try: index = msg_chunks.index(MSG_DELIMITER) + except ValueError: pass + else: + (rs,self.next_msg) = split_msg(msg_chunks, index) + break + + return rs + # No subscription for the sync client def subscribe(self, signal_name, callback): raise NotImplementedError def unsubscribe(self, sub_id): raise NotImplementedError @@ -610,9 +728,11 @@ ac_in_buffer_size = 256 ac_out_buffer_size = 256 - def __init__(self, socket_map, deejayd): + def __init__(self, socket_map, deejayd, ignore_version=False): asyncore.dispatcher.__init__(self, map=socket_map) self.deejayd = deejayd + self.ignore_version = ignore_version + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.socket_die_callback = [] @@ -635,7 +755,7 @@ def handle_error(self): t, v, tb = sys.exc_info() assert tb # Must have a traceback - if self.state != "xml_protocol": + if self.state != "json_protocol": # error appears when we try to connect for cb in self.__connect_callback: cb(False,str(v)) @@ -644,7 +764,7 @@ self.state = "connected" def handle_close(self): - if self.state == "xml_protocol": + if self.state == "json_protocol": self.__error_callbacks('disconnected') self.state = "disconnected" self.close() @@ -654,15 +774,30 @@ return (msg[0:index], msg[index+len(MSG_DELIMITER):len(msg)]) if self.state == "connected": - # Catch version 17 character exactly - self.version = self.recv(17) - if self.version.startswith("OK DEEJAYD"): - self.state = 'xml_protocol' - # now we are sure to be connected - for cb in self.__connect_callback: - cb(True,"") + # Catch banner until first newline char + banner_line = '' + newchar = '' + while newchar != '\n' and len(banner_line) < MAX_BANNER_LENGTH: + banner_line += newchar + newchar = self.recv(1) + if banner_line.startswith("OK DEEJAYD"): + try: + versions = self.deejayd._version_from_banner(banner_line) + except ValueError: + for cb in self.__connect_callback: + cb(False, 'Initial version dialog with server failed') + if self.ignore_version\ + or self.deejayd._version_is_supported(versions): + self.state = 'json_protocol' + # now we are sure to be connected + for cb in self.__connect_callback: + cb(True,"") + else: + for cb in self.__connect_callback: + cb(False,\ + 'This server version protocol is not handled by this client version') - elif self.state == "xml_protocol": + elif self.state == "json_protocol": msg_chunk = self.recv(256) # socket.recv returns an empty string if the socket is closed, so # catch this. @@ -691,17 +826,17 @@ raise AttributeError def answer_received(self,rawmsg): - try: self.deejayd._build_answer(StringIO(rawmsg)) - except SyntaxError: + try: self.deejayd._build_answer(rawmsg) + except DeejaydError: self.__error_callbacks("Unable to parse server answer : %s" %rawmsg) self.close() def handle_write(self): cmd = self.deejayd.command_queue.get() - self.send(cmd.to_xml()+MSG_DELIMITER) + self.send(cmd.to_json()+MSG_DELIMITER) def writable(self): - if self.state != "xml_protocol": return False + if self.state != "json_protocol": return False return not self.deejayd.command_queue.empty() @@ -741,9 +876,9 @@ self.__err_cb = [] self.socket_to_server = None - def __create_socket(self): + def __create_socket(self, ignore_version=False): self.socket_to_server = _DeejaydSocket(self.socket_thread.socket_map, - self) + self, ignore_version) self.socket_to_server.add_socket_die_callback(\ self.__make_answers_obsolete) self.socket_to_server.add_socket_die_callback(self.__disconnect) @@ -782,11 +917,11 @@ def add_error_callback(self,cb): self.__err_cb.append(cb) - def connect(self, host, port): + def connect(self, host, port, ignore_version=False): if self.connected: self.disconnect() - self.__create_socket() + self.__create_socket(ignore_version) try: self.socket_to_server.connect((host, port)) except socket.connecterror, msg: raise ConnectError('Connection error %s' % str(msg)) @@ -815,6 +950,7 @@ # Set a default answer by default if expected_answer == None: expected_answer = DeejaydAnswer(self) + expected_answer.set_id(cmd.get_id()) self.expected_answers_queue.put(expected_answer) self.command_queue.put(cmd) @@ -822,9 +958,7 @@ def subscribe(self, signal_name, callback): if signal_name not in self.get_subscriptions().values(): - cmd = DeejaydXMLCommand('setSubscription') - cmd.add_simple_arg('signal', signal_name) - cmd.add_simple_arg('value', 1) + cmd = JSONRPCRequest('signal.setSubscription', [signal_name, True]) ans = self._send_command(cmd) # Subscription are sync because there should be a way to tell the # client that subscription failed. @@ -840,12 +974,62 @@ _DeejayDaemon.unsubscribe(self, sub_id) # Remote unsubscribe if last local subscription laid off if signal_name not in self.get_subscriptions().values(): - cmd = DeejaydXMLCommand('setSubscription') - cmd.add_simple_arg('signal', signal_name) - cmd.add_simple_arg('value', 0) + cmd = JSONRPCRequest('signal.setSubscription', [signal_name, False]) ans = self._send_command(cmd) # As subscription, unsubscription is sync. ans.get_contents() +# +# HTTP client +# +import httplib + +class DeejayDaemonHTTP(_DeejayDaemon): + """HTTP deejayd client library.""" + + def __init__(self, host, port = 6880): + _DeejayDaemon.__init__(self) + self.host = host + self.port = port + self.connection = httplib.HTTPConnection(self.host, self.port) + self.hdrs = { + "Content-Type": "text/json", + "Accept": "text/json", + "User-Agent": "Deejayd Client Library", + } + + def test_compatibility(self): + # get server informations + cmd = JSONRPCRequest('serverInfo', []) + ans = self._send_command(cmd, DeejaydKeyValue()) + + versions = (ans["server_version"], ans["protocol_version"]) + return self._version_is_supported(versions) + + def _send_command(self, cmd, expected_answer = None): + # Set a default answer by default + if expected_answer == None: + expected_answer = DeejaydAnswer(self) + expected_answer.set_id(cmd.get_id()) + self.expected_answers_queue.put(expected_answer) + + # send http request + try: self.connection.request("POST", "/rpc/", cmd.to_json(), self.hdrs) + except Exception, ex: + raise DeejaydError("Unable to send request : %s" % str(ex)) + + # get answer + response = self.connection.getresponse() + if response.status != 200: + raise DeejaydError("Server return error code %d - %s" %\ + (response.status, response.reason)) + rawmsg = response.read() + self._build_answer(rawmsg) + return expected_answer + + # No subscription for the http client + def subscribe(self, signal_name, callback): raise NotImplementedError + def unsubscribe(self, sub_id): raise NotImplementedError + # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/net/commandsXML.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/net/commandsXML.py --- deejayd-0.7.2/deejayd/net/commandsXML.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/net/commandsXML.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,809 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -from os import path - -from deejayd.net.xmlbuilders import DeejaydXMLAnswerFactory -from deejayd import sources -from deejayd.interfaces import DeejaydError, DeejaydSignal - - -class queueCommands: - - def __init__(self, deejayd_core): - self.commands = [] - self.deejayd_core = deejayd_core - self.__rspFactory = DeejaydXMLAnswerFactory() - self.__connector = None - - def addCommand(self,name,cmd,args): - self.commands.append((name,cmd,args)) - - def register_connector(self, connector): - self.__connector = connector - - def execute(self): - motherRsp = None - - for (cmdName, cmdType, args) in self.commands: - cmd = cmdType(cmdName, args, self.__rspFactory, self.deejayd_core, - self.__connector) - - error = cmd.args_validation() - if error != None: - motherRsp = error - self.__rspFactory.set_mother(motherRsp) - break - - rsp = cmd.execute() - if motherRsp == None: - motherRsp = rsp - self.__rspFactory.set_mother(motherRsp) - - del self.__rspFactory - return motherRsp.to_xml() - - -class UnknownCommand: - command_args = [] - command_rvalue = 'Ack' - - def __init__(self, cmdName, args, rspFactory=None, - deejayd_core=None, connector=None): - self.name = cmdName - self.args = args - self.connector = connector - self.deejayd_core = deejayd_core - self.__rspFactory = rspFactory or DeejaydXMLAnswerFactory() - - def execute(self): - try: rsp = self._execute() - except DeejaydError, err: - return self.get_error_answer(str(err)) - else: - if rsp == None: - return self.get_ok_answer() - return rsp - - def _execute(self): - return self.get_error_answer(_("Unknown command : %s") % self.name) - - def get_answer(self, type): - return self.__rspFactory.get_deejayd_xml_answer(type, self.name) - - def get_error_answer(self, errorString): - error = self.get_answer('error') - error.set_error_text(errorString) - return error - - def get_ok_answer(self): - return self.get_answer('Ack') - - def get_keyvalue_answer(self, keyValueList): - rsp = self.get_answer('KeyValue') - rsp.set_pairs(dict(keyValueList)) - return rsp - - def args_validation(self): - for arg in self.__class__.command_args: - if arg['name'] in self.args: - # FIXME need to remove this - if self.args[arg['name']] == None: - self.args[arg['name']] = "" - - value = self.args[arg['name']] - if isinstance(value,list) and "mult" not in arg: - return self.get_error_answer(\ - _("Arg %s can not be a list") % arg['name']) - elif not isinstance(value,list): - value = [value] - if "mult" in arg: - self.args[arg['name']] = value - - for v in value: - if arg['type'] == "string": - try: v.split() - except AttributeError: - return self.get_error_answer(\ - _("Arg %s (%s) is not a string") % \ - (arg['name'], str(v))) - - elif arg['type'] == "int": - try: v = int(v) - except (ValueError,TypeError): - return self.get_error_answer(\ - _("Arg %s (%s) is not a int") % (arg['name'],\ - str(v))) - - elif arg['type'] == "enum_str": - if v not in arg['values']: - return self.get_error_answer(\ - _("Arg %s (%s) is not in the possible list")\ - % (arg['name'],str(v))) - - elif arg['type'] == "enum_int": - try: v = int(v) - except (ValueError,TypeError): - return self.get_error_answer(\ - _("Arg %s (%s) is not a int") %\ - (arg['name'],str(v))) - else: - if v not in arg['values']: - return self.get_error_answer(\ - _("Arg %s (%s) is not in the possible list")\ - % (arg['name'],str(v))) - - elif arg['type'] == "regexp": - import re - if not re.compile(arg['value']).search(v): - return self.get_error_answer(\ - _("Arg %s (%s) not match to the regular exp (%s)") % - (arg['name'],v,arg['value'])) - - elif arg['req']: - return self.get_error_answer(_("Arg %s is mising")%arg['name']) - else: - self.args[arg['name']] = arg['default'] - return None - - -class Close(UnknownCommand): - """Close the connection with the server""" - command_name = 'close' - - def execute(self): - # Connection is closed after the answer has been written (in - # deejaydProtocol.py). - return self.get_ok_answer() - - -class Ping(UnknownCommand): - """Does nothing, just replies with an acknowledgement that the command was received""" - command_name = 'ping' - - def execute(self): - return self.get_ok_answer() - - -class Mode(UnknownCommand): - """Change the player mode. Possible values are : - * playlist : to manage and listen songs - * video : to manage and wath video file - * dvd : to wath dvd - * webradio : to manage and listen webradios""" - command_name = 'setMode' - command_args = [{"name":"mode", "type":"string", "req":True}] - - def _execute(self): - self.deejayd_core.set_mode(self.args["mode"], objanswer=False) - - -class Status(UnknownCommand): - """Return status of deejayd. Given informations are : - * playlist : _int_ id of the current playlist - * playlistlength : _int_ length of the current playlist - * playlisttimelength : _int_ time length of the current playlist - * webradio : _int_ id of the current webradio list - * webradiolength : _int_ number of recorded webradio - * queue : _int_ id of the current queue - * queuelength : _int_ length of the current queue - * queuetimelength : _int_ time length of the current queue - * video : _int_ id of the current video list - * videolength : _int_ length of the current video list - * videotimelength : _int_ time length of the current video list - * dvd : _int_ id of the current dvd - * random : 0 (not activated) or 1 (activated) - * repeat : 0 (not activated) or 1 (activated) - * volume : `[0-100]` current volume value - * state : [play-pause-stop] the current state of the player - * current : _int_:_int_:_str_ current media pos : current media file id : - playing source name - * time : _int_:_int_ position:length of the current media file - * mode : [playlist-webradio-video] the current mode - * audio_updating_db : _int_ show when a audio library update is in progress - * audio_updating_error : _string_ error message that apppears when the - audio library update has failed - * video_updating_db : _int_ show when a video library update is in progress - * video_updating_error : _string_ error message that apppears when the - video library update has failed""" - command_name = 'status' - command_rvalue = 'KeyValue' - - def _execute(self): - status = self.deejayd_core.get_status(objanswer=False) - return self.get_keyvalue_answer(status) - - -class Stats(UnknownCommand): - """Return statistical informations : - * audio_library_update : UNIX time of the last audio library update - * video_library_update : UNIX time of the last video library update - * videos : number of videos known by the database - * songs : number of songs known by the database - * artists : number of artists in the database - * albums : number of albums in the database""" - command_name = 'stats' - command_rvalue = 'KeyValue' - - def _execute(self): - status = self.deejayd_core.get_stats(objanswer=False) - return self.get_keyvalue_answer(status) - - -class GetMode(UnknownCommand): - """For each available source, shows if it is activated or not. The answer - consists in : - * playlist : 0 or 1 (actually always 1 because it does not need optionnal - dependencies) - * queue : 0 or 1 (actually always 1 because it does not need optionnal - dependencies) - * webradio : 0 or 1 (needs gst-plugins-gnomevfs to be activated) - * video : 0 or 1 (needs video dependencies, X display and needs to be - activated in configuration) - * dvd : 0 or 1 (media backend has to be able to read dvd)""" - command_name = 'getMode' - command_rvalue = 'KeyValue' - - def _execute(self): - modes = self.deejayd_core.get_mode(objanswer=False) - return self.get_keyvalue_answer(modes) - - -################################################### -# MediaDB Commands # -################################################### - -class UpdateAudioLibrary(UnknownCommand): - """Update the audio library. - * audio_updating_db : the id of this task. It appears in the status until the - update are completed.""" - command_name = 'audioUpdate' - command_rvalue = 'KeyValue' - - def _execute(self): - rsp = self.deejayd_core.update_audio_library(objanswer=False) - return self.get_keyvalue_answer(rsp) - - -class UpdateVideoLibrary(UnknownCommand): - """Update the video library. - * video_updating_db : the id of this task. It appears in the status until the - update are completed.""" - command_name = 'videoUpdate' - command_rvalue = 'KeyValue' - - - def _execute(self): - rsp = self.deejayd_core.update_video_library(objanswer=False) - return self.get_keyvalue_answer(rsp) - - -class GetDir(UnknownCommand): - """List the files of the directory supplied as argument.""" - command_name = 'getdir' - command_args = [{"name":"directory", "type":"string", "req":False, \ - "default":""}] - command_rvalue = 'FileAndDirList' - - def _execute(self): - root_dir, dirs, files = self.deejayd_core.get_audio_dir(\ - self.args["directory"], objanswer=False) - rsp = self.get_answer('FileAndDirList') - rsp.set_directory(root_dir) - rsp.set_filetype('song') - - rsp.set_files(files) - rsp.set_directories(dirs) - - return rsp - - -class Search(UnknownCommand): - """Search files where "type" contains "txt" content.""" - command_name = 'search' - command_args = [{"name":"type", "type":"enum_str", - "values": ('all','title','genre','filename','artist', - 'album'),"req":True}, - {"name":"txt", "type":"string", "req":True}] - command_rvalue = 'FileAndDirList' - - def _execute(self): - root_dir, dirs, files = self.deejayd_core.audio_search(\ - self.args['txt'], self.args['type'], objanswer=False) - rsp = self.get_answer('FileAndDirList') - rsp.set_filetype('song') - rsp.set_files(files) - return rsp - - -class GetVideoDir(GetDir): - """Lists the files in video dir "directory".""" - command_name = 'getvideodir' - command_args = [{"name":"directory","type":"string","req":False,\ - "default": ""}] - command_rvalue = 'FileAndDirList' - - def _execute(self): - root_dir, dirs, files = self.deejayd_core.get_video_dir(\ - self.args["directory"], objanswer=False) - rsp = self.get_answer('FileAndDirList') - rsp.set_filetype('video') - rsp.set_directory(root_dir) - - rsp.set_files(files) - rsp.set_directories(dirs) - - return rsp - - -################################################### -# Video Commands # -################################################### - -class SetVideo(UnknownCommand): - """Set the current video directory to "directory".""" - command_name = 'setvideo' - command_args = [{"name":"type", "type":"enum_str",\ - "values": ("directory", "search"), "req":False, "default": "directory"},\ - {"name":"value", "type":"str", "req": False, "default": ""}] - - def _execute(self): - self.deejayd_core.get_video().set(self.args['value'],\ - self.args['type'], objanswer=False) - - -class VideoInfo(UnknownCommand): - """Set the current video directory to "directory".""" - command_name = 'videoInfo' - command_rvalue = 'MediaList' - command_args = [{"name":"first","type":"int","req":False,"default":0},\ - {"name":"length","type":"int","req":False,"default":-1}] - - def _execute(self): - videos = self.deejayd_core.get_video().get(self.args["first"],\ - self.args["length"], objanswer=False) - - rsp = self.get_answer('MediaList') - rsp.set_mediatype('video') - rsp.set_medias(videos) - if self.args["length"] != -1: - status = self.deejayd_core.get_status(objanswer=False) - rsp.set_total_length(status["videolength"]) - - return rsp - - -################################################### -# Playlist Commands # -################################################### - -class SimplePlaylistCommand(UnknownCommand): - func_name = None - - def _execute(self): - pls = self.deejayd_core.get_playlist(self.args["name"]) - getattr(pls, self.func_name)(objanswer=False) - - -class PlaylistClear(SimplePlaylistCommand): - """Clear the current playlist.""" - command_name = 'playlistClear' - command_args = [{"name":"name","type":"string","req":False,"default":None}] - func_name = "clear" - - -class PlaylistShuffle(SimplePlaylistCommand): - """Shuffle the current playlist.""" - command_name = 'playlistShuffle' - command_args = [{"name":"name","type":"string","req":False,"default":None}] - func_name = "shuffle" - - -class PlaylistSave(UnknownCommand): - """Save the current playlist to "name" in the database.""" - command_name = 'playlistSave' - command_args = [{"name":"name", "type":"string", "req":True}] - - def _execute(self): - pls = self.deejayd_core.get_playlist() - pls.save(self.args["name"], objanswer=False) - - -class PlaylistLoad(UnknownCommand): - """Load playlists passed as arguments ("name") at the position "pos".""" - command_name = 'playlistLoad' - command_args = [{"name":"name", "type":"string", "req":True, "mult":True}, - {"name":"pos", "type":"int", "req":False,"default": None}] - - def _execute(self): - pls = self.deejayd_core.get_playlist() - pls.loads(self.args["name"], self.args["pos"], objanswer=False) - - -class PlaylistErase(UnknownCommand): - """Erase playlists passed as arguments.""" - command_name = 'playlistErase' - command_args = [{"mult":"true","name":"name", "type":"string", "req":True}] - - def _execute(self): - self.deejayd_core.erase_playlist(self.args["name"], objanswer=False) - - -class PlaylistAdd(UnknownCommand): - """Load files or directories passed as arguments ("path") at the position - "pos" in the playlist "name". If no playlist name is provided, adds files - in the current playlist.""" - command_name = 'playlistAdd' - command_args = [{"mult":True,"name":"path", "type":"string", "req":True}, - {"name":"pos", "type":"int", "req":False,"default":None}, - {"name":"name", "type":"string","req":False,"default":None}] - - def _execute(self): - pls = self.deejayd_core.get_playlist(self.args["name"]) - pls.add_songs(self.args["path"], self.args["pos"], objanswer=False) - - -class PlaylistInfo(UnknownCommand): - """Return the content of the playlist "name". If no name is given, return - the content of the current playlist.""" - command_name = 'playlistInfo' - command_args = [{"name":"name","type":"string","req":False,"default":None},\ - {"name":"first","type":"int","req":False,"default":0},\ - {"name":"length","type":"int","req":False,"default":-1}] - command_rvalue = 'MediaList' - - def _execute(self): - pls = self.deejayd_core.get_playlist(self.args["name"]) - songs = pls.get(self.args["first"], self.args["length"], \ - objanswer=False) - - rsp = self.get_answer('MediaList') - rsp.set_mediatype("song") - rsp.set_medias(songs) - if self.args["length"] != -1: - status = self.deejayd_core.get_status(objanswer=False) - rsp.set_total_length(status["playlistlength"]) - return rsp - - -class PlaylistRemove(UnknownCommand): - """Remove songs with ids passed as argument ("id"), from the playlist - "name". If no name are given, remove songs from current playlist.""" - command_name = 'playlistRemove' - command_args = [{"mult":True, "name":"id", "type":"int", "req":True}, - {"name":"name","type":"string","req":False,"default":None}] - - def _execute(self): - pls = self.deejayd_core.get_playlist(self.args["name"]) - pls.del_songs(self.args['id'], objanswer=False) - - -class PlaylistMove(UnknownCommand): - """Move song with id "id" to position "new_position".""" - command_name = 'playlistMove' - command_args = [{"name":"ids", "type":"int", "req":True, "mult": True}, - {"name":"new_pos", "type":"int", "req":True}] - - def _execute(self): - pls = self.deejayd_core.get_playlist() - pls.move(self.args['ids'], self.args['new_pos'], objanswer=False) - - -class PlaylistList(UnknownCommand): - """Return the list of recorded playlists.""" - command_name = 'playlistList' - command_rvalue = 'MediaList' - - def _execute(self): - pls = self.deejayd_core.get_playlist_list(objanswer=False) - rsp = self.get_answer('MediaList') - rsp.set_mediatype('playlist') - rsp.set_medias(pls) - return rsp - - -################################################### -# Webradios Commands # -################################################### -class WebradioList(UnknownCommand): - """Return the list of recorded webradios.""" - command_name = 'webradioList' - command_rvalue = 'MediaList' - command_args = [{"name":"first","type":"int","req":False,"default":0},\ - {"name":"length","type":"int","req":False,"default":-1}] - - def _execute(self): - wrs = self.deejayd_core.get_webradios().get(self.args["first"],\ - self.args["length"], objanswer=False) - - rsp = self.get_answer('MediaList') - rsp.set_mediatype('webradio') - rsp.set_medias(wrs) - if self.args["length"] != -1: - status = self.deejayd_core.get_status(objanswer=False) - rsp.set_total_length(status["webradiolength"]) - return rsp - - -class WebradioClear(UnknownCommand): - """Remove all recorded webradios.""" - command_name = 'webradioClear' - - def _execute(self): - self.deejayd_core.get_webradios().clear(objanswer=False) - - -class WebradioDel(UnknownCommand): - """Remove webradios with id equal to "id".""" - command_name = 'webradioRemove' - command_args = [{"mult":True, "name":"id", "type":"int", "req":True}] - - def _execute(self): - wr = self.deejayd_core.get_webradios() - wr.delete_webradios(self.args['id'], objanswer=False) - - -class WebradioAdd(UnknownCommand): - """Add a webradio. Its name is "name" and the url of the webradio is - "url". You can pass a playlist for "url" argument (.pls and .m3u format - are supported).""" - command_name = 'webradioAdd' - command_args = [{"name":"url", "type":"string", "req":True}, - {"name":"name", "type":"string", "req":True}] - - def _execute(self): - wr = self.deejayd_core.get_webradios() - wr.add_webradio(self.args['name'], self.args['url'], objanswer=False) - - -################################################### -# Queue Commands # -################################################### -class QueueAdd(UnknownCommand): - """Load files or directories passed as arguments ("path") at the position - "pos" in the queue.""" - command_name = 'queueAdd' - command_args = [{"mult":True, "name":"path", "type":"string", "req":True}, - {"name":"pos", "type":"int", "req":False, "default":None}] - - def _execute(self): - queue = self.deejayd_core.get_queue() - queue.add_medias(self.args["path"], self.args["pos"], objanswer=False) - - -class QueueLoadPlaylist(UnknownCommand): - """Load playlists passed in arguments ("name") at the position "pos" in - the queue.""" - command_name = 'queueLoadPlaylist' - command_args = [{"name":"name", "type":"string", "req":True, "mult":True}, - {"name":"pos", "type":"int", "req":False, "default":None}] - - def _execute(self): - queue = self.deejayd_core.get_queue() - queue.load_playlists(self.args["name"],\ - self.args["pos"], objanswer=False) - - -class QueueInfo(PlaylistInfo): - """Return the content of the queue.""" - command_name = 'queueInfo' - command_rvalue = 'MediaList' - command_args = [{"name":"first","type":"int","req":False,"default":0},\ - {"name":"length","type":"int","req":False,"default":-1}] - - def _execute(self): - medias = self.deejayd_core.get_queue().get(self.args["first"],\ - self.args["length"], objanswer=False) - - rsp = self.get_answer('MediaList') - rsp.set_mediatype("song") - rsp.set_medias(medias) - if self.args["length"] != -1: - status = self.deejayd_core.get_status(objanswer=False) - rsp.set_total_length(status["queuelength"]) - return rsp - - -class QueueRemove(UnknownCommand): - """Remove songs with ids passed as argument ("id"), from the queue.""" - command_name = 'queueRemove' - command_args = [{"mult":True, "name":"id", "type":"int", "req":True}] - - def _execute(self): - queue = self.deejayd_core.get_queue() - queue.del_songs(self.args['id'], objanswer=False) - - -class QueueClear(UnknownCommand): - """Remove all songs from the queue.""" - command_name = 'queueClear' - - def _execute(self): - self.deejayd_core.get_queue().clear(objanswer=False) - - -################################################### -# DVD Commands # -################################################### -class DvdLoad(UnknownCommand): - """Load the content of the dvd player.""" - command_name = 'dvdLoad' - - def _execute(self): - self.deejayd_core.dvd_reload(objanswer=False) - - -class DvdInfo(UnknownCommand): - """Get the content of the current dvd.""" - command_name = 'dvdInfo' - command_rvalue = 'DvdInfo' - - def _execute(self): - content = self.deejayd_core.get_dvd_content(objanswer=False) - rsp = self.get_answer('DvdInfo') - rsp.set_info(content) - return rsp - - -################################################### -# Player Commands # -################################################### -class SimplePlayerCommand(UnknownCommand): - - def _execute(self): - getattr(self.deejayd_core, self.name)(objanswer=False) - - -class Next(SimplePlayerCommand): - """Go to next song or webradio.""" - command_name = 'next' - - -class Previous(SimplePlayerCommand): - """Go to previous song or webradio.""" - command_name = 'previous' - - -class Stop(SimplePlayerCommand): - """Stop playing.""" - command_name = 'stop' - - -class PlayToggle(UnknownCommand): - """Toggle play/pause.""" - command_name = 'playToggle' - - def _execute(self): - self.deejayd_core.play_toggle(objanswer=False) - - -class GoTo(UnknownCommand): - """Begin playing at media file with id "id" or toggle play/pause.""" - command_name = 'goto' - command_args = [{"name":"id", "type":"regexp", \ - "value":"^\w{1,}|\w{1,}\.\w{1,}$","req":True}, - {"name":"id_type","type":"enum_str",\ - "values":("dvd_id","track","chapter","id","pos"),\ - "req":False,"default":"id"}, - {"name":"source","type":"string","req":False,"default":None},] - - def _execute(self): - self.deejayd_core.go_to(self.args['id'], self.args['id_type'],\ - self.args['source'], objanswer=False) - - -class SetPlayerOption(UnknownCommand): - """Set player option for the current media - Possible options are : - * zoom : set zoom (video only), min=-85, max=400 - * audio_lang : select audio channel (video only) - * sub_lang : select subtitle channel (video only) - * av_offset : set audio/video offset (video only) - * sub_offset : set subtitle/video offset (video only)""" - command_name = 'setPlayerOption' - command_args = [{"name":"option_name", "type":"string", "req":True},\ - {"name":"option_value", "type":"int", "req":True}] - - def _execute(self): - self.deejayd_core.set_player_option(self.args['option_name'],\ - self.args['option_value'], objanswer=False) - - -class Volume(UnknownCommand): - """Set volume to "volume". The volume range is 0-100.""" - command_name = 'setVolume' - command_args = [{"name":"volume", "type":"enum_int", "req":True, - "values": range(0,101)}] - - def _execute(self): - self.deejayd_core.set_volume(self.args['volume'], objanswer=False) - - -class Seek(UnknownCommand): - """Seeks to the position "time" (in seconds) of the current song (in - playlist mode).""" - command_name = 'seek' - command_args = [{"name":"time", "type":"int", "req":True}] - - def _execute(self): - self.deejayd_core.seek(self.args['time'], objanswer=False) - - -class SetOption(UnknownCommand): - """Set player options "name" to "value", "value" should be 0 or 1. - Available options are : - * random - * qrandom (queue random) - * repeat - You can pass several options in the same command""" - command_name = 'setOption' - command_args = [{"name":"option_name", "type":"enum_str","req":True, - "values":("random","qrandom","repeat")}, - {"name":"option_value","type":"enum_int","req":True, - "values":(0,1)} ] - - def _execute(self): - self.deejayd_core.set_option(self.args['option_name'],\ - self.args['option_value'], objanswer=False) - - -class CurrentSong(UnknownCommand): - """Return informations on the current song, webradio or video info.""" - command_name = 'current' - command_rvalue = 'MediaList' - - def _execute(self): - item = self.deejayd_core.get_current(objanswer=False) - rsp = self.get_answer('MediaList') - if len(item) == 1: - rsp.set_mediatype(item[0]["type"]) - rsp.set_medias(item) - - return rsp - - -class SetSubscription(UnknownCommand): - """Set subscribtion to "signal" signal notifications to "value" which should be 0 or 1.""" - command_name = 'setSubscription' - command_args = ({"name":"signal", "type":"enum_str", "req":True, - "values":DeejaydSignal.SIGNALS}, - {"name":"value", "type":"enum_int", "req":True, - "values":(0,1)} ) - - def _execute(self): - if self.args['value'] == '0': - self.connector.set_not_signaled(self.args['signal']) - elif self.args['value'] == '1': - self.connector.set_signaled(self.args['signal']) - return self.get_ok_answer() - - -# Build the list of available commands -commands = {} - -import sys -thismodule = sys.modules[__name__] -for itemName in dir(thismodule): - try: - item = getattr(thismodule, itemName) - commands[item.command_name] = item - except AttributeError: - pass - - -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/net/deejaydProtocol.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/net/deejaydProtocol.py --- deejayd-0.7.2/deejayd/net/deejaydProtocol.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/net/deejaydProtocol.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,176 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import sys -from twisted.application import service, internet -from twisted.internet import protocol, reactor -from twisted.internet.error import ConnectionDone -from twisted.protocols.basic import LineReceiver -try: from xml.etree import cElementTree as ET # python 2.5 -except ImportError: # python 2.4 - import cElementTree as ET - -from deejayd.interfaces import DeejaydSignal -from deejayd.ui import log -from deejayd.net import commandsXML, xmlbuilders - -class DeejaydProtocol(LineReceiver): - - def __init__(self, deejayd_core, protocol_manager): - self.delimiter = "ENDXML\n" - self.MAX_LENGTH = 40960 - self.deejayd_core = deejayd_core - self.manager = protocol_manager - - def connectionMade(self): - from deejayd import __version__ - self.cmdFactory = CommandFactory(self.deejayd_core) - self.send_buffer("OK DEEJAYD %s\n" % (__version__,), xml=False) - - def connectionLost(self, reason=ConnectionDone): - self.manager.close_signals(self) - - def lineReceived(self, line): - line = line.strip("\r") - # DEBUG Informations - log.debug(line) - - remoteCmd = self.cmdFactory.createCmdFromXML(line) - remoteCmd.register_connector(self) - rsp = remoteCmd.execute() - self.send_buffer(rsp) - - if 'close' in remoteCmd.commands: - self.transport.loseConnection() - - def send_buffer(self, buf, xml=True): - if isinstance(buf, unicode): - buf = buf.encode("utf-8") - self.transport.write(buf) - if xml: - self.transport.write(self.delimiter) - log.debug(buf) - - def lineLengthExceeded(self, line): - log.err(_("Request too long, close the connection")) - self.transport.loseConnection() - - def set_signaled(self, signal_name): - self.manager.set_signaled(self, signal_name) - - def set_not_signaled(self, signal_name): - self.manager.set_not_signaled(self, signal_name) - - -class DeejaydFactory(protocol.ServerFactory): - protocol = DeejaydProtocol - obj_supplied = False - - def __init__(self, deejayd_core): - self.deejayd_core = deejayd_core - self.signaled_clients = dict([(signame, []) for signame\ - in DeejaydSignal.SIGNALS]) - self.core_sub_ids = {} - - def startFactory(self): - log.info(_("Net Protocol activated")) - - def buildProtocol(self, addr): - p = self.protocol(self.deejayd_core, self) - p.factory = self - return p - - def clientConnectionLost(self, connector, reason): - for signal_name in DeejaydSignal.SIGNALS: - self.set_not_signaled(connector, signal_name) - - def set_signaled(self, connector, signal_name): - client_list = self.signaled_clients[signal_name] - if len(client_list) < 1: - # First subscription for this signal, so subscribe - sub_id = self.deejayd_core.subscribe(signal_name, - self.sig_bcast_to_clients) - self.core_sub_ids[signal_name] = sub_id - - self.signaled_clients[signal_name].append(connector) - - def set_not_signaled(self, connector, signal_name): - client_list = self.signaled_clients[signal_name] - if connector in client_list: - client_list.remove(connector) - if len(client_list) < 1: - # No more clients for this signal, we can unsubscribe - self.deejayd_core.unsubscribe(self.core_sub_ids[signal_name]) - - def close_signals(self, connector): - for signal_name in self.signaled_clients.keys(): - if len(self.signaled_clients[signal_name]) > 0: - self.set_not_signaled(connector, signal_name) - - def sig_bcast_to_clients(self, signal): - interested_clients = self.signaled_clients[signal.get_name()] - if len(interested_clients) > 0: - xml_sig = xmlbuilders.DeejaydXMLSignal(signal.get_name()) - for client in interested_clients: - # http://twistedmatrix.com/pipermail/twisted-python/2007-August/015905.html - # says : "Don't call reactor methods from any thread except the - # one which is running the reactor. This will have - # unpredictable results and generally be broken." - # This is the "why" of this weird call instead of a simple - # client.send_buffer(xml_sig.to_xml()). - reactor.callFromThread(client.send_buffer, xml_sig.to_xml()) - - -class CommandFactory: - - def __init__(self, deejayd_core=None): - self.deejayd_core = deejayd_core - - def createCmdFromXML(self,line): - queueCmd = commandsXML.queueCommands(self.deejayd_core) - - try: xml_tree = ET.fromstring(line) - except: - queueCmd.addCommand('parsing error', commandsXML.UnknownCommand, []) - else: - cmds = xml_tree.findall("command") - for cmd in cmds: - (cmdName,cmdClass,args) = self.parseXMLCommand(cmd) - queueCmd.addCommand(cmdName,cmdClass,args) - - return queueCmd - - def parseXMLCommand(self,cmd): - cmdName = cmd.attrib["name"] - args = {} - for arg in cmd.findall("arg"): - name = arg.attrib["name"] - type = arg.attrib["type"] - if type == "simple": - value = arg.text - elif type == "multiple": - value = [] - for val in arg.findall("value"): - value.append(val.text) - args[name] = value - - if cmdName in commandsXML.commands.keys(): - return (cmdName, commandsXML.commands[cmdName], args) - else: return (cmdName,commandsXML.UnknownCommand,{}) - -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/net/__init__.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/net/__init__.py --- deejayd-0.7.2/deejayd/net/__init__.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/net/__init__.py 2009-09-21 08:44:21.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/net/protocol.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/net/protocol.py --- deejayd-0.7.2/deejayd/net/protocol.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/net/protocol.py 2009-09-21 08:44:21.000000000 +0100 @@ -0,0 +1,165 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import sys, traceback +from twisted.application import service, internet +from twisted.internet import protocol, reactor +from twisted.internet.error import ConnectionDone +from twisted.protocols.basic import LineReceiver + +from deejayd import __version__ +from deejayd.interfaces import DeejaydSignal +from deejayd.mediafilters import * +from deejayd.ui import log +from deejayd.utils import str_encode +from deejayd.rpc import Fault, DEEJAYD_PROTOCOL_VERSION +from deejayd.rpc.jsonparsers import loads_request +from deejayd.rpc.jsonbuilders import JSONRPCResponse, DeejaydJSONSignal +from deejayd.rpc import protocol as deejayd_protocol + +class DeejaydProtocol(LineReceiver, deejayd_protocol.DeejaydTcpJSONRPC): + NOT_FOUND = 8001 + FAILURE = 8002 + delimiter = 'ENDJSON\n' + + def __init__(self, deejayd_core, protocol_manager): + super(DeejaydProtocol, self).__init__() + self.MAX_LENGTH = 40960 + self.subHandlers = {} + self.deejayd_core = deejayd_core + self.manager = protocol_manager + + def connectionMade(self): + self.send_buffer("OK DEEJAYD %s protocol %d\n" % + (__version__, DEEJAYD_PROTOCOL_VERSION, )) + + def connectionLost(self, reason=ConnectionDone): + self.manager.close_signals(self) + + def lineReceived(self, line): + line = line.strip("\r") + # DEBUG Informations + log.debug(line) + + need_to_close = False + try: + parsed = loads_request(line) + args, functionPath = parsed['params'], parsed["method"] + function = self._getFunction(functionPath) + if parsed["method"] == "close": + # close the connection after send answer + need_to_close = True + except Fault, f: + try: id = parsed["id"] + except: + id = None + ans = JSONRPCResponse(f, id) + else: + try: result = function(*args) + except Exception, ex: + if not isinstance(ex, Fault): + log.err(str_encode(traceback.format_exc())) + result = Fault(self.FAILURE, _("error, see deejayd log")) + else: + result = ex + ans = JSONRPCResponse(result, parsed["id"]) + + self.send_buffer(ans.to_json()+self.delimiter) + if need_to_close: + self.transport.loseConnection() + + def send_buffer(self, buf): + if isinstance(buf, unicode): + buf = buf.encode("utf-8") + self.transport.write(buf) + log.debug(buf) + + def lineLengthExceeded(self, line): + log.err(_("Request too long, close the connection")) + self.transport.loseConnection() + + def set_signaled(self, signal_name): + self.manager.set_signaled(self, signal_name) + + def set_not_signaled(self, signal_name): + self.manager.set_not_signaled(self, signal_name) + + +class DeejaydFactory(protocol.ServerFactory): + protocol = DeejaydProtocol + obj_supplied = False + + def __init__(self, deejayd_core): + self.deejayd_core = deejayd_core + self.signaled_clients = dict([(signame, []) for signame\ + in DeejaydSignal.SIGNALS]) + self.core_sub_ids = {} + + def startFactory(self): + log.info(_("Net Protocol activated")) + + def buildProtocol(self, addr): + p = self.protocol(self.deejayd_core, self) + p = deejayd_protocol.build_protocol(self.deejayd_core, p) + # set specific signal subhandler + p = deejayd_protocol.set_signal_subhandler(self.deejayd_core, p) + p.factory = self + return p + + def clientConnectionLost(self, connector, reason): + for signal_name in DeejaydSignal.SIGNALS: + self.set_not_signaled(connector, signal_name) + + def set_signaled(self, connector, signal_name): + client_list = self.signaled_clients[signal_name] + if len(client_list) < 1: + # First subscription for this signal, so subscribe + sub_id = self.deejayd_core.subscribe(signal_name, + self.sig_bcast_to_clients) + self.core_sub_ids[signal_name] = sub_id + + self.signaled_clients[signal_name].append(connector) + + def set_not_signaled(self, connector, signal_name): + client_list = self.signaled_clients[signal_name] + if connector in client_list: + client_list.remove(connector) + if len(client_list) < 1: + # No more clients for this signal, we can unsubscribe + self.deejayd_core.unsubscribe(self.core_sub_ids[signal_name]) + + def close_signals(self, connector): + for signal_name in self.signaled_clients.keys(): + if len(self.signaled_clients[signal_name]) > 0: + self.set_not_signaled(connector, signal_name) + + def sig_bcast_to_clients(self, signal): + interested_clients = self.signaled_clients[signal.get_name()] + if len(interested_clients) > 0: + j_sig = DeejaydJSONSignal(signal) + ans = JSONRPCResponse(j_sig.dump(), None).to_json() + for client in interested_clients: + # http://twistedmatrix.com/pipermail/twisted-python/2007-August/015905.html + # says : "Don't call reactor methods from any thread except the + # one which is running the reactor. This will have + # unpredictable results and generally be broken." + # This is the "why" of this weird call instead of a simple + # client.send_buffer(xml_sig.to_xml()). + reactor.callFromThread(client.send_buffer, ans+client.delimiter) + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/net/xmlbuilders.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/net/xmlbuilders.py --- deejayd-0.7.2/deejayd/net/xmlbuilders.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/net/xmlbuilders.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,391 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -try: from xml.etree import cElementTree as ET # python 2.5 -except ImportError: # python 2.4 - import cElementTree as ET - -class _DeejaydXML: - - def __init__(self, mother_xml_object = None): - self.appended_xml_objects = None - self.__is_mother = True - self.__xmlbuilt = False - - if mother_xml_object == None: - self.xmlroot = ET.Element('deejayd') - self.appended_xml_objects = [self] - else: - self.__is_mother = False - self.xmlroot = mother_xml_object.xmlroot - mother_xml_object.append_another_xml_object(self) - - self.xmlcontent = None - - def append_another_xml_object(self, xml_object): - self.appended_xml_objects.append(xml_object) - - def __really_build_xml(self): - if self.__is_mother: - if not self.__xmlbuilt: - for xml_object in self.appended_xml_objects: - xml_object.build_xml() - self.xmlroot.append(xml_object.xmlcontent) - del self.appended_xml_objects - self.__xmlbuilt = True - else: - raise NotImplementedError('Do not build directly deejayd\ - XML that has a mother.') - - def _to_xml_string(self, s): - if isinstance(s, int) or isinstance(s, float) or isinstance(s, long): - return "%d" % (s,) - elif isinstance(s, str): - return "%s" % (s.decode('utf-8')) - elif isinstance(s, unicode): - rs = s.encode("utf-8") - return "%s" % (rs.decode('utf-8')) - else: - raise TypeError - - def to_xml(self): - self.__really_build_xml() - return '' + \ - ET.tostring(self.xmlroot,'utf-8') - - def _indent(self,elem, level=0): - indent_char = " " - i = "\n" + level*indent_char - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + indent_char - for e in elem: - self._indent(e, level+1) - if not e.tail or not e.tail.strip(): - e.tail = i + indent_char - if not e.tail or not e.tail.strip(): - e.tail = i - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i - - def to_pretty_xml(self): - self.__really_build_xml() - self._indent(self.xmlroot) - return '' + "\n" +\ - ET.tostring(self.xmlroot,'utf-8') + "\n" - - def build_xml(self): - raise NotImplementedError - - -class DeejaydXMLCommand(_DeejaydXML): - - def __init__(self, name, mother_xml_object = None): - _DeejaydXML.__init__(self, mother_xml_object) - self.name = name - self.args = {} - - def add_simple_arg(self, name, value): - self.args[name] = value - - def add_multiple_arg(self, name, valuelist): - self.add_simple_arg(name, valuelist) - - def build_xml(self): - # Add command - self.xmlcontent = ET.Element('command', name = self.name) - - # Add args - for arg in self.args.keys(): - xmlarg = ET.SubElement(self.xmlcontent, 'arg', name = arg) - - arg_param = self.args[arg] - if type(arg_param) is list: - # We've got multiple args - xmlarg.attrib['type'] = 'multiple' - - for arg_param_value in arg_param: - xmlval = ET.SubElement(xmlarg,'value') - xmlval.text = self._to_xml_string(arg_param_value) - - else: - # We've got a simple arg - xmlarg.attrib['type'] = 'simple' - xmlarg.text = self._to_xml_string(arg_param) - - -class DeejaydXMLSignal(_DeejaydXML): - - def __init__(self, name=None, mother_xml_object=None): - _DeejaydXML.__init__(self, mother_xml_object) - self.name = name - - def set_name(self, name): - self.name = name - - def build_xml(self): - self.xmlcontent = ET.Element('signal', name=self.name) - - -class _DeejaydXMLAnswer(_DeejaydXML): - - def __init__(self, originating_cmd, mother_xml_object = None): - _DeejaydXML.__init__(self, mother_xml_object) - self.originating_cmd = originating_cmd - - def build_xml_parm(self, name, value): - xmlparm = ET.Element('parm', name = name) - xmlparm.attrib['value'] = self._to_xml_string(value) - return xmlparm - - def build_xml_list_parm(self, name, value_list): - xml_list_parm = ET.Element('listparm') - if name: xml_list_parm.attrib['name'] = name - for value in value_list: - if type(value) == dict: - xmlvalue = self.build_xml_dict_parm(None,value) - else: - xmlvalue = ET.Element('listvalue') - xmlvalue.attrib['value'] = self._to_xml_string(value) - xml_list_parm.append(xmlvalue) - return xml_list_parm - - def build_xml_dict_parm(self, name, value_dict): - xml_dict_parm = ET.Element('dictparm') - if name: xml_dict_parm.attrib['name'] = name - for key in value_dict.keys(): - xmlitem = ET.SubElement(xml_dict_parm, 'dictitem', name = key) - xmlitem.attrib['value'] = self._to_xml_string(value_dict[key]) - return xml_dict_parm - - def build_xml_parm_list(self, data, parent_element): - for data_key in data.keys(): - data_value = data[data_key] - xml_parm = None - if type(data_value) is list: - xml_parm = self.build_xml_list_parm(data_key, data_value) - elif type(data_value) is dict: - xml_parm = self.build_xml_dict_parm(data_key, data_value) - else: - xml_parm = self.build_xml_parm(data_key, data_value) - parent_element.append(xml_parm) - - -class DeejaydXMLError(_DeejaydXMLAnswer): - """Error notification.""" - - response_type = 'error' - - def set_error_text(self, txt): - self.error_text = txt - - def build_xml(self): - self.xmlcontent = ET.Element(self.response_type, \ - name=self.originating_cmd) - self.xmlcontent.text = str(self.error_text) - - -class DeejaydXMLAck(_DeejaydXMLAnswer): - """Acknowledgement of a command.""" - - response_type = 'Ack' - - def build_xml(self): - self.xmlcontent = ET.Element('response',name = self.originating_cmd,\ - type = self.response_type) - - -class DeejaydXMLKeyValue(DeejaydXMLAck): - """A list of key, value pairs.""" - - response_type = 'KeyValue' - - def __init__(self, originating_cmd, mother_xml_object = None): - DeejaydXMLAck.__init__(self, originating_cmd, mother_xml_object) - self.contents = {} - - def add_pair(self, key, value): - self.contents[key] = value - - def set_pairs(self, key_value): - self.contents = key_value - - def build_xml(self): - DeejaydXMLAck.build_xml(self) - - for k, v in self.contents.items(): - self.xmlcontent.append(self.build_xml_parm(k, v)) - - -class DeejaydXMLFileDirList(DeejaydXMLAck): - """A list of files and directories.""" - - response_type = 'FileAndDirList' - - def __init__(self, originating_cmd, mother_xml_object = None): - DeejaydXMLAck.__init__(self, originating_cmd, mother_xml_object) - - self.directory = None - self.file_type = None - - self.contents = {'directory' : [], - 'file' : []} - - def set_directory(self, directory): - self.directory = directory - - def set_filetype(self, file_type): - self.file_type = file_type - - def add_directory(self, dirname): - self.contents['directory'].append(dirname) - - def set_directories(self, directories): - self.contents['directory'] = directories - - def add_file(self, file_info): - self.contents['file'].append(file_info) - - def set_files(self, file_list): - self.contents['file'] = file_list - - def build_xml(self): - DeejaydXMLAck.build_xml(self) - - if self.directory != None: - self.xmlcontent.attrib['directory'] = \ - self._to_xml_string(self.directory) - - for dirname in self.contents['directory']: - ET.SubElement(self.xmlcontent, 'directory', \ - name = self._to_xml_string(dirname)) - - for item in self.contents['file']: - xmlitem = ET.SubElement(self.xmlcontent,'file',type=self.file_type) - self.build_xml_parm_list(item, xmlitem) - - -class DeejaydXMLMediaList(DeejaydXMLAck): - """A list of media (song, webradio,playlist or video) with information for each media : - * artist, album, title, id, etc. if it is a song - * title, url, id, etc. if it is a webradio - * title, id, length, subtitle, audio, etc. if it is a video""" - - response_type = 'MediaList' - - def __init__(self, originating_cmd, mother_xml_object = None): - DeejaydXMLAck.__init__(self, originating_cmd, mother_xml_object) - self.media_items = [] - self.total_length = None - - def set_total_length(self, length): - self.total_length = length - - def set_mediatype(self,type): - self.media_type = type - - def add_media(self, media): - self.media_items.append(media) - - def set_medias(self, medias): - self.media_items = medias - - def build_xml(self): - DeejaydXMLAck.build_xml(self) - if self.total_length: - self.xmlcontent.attrib["total_length"] = \ - self._to_xml_string(self.total_length) - for item in self.media_items: - xmlitem = ET.SubElement(self.xmlcontent,'media',\ - type=self.media_type) - self.build_xml_parm_list(item, xmlitem) - - -class DeejaydXMLDvdInfo(DeejaydXMLAck): - """Format dvd content.""" - - response_type = 'DvdInfo' - - def __init__(self, originating_cmd, mother_xml_object = None): - DeejaydXMLAck.__init__(self, originating_cmd, mother_xml_object) - self.dvd_info = None - - def set_info(self,dvd_info): - self.dvd_info = dvd_info - - def build_xml(self): - DeejaydXMLAck.build_xml(self) - xmldvd = ET.SubElement(self.xmlcontent,'dvd') - - xmldvd.attrib['title'] = self._to_xml_string(self.dvd_info['title']) - xmldvd.attrib['longest_track'] = \ - self._to_xml_string(self.dvd_info['longest_track']) - # dvd's title - for track in self.dvd_info["track"]: - xmltrack = ET.SubElement(xmldvd,'track') - for info in ('ix','length'): - xmltrack.attrib[info] = self._to_xml_string(track[info]) - - # avalaible audio channels - for audio in track["audio"]: - xmlaudio = ET.SubElement(xmltrack, 'audio') - for info in ('ix','lang'): - xmlaudio.attrib[info] = self._to_xml_string(audio[info]) - - # avalaible subtitle channels - for sub in track["subp"]: - xmlsub = ET.SubElement(xmltrack, 'subtitle') - for info in ('ix','lang'): - xmlsub.attrib[info] = self._to_xml_string(sub[info]) - - # chapter list - for chapter in track["chapter"]: - xmlchapter = ET.SubElement(xmltrack, 'chapter') - for info in ('ix','length'): - xmlchapter.attrib[info] = self._to_xml_string(chapter[info]) - - -class DeejaydXMLAnswerFactory: - - response_types = [ DeejaydXMLError, - DeejaydXMLAck, - DeejaydXMLKeyValue, - DeejaydXMLFileDirList, - DeejaydXMLMediaList, - DeejaydXMLDvdInfo ] - - def __init__(self): - self.mother_answer = None - - def set_mother(self, mother_answer): - self.mother_answer = mother_answer - - def get_deejayd_xml_answer(self, type, originating_cmd): - iat = iter(self.response_types) - try: - while True: - type_class = iat.next() - if type_class.response_type == type: - ans = type_class(originating_cmd, self.mother_answer) - return ans - except StopIteration: - raise NotImplementedError - - -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/player/_base.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/player/_base.py --- deejayd-0.7.2/deejayd/player/_base.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/player/_base.py 2009-09-21 08:44:21.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -44,14 +44,20 @@ # Restore current media media_pos = int(self.db.get_state("current")) source = self.db.get_state("current_source") - if media_pos != -1 and source not in ("queue", "none"): + if media_pos != -1 and source not in ("queue", "none", 'webradio'): self._media_file = self._source.get(media_pos, "pos", source) # Update state state = self.db.get_state("state") - if state != PLAYER_STOP: - self.play() - if self._media_file and self._media_file["source"] != "queue": - self.set_position(int(self.db.get_state("current_pos"))) + if state != PLAYER_STOP and source != 'webradio': + try: + self.play() + except PlayerError: + # There is an issue restoring the playing state on this file + # so pause for now. + self.stop() + else: + if self._media_file and self._media_file["source"] != "queue": + self.set_position(int(self.db.get_state("current_pos"))) if state == PLAYER_PAUSE: self.pause() @@ -76,6 +82,9 @@ raise NotImplementedError def next(self): + if self.get_state() != PLAYER_STOP: + try: self._media_file.skip() + except AttributeError: pass self._change_file(self._source.next()) def previous(self): @@ -93,7 +102,14 @@ def get_position(self): raise NotImplementedError - def set_position(self,pos): + def set_position(self, pos, relative = False): + if relative and self.get_state()!="stop"\ + and self._media_file["type"]!="webradio": + cur_pos = self.get_position() + pos = int(pos) + cur_pos + self._set_position(pos) + + def _set_position(self, pos): raise NotImplementedError def get_state(self): @@ -110,6 +126,7 @@ "av_offset": self.set_avoffset, "sub_offset": self.set_suboffset, "zoom": self.set_zoom, + "aspect_ratio": self.set_aspectratio, } options[name](value) @@ -122,6 +139,9 @@ def set_suboffset(self, offset): raise NotImplementedError + def set_aspectratio(self, aspect): + raise NotImplementedError + def set_alang(self,lang_idx): try: audio_tracks = self._media_file["audio"] except KeyError: @@ -175,11 +195,13 @@ str(self._media_file["id"]), self._media_file["source"]))) if self.get_state() != PLAYER_STOP: + position = self.get_position() if "length" not in self._media_file.keys() or \ self._media_file["length"] == 0: - self._media_file["length"] = self.get_position() - status.extend([ ("time","%d:%d" % (self.get_position(),\ - self._media_file["length"])) ]) + length = position + else: + length = int(self._media_file["length"]) + status.extend([ ("time","%d:%d" % (position, length)) ]) return status @@ -198,29 +220,4 @@ if self.get_state() != PLAYER_STOP: self.stop() - def _is_lsdvd_exists(self): - path = os.getenv('PATH') - if not path: return False - for p in path.split(':'): - if os.path.isfile(os.path.join(p,"lsdvd")): - return True - return False - - def _get_dvd_info(self): - command = 'lsdvd -Oy -s -a -c' - lsdvd_process = subprocess.Popen(command, shell=True, stdin=None,\ - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - lsdvd_process.wait() - - # read error - error = lsdvd_process.stderr.read() - output = lsdvd_process.stdout.read() - if error and output == '': - raise PlayerError(error) - - try: exec(output) - except: - raise PlayerError(_("error in lsdvd command")) - return lsdvd - # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/player/display/__init__.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/player/display/__init__.py --- deejayd-0.7.2/deejayd/player/display/__init__.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/player/display/__init__.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,18 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/player/display/x11.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/player/display/x11.py --- deejayd-0.7.2/deejayd/player/display/x11.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/player/display/x11.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,152 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import threading -from ctypes import byref, cast, c_char_p, c_int -from deejayd.player.display._X11 import * - -class X11Error(Exception): pass - -class X11Display: - - def __init__(self, opts, fullscreen): - self.__dsp_name = opts["display"] - self.__fullscreen = fullscreen - if not XInitThreads(): - raise X11Error("Unable to init X Threads") - - # init options - self.__lock = threading.Lock() - self.infos = None - self.video_area = None - - def create(self): - self.infos = {} - self.video_area = {} - # open display - self.infos['dsp'] = XOpenDisplay(self.__dsp_name) - if not self.infos['dsp']: - raise X11Error("Unable to open display %s" % self.__dsp_name) - - # calculate screen pixel aspect - screen = XDefaultScreen(self.infos['dsp']) - screen_width = float(XDisplayWidth(self.infos['dsp'], screen)*1000)/\ - float(XDisplayWidthMM(self.infos['dsp'], screen)) - screen_height = float(XDisplayHeight(self.infos['dsp'], screen)*1000)/\ - float(XDisplayHeightMM(self.infos['dsp'], screen)) - self.video_area["pixel_aspect"] = screen_height / screen_width - - if self.__fullscreen: - width = XDisplayWidth(self.infos['dsp'], screen) - height = XDisplayHeight(self.infos['dsp'], screen) - else: - width = 320 - height = 200 - - # create window - root_window = XDefaultRootWindow(self.infos['dsp']) - self.infos["window"] = XCreateSimpleWindow(self.infos['dsp'],\ - root_window, 0, 0, width, height, 1, 0, 0) - self.infos["screen"] = screen - - # hide cursor - XSetNullCursor(self.infos['dsp'], self.infos['screen'],\ - self.infos['window']) - - # remove window decoration - mwmhints = MWMHints() - mwmhints.decorations = 0 - mwmhints.flags = MWM_HINTS_DECORATIONS - data = cast(byref(mwmhints), c_char_p) - XA_NO_BORDER = XInternAtom(self.infos['dsp'], "_MOTIF_WM_HINTS", False) - XChangeProperty(self.infos['dsp'], self.infos['window'],\ - XA_NO_BORDER, XA_NO_BORDER, 32, PropModeReplace, data,\ - PROP_MWM_HINTS_ELEMENTS) - - # do not manage this window with the WM - attr = XSetWindowAttributes() - attr.override_redirect = True - XChangeWindowAttributes(self.infos['dsp'], self.infos["window"],\ - CWOverrideRedirect, byref(attr)) - - # update video area - self.__update_video_area() - - self.set_dpms(False) - - def destroy(self): - if not self.infos: - return - # destroy windows - XLockDisplay(self.infos['dsp']) - XUnmapWindow(self.infos['dsp'], self.infos["window"]) - XDestroyWindow(self.infos['dsp'], self.infos["window"]) - XSync(self.infos['dsp'], False) - XUnlockDisplay(self.infos['dsp']) - - self.set_dpms(True) - - # close display - XCloseDisplay(self.infos['dsp']) - - # reset options - self.infos = None - self.video_area = None - - def show(self, do_show = True): - """ show/hide window """ - if not self.infos: - raise X11Error("No window exists") - XLockDisplay(self.infos['dsp']) - if do_show: - XMapRaised(self.infos['dsp'], self.infos["window"]) - else: - XUnmapWindow(self.infos['dsp'], self.infos["window"]) - XSync(self.infos['dsp'], False) - XUnlockDisplay(self.infos['dsp']) - - def get_infos(self): - return self.infos - - def get_and_lock_video_area(self): - self.__lock.acquire() - return self.video_area - - def release_video_area(self): - self.__lock.release() - - def __update_video_area(self): - self.__lock.acquire() - x, y, width, height = XGetGeometry(self.infos["dsp"],\ - self.infos["window"]) - self.video_area["x"] = x - self.video_area["y"] = y - self.video_area["width"] = width - self.video_area["height"] = height - self.__lock.release() - - def set_dpms(self, activated): - dummy = c_int() - if DPMSQueryExtension(self.infos['dsp'], byref(dummy), byref(dummy)): - if activated: - DPMSEnable(self.infos['dsp']) - else: - DPMSDisable(self.infos['dsp']) - - -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/player/display/_X11.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/player/display/_X11.py --- deejayd-0.7.2/deejayd/player/display/_X11.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/player/display/_X11.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,303 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import sys -import ctypes - -try: - _libX11 = ctypes.cdll.LoadLibrary('libX11.so.6') -except (ImportError, OSError), e: - raise ImportError, e - -class XColor(ctypes.Structure): - _fields_ = [ - ('pixel', ctypes.c_ulong), - ('red', ctypes.c_ushort), - ('green', ctypes.c_ushort), - ('blue', ctypes.c_ushort), - ('flags', ctypes.c_char), - ('pad', ctypes.c_char), - ] - -class XSetWindowAttributes(ctypes.Structure): - _fields_ = [ - ('background_pixmap', ctypes.c_ulong), - ('background_pixel', ctypes.c_ulong), - ('border_pixmap', ctypes.c_ulong), - ('border_pixel', ctypes.c_ulong), - ('bit_gravity', ctypes.c_int), - ('win_gravity', ctypes.c_int), - ('backing_store', ctypes.c_int), - ('backing_planes', ctypes.c_ulong), - ('backing_pixel', ctypes.c_ulong), - ('save_under', ctypes.c_int), - ('event_mask', ctypes.c_long), - ('do_not_propagate_mask', ctypes.c_long), - ('override_redirect', ctypes.c_int), - ('colormap', ctypes.c_ulong), - ('cursor', ctypes.c_ulong), - ] - -# custom declaration -MWM_HINTS_DECORATIONS = (1L << 1) -PROP_MWM_HINTS_ELEMENTS = 5 -class MWMHints(ctypes.Structure): - _fields_ = [ - ('flags', ctypes.c_uint), - ('functions', ctypes.c_uint), - ('decorations', ctypes.c_uint), - ('input_mode', ctypes.c_int), - ('status', ctypes.c_uint), - ] - -# Property modes -PropModeReplace = 0 -PropModePrepend = 1 -PropModeAppend = 2 - -# Window attributes for CreateWindow and ChangeWindowAttributes -CWOverrideRedirect = (1L<<9) - -# Input Event Masks -KeyPressMask = (1L<<0) -VisibilityChangeMask = (1L<<16) -StructureNotifyMask = (1L<<17) -ExposureMask = (1L<<15) -PropertyChangeMask = (1L<<22) - -# int XInitThreads() -_libX11.XInitThreads.restype = ctypes.c_int - -# Display *XOpenDisplay(char *display_name) -_libX11.XOpenDisplay.restype = ctypes.c_void_p -_libX11.XOpenDisplay.argtypes = [ctypes.c_char_p] - -# void XCloseDisplay(Display *display) -_libX11.XCloseDisplay.argtypes = [ctypes.c_void_p] - -# void XLockDisplay(Display *display) -_libX11.XLockDisplay.argtypes = [ctypes.c_void_p] - -# void XUnlockDisplay(Display *display) -_libX11.XUnlockDisplay.argtypes = [ctypes.c_void_p] - -# int XDefaultScreen(Display *display) -_libX11.XDefaultScreen.restype = ctypes.c_int -_libX11.XDefaultScreen.argtypes = [ctypes.c_void_p] - -# int XDisplayWidth(Display *display, int screen_number); -_libX11.XDisplayWidth.restype = ctypes.c_int -_libX11.XDisplayWidth.argtypes = [ctypes.c_void_p, ctypes.c_int] - -# int XDisplayWidthMM(Display *display, int screen_number); -_libX11.XDisplayWidthMM.restype = ctypes.c_int -_libX11.XDisplayWidthMM.argtypes = [ctypes.c_void_p, ctypes.c_int] - -# int XDisplayHeight(Display *display, int screen_number); -_libX11.XDisplayHeight.restype = ctypes.c_int -_libX11.XDisplayHeight.argtypes = [ctypes.c_void_p, ctypes.c_int] - -# int XDisplayHeightMM(Display *display, int screen_number); -_libX11.XDisplayHeightMM.restype = ctypes.c_int -_libX11.XDisplayHeightMM.argtypes = [ctypes.c_void_p, ctypes.c_int] - -# void XSync(Display *display, Bool discard) -_libX11.XSync.argtypes = [ctypes.c_void_p, ctypes.c_int] - -# Window XCreateWindow(Display *display, Window parent, int x, int y, -# uint width, uint height, uint border_width, int depth, -# uint class, Visual *visual, ulong valuemask, -# XSetWindowAttributes *attributes) -_libX11.XCreateWindow.restype = ctypes.c_ulong -_libX11.XCreateWindow.argtypes = [ctypes.c_void_p, ctypes.c_ulong, \ - ctypes.c_int,\ - ctypes.c_int, ctypes.c_uint, ctypes.c_uint,\ - ctypes.c_uint, ctypes.c_int, ctypes.c_uint,\ - ctypes.c_void_p, ctypes.c_ulong,\ - ctypes.c_void_p] - -# Window XCreateSimpleWindow(Display *display, Window parent, int x, int y, -# uint width, uint height, uint border_width, -# ulong border, ulong background) -_libX11.XCreateSimpleWindow.restype = ctypes.c_ulong -_libX11.XCreateSimpleWindow.argtypes = [ctypes.c_void_p, ctypes.c_ulong,\ - ctypes.c_int, ctypes.c_int,\ - ctypes.c_uint, ctypes.c_uint,\ - ctypes.c_uint, ctypes.c_ulong,\ - ctypes.c_ulong] - -# void XDestroyWindow(Display *display, Window w) -_libX11.XDestroyWindow.argtypes = [ctypes.c_void_p, ctypes.c_ulong] - -# void XSelectInput(Display *display, Window w, long event_mask) -_libX11.XSelectInput.argtypes = [ctypes.c_void_p, ctypes.c_ulong, ctypes.c_long] - -# Window XDefaultRootWindow(Display *display) -_libX11.XDefaultRootWindow.restype = ctypes.c_ulong -_libX11.XDefaultRootWindow.argtypes = [ctypes.c_void_p] - -# void XMapRaised(Display *display, Window w) -_libX11.XMapRaised.argtypes = [ctypes.c_void_p, ctypes.c_ulong] - -# void XUnmapWindow(Display *display, Window w) -_libX11.XUnmapWindow.argtypes = [ctypes.c_void_p, ctypes.c_ulong] - -# XResizeWindow(Display *display, Window w, uint width, uint height) -_libX11.XResizeWindow.argtypes = [ctypes.c_void_p, ctypes.c_ulong,\ - ctypes.c_uint, ctypes.c_uint] - -# XChangeProperty(Display *display, Window w, Atom property, Atom type, -# int format, int mode, uchar *data, int nelements) -_libX11.XChangeProperty.argtypes = [ctypes.c_void_p, ctypes.c_ulong,\ - ctypes.c_ulong, ctypes.c_ulong, ctypes.c_int, ctypes.c_int,\ - ctypes.c_char_p, ctypes.c_int] - -# XChangeWindowAttributes(Display *display, Window w, ulong valuemask, -# XSetWindowAttributes *attributes) -_libX11.XChangeWindowAttributes.argtypes = [ctypes.c_void_p, ctypes.c_ulong,\ - ctypes.c_ulong, ctypes.POINTER(XSetWindowAttributes)] - -# Atom XInternAtom(Display *display,char *atom_name, Bool only_if_exists) -_libX11.XInternAtom.restype = ctypes.c_ulong -_libX11.XInternAtom.argtypes = [ctypes.c_void_p, ctypes.c_char_p,\ - ctypes.c_int] - -# int XGetGeometry(Display *display, Drawable d, Window *root_return, -# int *x_return, int *y_return, uint *width_return, -# uint *height_return, uint *border_width_return, -# uint *depth_return) -_libX11.XGetGeometry.restype = ctypes.c_int -_libX11.XGetGeometry.argtypes = [ctypes.c_void_p, ctypes.c_ulong,\ - ctypes.POINTER(ctypes.c_ulong), ctypes.POINTER(ctypes.c_int),\ - ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_uint),\ - ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint),\ - ctypes.POINTER(ctypes.c_uint)] - -# XDefineCursor(Display *display, Window w, Cursor cursor) -_libX11.XDefineCursor.argtypes = [ctypes.c_void_p, ctypes.c_ulong,\ - ctypes.c_ulong] - -# XUndefineCursor(Display *display, Window w) -_libX11.XUndefineCursor.argtypes = [ctypes.c_void_p, ctypes.c_ulong] - -# Cursor XCreatePixmapCursor(Display *display, Pixmap source, Pixmap mask, -# XColor *foreground_color, -# XColor *background_color, uint x, uint y) -_libX11.XCreatePixmapCursor.restype = ctypes.c_ulong -_libX11.XCreatePixmapCursor.argtypes = [ctypes.c_void_p, ctypes.c_ulong,\ - ctypes.c_ulong, ctypes.POINTER(XColor),\ - ctypes.POINTER(XColor), ctypes.c_uint, ctypes.c_uint] - -# XFreeCursor(Display *display, Cursor cursor) -_libX11.XFreeCursor.argtypes = [ctypes.c_void_p, ctypes.c_ulong] - -# Colormap XDefaultColormap(Display *display, int screen) -_libX11.XDefaultColormap.restype = ctypes.c_ulong -_libX11.XDefaultColormap.argtypes = [ctypes.c_void_p, ctypes.c_int] - -# int XAllocNamedColor(Display *display, Colormap cmap, char *color_name, -# XColor *screen_def_return, XColor *exact_def_return) -_libX11.XAllocNamedColor.restype = ctypes.c_int -_libX11.XAllocNamedColor.argtypes = [ctypes.c_void_p, ctypes.c_ulong,\ - ctypes.c_char_p, ctypes.POINTER(XColor), ctypes.POINTER(XColor)] - -# Pixmap XCreateBitmapFromData(Display *display, Drawable d, char *data, -# uint width, uint height) -_libX11.XCreateBitmapFromData.restype = ctypes.c_ulong -_libX11.XCreateBitmapFromData.argtypes = [ctypes.c_void_p, ctypes.c_ulong,\ - ctypes.c_char_p, ctypes.c_uint, ctypes.c_uint] - -# void XFreePixmap(Display *display, Pixmap p) -_libX11.XFreePixmap.argtypes = [ctypes.c_void_p, ctypes.c_ulong] - -# void XFreeColors(Display *display, Colormap colormap, ulong pixels[], -# int npixels, ulong planes)) -_libX11.XFreeColors.argtypes = [ctypes.c_void_p, ctypes.c_ulong,\ - ctypes.POINTER(ctypes.c_ulong), ctypes.c_int, ctypes.c_ulong] - -# copy functions from the library -module = sys.modules[__name__] -for name in dir(_libX11): - if name.startswith('X'): - setattr(module, name, getattr(_libX11, name)) - -def XGetGeometry(display, window): - root = ctypes.c_ulong() - x = ctypes.c_int() - y = ctypes.c_int() - width =ctypes. c_uint() - height = ctypes.c_uint() - border_width = ctypes.c_uint() - depth = ctypes.c_uint() - - _libX11.XGetGeometry(display, window, ctypes.byref(root), ctypes.byref(x),\ - ctypes.byref(y), ctypes.byref(width), ctypes.byref(height),\ - ctypes.byref(border_width), ctypes.byref(depth)) - - return x.value, y.value, width.value, height.value - -def XSetNullCursor(display, screen, window): - black = XColor() - dummy = XColor() - bitmap_struct = ctypes.c_char * 8 - bm_no_data = bitmap_struct('0', '0', '0', '0', '0', '0', '0', '0') - - cmap = XDefaultColormap(display, screen) - XAllocNamedColor(display, cmap, "black", ctypes.byref(black),\ - ctypes.byref(dummy)) - bm_no = XCreateBitmapFromData(display, window, bm_no_data, 1, 1) - no_ptr = XCreatePixmapCursor(display, bm_no, bm_no, ctypes.byref(black),\ - ctypes.byref(black), 0, 0) - - XDefineCursor(display, window, no_ptr) - - XFreeCursor(display, no_ptr) - if bm_no != None: - XFreePixmap(display, bm_no) - pixel = ctypes.c_ulong(black.pixel) - XFreeColors(display, cmap, ctypes.byref(pixel), 1, 0) - - -# DPMS manipulation -try: - _libXext = ctypes.cdll.LoadLibrary('libXext.so.6') -except (ImportError, OSError), e: - raise ImportError, e - - -# Bool DPMSQueryExtension (Display *display, int *event_base, int *error_base) -_libXext.DPMSQueryExtension.restype = ctypes.c_int -_libXext.DPMSQueryExtension.argtypes = [ctypes.c_void_p, - ctypes.POINTER(ctypes.c_int), - ctypes.POINTER(ctypes.c_int)] -# Status DPMSEnable (Display *display ) -_libXext.DPMSEnable.restype = ctypes.c_int -_libXext.DPMSEnable.argtypes = [ctypes.c_void_p] -# Status DPMSDisable (Display *display ) -_libXext.DPMSDisable.restype = ctypes.c_int -_libXext.DPMSDisable.argtypes = [ctypes.c_void_p] - - -# copy functions from the library -module = sys.modules[__name__] -for name in dir(_libXext): - if name.startswith('DPMS'): - setattr(module, name, getattr(_libXext, name)) - - -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/player/gstreamer.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/player/gstreamer.py --- deejayd-0.7.2/deejayd/player/gstreamer.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/player/gstreamer.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -62,32 +62,14 @@ bus.connect('message', self.on_message) def init_video_support(self): - UnknownPlayer.init_video_support(self) - - import pygtk - pygtk.require('2.0') - - self.video_window = None - self.deejayd_window = None - - # Open a Video pipeline - pipeline_dict = {"x":"ximagesink", "xv":"xvimagesink",\ - "auto":"autovideosink"} - pipeline = self.config.get("gstreamer", "video_output") - try: video_sink = gst.parse_launch(pipeline_dict[pipeline]) - except gobject.GError, err: - self._video_support = False - raise PlayerError(_("No video sink found for Gstreamer")) - else: - self.bin.set_property('video-sink', video_sink) - - bus = self.bin.get_bus() - bus.enable_sync_message_emission() - bus.connect('sync-message::element', self.on_sync_message) + raise NotImplementedError def on_message(self, bus, message): if message.type == gst.MESSAGE_EOS: - self.next() + if self._media_file: + try: self._media_file.played() + except AttributeError: pass + self._change_file(self._source.next(explicit = False)) elif message.type == gst.MESSAGE_TAG: self._update_metadata(message.parse_tag()) elif message.type == gst.MESSAGE_ERROR: @@ -97,51 +79,10 @@ return True - def on_sync_message(self, bus, message): - if message.structure is None: - return - message_name = message.structure.get_name() - if message_name == 'prepare-xwindow-id' and self._video_support: - imagesink = message.src - imagesink.set_property("force-aspect-ratio","true") - imagesink.set_xwindow_id(self.video_window.window.xid) - def start_play(self): if not self._media_file: return - if self._media_file["type"] == "video" and not self.deejayd_window: - import gtk - self.deejayd_window = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.deejayd_window.set_default_size(640,400) - self.deejayd_window.set_title("deejayd") - self.deejayd_window.connect("destroy", self.stop) - - self.video_window = gtk.DrawingArea() - self.deejayd_window.add(self.video_window) - self.deejayd_window.connect("map_event",self.start_gstreamer) - if self._fullscreen: - # Hide the cursor - empty_pixmap = gtk.gdk.Pixmap(None, 1, 1, 1) - empty_color = gtk.gdk.Color() - empty_cursor = gtk.gdk.Cursor(empty_pixmap, empty_pixmap, - empty_color, empty_color, 0, 0) - self.deejayd_window.window.set_cursor(empty_cursor) - self.deejayd_window.fullscreen() - self.deejayd_window.show_all() - else: self.start_gstreamer() - - def start_gstreamer(self,widget = None, event = None): self.bin.set_property('uri',self._media_file["uri"]) - # load external subtitle if available - self.bin.set_property("suburi",'') - if "external_subtitle" in self._media_file and \ - self._media_file["external_subtitle"].startswith("file://"): - self.bin.set_property("suburi",\ - self._media_file["external_subtitle"]) - self.bin.set_property("subtitle-font-desc", "Sans Normal 24") - #self._media_file["subtitle"] = [{"lang": "none", "ix": -1},\ - # {"lang": "external", "ix":0}] - state_ret = self.bin.set_state(gst.STATE_PLAYING) timeout = 4 state = None @@ -153,13 +94,6 @@ if state_ret != gst.STATE_CHANGE_SUCCESS: msg = _("Unable to play file %s") % self._media_file["uri"] raise PlayerError(msg) - elif self._media_file["type"] == "video": - if "audio" in self._media_file: - self._media_file["audio_idx"] = \ - self.bin.get_property("current-audio") - if "subtitle" in self._media_file: - self._media_file["subtitle_idx"] = \ - self.bin.get_property("current-text") def pause(self): if self.get_state() == PLAYER_PLAY: @@ -175,11 +109,6 @@ # FIXME : try to remove this one day ... self._source.queue_reset() - # destroy video window if necessary - if self._video_support and self.deejayd_window: - self.deejayd_window.destroy() - self.deejayd_window = None - self.dispatch_signame('player.status') def _change_file(self,new_file): @@ -219,7 +148,7 @@ return p return 0 - def set_position(self,pos): + def _set_position(self,pos): if gst.STATE_NULL != self.__get_gst_state() and \ self.bin.get_property('uri'): pos = max(0, int(pos)) @@ -232,18 +161,6 @@ self.bin.send_event(event) self.dispatch_signame('player.status') - def _player_get_alang(self): - return self.bin.get_property("current-audio") - - def _player_set_alang(self,lang_idx): - self.bin.set_property("current-audio",lang_idx) - - def _player_get_slang(self): - return self.bin.get_property("current-text") - - def _player_set_slang(self,lang_idx): - self.bin.set_property("current-text",lang_idx) - def _update_metadata(self, tags): if not self._media_file or self._media_file["type"] != "webradio": return @@ -275,9 +192,7 @@ return state def is_supported_uri(self,uri_type): - if uri_type == "dvd" and not self._is_lsdvd_exists(): - # test lsdvd installation - return False + if uri_type == "dvd": return False return gst.element_make_from_uri(gst.URI_SRC,uri_type+"://", '') \ is not None @@ -288,9 +203,6 @@ ".ogg": ("ogg", "vorbis"), ".mp4": ("faad",), ".flac": ("flac",), - ".avi": ("ffmpeg",), - ".mpeg": ("ffmpeg",), - ".mpg": ("ffmpeg",), } if format in formats.keys(): @@ -301,63 +213,4 @@ return False - def get_video_file_info(self,file): - return DiscoverVideoFile(file) - - def get_dvd_info(self): - dvd_info = self._get_dvd_info() - ix = 0 - for track in dvd_info["track"]: - # FIXME find audio channels - audio_channels = [{"lang":"none","ix":-1},{"lang":"auto","ix":0}] - dvd_info['track'][ix]["audio"] = [] - # FIXME find subtitles channels - sub_channels = [{"lang":"none","ix":-1},{"lang":"auto","ix":0}] - dvd_info['track'][ix]["subp"] = [] - - ix += 1 - return dvd_info - - -class DiscoverVideoFile: - - def __init__(self,f): - self.__file = f.encode('utf-8') - self.__process = False - self.__file_info = None - - def __getitem__(self,key): - if not self.__file_info: - self.__getinfo() - - if key in self.__file_info: return self.__file_info[key] - else: raise PlayerError - - def __getinfo(self): - self.__process = True - - from gst.extend.discoverer import Discoverer - self.current = Discoverer(self.__file) - self.current.connect('discovered', self._process_end) - # start the discover - self.current.discover() - self.__wait() - - def _process_end(self,discoverer,ismedia): - # format file infos - self.__file_info = {} - self.__file_info["videowidth"] = self.current.videowidth - self.__file_info["videoheight"] = self.current.videoheight - self.__file_info["length"] = self.__format_time(\ - max(self.current.audiolength, self.current.videolength)) - - self.__process = False - - def __wait(self): - while self.__process: - time.sleep(0.1) - - def __format_time(self,value): - return value / gst.SECOND - # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/player/__init__.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/player/__init__.py --- deejayd-0.7.2/deejayd/player/__init__.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/player/__init__.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -16,13 +16,14 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from deejayd.interfaces import DeejaydError from deejayd.ui import log AVAILABLE_BACKENDS = ('xine', 'gstreamer', ) -class PlayerError(Exception):pass +class PlayerError(DeejaydError):pass def init(db,config): media_backend = config.get("general","media_backend") diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/player/_xine.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/player/_xine.py --- deejayd-0.7.2/deejayd/player/_xine.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/player/_xine.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,450 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2006 Lukas Lalinsky -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import sys -import ctypes - -try: - _libxine = ctypes.cdll.LoadLibrary('libxine.so.1') -except (ImportError, OSError), e: - raise ImportError, e - -class xine_event_t(ctypes.Structure): - _fields_ = [ - ('type', ctypes.c_int), - ('stream', ctypes.c_void_p), - ('data', ctypes.c_void_p), - ('data_length', ctypes.c_int), - ] - -class xine_ui_message_data_t(ctypes.Structure): - _fields_ = [ - ('compatibility_num_buttons', ctypes.c_int), - ('compatibility_str_len', ctypes.c_int), - ('compatibility_str', 256 * ctypes.c_char), - ('type', ctypes.c_int), - ('explanation', ctypes.c_int), - ('num_parameters', ctypes.c_int), - ('parameters', ctypes.c_void_p), - ('messages', ctypes.c_char), - ] - -class x11_visual_t(ctypes.Structure): - _fields_ = [ - ('display', ctypes.c_void_p), - ('screen', ctypes.c_int), - ('d', ctypes.c_ulong), # Drawable - ('user_data', ctypes.c_void_p), - ('dest_size_cb', ctypes.c_void_p), - ('frame_output_cb', ctypes.c_void_p), - ('lock_display', ctypes.c_void_p), - ('unlock_display', ctypes.c_void_p), - ] - -# dest size callback -xine_dest_size_cb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p,\ - ctypes.c_int, ctypes.c_int, ctypes.c_double,\ - ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int),\ - ctypes.POINTER(ctypes.c_double)) - -# frame output callback -xine_frame_output_cb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, - ctypes.c_int, ctypes.c_int, ctypes.c_double,\ - ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int),\ - ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int),\ - ctypes.POINTER(ctypes.c_double),\ - ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)) - -# event listener callback type -xine_event_listener_cb_t = ctypes.CFUNCTYPE( - ctypes.c_void_p, ctypes.c_void_p, - ctypes.POINTER(xine_event_t)) - -# event types -XINE_EVENT_UI_PLAYBACK_FINISHED = 1 -XINE_EVENT_UI_CHANNELS_CHANGED = 2 -XINE_EVENT_UI_SET_TITLE = 3 -XINE_EVENT_UI_MESSAGE = 4 -XINE_EVENT_FRAME_FORMAT_CHANGE = 5 -XINE_EVENT_AUDIO_LEVEL = 6 -XINE_EVENT_QUIT = 7 -XINE_EVENT_PROGRESS = 8 - -# stream parameters -XINE_PARAM_SPEED = 1 # see below -XINE_PARAM_AV_OFFSET = 2 # unit: 1/90000 ses -XINE_PARAM_AUDIO_CHANNEL_LOGICAL = 3 # -1 => auto, -2 => off -XINE_PARAM_SPU_CHANNEL = 4 -XINE_PARAM_VIDEO_CHANNEL = 5 -XINE_PARAM_AUDIO_VOLUME = 6 # 0..100 -XINE_PARAM_AUDIO_MUTE = 7 # 1=>mute, 0=>unmute -XINE_PARAM_AUDIO_COMPR_LEVEL = 8 # <100=>off, % compress otherw -XINE_PARAM_AUDIO_AMP_LEVEL = 9 # 0..200, 100=>100% (default) -XINE_PARAM_AUDIO_REPORT_LEVEL = 10 # 1=>send events, 0=> don't -XINE_PARAM_VERBOSITY = 11 # control console output -XINE_PARAM_SPU_OFFSET = 12 # unit: 1/90000 sec -XINE_PARAM_IGNORE_VIDEO = 13 # disable video decoding -XINE_PARAM_IGNORE_AUDIO = 14 # disable audio decoding -XINE_PARAM_IGNORE_SPU = 15 # disable spu decoding -XINE_PARAM_BROADCASTER_PORT = 16 # 0: disable, x: server port -XINE_PARAM_METRONOM_PREBUFFER = 17 # unit: 1/90000 sec -XINE_PARAM_EQ_30HZ = 18 # equalizer gains -100..100 -XINE_PARAM_EQ_60HZ = 19 # equalizer gains -100..100 -XINE_PARAM_EQ_125HZ = 20 # equalizer gains -100..100 -XINE_PARAM_EQ_250HZ = 21 # equalizer gains -100..100 -XINE_PARAM_EQ_500HZ = 22 # equalizer gains -100..100 -XINE_PARAM_EQ_1000HZ = 23 # equalizer gains -100..100 -XINE_PARAM_EQ_2000HZ = 24 # equalizer gains -100..100 -XINE_PARAM_EQ_4000HZ = 25 # equalizer gains -100..100 -XINE_PARAM_EQ_8000HZ = 26 # equalizer gains -100..100 -XINE_PARAM_EQ_16000HZ = 27 # equalizer gains -100..100 -XINE_PARAM_AUDIO_CLOSE_DEVICE = 28 # force closing audio device -XINE_PARAM_AUDIO_AMP_MUTE = 29 # 1=>mute, 0=>unmute -XINE_PARAM_FINE_SPEED = 30 # 1.000.000 => normal speed -XINE_PARAM_EARLY_FINISHED_EVENT = 31 # send event when demux finish -XINE_PARAM_GAPLESS_SWITCH = 32 # next stream only gapless swi -XINE_PARAM_DELAY_FINISHED_EVENT = 33 # 1/10sec,0=>disable,-1=>forev - -# video parameters -XINE_PARAM_VO_ZOOM_X = 0x01000008 # percent -XINE_PARAM_VO_ZOOM_Y = 0x0100000d # percent - -XINE_VO_ZOOM_MAX = 400 -XINE_VO_ZOOM_MIN = -85 - -# speeds -XINE_SPEED_PAUSE = 0 -XINE_SPEED_SLOW_4 = 1 -XINE_SPEED_SLOW_2 = 2 -XINE_SPEED_NORMAL = 4 -XINE_SPEED_FAST_2 = 8 -XINE_SPEED_FAST_4 = 16 - -# metadata -XINE_META_INFO_TITLE = 0 -XINE_META_INFO_COMMENT = 1 -XINE_META_INFO_ARTIST = 2 -XINE_META_INFO_GENRE = 3 -XINE_META_INFO_ALBUM = 4 -XINE_META_INFO_YEAR = 5 -XINE_META_INFO_VIDEOCODEC = 6 -XINE_META_INFO_AUDIOCODEC = 7 -XINE_META_INFO_SYSTEMLAYER = 8 -XINE_META_INFO_INPUT_PLUGIN = 9 - -# stream info -XINE_STREAM_INFO_BITRATE = 0 -XINE_STREAM_INFO_SEEKABLE = 1 -XINE_STREAM_INFO_VIDEO_WIDTH = 2 -XINE_STREAM_INFO_VIDEO_HEIGHT = 3 -XINE_STREAM_INFO_VIDEO_RATIO = 4 - -# statuses -XINE_STATUS_IDLE = 0 -XINE_STATUS_STOP = 1 -XINE_STATUS_PLAY = 2 -XINE_STATUS_QUIT = 3 - -XINE_MSG_NO_ERROR = 0 # (messages to UI) -XINE_MSG_GENERAL_WARNING = 1 # (warning message) -XINE_MSG_UNKNOWN_HOST = 2 # (host name) -XINE_MSG_UNKNOWN_DEVICE = 3 # (device name) -XINE_MSG_NETWORK_UNREACHABLE = 4 # none -XINE_MSG_CONNECTION_REFUSED = 5 # (host name) -XINE_MSG_FILE_NOT_FOUND = 6 # (file name or mrl) -XINE_MSG_READ_ERROR = 7 # (device/file/mrl) -XINE_MSG_LIBRARY_LOAD_ERROR = 8 # (library/decoder) -XINE_MSG_ENCRYPTED_SOURCE = 9 # none -XINE_MSG_SECURITY = 10 # (security message) -XINE_MSG_AUDIO_OUT_UNAVAILABLE = 11 # none -XINE_MSG_PERMISSION_ERROR = 12 # (file name or mrl) -XINE_MSG_FILE_EMPTY = 13 # file is empty - -# valid visual types -XINE_VISUAL_TYPE_NONE = 0 -XINE_VISUAL_TYPE_X11 = 1 -XINE_VISUAL_TYPE_X11_2 = 10 -XINE_VISUAL_TYPE_AA = 2 -XINE_VISUAL_TYPE_FB = 3 -XINE_VISUAL_TYPE_GTK = 4 -XINE_VISUAL_TYPE_DFB = 5 -XINE_VISUAL_TYPE_PM = 6 # used by the OS/2 port -XINE_VISUAL_TYPE_DIRECTX = 7 # used by the win32/msvc port -XINE_VISUAL_TYPE_CACA = 8 -XINE_VISUAL_TYPE_MACOSX = 9 -XINE_VISUAL_TYPE_XCB = 11 - -# "type" constants for xine_port_send_gui_data -XINE_GUI_SEND_DRAWABLE_CHANGED = 2 -XINE_GUI_SEND_EXPOSE_EVENT = 3 -XINE_GUI_SEND_VIDEOWIN_VISIBLE = 5 - -# osd constants -XINE_TEXT_PALETTE_SIZE = 11 - -XINE_OSD_TEXT1 = (0 * XINE_TEXT_PALETTE_SIZE) -XINE_OSD_TEXT2 = (1 * XINE_TEXT_PALETTE_SIZE) -XINE_OSD_TEXT3 = (2 * XINE_TEXT_PALETTE_SIZE) -XINE_OSD_TEXT4 = (3 * XINE_TEXT_PALETTE_SIZE) -XINE_OSD_TEXT5 = (4 * XINE_TEXT_PALETTE_SIZE) -XINE_OSD_TEXT6 = (5 * XINE_TEXT_PALETTE_SIZE) -XINE_OSD_TEXT7 = (6 * XINE_TEXT_PALETTE_SIZE) -XINE_OSD_TEXT8 = (7 * XINE_TEXT_PALETTE_SIZE) -XINE_OSD_TEXT9 = (8 * XINE_TEXT_PALETTE_SIZE) -XINE_OSD_TEXT10 = (9 * XINE_TEXT_PALETTE_SIZE) - -# white text, black border, transparent background */ -XINE_TEXTPALETTE_WHITE_BLACK_TRANSPARENT = 0 -# white text, noborder, transparent background */ -XINE_TEXTPALETTE_WHITE_NONE_TRANSPARENT = 1 -# white text, no border, translucid background */ -XINE_TEXTPALETTE_WHITE_NONE_TRANSLUCID = 2 -# yellow text, black border, transparent background */ -XINE_TEXTPALETTE_YELLOW_BLACK_TRANSPARENT = 3 - -XINE_OSD_CAP_FREETYPE2 = 0x0001 -XINE_OSD_CAP_UNSCALED = 0x0002 - -# xine_t *xine_new(void) -_libxine.xine_new.restype = ctypes.c_void_p - -# void xine_init(xine_t *self) -_libxine.xine_init.argtypes = [ctypes.c_void_p] - -# void xine_exit(xine_t *self) -_libxine.xine_exit.argtypes = [ctypes.c_void_p] - -# void xine_config_load(xine_t *self, const char *cfg_filename) -_libxine.xine_config_load.argtypes = [ctypes.c_void_p, ctypes.c_char_p] - -# const char *xine_get_homedir(void) -_libxine.xine_get_homedir.restype = ctypes.c_char_p - -# xine_audio_port_t *xine_open_audio_driver(xine_t *self, const char *id, -# void *data) -_libxine.xine_open_audio_driver.argtypes = [ctypes.c_void_p, - ctypes.c_char_p, ctypes.c_void_p] -_libxine.xine_open_audio_driver.restype = ctypes.c_void_p - -# xine_video_port_t *xine_open_video_driver(xine_t *self, const char *id, -# int visual, void *data) -_libxine.xine_open_video_driver.restype = ctypes.c_void_p -_libxine.xine_open_video_driver.argtypes = [ctypes.c_void_p, - ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p] - -# int xine_port_send_gui_data (xine_video_port_t *vo, int type, void *data) -_libxine.xine_port_send_gui_data.restype = ctypes.c_int -_libxine.xine_port_send_gui_data.argtypes = [ctypes.c_void_p, ctypes.c_int,\ - ctypes.c_void_p] - -# void xine_close_audio_driver(xine_t *self, xine_audio_port_t *driver) -_libxine.xine_close_audio_driver.argtypes = [ctypes.c_void_p, - ctypes.c_void_p] - -# void xine_close_video_driver(xine_t *self, xine_video_port_t *driver) -_libxine.xine_close_video_driver.argtypes = [ctypes.c_void_p, - ctypes.c_void_p] - -# xine_stream_t *xine_stream_new(xine_t *self, -# xine_audio_port_t *ao, xine_video_port_t *vo) -_libxine.xine_stream_new.argtypes = [ctypes.c_void_p, ctypes.c_void_p, - ctypes.c_void_p] -_libxine.xine_stream_new.restype = ctypes.c_void_p - -# void xine_close(xine_sxine_event_create_listener_threadtream_t *stream) -_libxine.xine_close.argtypes = [ctypes.c_void_p] - -# int xine_open (xine_stream_t *stream, const char *mrl) -_libxine.xine_open.argtypes = [ctypes.c_void_p, ctypes.c_char_p] -_libxine.xine_open.restype = ctypes.c_int - -# int xine_play(xine_stream_t *stream, int start_pos, int start_time) -_libxine.xine_play.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int] -_libxine.xine_play.restype = ctypes.c_int - -# void xine_stop(xine_stream_t *stream) -_libxine.xine_stop.argtypes = [ctypes.c_void_p] - -# void xine_dispose(xine_stream_t *stream) -_libxine.xine_dispose.argtypes = [ctypes.c_void_p] - -# xine_event_queue_t *xine_event_new_queue(xine_stream_t *stream) -_libxine.xine_event_new_queue.argtypes = [ctypes.c_void_p] -_libxine.xine_event_new_queue.restype = ctypes.c_void_p - -# void xine_event_dispose_queue(xine_event_queue_t *queue) -_libxine.xine_event_dispose_queue.argtypes = [ctypes.c_void_p] - -# void xine_event_create_listener_thread(xine_event_queue_t *queue, -# xine_event_listener_cb_t callback, -# void *user_data) -_libxine.xine_event_create_listener_thread.argtypes = [ctypes.c_void_p, - ctypes.c_void_p, ctypes.c_void_p] - -# int xine_get_audio_lang(xine_stream_t *stream, int channel, char *lang) -_libxine.xine_get_audio_lang.restype = ctypes.c_int -_libxine.xine_get_audio_lang.argtypes = [ctypes.c_void_p, ctypes.c_int,\ - ctypes.c_char_p] - -# int xine_get_spu_lang(xine_stream_t *stream, int channel, char *lang) -_libxine.xine_get_spu_lang.restype = ctypes.c_int -_libxine.xine_get_spu_lang.argtypes = [ctypes.c_void_p, ctypes.c_int,\ - ctypes.c_char_p] - -_libxine.xine_usec_sleep.argtypes = [ctypes.c_int] - -# void xine_set_param (xine_stream_t *stream, int param, int value) -_libxine.xine_set_param.argtypes = [ctypes.c_void_p, ctypes.c_int, - ctypes.c_int] - -# int xine_get_param (xine_stream_t *stream, int param) -_libxine.xine_get_param.argtypes = [ctypes.c_void_p, ctypes.c_int] -_libxine.xine_get_param.restype = ctypes.c_int - -# char *xine_get_meta_info(xine_stream_t *stream, int info) -_libxine.xine_get_meta_info.argtypes = [ctypes.c_void_p, ctypes.c_int] -_libxine.xine_get_meta_info.restype = ctypes.c_char_p - -# int xine_get_stream_info(xine_stream_t *stream, int info) -_libxine.xine_get_stream_info.argtypes = [ctypes.c_void_p, ctypes.c_int] -_libxine.xine_get_stream_info.restype = ctypes.c_int - -# int xine_get_status (xine_stream_t *stream) -_libxine.xine_get_status.argtypes = [ctypes.c_void_p] -_libxine.xine_get_status.restype = ctypes.c_int - -# int xine_get_pos_length (xine_stream_t *stream, int *pos_stream, -# int *pos_time, int *length_time) -_libxine.xine_get_pos_length.argtypes = [ctypes.c_void_p, - ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), - ctypes.POINTER(ctypes.c_int)] - -# char *xine_get_version_string(void) -_libxine.xine_get_version_string.restype = ctypes.c_char_p - -# char *xine_get_file_extensions (xine_t *self) -_libxine.xine_get_file_extensions.argtypes = [ctypes.c_void_p] -_libxine.xine_get_file_extensions.restype = ctypes.c_char_p - -# char *xine_get_mime_types (xine_t *self) -_libxine.xine_get_mime_types.argtypes = [ctypes.c_void_p] -_libxine.xine_get_mime_types.restype = ctypes.c_char_p - -# char *const *xine_list_input_plugins(xine_t *self) -_libxine.xine_list_input_plugins.argtypes = [ctypes.c_void_p] -_libxine.xine_list_input_plugins.restype = ctypes.POINTER(ctypes.c_char_p) - -# int xine_check_version (int major, int minor, int sub) -_libxine.xine_check_version.argtypes = [ctypes.c_int, ctypes.c_int,\ - ctypes.c_int] -_libxine.xine_check_version.restype = ctypes.c_int; - -# xine_osd_t *xine_osd_new(xine_stream_t *self, int x, int y, -# int width, int height) -_libxine.xine_osd_new.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int,\ - ctypes.c_int, ctypes.c_int] -_libxine.xine_osd_new.restype = ctypes.c_void_p - -# void xine_osd_free(xine_osd_t *self) -_libxine.xine_osd_free.argtypes = [ctypes.c_void_p] - -# uint32_t xine_osd_get_capabilities(xine_osd_t *self) -_libxine.xine_osd_get_capabilities.restype = ctypes.c_int -_libxine.xine_osd_get_capabilities.argtypes = [ctypes.c_void_p] - -# void xine_osd_set_text_palette(xine_osd_t *self,int palette_number, -# int color_base ) -_libxine.xine_osd_set_text_palette.argtypes = [ctypes.c_void_p, ctypes.c_int,\ - ctypes.c_int] - -# int xine_osd_set_font(xine_osd_t *self, const char *fontname, int size) -_libxine.xine_osd_set_font.restype = ctypes.c_int -_libxine.xine_osd_set_font.argtypes = [ctypes.c_void_p, ctypes.c_char_p,\ - ctypes.c_int] - -# void xine_osd_set_position(xine_osd_t *self, int x, int y) -_libxine.xine_osd_set_position.argtypes = [ctypes.c_void_p, ctypes.c_int,\ - ctypes.c_int] - -# void xine_osd_draw_text(xine_osd_t *self, int x1, int y1, char *text, -# int color_base) -_libxine.xine_osd_draw_text.argtypes = [ctypes.c_void_p, ctypes.c_int,\ - ctypes.c_int, ctypes.c_char_p,\ - ctypes.c_int] - -# void xine_osd_show(xine_osd_t *self, int64_t vpts) -_libxine.xine_osd_show.argtypes = [ctypes.c_void_p, ctypes.c_int] - -# void xine_osd_show_unscaled (xine_osd_t *self, int64_t vpts) -_libxine.xine_osd_show_unscaled.argtypes = [ctypes.c_void_p, ctypes.c_int] - -# void xine_osd_hide(xine_osd_t *self, int64_t vpts) -_libxine.xine_osd_hide.argtypes = [ctypes.c_void_p, ctypes.c_int] - -# void xine_osd_clear(xine_osd_t *self) -_libxine.xine_osd_clear.argtypes = [ctypes.c_void_p] - -# copy functions from the library -module = sys.modules[__name__] -for name in dir(_libxine): - if name.startswith('xine_'): - setattr(module, name, getattr(_libxine, name)) - -_evt_callbacks = [] -def xine_event_create_listener_thread(queue, callback, user_data): - cb = xine_event_listener_cb_t(callback) - _evt_callbacks.append(cb) - _libxine.xine_event_create_listener_thread(queue, cb, user_data) - -def xine_event_dispose_queue(queue): - _libxine.xine_event_dispose_queue(queue) - _evt_callbacks = [] - -def xine_get_pos_length(stream): - _pos_stream = ctypes.c_int() - _pos_time = ctypes.c_int() - _length_time = ctypes.c_int() - result = _libxine.xine_get_pos_length(stream, ctypes.byref(_pos_stream), - ctypes.byref(_pos_time), ctypes.byref(_length_time)) - if result: - return _pos_stream.value, _pos_time.value, _length_time.value - else: - return 0, 0, 0 - -XINE_LANG_MAX = 256 - -def xine_get_audio_lang(stream, channel): - _lang = ctypes.create_string_buffer(XINE_LANG_MAX) - - result = _libxine.xine_get_audio_lang(stream, channel, _lang) - if not result: - _lang.raw = "unknown" - - return _lang.value - -def xine_get_spu_lang(stream, channel): - _lang = ctypes.create_string_buffer(XINE_LANG_MAX) - - result = _libxine.xine_get_spu_lang(stream, channel, _lang) - if not result: - _lang.raw = "unknown" - - return _lang.value - -# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/player/xine.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/player/xine.py --- deejayd-0.7.2/deejayd/player/xine.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/player/xine.py 2009-09-21 08:44:21.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -16,13 +16,14 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import os, subprocess from os import path -from ctypes import * +import kaa.metadata from twisted.internet import reactor +from pytyxi import xine + from deejayd.player import PlayerError from deejayd.player._base import * -from deejayd.player._xine import * -from deejayd.player.display import x11 from deejayd.ui import log @@ -38,77 +39,102 @@ "display" : self.config.get("xine", "video_display"), "osd_support" : self.config.getboolean("xine", "osd_support"), "osd_font_size" : self.config.getint("xine", "osd_font_size"), + "software_mixer": self.config.getboolean("xine", "software_mixer"), + } + self.__video_aspects = { + "auto": xine.Stream.XINE_VO_ASPECT_AUTO, + "1:1": xine.Stream.XINE_VO_ASPECT_SQUARE, + "4:3": xine.Stream.XINE_VO_ASPECT_4_3, + "16:9": xine.Stream.XINE_VO_ASPECT_ANAMORPHIC, + "2.11:1": xine.Stream.XINE_VO_ASPECT_DVB, } + self.__default_aspect_ratio = "auto" # init main instance - self.__xine = xine_new() - if not self.__xine: + try: + self.__xine = xine.XinePlayer() + except xine.XineError: raise PlayerError(_("Unable to init a xine instance")) - xine_config_load(self.__xine, xine_get_homedir() + "/.xine/config") - xine_init(self.__xine) # init vars - self.__supports_gapless = xine_check_version(1, 1, 1) == 1 - self.__volume = 100 - self.__audio_port = None - self.__video_port = None + self.__supports_gapless = self.__xine.has_gapless() + + self.__audio_volume = 100 + self.__video_volume = 100 + + self.__window = None self.__stream = None - self.__event_queue = None - self.__mine_stream = None self.__osd = None - def init_video_support(self): - UnknownPlayer.init_video_support(self) - # init display - try: self.__display = x11.X11Display(self.__xine_options,\ - self._fullscreen) - except x11.X11Error, err: - log.err(str(err)) - raise PlayerError(str(err)) - # init instance to get video and dvd informations - self.__mine_stream = xine_stream_new(self.__xine, None, None) - def start_play(self): if not self._media_file: return # format correctly the uri - uri = self._media_file["uri"] + uri = self._media_file["uri"].encode("utf-8") # For dvd chapter if "chapter" in self._media_file.keys() and \ self._media_file["chapter"] != -1: uri += ".%d" % self._media_file["chapter"] - # load external subtitle + # load subtitle if "external_subtitle" in self._media_file and \ self._media_file["external_subtitle"].startswith("file://"): - uri += "#subtitle:%s" % self._media_file["external_subtitle"] + # external subtitle + uri += "#subtitle:%s" \ + % self._media_file["external_subtitle"].encode("utf-8") self._media_file["subtitle"] = [{"lang": "none", "ix": -2},\ {"lang": "auto", "ix": -1},\ {"lang": "external", "ix":0}] + elif "subtitle_channels" in self._media_file.keys() and\ + int(self._media_file["subtitle_channels"]) > 0: + self._media_file["subtitle"] = [{"lang": "none", "ix": -2},\ + {"lang": "auto", "ix": -1}] + for i in range(int(self._media_file["subtitle_channels"])): + self._media_file["subtitle"].append(\ + {"lang": _("Sub channel %d") % (i+1,), "ix": i}) + # audio channels + if "audio_channels" in self._media_file.keys() and \ + int(self._media_file["audio_channels"]) > 1: + audio_channels = [{"lang":"none","ix":-2},{"lang":"auto","ix":-1}] + for i in range(int(self._media_file["audio_channels"])): + audio_channels.append(\ + {"lang": _("Audio channel %d") % (i+1,), "ix": i}) + self._media_file["audio"] = audio_channels - if not self.__stream: - has_video = self._media_file["type"] == "video" - self._create_stream(has_video) - if not xine_open(self.__stream, uri) or \ - not xine_play(self.__stream, 0, 0): + needs_video = self.current_is_video() + if self.__stream: + stream_should_change = (needs_video and\ + not self.__stream.has_video())\ + or (not needs_video and + self.__stream.has_video()) + else: + stream_should_change = True + if stream_should_change: + self._create_stream(needs_video) + + try: + self.__stream.open(uri) + self.__stream.play(0, 0) + except xine.XineError: self._destroy_stream() msg = _("Unable to play file %s") % uri log.err(msg) raise PlayerError(msg) - isvideo = self._media_file["type"] == "video" - if self.__video_port: - self.__display.show(isvideo) + if self.__window: + self.__window.show(self.current_is_video()) # init video information - if self._media_file["type"] == "video": + if needs_video: self._media_file["av_offset"] = 0 self._media_file["zoom"] = 100 if "audio" in self._media_file: - self._media_file["audio_idx"] = \ - self.__do_get_property(XINE_PARAM_AUDIO_CHANNEL_LOGICAL) + self._media_file["audio_idx"] = self.__stream.get_param(\ + xine.Stream.XINE_PARAM_AUDIO_CHANNEL_LOGICAL) if "subtitle" in self._media_file: self._media_file["sub_offset"] = 0 - self._media_file["subtitle_idx"] = \ - self.__do_get_property(XINE_PARAM_SPU_CHANNEL) + self._media_file["subtitle_idx"] = self.__stream.get_param(\ + xine.Stream.XINE_PARAM_SPU_CHANNEL) + # set video aspect ration to default value + self.set_aspectratio(self.__default_aspect_ratio) def _change_file(self, new_file, gapless = False): sig = self.get_state() == PLAYER_STOP and True or False @@ -120,22 +146,24 @@ self._media_file = new_file if gapless and self.__supports_gapless: - xine_set_param(self.__stream, XINE_PARAM_GAPLESS_SWITCH, 1) + self.__stream.set_param(xine.Stream.XINE_PARAM_GAPLESS_SWITCH, 1) self.start_play() if gapless and self.__supports_gapless: - xine_set_param(self.__stream, XINE_PARAM_GAPLESS_SWITCH, 0) + self.__stream.set_param(xine.Stream.XINE_PARAM_GAPLESS_SWITCH, 0) # replaygain reset - self.set_volume(self.__volume, sig=False) + self.set_volume(self.get_volume(), sig=False) if sig: self.dispatch_signame('player.status') self.dispatch_signame('player.current') def pause(self): if self.get_state() == PLAYER_PAUSE: - self.__do_set_property(XINE_PARAM_SPEED, XINE_SPEED_NORMAL) + self.__stream.set_param(xine.Stream.XINE_PARAM_SPEED, + xine.Stream.XINE_SPEED_NORMAL) elif self.get_state() == PLAYER_PLAY: - self.__do_set_property(XINE_PARAM_SPEED, XINE_SPEED_PAUSE) + self.__stream.set_param(xine.Stream.XINE_PARAM_SPEED, + xine.Stream.XINE_SPEED_PAUSE) else: return self.dispatch_signame('player.status') @@ -146,165 +174,121 @@ self.dispatch_signame('player.status') def set_zoom(self, zoom): - if zoom > XINE_VO_ZOOM_MAX or zoom < XINE_VO_ZOOM_MIN: + if zoom > xine.Stream.XINE_VO_ZOOM_MAX\ + or zoom < xine.Stream.XINE_VO_ZOOM_MIN: raise PlayerError(_("Zoom value not accepted")) - self.__do_set_property(XINE_PARAM_VO_ZOOM_X, zoom) - self.__do_set_property(XINE_PARAM_VO_ZOOM_Y, zoom) + self.__stream.set_param(xine.Stream.XINE_PARAM_VO_ZOOM_X, zoom) + self.__stream.set_param(xine.Stream.XINE_PARAM_VO_ZOOM_Y, zoom) self._media_file["zoom"] = zoom self._osd_set(_("Zoom: %d percent") % zoom) + def set_aspectratio(self, aspect_ratio): + try: asp = self.__video_aspects[aspect_ratio] + except KeyError: + raise PlayerError(_("Video aspect ration %s is not known.")\ + % aspect_ratio) + self.__default_aspect_ratio = aspect_ratio + self._media_file["aspect_ratio"] = self.__default_aspect_ratio + if self.__stream.has_video(): + self.__stream.set_param(xine.Stream.XINE_PARAM_VO_ASPECT_RATIO, asp) + def set_avoffset(self, offset): - self.__do_set_property(XINE_PARAM_AV_OFFSET, offset * 90) + self.__stream.set_param(xine.Stream.XINE_PARAM_AV_OFFSET, offset * 90) self._media_file["av_offset"] = offset self._osd_set(_("Audio/Video offset: %d ms") % offset) def set_suboffset(self, offset): if "subtitle" in self._media_file.keys(): - self.__do_set_property(XINE_PARAM_SPU_OFFSET, offset * 90) + self.__stream.set_param(xine.Stream.XINE_PARAM_SPU_OFFSET, + offset * 90) self._media_file["sub_offset"] = offset self._osd_set(_("Subtitle offset: %d ms") % offset) def _player_set_alang(self,lang_idx): - self.__do_set_property(XINE_PARAM_AUDIO_CHANNEL_LOGICAL, lang_idx) + self.__stream.set_param(xine.Stream.XINE_PARAM_AUDIO_CHANNEL_LOGICAL, + lang_idx) def _player_set_slang(self,lang_idx): - self.__do_set_property(XINE_PARAM_SPU_CHANNEL, lang_idx) + self.__stream.set_param(xine.Stream.XINE_PARAM_SPU_CHANNEL, lang_idx) def _player_get_alang(self): - return self.__do_get_property(XINE_PARAM_AUDIO_CHANNEL_LOGICAL) + return self.__stream.get_param(xine.Stream.\ + XINE_PARAM_AUDIO_CHANNEL_LOGICAL) def _player_get_slang(self): - return self.__do_get_property(XINE_PARAM_SPU_CHANNEL) + return self.__stream.get_param(xine.Stream.XINE_PARAM_SPU_CHANNEL) def get_volume(self): - return self.__volume + if self.current_is_video(): + return self.__video_volume + else: + return self.__audio_volume def set_volume(self, vol, sig = True): - self.__volume = min(100, int(vol)) + new_volume = min(100, int(vol)) + if self.current_is_video(): + self.__video_volume = new_volume + else: + self.__audio_volume = new_volume + # replaygain support - vol = self.__volume + vol = self.get_volume() if self._replaygain and self._media_file is not None: try: scale = self._media_file.replay_gain() except AttributeError: pass # replaygain not supported else: vol = max(0.0, min(4.0, float(vol)/100.0 * scale)) vol = min(100, int(vol * 100)) - self.__do_set_property(XINE_PARAM_AUDIO_VOLUME, vol) + if self.__stream: + self.__stream.set_volume(vol) if sig: - self._osd_set("Volume: %s" % int(vol)) + self._osd_set("Volume: %d" % self.get_volume()) self.dispatch_signame('player.status') def get_position(self): if not self.__stream: return 0 - # Workaround for problems when you seek too quickly - i = 0 - while i < 4: - pos_s, pos_t, length = xine_get_pos_length(self.__stream) - if int(pos_t) > 0: break - xine_usec_sleep(100000) - i += 1 - - return int(pos_t / 1000) + return self.__stream.get_pos() - def set_position(self,pos): + def _set_position(self,pos): pos = int(pos * 1000) state = self.get_state() if state == PLAYER_PAUSE: - xine_play(self.__stream, 0, pos) - xine_set_param(self.__stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE) + self.__stream.play(0, pos) + self.__stream.set_param(xine.Stream.XINE_PARAM_SPEED, + xine.Stream.XINE_SPEED_PAUSE) elif state == PLAYER_PLAY: - xine_play(self.__stream, 0, pos) + self.__stream.play(0, pos) self.dispatch_signame('player.status') def get_state(self): if not self.__stream: return PLAYER_STOP - status = xine_get_status(self.__stream) - if status == XINE_STATUS_PLAY: - if self.__do_get_property(XINE_PARAM_SPEED) == XINE_SPEED_NORMAL: + status = self.__stream.get_status() + if status == xine.Stream.XINE_STATUS_PLAY: + if self.__stream.get_param(xine.Stream.XINE_PARAM_SPEED)\ + == xine.Stream.XINE_SPEED_NORMAL: return PLAYER_PLAY return PLAYER_PAUSE return PLAYER_STOP def is_supported_uri(self,uri_type): if self.plugins == None: - self.plugins = [] - for plugin in xine_list_input_plugins(self.__xine): - if not plugin: - break - self.plugins.append(plugin.lower()) - - if uri_type == "dvd": - # test lsdvd installation - if not self._is_lsdvd_exists(): return False + self.plugins = self.__xine.list_input_plugins() + return uri_type in self.plugins def is_supported_format(self,format): if self.supported_extensions == None: - extensions = xine_get_file_extensions(self.__xine) - self.supported_extensions = extensions.split() + self.supported_extensions = self.__xine.get_supported_extensions() return format.strip(".") in self.supported_extensions - def get_video_file_info(self,file): - if not xine_open(self.__mine_stream, file): - raise PlayerError - - rs = {} - rs["videowidth"] = xine_get_stream_info(self.__mine_stream,\ - XINE_STREAM_INFO_VIDEO_WIDTH) - rs["videoheight"] = xine_get_stream_info(self.__mine_stream,\ - XINE_STREAM_INFO_VIDEO_HEIGHT) - pos_s, pos_t, length = xine_get_pos_length(self.__mine_stream) - rs["length"] = length / 1000 - # close stream - xine_stop(self.__mine_stream) - xine_close(self.__mine_stream) - - return rs - - def get_dvd_info(self): - dvd_info = self._get_dvd_info() - ix = 0 - for track in dvd_info['track']: - if not xine_open(self.__mine_stream, "dvd://%d"%track['ix']): - raise PlayerError - # get audio channels info - channels_number = len(track['audio']) - audio_channels = [{"lang":"none","ix":-2},{"lang":"auto","ix":-1}] - for ch in range(0,channels_number): - lang = xine_get_audio_lang(self.__mine_stream,ch) - audio_channels.append({'ix':ch, "lang":lang.encode("utf-8")}) - dvd_info['track'][ix]["audio"] = audio_channels - - # get subtitles channels info - channels_number = len(track['subp']) - sub_channels = [{"lang":"none","ix":-2},{"lang":"auto","ix":-1}] - for ch in range(0,channels_number): - lang = xine_get_spu_lang(self.__mine_stream,ch) - sub_channels.append({'ix':ch, "lang":lang.encode("utf-8")}) - dvd_info['track'][ix]["subp"] = sub_channels - - ix += 1 - - return dvd_info + def current_is_video(self): + return self._media_file is not None\ + and self._media_file['type'] == 'video' def close(self): UnknownPlayer.close(self) - if self.__mine_stream: - xine_close(self.__mine_stream) - xine_dispose(self.__mine_stream) - xine_exit(self.__xine) - - # - # Specific xine functions - # - def __do_set_property(self, property, v): - if not self.__stream: return - xine_set_param(self.__stream, property, v) - - def __do_get_property(self, property): - if not self.__stream: return -1 - return xine_get_param(self.__stream, property) + self.__xine.destroy() def _create_stream(self, has_video = True): if self.__stream != None: @@ -312,121 +296,69 @@ # open audio driver driver_name = self.config.get("xine", "audio_output") - self.__audio_port = xine_open_audio_driver(self.__xine,driver_name,None) - if not self.__audio_port: + try: + audio_port = xine.AudioDriver(self.__xine, driver_name) + except xine.xineError: raise PlayerError(_("Unable to open audio driver")) # open video driver if has_video and self._video_support\ and self.__xine_options["video"] != "none": - try: self.__display.create() - except x11.X11Error, err: - raise PlayerError(str(err)) - - # Those callbacks are required to be kept in this list in order to - # be safe from the garbage collector. - self.__x11_callbacks = [ - xine_dest_size_cb(self._dest_size_cb), - xine_frame_output_cb(self._frame_output_cb) - ] - - x11_infos = self.__display.get_infos() - vis = x11_visual_t() - vis.display = x11_infos["dsp"] - vis.screen = x11_infos["screen"] - vis.d = x11_infos["window"] - vis.user_data = None - vis.dest_size_cb = cast(self.__x11_callbacks[0], c_void_p) - vis.frame_output_cb = cast(self.__x11_callbacks[1],c_void_p) - vis.lock_display = None - vis.unlock_display = None - - self.__video_port = xine_open_video_driver(self.__xine,\ - self.__xine_options["video"], XINE_VISUAL_TYPE_X11,\ - cast(byref(vis), c_void_p)) - if not self.__video_port: + try: + video_port = xine.VideoDriver(self.__xine, + self.__xine_options["video"], + self.__xine_options["display"], + self._fullscreen) + except xine.XineError: msg = _("Unable to open video driver") log.err(msg) raise PlayerError(msg) + else: + self.__window = video_port.window + else: + video_port = None # create stream - self.__stream = xine_stream_new(self.__xine, self.__audio_port,\ - self.__video_port) - if not self.__video_port: - xine_set_param(self.__stream, XINE_PARAM_IGNORE_VIDEO, 1) - xine_set_param(self.__stream, XINE_PARAM_IGNORE_SPU, 1) - elif self.__xine_options["osd_support"]: # osd qupport - video_area = self.__display.get_and_lock_video_area() - self.__osd = xine_osd_new(self.__stream, 0, 0,\ - video_area["width"], video_area["height"]) - xine_osd_set_font(self.__osd, "sans",\ - self.__xine_options["osd_font_size"]) - xine_osd_set_text_palette(self.__osd,\ - XINE_TEXTPALETTE_WHITE_BLACK_TRANSPARENT, XINE_OSD_TEXT1) - self.__display.release_video_area() - self.__osd_unscaled = xine_osd_get_capabilities(self.__osd) \ - & XINE_OSD_CAP_UNSCALED - - # gapless support - if self.__supports_gapless: - xine_set_param(self.__stream, XINE_PARAM_EARLY_FINISHED_EVENT, 1) + self.__stream = self.__xine.stream_new(audio_port, video_port) + self.__stream.set_software_mixer(self.__xine_options["software_mixer"]) + + if video_port and self.__xine_options["osd_support"]: + self.__osd = self.__stream.osd_new(\ + self.__xine_options["osd_font_size"]) # add event listener - if self.__event_queue: - xine_event_dispose_queue(self.__event_queue) - self.__event_queue = xine_event_new_queue(self.__stream) - xine_event_create_listener_thread(self.__event_queue, - self._event_callback, None) + self.__stream.add_event_callback(self._event_callback) # restore volume - self.__do_set_property(XINE_PARAM_AUDIO_VOLUME, self.__volume) + self.__stream.set_volume(self.get_volume()) def _destroy_stream(self): if self.__stream: - xine_stop(self.__stream) - xine_close(self.__stream) - xine_set_param(self.__stream, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1) - if self.__event_queue: - xine_event_dispose_queue(self.__event_queue) - self.__event_queue = None - # close osd - if self.__osd: - xine_osd_clear(self.__osd) - xine_osd_free(self.__osd) - self.__osd = None - xine_dispose(self.__stream) + self.__stream.destroy() self.__stream = None - - xine_close_audio_driver(self.__xine, self.__audio_port) - self.__audio_port = None - - # close video driver - if self.__video_port: - xine_close_video_driver(self.__xine, self.__video_port) - self.__video_port = None - self.__display.destroy() - self.__x11_callbacks = None + self.__window = None + self.__osd = None def _osd_set(self, text): if not self.__osd: return - xine_osd_clear(self.__osd) - xine_osd_draw_text(self.__osd, 0, 0, text, XINE_OSD_TEXT1) - xine_osd_set_position(self.__osd, 60, 20) - if self.__osd_unscaled: xine_osd_show_unscaled(self.__osd, 0) - else: xine_osd_show(self.__osd, 0) - # hide osd 2 seconds later - self.__osd_text = text + self.__osd.clear() + self.__osd.draw_text(60, 20, text.encode("utf-8")) + self.__osd.show() + reactor.callLater(2, self._osd_hide, text) def _osd_hide(self, text): - if self.__osd and self.__osd_text == text: - xine_osd_hide(self.__osd, 0) + if self.__osd: + self.__osd.hide(text) # # callbacks # def _eof(self): - new_file = self._source.next() + if self._media_file: + try: self._media_file.played() + except AttributeError: pass + new_file = self._source.next(explicit = False) try: self._change_file(new_file, gapless = True) except PlayerError: pass @@ -438,12 +370,12 @@ # update webradio song info meta = [ - (XINE_META_INFO_TITLE, 'song-title'), - (XINE_META_INFO_ARTIST, 'song-artist'), - (XINE_META_INFO_ALBUM, 'song-album'), + (xine.Stream.XINE_META_INFO_TITLE, 'song-title'), + (xine.Stream.XINE_META_INFO_ARTIST, 'song-artist'), + (xine.Stream.XINE_META_INFO_ALBUM, 'song-album'), ] for info, name in meta: - text = xine_get_meta_info(self.__stream, info) + text = self.__stream.get_meta_info(info) if not text: continue text = text.decode('UTF-8', 'replace') @@ -458,46 +390,86 @@ # see |http://twistedmatrix.com/documents/current/api/ # |twisted.internet.interfaces.IReactorThreads.callFromThread.html def _event_callback(self, user_data, event): - event = event.contents - if event.type == XINE_EVENT_UI_PLAYBACK_FINISHED: + if event.type == xine.Event.XINE_EVENT_UI_PLAYBACK_FINISHED: log.info("Xine event : playback finished") reactor.callFromThread(self._eof) - elif event.type == XINE_EVENT_UI_SET_TITLE: + elif event.type == xine.Event.XINE_EVENT_UI_SET_TITLE: log.info("Xine event : set title") reactor.callFromThread(self._update_metadata) - elif event.type == XINE_EVENT_UI_MESSAGE: + elif event.type == xine.Event.XINE_EVENT_UI_MESSAGE: log.info("Xine event : message") - msg = cast(event.data, POINTER(xine_ui_message_data_t)).contents - if msg.type != XINE_MSG_NO_ERROR: + msg = event.data.contents + if msg.type != xine.XinePlayer.XINE_MSG_NO_ERROR: if msg.explanation: + # FIXME : This is strange to use pointer arithemtics here, + # as ctypes may automatically dereference pointers, I think + # msg.explanation should suffice... message = string_at(addressof(msg) + msg.explanation) else: message = _("Xine error %s") % msg.type reactor.callFromThread(log.err, message) return True - def _dest_size_cb(self, data, video_width, video_height,\ - video_pixel_aspect, dest_width, dest_height,\ - dest_pixel_aspect): - infos = self.__display.get_and_lock_video_area() - dest_width[0] = infos["width"] - dest_height[0] = infos["height"] - dest_pixel_aspect[0] = c_double(infos["pixel_aspect"]) - self.__display.release_video_area() - return True - def _frame_output_cb(self, data, video_width, video_height,\ - video_pixel_aspect, dest_x, dest_y, dest_width,\ - dest_height, dest_pixel_aspect, win_x, win_y): - infos = self.__display.get_and_lock_video_area() - win_x[0] = 0 - win_y[0] = 0 - dest_x[0] = 0 - dest_y[0] = 0 - dest_width[0] = infos["width"] - dest_height[0] = infos["height"] - dest_pixel_aspect[0] = c_double(infos["pixel_aspect"]) - self.__display.release_video_area() - return True +class DvdParser: + DEVICE = "/dev/dvd" + + def __init__(self): + try: + self.__xine = xine.XinePlayer() + except xine.XineError: + raise PlayerError(_("Unable to init a xine instance")) + self.__mine_stream = self.__xine.stream_new(None, None) + + def get_dvd_info(self): + kaa_infos = kaa.metadata.parse(self.DEVICE) + if kaa_infos is None: + raise PlayerError(_("Unable to identify dvd device")) + + dvd_info = {"title": kaa_infos["label"], 'track': []} + longest_track = {"ix": 0, "length": 0} + for idx, t in enumerate(kaa_infos['tracks']): + try: self.__mine_stream.open("dvd://%d" % (idx+1,)) + except xine.XineError, ex: + raise PlayerError, ex + track = {"ix": idx+1, "length": int(t['length']), "chapter": []} + if track['length'] > longest_track["length"]: + longest_track = track + + # get audio channels info + channels_number = len(t['audio']) + audio_channels = [{"lang":"none","ix":-2},{"lang":"auto","ix":-1}] + for ch in range(0,channels_number): + lang = self.__mine_stream.get_audio_lang(ch) + audio_channels.append({'ix':ch, "lang":lang.encode("utf-8")}) + track["audio"] = audio_channels + + # get subtitles channels info + channels_number = len(t['subtitles']) + sub_channels = [{"lang":"none","ix":-2},{"lang":"auto","ix":-1}] + for ch in range(0,channels_number): + lang = self.__mine_stream.get_spu_lang(ch) + sub_channels.append({'ix':ch, "lang":lang.encode("utf-8")}) + track["subp"] = sub_channels + + # chapters + for c_i,chapter in enumerate(kaa_infos['tracks'][idx]['chapters']): + track["chapter"].append({ "ix": c_i+1,\ + 'length': int(chapter["pos"]) }) + if c_i > 0: + track["chapter"][c_i-1]['length'] = int(chapter["pos"]) - \ + track["chapter"][c_i-1]['length'] + if c_i == len(kaa_infos['tracks'][idx]['chapters']) - 1: + track["chapter"][c_i]['length'] = track["length"] - \ + int(track["chapter"][c_i]['length']) + + dvd_info['track'].append(track) + + dvd_info['longest_track'] = longest_track["ix"] + return dvd_info + + def close(self): + self.__mine_stream.destroy() + self.__xine.destroy() # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/rpc/__init__.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/rpc/__init__.py --- deejayd-0.7.2/deejayd/rpc/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/rpc/__init__.py 2009-09-21 08:44:21.000000000 +0100 @@ -0,0 +1,41 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from deejayd.interfaces import DeejaydError + +# Deejayd protocol version number +DEEJAYD_PROTOCOL_VERSION = 3 + +# from specification +# http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal +NOT_WELLFORMED_ERROR = -32700 +INVALID_JSONRPC = -32600 +METHOD_NOT_FOUND = -32601 +INVALID_METHOD_PARAMS = -32602 +INTERNAL_ERROR = -32603 +METHOD_NOT_CALLABLE = -32604 + + +class Fault(DeejaydError): + """Indicates an JSON-RPC fault package.""" + def __init__(self, code, message): + super(Fault, self).__init__() + self.code = code + self.message = message + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/rpc/jsonbuilders.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/rpc/jsonbuilders.py --- deejayd-0.7.2/deejayd/rpc/jsonbuilders.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/rpc/jsonbuilders.py 2009-09-21 08:44:21.000000000 +0100 @@ -0,0 +1,146 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import time +from datetime import datetime +try: import json # python 2.6 +except ImportError: # if python < 2.6, require simplejson + import simplejson as json +from deejayd.rpc import * + + +class JSONRPCEncoder(json.JSONEncoder): + """ + Provide custom serializers for JSON-RPC. + """ + def default(self, obj): + if isinstance(obj, datetime): + return obj.strftime("%Y%m%dT%H:%M:%S") + raise TypeError("%r is not JSON serializable" % (obj,)) + + +class _DeejaydJSON: + + def dump(self): + return self._build_obj() + + def to_json(self): + return json.dumps(self._build_obj(), cls=JSONRPCEncoder) + + def to_pretty_json(self): + s = json.dumps(self._build_obj(), sort_keys=True, indent=4) + return '\n'.join([l.rstrip() for l in s.splitlines()]) + + +class JSONRPCRequest(_DeejaydJSON): + """ + Build JSON-RPC Request + """ + def __init__(self, method_name, params, notification = False, id = None): + self.method = method_name + self.params = params + # use timestamp as id if no id has been given + self.id = None + if not notification: + self.id = id or int(time.time()) + + def _build_obj(self): + return {"method": self.method, "params": self.params, "id": self.id} + + def get_id(self): + return self.id + + +class JSONRPCResponse(_DeejaydJSON): + """ + Build JSON-RPC Response + """ + def __init__(self, result, id): + self.id = id + self.result = result + + def _build_obj(self): + result, error = self.result, None + if isinstance(self.result, Fault): + error = {"code": self.result.code, "message": str(self.result)} + result = None + + return {"result": result, "error": error, "id": self.id} + + def to_json(self): + try: + return json.dumps(self._build_obj(), cls=JSONRPCEncoder) + except TypeError, ex: + error = {"code": NOT_WELLFORMED_ERROR, "message": str(ex)} + obj = {"result": None, "error": error, "id": self.id} + return json.dumps(obj) + +# +# JSON filter serializer +# +class JSONFilter(_DeejaydJSON): + + def __init__(self, filter): + self.filter = filter + + def _get_value(self): + raise NotImplementedError + + def _build_obj(self): + return { + "type": self.type, + "id": self.filter.get_identifier(), + "value": self._get_value(), + } + +class JSONBasicFilter(JSONFilter): + type = "basic" + def _get_value(self): + return {"tag": self.filter.tag, "pattern": self.filter.pattern} + +class JSONComplexFilter(JSONFilter): + type = "complex" + def _get_value(self): + return [Get_json_filter(f).dump() for f in self.filter.filterlist] + +def Get_json_filter(filter): + if filter is None: + return None + if filter.type == 'basic': + json_filter_class = JSONBasicFilter + elif filter.type == 'complex': + json_filter_class = JSONComplexFilter + return json_filter_class(filter) + +# +# JSON signal serializer +# +class DeejaydJSONSignal(_DeejaydJSON): + + def __init__(self, signal): + self.name = signal is not None and signal.get_name() or "" + self.attrs = signal is not None and signal.get_attrs() or {} + + def set_name(self, name): + self.name = name + + def _build_obj(self): + return {"type": "signal",\ + "answer": {"name": self.name, "attrs": self.attrs}} + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/rpc/jsonparsers.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/rpc/jsonparsers.py --- deejayd-0.7.2/deejayd/rpc/jsonparsers.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/rpc/jsonparsers.py 2009-09-21 08:44:21.000000000 +0100 @@ -0,0 +1,73 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +try: import json # python 2.6 +except ImportError: # if python < 2.6, require simplejson + import simplejson as json +from deejayd.mediafilters import * +from deejayd.rpc import * + + +def loads_request(string, **kws): + err = Fault(NOT_WELLFORMED_ERROR, "Bad json-rpc request") + + try: unmarshalled = json.loads(string, **kws) + except ValueError: + raise err + + if (isinstance(unmarshalled, dict)): + for key in ("method", "params", "id"): + if key not in unmarshalled: + raise err + return unmarshalled + raise err + +def loads_response(string, **kws): + err = Fault(NOT_WELLFORMED_ERROR, "Bad json-rpc response") + + try: ans = json.loads(string, **kws) + except ValueError: + raise err + + for key in ("error", "result", "id"): + if key not in ans: + raise err + return ans + + +def Parse_json_filter(json_filter): + try: + name = json_filter["id"] + type = json_filter["type"] + if type == "basic": + filter_class = NAME2BASIC[name] + filter = filter_class(json_filter["value"]["tag"], \ + json_filter["value"]["pattern"]) + elif type == "complex": + filter = NAME2COMPLEX[name]() + for f in json_filter["value"]: + filter.combine(Parse_json_filter(f)) + else: + raise TypeError + except (KeyError, TypeError): + raise Fault(NOT_WELLFORMED_ERROR,\ + "Bad filter argument for this json-rpc request") + + return filter + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/rpc/jsonrpc.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/rpc/jsonrpc.py --- deejayd-0.7.2/deejayd/rpc/jsonrpc.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/rpc/jsonrpc.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,138 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from twisted.python import reflect +from deejayd import rpc as jsonrpclib + +class JSONRPC(object): + separator = '.' + + def __init__(self): + self.subHandlers = {} + + def putSubHandler(self, prefix, handler): + self.subHandlers[prefix] = handler + + def getSubHandler(self, prefix): + return self.subHandlers.get(prefix, None) + + def getSubHandlerPrefixes(self): + return self.subHandlers.keys() + + def _getFunction(self, functionPath): + """Given a string, return a function, or raise jsonrpclib.Fault. + + This returned function will be called, and should return the result + of the call, a Deferred, or a Fault instance. + + Override in subclasses if you want your own policy. The default + policy is that given functionPath 'foo', return the method at + self.jsonrpc_foo, i.e. getattr(self, "jsonrpc_" + functionPath). + If functionPath contains self.separator, the sub-handler for + the initial prefix is used to search for the remaining path. + """ + if functionPath.find(self.separator) != -1: + prefix, functionPath = functionPath.split(self.separator, 1) + handler = self.getSubHandler(prefix) + if handler is None: + raise jsonrpclib.Fault(jsonrpclib.METHOD_NOT_FOUND, + "no such sub-handler %s" % prefix) + return handler._getFunction(functionPath) + + f = getattr(self, "jsonrpc_%s" % functionPath, None) + if not f: + raise jsonrpclib.Fault(jsonrpclib.METHOD_NOT_FOUND, + "function %s not found" % functionPath) + elif not callable(f): + raise jsonrpclib.Fault(jsonrpclib.METHOD_NOT_CALLABLE, + "function %s not callable" % functionPath) + else: + return f + + def _listFunctions(self): + """Return a list of the names of all jsonrpc methods.""" + return reflect.prefixedMethodNames(self.__class__, 'jsonrpc_') + + +class JSONRPCIntrospection(JSONRPC): + """Implement a JSON-RPC Introspection API. + + By default, the methodHelp method returns the 'help' method attribute, + if it exists, otherwise the __doc__ method attribute, if it exists, + otherwise the empty string. + + To enable the methodSignature method, add a 'signature' method attribute + containing a list of lists. See methodSignature's documentation for the + format. Note the type strings should be JSON-RPC types, not Python types. + """ + + def __init__(self, parent): + """Implement Introspection support for a JSONRPC server. + + @param parent: the JSONRPC server to add Introspection support to. + """ + JSONRPC.__init__(self) + self._jsonrpc_parent = parent + + def jsonrpc_listMethods(self): + """Return a list of the method names implemented by this server.""" + functions = [] + todo = [(self._jsonrpc_parent, '')] + while todo: + obj, prefix = todo.pop(0) + functions.extend([ prefix + name for name in obj._listFunctions() ]) + todo.extend([ (obj.getSubHandler(name), + prefix + name + obj.separator) + for name in obj.getSubHandlerPrefixes() ]) + return functions + + jsonrpc_listMethods.signature = [['array']] + + def jsonrpc_methodHelp(self, method): + """ + Return a documentation string describing the use of the given method. + """ + method = self._jsonrpc_parent._getFunction(method) + return (getattr(method, 'help', None) + or getattr(method, '__doc__', None) or '') + + jsonrpc_methodHelp.signature = [['string', 'string']] + + def jsonrpc_methodSignature(self, method): + """Return a list of type signatures. + + Each type signature is a list of the form [rtype, type1, type2, ...] + where rtype is the return type and typeN is the type of the Nth + argument. If no signature information is available, the empty + string is returned. + """ + method = self._jsonrpc_parent._getFunction(method) + return getattr(method, 'signature', None) or '' + + jsonrpc_methodSignature.signature = [['array', 'string'], + ['string', 'string']] + + +def addIntrospection(jsonrpc): + """Add Introspection support to an JSONRPC server. + + @param jsonrpc: The jsonrpc server to add Introspection support to. + """ + jsonrpc.putSubHandler('system', JSONRPCIntrospection(jsonrpc)) + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/rpc/protocol.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/rpc/protocol.py --- deejayd-0.7.2/deejayd/rpc/protocol.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/rpc/protocol.py 2009-09-21 08:44:21.000000000 +0100 @@ -0,0 +1,966 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os, re + +from deejayd import __version__ +from deejayd.interfaces import DeejaydError +from deejayd.mediafilters import * +from deejayd.rpc import Fault, INTERNAL_ERROR, INVALID_METHOD_PARAMS,\ + DEEJAYD_PROTOCOL_VERSION +from deejayd.rpc.jsonbuilders import Get_json_filter +from deejayd.rpc.jsonparsers import Parse_json_filter +from deejayd.rpc.jsonrpc import JSONRPC, addIntrospection +from deejayd.rpc.rdfbuilder import modes as rdf_modes + + +def returns_answer(type, params = None): + + def returns_answer_instance(func): + + def returns_answer_func(*__args, **__kw): + # verify arguments + if params: + for idx, p in enumerate(params): + try: arg = __args[idx+1] + except IndexError: + if p["req"] is True: + raise Fault(INVALID_METHOD_PARAMS,\ + _("Param %s is required") % p["name"]) + break + else: + if p['type'] == "int": + try: int(arg) + except (ValueError,TypeError): + raise Fault(INVALID_METHOD_PARAMS,\ + _("Param %s is not an int") % p["name"]) + elif p['type'] in ("bool", "list", "dict"): + types = {"bool": bool, "dict": dict, "list": list} + if not isinstance(arg, types[p['type']]): + raise Fault(INVALID_METHOD_PARAMS,\ + _("Param %s has wrong type") % p["name"]) + elif p['type'] == "int-list": + if not isinstance(arg, list): + raise Fault(INVALID_METHOD_PARAMS,\ + _("Param %s is not a list") % p["name"]) + try: map(int, arg) + except (ValueError,TypeError): + raise Fault(INVALID_METHOD_PARAMS,\ + _("Param %s is not an int-list") % p["name"]) + try: + res = func(*__args, **__kw) + except DeejaydError, txt: + raise Fault(INTERNAL_ERROR, str(txt)) + if type == "ack": res = True + return {"type": type, "answer": res} + + returns_answer_func.__name__ = func.__name__ + returns_answer_func.__doc__ = func.__doc__ + returns_answer_func.answer_type = type + returns_answer_func.params = params + # build help + p_desc = "\nNo arguments" + if params: + p_desc = "\nArguments : \n%s" %\ + "\n".join([" * name '%s', type '%s', required '%s'" %\ + (p["name"], p["type"], p["req"]) for p in params]) + returns_answer_func.help = """ +Description : + %(description)s +Answer type : %(answer)s %(p_desc)s""" % {\ + "description": func.__doc__,\ + "answer": type,\ + "p_desc": p_desc,\ + } + # build signature + p_dict = { + "list": "array", + "int-list": "array", + "dict": "object", + "string": "string", + "int": "number", + "bool": "boolean", + "filter": "object", + "sort": "array", + } + if params: + signature = [] + opt_params = [p_dict[p["type"]] for p in params if not p["req"]] + for i in range(len(opt_params)+1): + s = ["object"] + s.extend([p_dict[p["type"]] for p in params if p["req"]]) + s.extend(opt_params[:i]) + signature.append(s) + else: + signature = [["object"]] + returns_answer_func.signature = signature + + return returns_answer_func + + return returns_answer_instance + + +class _DeejaydJSONRPC(JSONRPC): + def __init__(self, deejayd = None): + super(_DeejaydJSONRPC, self).__init__() + self.deejayd_core = deejayd + + +class _DeejaydMainJSONRPC(_DeejaydJSONRPC): + + @returns_answer('ack') + def jsonrpc_ping(self): + """Does nothing, just replies with an acknowledgement that the + command was received""" + return None + + @returns_answer('ack', params=[{"name":"mode","type":"string","req":True}]) + def jsonrpc_setmode(self, mode): + """Change the player mode. Possible values are : + * playlist : to manage and listen songs + * video : to manage and wath video file + * dvd : to wath dvd + * webradio : to manage and listen webradios""" + self.deejayd_core.set_mode(mode, objanswer=False) + + @returns_answer('dict') + def jsonrpc_availablemodes(self): + """For each available source, shows if it is activated or not. + The answer consists in : + * playlist : 0 or 1 (actually always 1 because it does not need optionnal + dependencies) + * queue : 0 or 1 (actually always 1 because it does not need optionnal + dependencies) + * webradio : 0 or 1 (needs gst-plugins-gnomevfs to be activated) + * video : 0 or 1 (needs video dependencies, X display and needs to be + activated in configuration) + * dvd : 0 or 1 (media backend has to be able to read dvd)""" + return dict(self.deejayd_core.get_mode(objanswer=False)) + + @returns_answer('dict') + def jsonrpc_status(self): + """Return status of deejayd. Given informations are : + * playlist : _int_ id of the current playlist + * playlistlength : _int_ length of the current playlist + * playlisttimelength : _int_ time length of the current playlist + * playlistrepeat : 0 (not activated) or 1 (activated) + * playlistplayorder : inorder | random | onemedia | random-weighted + * webradio : _int_ id of the current webradio list + * webradiolength : _int_ number of recorded webradio + * queue : _int_ id of the current queue + * queuelength : _int_ length of the current queue + * queuetimelength : _int_ time length of the current queue + * queueplayorder : inorder | random + * video : _int_ id of the current video list + * videolength : _int_ length of the current video list + * videotimelength : _int_ time length of the current video list + * videorepeat : 0 (not activated) or 1 (activated) + * videoplayorder : inorder | random | onemedia | random-weighted + * dvd : _int_ id of the current dvd + * dvdlength : _int_ number of tracks on the current dvd + * volume : `[0-100]` current volume value + * state : [play-pause-stop] the current state of the player + * current : _int_:_int_:_str_ current media pos : current media file id : + playing source name + * time : _int_:_int_ position:length of the current media file + * mode : [playlist-webradio-video] the current mode + * audio_updating_db : _int_ show when a audio library update + is in progress + * audio_updating_error : _string_ error message that apppears when the + audio library update has failed + * video_updating_db : _int_ show when a video library update + is in progress + * video_updating_error : _string_ error message that apppears when the + video library update has failed""" + return dict(self.deejayd_core.get_status(objanswer = False)) + + @returns_answer('dict') + def jsonrpc_stats(self): + """Return statistical informations : + * audio_library_update : UNIX time of the last audio library update + * video_library_update : UNIX time of the last video library update + * videos : number of videos known by the database + * songs : number of songs known by the database + * artists : number of artists in the database + * albums : number of albums in the database""" + return dict(self.deejayd_core.get_stats(objanswer = False)) + + @returns_answer('ack', [{"name":"source", "type":"string", "req":True},\ + {"name":"option_name", "type":"string","req":True},\ + {"name":"option_value","type":"string","req":True}]) + def jsonrpc_setOption(self, source, option_name, option_value): + """Set player options "name" to "value" for mode "source", + Available options are : + * playorder (inorder, onemedia, random or random-weighted) + * repeat (0 or 1) """ + self.deejayd_core.set_option(source, option_name,\ + option_value, objanswer=False) + + @returns_answer('ack', params=[\ + {"name":"ids", "type":"int-list", "req": True},\ + {"name": "value", "type": "int", "req": True},\ + {"name": "type", "type": "string", "req": False}]) + def jsonrpc_setRating(self, ids, value, type = "audio"): + """Set rating of media file with ids equal to media_id + for library 'type' """ + self.deejayd_core.set_media_rating(ids, value, type, objanswer=False) + + +class DeejaydTcpJSONRPC(_DeejaydMainJSONRPC): + + @returns_answer('ack') + def jsonrpc_close(self): + """Close the connection with the server""" + return None + +class DeejaydHttpJSONRPC(_DeejaydMainJSONRPC): + + @returns_answer('dict') + def jsonrpc_serverInfo(self): + """Return deejayd server informations : + * server_version : deejayd server version + * protocol_version : protocol version""" + return { + "server_version": __version__, + "protocol_version": DEEJAYD_PROTOCOL_VERSION + } + +# +# Player commands +# +class DeejaydPlayerJSONRPC(_DeejaydJSONRPC): + + @returns_answer('ack') + def jsonrpc_playToggle(self): + """Toggle play/pause.""" + self.deejayd_core.play_toggle(objanswer=False) + + @returns_answer('ack') + def jsonrpc_stop(self): + """Stop playing.""" + self.deejayd_core.stop(objanswer=False) + + @returns_answer('ack') + def jsonrpc_previous(self): + """Go to previous song or webradio.""" + self.deejayd_core.previous(objanswer=False) + + @returns_answer('ack') + def jsonrpc_next(self): + """Go to next song or webradio.""" + self.deejayd_core.next(objanswer=False) + + @returns_answer('ack', params=[{"name":"pos", "type":"int", "req":True},\ + {"name":"relative", "type":"bool", "req":False}]) + def jsonrpc_seek(self, pos, relative = False): + """Seeks to the position "pos" (in seconds) of the current media + set relative argument to true to set new pos in relative way""" + self.deejayd_core.seek(pos, relative, objanswer=False) + + @returns_answer('mediaList') + def jsonrpc_current(self): + """Return informations on the current song, webradio or video info.""" + medias = self.deejayd_core.get_current(objanswer=False) + media_type = len(medias) == 1 and medias[0]["type"] or None + return { + "media_type": media_type, + "medias": medias, + "filter": None, + "sort": None, + } + + @returns_answer('ack',params=[\ + {"name":"id", "type":"string", "req":True},\ + {"name":"id_type","type":"string", "req":False},\ + {"name":"source","type":"string","req":False}]) + def jsonrpc_goto(self, id, id_type = "id", source = None): + """Begin playing at media file with id "id" or toggle play/pause.""" + if not re.compile("^\w{1,}|\w{1,}\.\w{1,}$").search(id): + raise Fault(INVALID_METHOD_PARAMS, _("Wrong id parameter")) + self.deejayd_core.go_to(id, id_type, source, objanswer=False) + + @returns_answer('ack', params=[{"name":"volume", "type":"int", "req":True}]) + def jsonrpc_setVolume(self, volume): + """Set volume to "volume". The volume range is 0-100.""" + self.deejayd_core.set_volume(volume, objanswer=False) + + @returns_answer('ack', params=[\ + {"name":"option_name", "type":"string", "req":True},\ + {"name":"option_value", "type":"string", "req":True}]) + def jsonrpc_setPlayerOption(self, option_name, option_value): + """Set player option for the current media + Possible options are : + * zoom : set zoom (video only), min=-85, max=400 + * audio_lang : select audio channel (video only) + * sub_lang : select subtitle channel (video only) + * av_offset : set audio/video offset (video only) + * sub_offset : set subtitle/video offset (video only) + * aspect_ratio : set video aspect ratio (video only) + available value are : + * auto + * 1:1 + * 16:9 + * 4:3 + * 2.11:1 (for DVB)""" + self.deejayd_core.set_player_option(option_name, option_value,\ + objanswer=False) + + +# +# media library +# +class _DeejaydLibraryJSONRPC(_DeejaydJSONRPC): + type = "" + + @returns_answer('dict', params=[{"name":"force","type":"bool","req":False}]) + def jsonrpc_update(self, force = False): + """Update the library. + * 'type'_updating_db : the id of this task. + It appears in the status until the update are completed.""" + func = getattr(self.deejayd_core, "update_%s_library"%self.type) + return dict(func(force=force, objanswer=False)) + + @returns_answer('fileAndDirList',\ + params=[{"name":"directory","type":"string","req":False}]) + def jsonrpc_getDir(self, directory = ""): + """List the files of the directory supplied as argument.""" + func = getattr(self.deejayd_core, "get_%s_dir"%self.type) + root_dir, dirs, files = func(directory, objanswer=False) + type = self.type == "audio" and "song" or self.type + return { + "type": type, + "files": files, + "directories": dirs, + "root": root_dir, + } + + @returns_answer('mediaList',\ + params=[{"name":"pattern", "type":"string", "req":True},\ + {"name":"type","type":"string","req":True}]) + def jsonrpc_search(self, pattern, type): + """Search files in library where "type" contains "pattern" content.""" + func = getattr(self.deejayd_core, "%s_search"%self.type) + medias = func(pattern, type, objanswer=False) + type = self.type == "audio" and "song" or self.type + return {"media_type": type, "medias": medias} + +class DeejaydAudioLibraryJSONRPC(_DeejaydLibraryJSONRPC): + type = "audio" + + @returns_answer('list',\ + params=[{"name":"tag", "type":"string", "req":True},\ + {"name":"filter","type":"filter","req":False}]) + def jsonrpc_taglist(self, tag, filter = None): + """List all the possible values for a tag according to the optional + filter argument.""" + if filter is not None: + filter = Parse_json_filter(filter) + tag_list = self.deejayd_core.mediadb_list(tag, filter, objanswer=False) + return list(tag_list) + +class DeejaydVideoLibraryJSONRPC(_DeejaydLibraryJSONRPC): + type = "video" + +# +# generic class for modes +# +class _DeejaydModeJSONRPC(_DeejaydJSONRPC): + def __init__(self, deejayd): + super(_DeejaydModeJSONRPC, self).__init__(deejayd) + self.source = getattr(self.deejayd_core, "get_%s"%self.source_name)() + + @returns_answer('mediaList',\ + params=[{"name":"first", "type":"int", "req":False},\ + {"name":"length","type":"int","req":False}]) + def jsonrpc_get(self, first=0, length=-1): + """Return the content of this mode.""" + res = self.source.get(first, length, objanswer=False) + if isinstance(res, tuple): + medias, filter, sort = res + else: + medias, filter, sort = res, None, None + json_filter = filter is not None and \ + Get_json_filter(filter).dump() or None + return { + "media_type": self.media_type, + "medias": medias, + "filter": json_filter, + "sort": sort, + } + +# +# Dvd commands +# +class DeejaydDvdModeJSONRPC(_DeejaydJSONRPC): + + @returns_answer('dvdInfo') + def jsonrpc_get(self): + """Get the content of the current dvd.""" + return self.deejayd_core.get_dvd_content(objanswer=False) + + @returns_answer('ack') + def jsonrpc_reload(self): + """Load the content of the dvd player.""" + self.deejayd_core.dvd_reload(objanswer=False) + +# +# video command +# +class DeejaydVideoModeJSONRPC(_DeejaydModeJSONRPC): + media_type = "video" + source_name = "video" + + @returns_answer('ack',\ + params=[{"name":"value", "type":"string", "req":True},\ + {"name":"type","type":"string","req":False}]) + def jsonrpc_set(self, value, type="directory"): + """Set content of video mode""" + self.source.set(value, type, objanswer=False) + + @returns_answer('ack', params=[{"name":"sort", "type":"sort", "req":True}]) + def jsonrpc_sort(self, sort): + """Sort active medialist in video mode""" + self.source.set_sorts(sort, objanswer=False) + + +# +# playlist mode command +# +class DeejaydPanelModeJSONRPC(_DeejaydModeJSONRPC): + media_type = "song" + source_name = "panel" + + @returns_answer('list') + def jsonrpc_tags(self): + """Return tag list used in panel mode.""" + tags = self.source.get_panel_tags(objanswer=False) + return list(tags) + + @returns_answer('dict') + def jsonrpc_activeList(self): + """Return active list in panel mode + * type : 'playlist' if playlist is choosen as active medialist + 'panel' if panel navigation is active + * value : if 'playlist' is selected, return used playlist id""" + return dict(self.source.get_active_list(objanswer=False)) + + @returns_answer('ack', [{"name":"type", "type":"string", "req":True},\ + {"name":"value", "type":"string", "req":False}]) + def jsonrpc_setActiveList(self, type, value = ""): + """Set the active list in panel mode""" + self.source.set_active_list(type, value, objanswer=False) + + @returns_answer('ack', [{"name":"tag", "type":"string", "req":True},\ + {"name":"values", "type":"list", "req":True}]) + def jsonrpc_setFilter(self, tag, values): + """Set a filter for panel mode""" + self.source.set_panel_filters(tag, values, objanswer=False) + + @returns_answer('ack', [{"name":"tag", "type":"string", "req":True},]) + def jsonrpc_removeFilter(self, tag): + """Remove a filter for panel mode""" + self.source.remove_panel_filters(tag, objanswer=False) + + @returns_answer('ack') + def jsonrpc_clearFilter(self): + """Clear filters for panel mode""" + self.source.clear_panel_filters(objanswer=False) + + @returns_answer('ack', [{"name":"tag", "type":"string", "req":True},\ + {"name":"value", "type":"string", "req":True}]) + def jsonrpc_setSearch(self, tag, value): + """Set search filter in panel mode""" + self.source.set_search_filter(tag, value, objanswer=False) + + @returns_answer('ack') + def jsonrpc_clearSearch(self): + """Clear search filter in panel mode""" + self.source.clear_search_filter(objanswer=False) + + @returns_answer('ack') + def jsonrpc_clearAll(self): + """Clear search filter and panel filters""" + self.source.clear_search_filter(objanswer=False) + self.source.clear_panel_filters(objanswer=False) + + @returns_answer('ack', [{"name":"sort","type":"sort","req":True},]) + def jsonrpc_setSort(self, sort): + """Sort active medialist in panel mode""" + self.source.set_sorts(sort, objanswer=False) +# +# playlist mode command +# +class DeejaydPlaylistModeJSONRPC(_DeejaydModeJSONRPC): + media_type = "song" + source_name = "playlist" + + @returns_answer('ack') + def jsonrpc_clear(self): + """Clear the current playlist.""" + self.source.clear(objanswer=False) + + @returns_answer('ack') + def jsonrpc_shuffle(self): + """Shuffle the current playlist.""" + self.source.shuffle(objanswer=False) + + @returns_answer('dict',\ + params=[{"name":"pls_name", "type":"string", "req":True}]) + def jsonrpc_save(self, pls_name): + """Save the current playlist to "pls_name" in the database. + * playlist_id : id of the recorded playlist""" + return dict(self.source.save(pls_name, objanswer=False)) + + @returns_answer('ack', params=[\ + {"name":"pl_ids", "type":"int-list", "req":True}, + {"name":"pos", "type":"int", "req":False}]) + def jsonrpc_loads(self, pl_ids, pos = None): + """Load playlists passed as arguments "name" at the position "pos".""" + self.source.loads(pl_ids, pos, objanswer=False) + + @returns_answer('ack', params=[\ + {"name":"paths", "type":"list", "req":True}, + {"name":"pos", "type":"int", "req":False}]) + def jsonrpc_addPath(self, paths, pos = None): + """Load files or directories passed as arguments ("paths") + at the position "pos" in the current playlist.""" + self.source.add_paths(paths, pos,objanswer=False) + + @returns_answer('ack', params=[\ + {"name":"ids", "type":"int-list", "req":True}, + {"name":"pos", "type":"int", "req":False}]) + def jsonrpc_addIds(self, ids, pos = None): + """Load files with id passed as arguments ("ids") + at the position "pos" in the current playlist.""" + self.source.add_songs(ids, pos, objanswer=False) + + @returns_answer('ack', params=[{"name":"ids","type":"int-list","req":True}]) + def jsonrpc_remove(self, ids): + """Remove songs with ids passed as argument ("ids"), + from the current playlist""" + self.source.del_songs(ids, objanswer=False) + + @returns_answer('ack', params=[\ + {"name":"ids", "type":"int-list", "req":True}, + {"name":"pos", "type":"int", "req":True}]) + def jsonrpc_move(self, ids, pos): + """Move songs with id in "ids" to position "pos".""" + self.source.move(ids, pos, objanswer=False) + + +# +# webradios commands +# +class DeejaydWebradioModeJSONRPC(_DeejaydModeJSONRPC): + media_type = "webradio" + source_name = "webradios" + + @returns_answer('ack') + def jsonrpc_clear(self): + """Remove all recorded webradios.""" + self.source.clear(objanswer=False) + + @returns_answer('ack', params=[{"name":"ids","type":"int-list","req":True}]) + def jsonrpc_remove(self, ids): + """Remove webradios with id in "ids".""" + self.source.delete_webradios(ids, objanswer=False) + + @returns_answer('ack', params=[{"name":"name","type":"string","req":True},\ + {"name":"url", "type":"string", "req":True}]) + def jsonrpc_add(self, name, url): + """Add a webradio. Its name is "name" and the url of the webradio is + "url". You can pass a playlist for "url" argument (.pls and .m3u format + are supported).""" + self.source.add_webradio(name, url, objanswer=False) + + +# +# queue commands +# +class DeejaydQueueJSONRPC(_DeejaydModeJSONRPC): + media_type = "song" + source_name = "queue" + + @returns_answer('ack', params=[\ + {"name":"paths", "type":"list", "req":True}, + {"name":"pos", "type":"int", "req":False}]) + def jsonrpc_addPath(self, paths, pos = None): + """Load files or directories passed as arguments ("paths") + at the position "pos" in the queue.""" + self.source.add_paths(paths, pos,objanswer=False) + + @returns_answer('ack', params=[\ + {"name":"ids", "type":"int-list", "req":True}, + {"name":"pos", "type":"int", "req":False}]) + def jsonrpc_addIds(self, ids, pos = None): + """Load files with id passed as arguments ("ids") + at the position "pos" in the queue.""" + self.source.add_songs(ids, pos, objanswer=False) + + @returns_answer('ack', params=[\ + {"name":"pl_ids", "type":"int-list", "req":True}, + {"name":"pos", "type":"int", "req":False}]) + def jsonrpc_loads(self, pl_ids, pos = None): + """Load playlists passed as arguments "name" at the position "pos" + in the queue.""" + self.source.load_playlists(pl_ids, pos, objanswer=False) + + @returns_answer('ack', params=[{"name":"ids","type":"int-list","req":True}]) + def jsonrpc_remove(self, ids): + """Remove songs with ids passed as argument ("ids"), + from the queue""" + self.source.del_songs(ids, objanswer=False) + + @returns_answer('ack', params=[\ + {"name":"ids", "type":"int-list", "req":True}, + {"name":"pos", "type":"int", "req":True}]) + def jsonrpc_move(self, ids, pos): + """Move songs with id in "ids" to position "pos".""" + self.source.move(ids, pos, objanswer=False) + + @returns_answer('ack') + def jsonrpc_clear(self): + """Clear the queue.""" + self.source.clear(objanswer=False) + + +# +# recorded playlist +# +class DeejaydRecordedPlaylistJSONRPC(_DeejaydJSONRPC): + + @returns_answer('mediaList') + def jsonrpc_list(self): + """Return the list of recorded playlists.""" + pls = self.deejayd_core.get_playlist_list(objanswer=False) + return { + "media_type": 'playlist', + "medias": pls, + "filter": None, + "sort": None, + } + + @returns_answer('dict', [{"name":"name", "type":"string", "req":True},\ + {"name":"type","type":"string","req":True}]) + def jsonrpc_create(self, name, type): + """Create recorded playlist. The answer consist on + * pl_id : id of the created playlist + * name : name of the created playlist + * type : type of the created playlist + """ + pl_infos = self.deejayd_core.create_recorded_playlist(\ + name, type, objanswer=False) + return dict(pl_infos) + + @returns_answer('ack', [{"name":"pl_ids", "type":"int-list", "req":True}]) + def jsonrpc_erase(self, pl_ids): + """Erase recorded playlists passed as arguments.""" + self.deejayd_core.erase_playlist(pl_ids, objanswer=False) + + @returns_answer('mediaList', [{"name":"pl_id", "type":"int", "req":True},\ + {"name":"first", "type":"int", "req":False},\ + {"name":"length","type":"int","req":False}]) + def jsonrpc_get(self, pl_id, first = 0, length = -1): + """Return the content of a recorded playlist.""" + pls = self.deejayd_core.get_recorded_playlist(pl_id) + if pls.type == "static": + songs = pls.get(first, length, objanswer=False) + filter, sort = None, None + elif pls.type == "magic": + songs, filter, sort = pls.get(first, length, objanswer=False) + + json_filter = filter is not None and \ + Get_json_filter(filter).dump() or None + return { + "media_type": 'song', + "medias": songs, + "filter": json_filter, + "sort": sort, + } + + @returns_answer('ack', params=[{"name":"pl_id", "type":"int", "req":True},\ + {"name":"values", "type":"list", "req":True},\ + {"name":"type","type":"string","req":False}]) + def jsonrpc_staticAdd(self, pl_id, values, type = "path"): + """Add songs in a recorded static playlist. + Argument 'type' has to be 'path' (default) or 'id'""" + if type not in ("id", "path"): + raise Fault(INVALID_METHOD_PARAMS,\ + _("Param 'type' has a wrong value")) + pls = self.deejayd_core.get_recorded_playlist(pl_id) + if pls.type == "magic": + raise Fault(INVALID_METHOD_PARAMS,\ + _("Selected playlist is not static.")) + if type == "id": + try: values = map(int, values) + except (TypeError, ValueError): + raise Fault(INVALID_METHOD_PARAMS,\ + _("values arg must be integer")) + pls.add_songs(values, objanswer=False) + else: + pls.add_paths(values, objanswer=False) + + @returns_answer('ack', params=[{"name":"pl_id", "type":"int", "req":True},\ + {"name":"filter","type":"filter","req":True}]) + def jsonrpc_magicAddFilter(self, pl_id, filter): + """Add a filter in recorded magic playlist.""" + pls = self.deejayd_core.get_recorded_playlist(pl_id) + if pls.type != "magic": + raise Fault(INVALID_METHOD_PARAMS,\ + _("Selected playlist is not magic.")) + if filter is not None: + filter = Parse_json_filter(filter) + pls.add_filter(filter, objanswer=False) + + @returns_answer('ack', params=[{"name":"pl_id", "type":"int", "req":True},\ + {"name":"filter","type":"filter","req":True}]) + def jsonrpc_magicRemoveFilter(self, pl_id, filter): + """Remove a filter from recorded magic playlist.""" + pls = self.deejayd_core.get_recorded_playlist(pl_id) + if pls.type != "magic": + raise Fault(INVALID_METHOD_PARAMS,\ + _("Selected playlist is not magic.")) + if filter is not None: + filter = Parse_json_filter(filter) + pls.remove_filter(filter, objanswer=False) + + @returns_answer('ack', params=[{"name":"pl_id", "type":"int", "req":True}]) + def jsonrpc_magicClearFilter(self, pl_id): + """Remove all filter from recorded magic playlist.""" + pls = self.deejayd_core.get_recorded_playlist(pl_id) + if pls.type != "magic": + raise Fault(INVALID_METHOD_PARAMS,\ + _("Selected playlist is not magic.")) + pls.clear_filters(objanswer=False) + + @returns_answer('dict', [{"name":"pl_id", "type":"int", "req":True}]) + def jsonrpc_magicGetProperties(self, pl_id): + """Get properties of a magic playlist + * use-or-filter: if equal to 1, use "Or" filter + instead of "And" (0 or 1) + * use-limit: limit or not number of songs in the playlist (0 or 1) + * limit-value: number of songs for this playlist (integer) + * limit-sort-value: when limit is active sort playlist with this tag + * limit-sort-direction: sort direction for limit + (ascending or descending) + """ + pls = self.deejayd_core.get_recorded_playlist(pl_id) + if pls.type != "magic": + raise Fault(INVALID_METHOD_PARAMS,\ + _("Selected playlist is not magic.")) + return dict(pls.get_properties(objanswer=False)) + + @returns_answer('ack', [{"name":"pl_id", "type":"int", "req":True},\ + {"name":"key", "type":"string","req":True},\ + {"name":"value", "type":"string", "req":True}]) + def jsonrpc_magicSetProperty(self, pl_id, key, value): + """Set a property for a magic playlist.""" + pls = self.deejayd_core.get_recorded_playlist(pl_id) + if pls.type != "magic": + raise Fault(INVALID_METHOD_PARAMS,\ + _("Selected playlist is not magic.")) + pls.set_property(key, value, objanswer=False) + + +def build_protocol(deejayd, main): + # add introspection + addIntrospection(main) + + # add common deejayd subhandler + sub_handlers = { + "player": DeejaydPlayerJSONRPC, + "audiolib": DeejaydAudioLibraryJSONRPC, + "videolib": DeejaydVideoLibraryJSONRPC, + "recpls": DeejaydRecordedPlaylistJSONRPC, + } + for key in sub_handlers: + main.putSubHandler(key, sub_handlers[key](deejayd)) + # add mode deejayd subhandler + mode_handlers = { + "panel": DeejaydPanelModeJSONRPC, + "webradio": DeejaydWebradioModeJSONRPC, + "video": DeejaydVideoModeJSONRPC, + "playlist": DeejaydPlaylistModeJSONRPC, + "dvd": DeejaydDvdModeJSONRPC, + "queue": DeejaydQueueJSONRPC, + } + for key in mode_handlers: + try: mode = mode_handlers[key](deejayd) + except DeejaydError: # this mode is not activated + pass + else: + main.putSubHandler(key, mode) + + return main + + +############################################################################# +## part specific for jsonrpc over a TCP connecion : signals +############################################################################# +class DeejaydSignalJSONRPC(_DeejaydJSONRPC): + + def __init__(self, deejayd, connector): + super(DeejaydSignalJSONRPC, self).__init__(deejayd) + self.connector = connector + + @returns_answer('ack', [{"name":"signal", "type":"string", "req":True},\ + {"name":"value", "type":"bool", "req":True}]) + def jsonrpc_setSubscription(self, signal, value): + """Set subscribtion to "signal" signal notifications to "value" + which should be 0 or 1.""" + if value is False: + self.connector.set_not_signaled(signal) + elif value is True: + self.connector.set_signaled(signal) + +def set_signal_subhandler(deejayd, protocol): + protocol.putSubHandler("signal", DeejaydSignalJSONRPC(deejayd, protocol)) + return protocol + +############################################################################# +## part specific for jsonrpc over a http connecion +############################################################################# +class DeejaydWebJSONRPC(_DeejaydJSONRPC): + def __init__(self, deejayd, tmp_dir): + super(DeejaydWebJSONRPC, self).__init__(deejayd) + self._tmp_dir = tmp_dir + + def __find_ids(self, pattern): + ids = [] + for file in os.listdir(self._tmp_dir): + if re.compile("^%s-[0-9]+" % pattern).search(file): + t = file.split("-")[1] # id.ext + t = t.split(".") + try : ids.append(int(t[0])) + except ValueError: pass + return ids + + @returns_answer('dict', params=[{"name":"mid","type":"int","req":True}]) + def jsonrpc_writecover(self, mid): + """ Record requested cover in the temp directory """ + try: + cover = self.deejayd_core.get_audio_cover(mid,objanswer=False) + except (TypeError, DeejaydError, KeyError): + return {"cover": None} + + cover_ids = self.__find_ids("cover") + ext = cover["mime"] == "image/jpeg" and "jpg" or "png" + filename = "cover-%s.%s" % (str(cover["id"]), ext) + if cover["id"] not in cover_ids: + file_path = os.path.join(self._tmp_dir,filename) + fd = open(file_path, "w") + fd.write(cover["cover"]) + fd.close() + os.chmod(file_path,0644) + # erase unused cover files + for id in cover_ids: + try: + os.unlink(os.path.join(self._tmp_dir,\ + "cover-%s.jpg" % id)) + os.unlink(os.path.join(self._tmp_dir,\ + "cover-%s.png" % id)) + except OSError: + pass + return {"cover": os.path.join('tmp', filename), "mime": cover["mime"]} + + @returns_answer('dict', params=[{"name":"mode","type":"string","req":True}]) + def jsonrpc_buildSourceRDF(self, mode): + """ Build rdf file with current medialist of the specified mode + return dict with specific informations (like a description)""" + try: rdf = rdf_modes[mode](self.deejayd_core, self._tmp_dir) + except KeyError: + raise Fault(INVALID_METHOD_PARAMS,_("mode %s is not known") % mode) + return rdf.update() + + @returns_answer('dict',[{"name":"updated_tag","type":"string","req":False}]) + def jsonrpc_buildPanel(self, updated_tag = None): + """ Build panel list """ + panel = self.deejayd_core.get_panel() + + medias, filters, sort = panel.get(objanswer=False) + try: filter_list = filters.filterlist + except (TypeError, AttributeError): + filter_list = [] + + answer = {"panels": {}} + panel_filter = And() + # find search filter + for ft in filter_list: + if ft.type == "basic" and ft.get_name() == "contains": + panel_filter.combine(ft) + answer["search"] = Get_json_filter(ft).dump() + break + elif ft.type == "complex" and ft.get_name() == "or": + panel_filter.combine(ft) + answer["search"] = Get_json_filter(ft).dump() + break + + # find panel filter list + for ft in filter_list: + if ft.type == "complex" and ft.get_name() == "and": + filter_list = ft + break + + tag_list = panel.get_panel_tags(objanswer=False) + try: idx = tag_list.index(updated_tag) + except ValueError: + pass + else: + tag_list = tag_list[idx+1:] + + for t in panel.get_panel_tags(objanswer=False): + selected = [] + + for ft in filter_list: # OR filter + try: tag = ft[0].tag + except (IndexError, TypeError): # bad filter + continue + if tag == t: + selected = [t_ft.pattern for t_ft in ft] + tag_filter = ft + break + + if t in tag_list: + list = self.deejayd_core.mediadb_list(t,\ + panel_filter, objanswer=False) + items = [{"name": _("All"), "value":"__all__", \ + "class":"list-all", "sel":str(selected==[]).lower()}] + if t == "various_artist" and "__various__" in list: + items.append({"name": _("Various Artist"),\ + "value":"__various__",\ + "class":"list-unknown",\ + "sel":str("__various__" in selected).lower()}) + items.extend([{"name": l,"value":l,\ + "sel":str(l in selected).lower(), "class":""}\ + for l in list if l != "" and l != "__various__"]) + if "" in list: + items.append({"name": _("Unknown"), "value":"",\ + "class":"list-unknown",\ + "sel":str("" in selected).lower()}) + answer["panels"][t] = items + # add filter for next panel + if len(selected) > 0: + panel_filter.combine(tag_filter) + + return answer + +def set_web_subhandler(deejayd, tmp_dir, main): + main.putSubHandler("web", DeejaydWebJSONRPC(deejayd, tmp_dir)) + return main + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/rpc/rdfbuilder.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/rpc/rdfbuilder.py --- deejayd-0.7.2/deejayd/rpc/rdfbuilder.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/rpc/rdfbuilder.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,281 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os,re + +from deejayd.xmlobject import DeejaydXMLObject, ET +from deejayd.utils import * + + +class RdfBuilder(DeejaydXMLObject): + + def __init__(self, source_name): + self.rdf_nsp = "{http://www.w3.org/1999/02/22-rdf-syntax-ns#}" + self.file_nsp = "{http://%s/rdf#}" % source_name + self.xmlroot = ET.Element(self.rdf_nsp+"RDF") + + def build_seq(self, url, parent = None): + if parent is None: parent = self.xmlroot + seq = ET.SubElement(parent, self.rdf_nsp+"Seq") + seq.attrib[self.rdf_nsp+"about"] = self._to_xml_string(url) + + return seq + + def build_li(self, parent, url = None): + li = ET.SubElement(parent, self.rdf_nsp+"li") + if url: li.attrib[self.rdf_nsp+"about"] = self._to_xml_string(url) + + return li + + def build_item_desc(self, parms, parent = None, url = None): + if parent is None: parent = self.xmlroot + desc = ET.SubElement(parent, self.rdf_nsp+"Description") + if url: desc.attrib[self.rdf_nsp+"about"] = self._to_xml_string(url) + + for p in parms.keys(): + if p in ("time","length"): + if parms[p]: + value = format_time(int(parms[p])) + else: + value = self._to_xml_string(0) + elif p == "external_subtitle": + value = parms[p] == "" and _("No") or _("Yes") + elif p == "rating": + rating = u'\u266a' * int(parms[p]) + value = self._to_xml_string(rating) + else: + try: value = self._to_xml_string(parms[p]) + except TypeError: + continue + node = ET.SubElement(desc, self.file_nsp+"%s"\ + % self._to_xml_string(p)) + node.text = value + + return desc + + def set_resource(self, elt, url): + elt.attrib[self.rdf_nsp+"resource"] = self._to_xml_string(url) + + def to_xml(self): + return '' + "\n" + \ + ET.tostring(self.xmlroot) + + +class _DeejaydSourceRdf(DeejaydXMLObject): + name = "unknown" + locale_strings = ("%d Song", "%d Songs") + + def __init__(self, deejayd, rdf_dir): + self._deejayd = deejayd + self._rdf_dir = rdf_dir + + def update(self): + current_id = self._get_current_id() + try: + status = self._deejayd.get_status(objanswer=False) + new_id = status[self.__class__.name] + except KeyError: + return {"desc": ""}# this mode is not active + new_id = int(new_id) % 10000 + # get media list + obj = getattr(self._deejayd, self.__class__.get_list_func)() + res = obj.get(objanswer=False) + if isinstance(res, tuple): + medias, filter, sort = res + else: + medias, filter, sort = res, None, None + + if current_id != new_id: + self._build_rdf_file(medias, new_id) + # build description + single, plural = self.__class__.locale_strings + len = status[self.__class__.name + "length"] + desc = ngettext(single,plural,int(len))%int(len) + try: time = int(status[self.__class__.name + "timelength"]) + except KeyError: + pass + else: + if time > 0: desc += " (%s)" % format_time_long(time) + return {"desc": desc, "sort": sort} + + def _build_rdf_file(self, media_list, new_id): + # build xml + rdf_builder = RdfBuilder(self.__class__.name) + seq = rdf_builder.build_seq("http://%s/all-content" % self.name) + for media in media_list: + li = rdf_builder.build_li(seq) + rdf_builder.build_item_desc(media, li,\ + "http://%s/%s" % (self.name, media["id"])) + + self._save_rdf(rdf_builder, new_id) + + def _save_rdf(self, rdf_builder, new_id, name = None): + name = name or self.__class__.name + # first clean rdf dir + for file in os.listdir(self._rdf_dir): + path = os.path.join(self._rdf_dir,file) + if os.path.isfile(path) and file.startswith(name+"-"): + os.unlink(path) + + filename = "%s-%d.rdf" % (name, new_id); + file_path = os.path.join(self._rdf_dir,filename) + + fd = open(file_path, "w") + fd.write(rdf_builder.to_xml()) + fd.close() + os.chmod(file_path,0644) + + def _get_current_id(self): + ids = [] + for file in os.listdir(self._rdf_dir): + if re.compile("^"+self.name+"-[0-9]+\.rdf$").search(file): + t = file.split("-")[1] # id.rdf + t = t.split(".") + try : ids.append(int(t[0])) + except ValueError: pass + + if ids == []: return 0 + else: return max(ids) + +class DeejaydPlaylistRdf(_DeejaydSourceRdf): + name = "playlist" + get_list_func = "get_playlist" + +class DeejaydPanelRdf(_DeejaydSourceRdf): + name = "panel" + get_list_func = "get_panel" + +class DeejaydQueueRdf(_DeejaydSourceRdf): + name = "queue" + get_list_func = "get_queue" + +class DeejaydWebradioRdf(_DeejaydSourceRdf): + name = "webradio" + locale_strings = ("%d Webradio", "%d Webradios") + get_list_func = "get_webradios" + +class DeejaydVideoRdf(_DeejaydSourceRdf): + name = "video" + locale_strings = ("%d Video", "%d Videos") + get_list_func = "get_video" + +class DeejaydVideoDirRdf(_DeejaydSourceRdf): + name = "videodir" + + def update(self): + try: + stats = self._deejayd.get_stats() + new_id = stats["video_library_update"] + except KeyError: + return {"id": None}# this mode is not active + current_id = self._get_current_id() + new_id = int(new_id) % 10000 + if current_id != new_id: + self._build_rdf_file(new_id) + return {"id": new_id} + + def _build_rdf_file(self,new_id): + rdf_builder = RdfBuilder(self.__class__.name) + + seq = rdf_builder.build_seq("http://videodir/all-content") + self._build_dir_list(rdf_builder, seq, "") + self._save_rdf(rdf_builder, new_id) + + def _build_dir_list(self, rdf_builder, seq_elt, dir, id = "1"): + dir_elt = rdf_builder.build_li(seq_elt) + dir_url = "http://videodir/%s" % os.path.join("root", dir) + title = dir == "" and _("Root Directory") or os.path.basename(dir) + rdf_builder.build_item_desc({"title": title}, url = dir_url) + + subdirs = self._deejayd.get_video_dir(dir).get_directories() + if subdirs == []: + rdf_builder.set_resource(dir_elt, dir_url) + else: + subdir_list = rdf_builder.build_seq(dir_url, parent = dir_elt) + for idx, d in enumerate(subdirs): + new_id = id + "/%d" % (idx+1,) + self._build_dir_list(rdf_builder, subdir_list,\ + os.path.join(dir, d), new_id) + +class DeejaydDvdRdf(_DeejaydSourceRdf): + name = "dvd" + locale_strings = ("%d Track", "%d Tracks") + + def update(self): + current_id = self._get_current_id() + try: + status = self._deejayd.get_status(objanswer=False) + new_id = status[self.__class__.name] + except KeyError: + return {"desc": ""}# this mode is not active + dvd_content = self._deejayd.get_dvd_content(objanswer=False) + new_id = int(new_id) % 10000 + if current_id != new_id: + self._build_rdf_file(dvd_content, new_id) + # build description + single, plural = self.__class__.locale_strings + len = status[self.__class__.name + "length"] + desc = ngettext(single,plural,int(len))%int(len) + return {"desc": desc, "title": dvd_content["title"],\ + "longest_track": dvd_content["longest_track"]} + + def _build_rdf_file(self, dvd_content, new_id): + rdf_builder = RdfBuilder(self.__class__.name) + + # dvd structure + seq = rdf_builder.build_seq("http://dvd/all-content") + for track in dvd_content["track"]: + track_li = rdf_builder.build_li(seq) + + track_url = "http://dvd/%s" % track["ix"] + track_struct = rdf_builder.build_seq(track_url,track_li) + + track_data = {"title": _("Title %s") % track["ix"],\ + "id": track["ix"],\ + "length": track["length"]} + rdf_builder.build_item_desc(track_data,None,track_url) + + for chapter in track["chapter"]: + chapter_url = track_url + "/%s" % chapter["ix"] + chapter_li = rdf_builder.build_li(track_struct) + rdf_builder.set_resource(chapter_li,chapter_url) + + chapter_data = {"title": _("Chapter %s") % chapter["ix"],\ + "id": chapter["ix"],\ + "length": chapter["length"]} + rdf_builder.build_item_desc(chapter_data,None,chapter_url) + + self._save_rdf(rdf_builder,new_id) + + +ngettext("%d Song", "%d Songs", 0) +ngettext("%d Video", "%d Videos", 0) +ngettext("%d Webradio", "%d Webradios", 0) +ngettext("%d Track", "%d Tracks", 0) + +modes = { + "playlist": DeejaydPlaylistRdf, + "queue": DeejaydQueueRdf, + "panel": DeejaydPanelRdf, + "webradio": DeejaydWebradioRdf, + "video": DeejaydVideoRdf, + "dvd": DeejaydDvdRdf, + "videodir": DeejaydVideoDirRdf, + } + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/sources/_base.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/sources/_base.py --- deejayd-0.7.2/deejayd/sources/_base.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/sources/_base.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -16,309 +16,238 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import os -import random, urllib +import os, locale +from twisted.internet import reactor +from deejayd import mediafilters +from deejayd.interfaces import DeejaydError from deejayd.component import SignalingComponent -from deejayd.mediadb._media import SongMedia +from deejayd.mediadb.library import NotFoundException +from deejayd.sources._medialist import * +from deejayd.sources._playorder import orders -class MediaNotFoundError(Exception):pass -class PlaylistNotFoundError(Exception):pass - -class SimpleMediaList: - - def __init__(self, id = 0): - self._list_id = id - self._media_id = 0 - self._time_length = 0 - self._content = [] - - def _update_list_id(self): - self._list_id += 1 - # update time length - self._time_length = 0 - for m in self._content: - try: length = m["length"] - except (IndexError, ValueError): - continue - if length: - self._time_length += length - - def get(self): - return self._content - - def get_ids(self): - return [m["id"] for m in self._content] - - def set(self, medias): - self._content = [] - self.add_media(medias) - - def length(self): - return len(self._content) - - def time_length(self): - return self._time_length - - def add_media(self, medias, first_pos = None): - if first_pos == None: - first_pos = len(self._content) - old_content = self._content[first_pos:] - self._content = self._content[:first_pos] - - i = 0 - for m in medias: - pos = first_pos + i - m["pos"] = pos - if "id" not in m.keys() or m["type"] == "song": - m["id"] = self.set_media_id() - self._content.append(m) - i += 1 - - for media in old_content: - media["pos"] = first_pos + i - i+=1 - - self._content.extend(old_content) - self._update_list_id() - - def clear(self): - self._content = [] - self._update_list_id() - - def delete(self, id, type = "id"): - i = 0 - for media in self._content: - if media[type] == id: - break - i += 1 - if i == len(self._content): - raise MediaNotFoundError - - pos = self._content[i]["pos"] - del self._content[i] - # Now we must reorder the media list - for media in self._content: - if media["pos"] > pos: - media["pos"] -= 1 - self._update_list_id() - - def get_media(self, id, type = "id"): - media = None - for m in self._content: - if m[type] == id: - media = m - break - - if media == None: - raise MediaNotFoundError - return media - - def get_list_id(self): - return self._list_id - - def set_media_id(self): - self._media_id += 1 - return self._media_id - - -class MediaList(SimpleMediaList): - - def __init__(self, db, id = 0): - SimpleMediaList.__init__(self, id) - self.db = db - - def __format_playlist_file(self, s, root_path): - song = SongMedia(self.db, s) - song["uri"] = "file://"+urllib.quote(os.path.join(root_path,s[1],s[2])) - song["pos"] = s[14] - - return song - - def load_playlist(self, name, root_path, pos = None): - content = self.db.get_audiolist(name) - if len(content) == 0 and (not name.startswith("__") or \ - not name.endswith("__")): - raise PlaylistNotFoundError - - medias = [self.__format_playlist_file(s, root_path) for s in content] - self.add_media(medias, pos) - - def move(self, ids, new_pos, type): - medias = [] - for id in ids: - medias.append(self.get_media(id, type)) - - old_content = self._content - self._content = [] - for index, media in enumerate(old_content): - if index == new_pos: - self._content.extend(medias) - if media not in medias: - self._content.append(media) - - # Reorder the list - ids = range(0,len(self._content)) - for id in ids: - self._content[id]["pos"] = id - self._update_list_id() - - def shuffle(self, current = None): - new_content = [] - old_content = self._content - pos = 0 - # First we have to put the current song at the first place - if current != None: - old_pos = current["pos"] - del old_content[old_pos] - new_content.append(current) - new_content[pos]["pos"] = pos - pos += 1 - - while len(old_content) > 0: - song = random.choice(old_content) - del old_content[old_content.index(song)] - new_content.append(song) - new_content[pos]["pos"] = pos - pos += 1 - - self._content = new_content - self._update_list_id() +class SourceError(DeejaydError): pass class _BaseSource(SignalingComponent): name = "unknown" def __init__(self, db): - SignalingComponent.__init__(self) + super(_BaseSource, self).__init__() self.db = db self._current = None - self._played = [] + self._playorder = orders["inorder"] def get_recorded_id(self): id = int(self.db.get_state(self.name+"id")) return id - def get_content(self): - return self._media_list.get() + def get_content(self, start = 0, stop = None): + return self._media_list.get(start, stop) def get_current(self): return self._current def go_to(self, id, type = "id"): - self._played = [] - try: self._current = self._media_list.get_media(id, type) - except MediaNotFoundError: - self._current = None - else: - if self._current["id"] not in self._played: - self._played.append(self._current["id"]) - + if type == "pos": + try: id = self._media_list._order[id] + except IndexError: return None + self._current = self._playorder.set_explicit(self._media_list, id) return self._current - def delete(self, id): - self._media_list.delete(id) - try: self._played.remove(id) - except ValueError: - pass + def delete(self, ids): + if not self._media_list.delete(ids): + raise SourceError(_("Unable to delete selected ids")) + self._media_list.reload_item_pos(self._current) def clear(self): self._media_list.clear() - self._played = [] - - def next(self, rd, rpt): - l = self._media_list.length() - if self._current == None: - pos = 0 - if rd and l > 0: - m = random.choice(self._media_list.get()) - pos = m["pos"] - self.go_to(pos, "pos") - return self._current - - # add current media in played list - if self._current["id"] not in self._played: - self._played.append(self._current["id"]) - - # Return a pseudo-random song - if rd and l > 0: - # first determine if the current song is in playedItems - id = self._played.index(self._current["id"]) - try: new_id = self._played[id+1] - except IndexError: pass - else: - self._current = self._media_list.get_media(new_id ,"id") - return self._current - - # Determine the id of the next song - values = [id for id in self._media_list.get_ids() \ - if id not in self._played] - try: new_id = random.choice(values) - except IndexError: # All songs are played - if rpt: - self._played = [] - new_id = random.choice(self.current_source.get_item_ids()) - else: - self._current = None - return None + self._playorder.reset(self._media_list) - # Obtain the choosed song - try: self._current = self._media_list.get_media(new_id, "id") - except MediaNotFoundError: - self._current = None - return self._current - - cur_pos = self._current["pos"] - if cur_pos < self._media_list.length()-1: - try: self._current = self._media_list.get_media(cur_pos + 1, "pos") - except MediaNotFoundError: - self._current = None - elif rpt: - self._current = self._media_list.get_media(0, "pos") + def next(self, explicit = True): + if explicit: + self._current = self._playorder.next_explicit(self._media_list,\ + self._current) else: - self._current = None - + self._current = self._playorder.next_implicit(self._media_list,\ + self._current) return self._current - def previous(self,rd,rpt): - if self._current == None: - return None - - # add current media in played list - if self._current["id"] not in self._played: - self._played.append(self._current["id"]) - - # Return the last pseudo-random media - if rd: - id = self._played.index(self._current["id"]) - if id == 0: - self._current = None - return self._current - try: self._current = self._media_list.get_media(self._played[id-1]) - except MediaNotFoundError: - self._current = None - return self._current - - cur_pos= self._current["pos"] - if cur_pos > 0: - self._current = self._media_list.get_media(cur_pos - 1, "pos") - else: - self._current = None - + def previous(self): + self._current = self._playorder.previous_explicit(self._media_list,\ + self._current) return self._current def get_status(self): return [ - (self.name, self._media_list.get_list_id()), - (self.name+"length", self._media_list.length()), - (self.name+"timelength", self._media_list.time_length()) + (self.name, self._media_list.list_id), + (self.name+"length", len(self._media_list)), + (self.name+"timelength", self._media_list.time_length) ] + def set_option(self, name, value): + raise NotImplementedError + def close(self): - states = [ - (str(self._media_list.get_list_id()),self.__class__.name+"id") - ] + states = [ (str(self._media_list.list_id),self.__class__.name+"id") ] + self.db.set_state(states) + + +class _BaseLibrarySource(_BaseSource): + available_playorder = ("inorder", "random", "onemedia","random-weighted") + has_repeat = True + source_signal = '' + + def __init__(self, db, library): + super(_BaseLibrarySource, self).__init__(db) + if self.medialist_type == "sorted": + self._media_list = SortedMediaList(self.get_recorded_id() + 1) + elif self.medialist_type == "unsorted": + self._media_list = UnsortedMediaList(self.get_recorded_id() + 1) + self.library = library + + if self.has_repeat: + self._media_list.repeat = int(db.get_state(self.name+"-repeat")) + self._playorder = orders[db.get_state(self.name+"-playorder")] + + def _get_playlist_content(self, pl_id): + try: + pl_id, name, type = self.db.is_medialist_exists(pl_id) + if type == "static": + return self.db.get_static_medialist(pl_id,\ + infos=self.library.media_attr) + elif type == "magic": + properties = dict(self.db.get_magic_medialist_properties(pl_id)) + if properties["use-or-filter"] == "1": + filter = mediafilters.Or() + else: + filter = mediafilters.And() + sorts = mediafilters.DEFAULT_AUDIO_SORT + if properties["use-limit"] == "1": + sorts = [(properties["limit-sort-value"],\ + properties["limit-sort-direction"])] + sorts + limit = int(properties["limit-value"]) + else: + limit = None + filter.filterlist = self.db.get_magic_medialist_filters(pl_id) + return self.library.search(filter, sorts, limit) + except TypeError: + raise SourceError(_("Playlist %s does not exist.") % str(pl_id)) + + def set_option(self, name, value): + if name == "playorder": + try: self._playorder = orders[value] + except KeyError: + raise SourceError(_("Unable to set %s order, not supported") % + value) + elif name == "repeat" and self.has_repeat: + self._media_list.repeat = int(value) + else: raise NotImplementedError + + def get_status(self): + status = super(_BaseLibrarySource, self).get_status() + status.append((self.name+"playorder", self._playorder.name)) + if self.has_repeat: + status.append((self.name+"repeat", self._media_list.repeat)) + return status + + def close(self): + super(_BaseLibrarySource, self).close() + self.db.set_static_medialist(self.base_medialist,self._media_list.get()) + states = [(self._playorder.name, self.name+"-playorder")] + if self.has_repeat: + states.append((self._media_list.repeat, self.name+"-repeat")) self.db.set_state(states) + def cb_library_changes(self, signal): + file_id = signal.get_attr("id") + getattr(self, "_%s_media" % signal.get_attr("type"))(file_id) + + def _add_media(self, media_id): + pass + + def _update_media(self, media_id): + try: media = self.library.get_file_withids([media_id]) + except NotFoundException: + return + if self._media_list.update_media(media[0]): + self.dispatch_signame(self.source_signal) + + def _remove_media(self, media_id): + if self._media_list.remove_media(media_id): + self.dispatch_signame(self.source_signal) + + +class _BaseSortedLibSource(_BaseLibrarySource): + medialist_type = "sorted" + + def set_sorts(self, sorts): + for (tag, direction) in sorts: + if tag not in self.sort_tags: + raise SourceError(_("Tag '%s' not supported for sort") % tag) + if direction not in ('ascending', 'descending'): + raise SourceError(_("Bad sort direction for source")) + self._sorts = sorts + self._media_list.sort(self._sorts + self.default_sorts) + self.dispatch_signame(self.source_signal) + + +class _BaseAudioLibSource(_BaseLibrarySource): + base_medialist = '' + medialist_type = "unsorted" + + def __init__(self, db, audio_library): + super(_BaseAudioLibSource, self).__init__(db, audio_library) + # load saved + try: ml_id = self.db.get_medialist_id(self.base_medialist, 'static') + except ValueError: # medialist does not exist + pass + else: + self._media_list.set(self._get_playlist_content(ml_id)) + + def add_song(self, song_ids, pos = None): + try: medias = self.library.get_file_withids(song_ids) + except NotFoundException: + raise SourceError(_("One of these ids %s not found") % \ + ",".join(map(str, song_ids))) + self._media_list.add_media(medias, pos) + if pos: self._media_list.reload_item_pos(self._current) + self.dispatch_signame(self.source_signal) + + def add_path(self, paths, pos = None): + medias = [] + for path in paths: + try: medias.extend(self.library.get_all_files(path)) + except NotFoundException: + try: medias.extend(self.library.get_file(path)) + except NotFoundException: + raise SourceError(_("%s not found") % path) + self._media_list.add_media(medias, pos) + if pos: self._media_list.reload_item_pos(self._current) + self.dispatch_signame(self.source_signal) + + def load_playlist(self, pl_ids, pos = None): + medias = [] + for id in pl_ids: + medias.extend(self._get_playlist_content(id)) + self._media_list.add_media(medias, pos) + if pos: self._media_list.reload_item_pos(self._current) + self.dispatch_signame(self.source_signal) + + def move(self, ids, new_pos): + if not self._media_list.move(ids, new_pos): + raise SourceError(_("Unable to move selected medias")) + self._media_list.reload_item_pos(self._current) + self.dispatch_signame(self.source_signal) + + def delete(self, ids): + super(_BaseAudioLibSource, self).delete(ids) + self.dispatch_signame(self.source_signal) + + def clear(self): + self._current = None + self._media_list.clear() + self.dispatch_signame(self.__class__.source_signal) + # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/sources/dvd.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/sources/dvd.py --- deejayd-0.7.2/deejayd/sources/dvd.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/sources/dvd.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -19,25 +19,25 @@ from deejayd.component import SignalingComponent from deejayd.player._base import PlayerError +from deejayd.player.xine import DvdParser class DvdError(Exception): pass class DvdSource(SignalingComponent): name = "dvd" - def __init__(self, player, db, config): - SignalingComponent.__init__(self) - self.player = player + def __init__(self, db, config): + super(DvdSource, self).__init__() + try: self.parser = DvdParser() + except PlayerError, ex: # dvd parser does not work + raise DvdError(ex) + self.db = db - self.current_id = int(self.db.get_state("dvdid")) + self.current_id = int(self.db.get_state("dvdid")) + 1 self.dvd_info = None self.selected_track = None - # load dvd content - #try: self.load() - #except DvdError: pass - def get_content(self): if not self.dvd_info: return {'title': "DVD NOT LOADED", 'longest_track': "0", \ @@ -50,7 +50,7 @@ self.selected_track = None self.current_id +=1 - try: self.dvd_info = self.player.get_dvd_info() + try: self.dvd_info = self.parser.get_dvd_info() except PlayerError, err: raise DvdError("Unable to load the dvd : %s " % err) # select the default track of the dvd @@ -100,7 +100,7 @@ return self.get_current() - def next(self,random,repeat): + def next(self, explicit = True): if not self.dvd_info or not self.selected_track: return None if self.selected_track["selected_chapter"] != -1: @@ -114,7 +114,7 @@ return self.get_current() - def previous(self,random,repeat): + def previous(self): if not self.dvd_info or not self.selected_track: return None if self.selected_track["selected_chapter"] != -1: @@ -128,11 +128,17 @@ return self.get_current() def get_status(self): - status = [("dvd",self.current_id)] + length = 0 + if self.dvd_info: length = len(self.dvd_info) + status = [ + (self.name, self.current_id), + (self.name+"length",length) + ] return status def close(self): states = [(str(self.current_id),self.__class__.name+"id")] self.db.set_state(states) + self.parser.close() # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/sources/__init__.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/sources/__init__.py --- deejayd-0.7.2/deejayd/sources/__init__.py 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/deejayd/sources/__init__.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -16,9 +16,12 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import os + from deejayd.component import SignalingComponent from deejayd.ui import log from deejayd.player import PlayerError +from deejayd.sources._base import SourceError class UnknownSourceException: pass @@ -33,15 +36,15 @@ class SourceFactory(SignalingComponent): - sources_list = ("playlist","queue","webradio","video","dvd") + sources_list = ("playlist","queue","webradio","video","dvd","panel") def __init__(self,player,db,audio_library,video_library,config): SignalingComponent.__init__(self) - self.sources_obj = {} self.current = "" self.db = db activated_sources = config.getlist('general', "activated_modes") + self.sources_obj = {} from deejayd.sources import queue self.sources_obj["queue"] = queue.QueueSource(db, audio_library) # playlist @@ -52,6 +55,14 @@ else: log.info(_("Playlist support disabled")) + # panel + if "panel" in activated_sources: + from deejayd.sources import panel + self.sources_obj["panel"] = panel.PanelSource(db, audio_library,\ + config) + else: + log.info(_("Panel support disabled")) + # Webradio if "webradio" in activated_sources and player.is_supported_uri("http"): from deejayd.sources import webradio @@ -66,6 +77,10 @@ # Critical error, we have to quit deejayd msg = _('Cannot initialise video support, either disable video and dvd mode or check your player video support.') log.err(msg, fatal = True) + except NotImplementedError: + # player not supported video playback, quit deejayd + msg = _("player '%s' don't support video playback, either disable video and dvd mode or change your player to have video support.") + log.err(msg % player.name, fatal = True) # Video if "video" in activated_sources: @@ -77,9 +92,9 @@ # dvd if "dvd" in activated_sources and player.is_supported_uri("dvd"): from deejayd.sources import dvd - try: self.sources_obj["dvd"] = dvd.DvdSource(player,db,config) - except dvd.DvdError: - log.err(_("Unable to init dvd support")) + try: self.sources_obj["dvd"] = dvd.DvdSource(db,config) + except dvd.DvdError, ex: + log.err(_("Unable to init dvd support : %s") % str(ex)) else: log.info(_("DVD support disabled")) @@ -90,18 +105,15 @@ log.err(_("Unable to set recorded source %s") % str(source)) self.set_source(self.get_available_sources()[0]) - # load random/repeat options state - self.source_options = {} - self.source_options["random"] = int(self.db.get_state("random")) - self.source_options["repeat"] = int(self.db.get_state("repeat")) - self.source_options["qrandom"] = int(self.db.get_state("qrandom")) - player.set_source(self) player.load_state() - def set_option(self,name,value): - if name not in self.source_options.keys(): raise KeyError - self.source_options[name] = value + def set_option(self, source, name, value): + try: self.sources_obj[source].set_option(name, value) + except KeyError: + raise UnknownSourceException + except NotImplementedError: + raise SourceError(_("option %s not supported for this mode")) self.dispatch_signame('player.status') def get_source(self,s): @@ -122,12 +134,11 @@ status = [("mode",self.current)] for k in self.sources_obj.keys(): status.extend(self.sources_obj[k].get_status()) - - for key in self.source_options.keys(): - status.append((key,self.source_options[key])) - return status + def get_all_sources(self): + return [m for m in self.sources_list if m != "queue"] + def get_available_sources(self): modes = self.sources_obj.keys() modes.remove("queue") @@ -137,11 +148,7 @@ return mode in self.sources_obj.keys() def close(self): - states = [(self.current,"source")] - for key in self.source_options: - states.append((str(self.source_options[key]),key)) - self.db.set_state(states) - + self.db.set_state([(self.current,"source")]) for k in self.sources_obj.keys(): self.sources_obj[k].close() @@ -156,28 +163,22 @@ @format_rsp def get_current(self): queue_media = self.sources_obj["queue"].get_current() or \ - self.sources_obj["queue"].next(self.source_options["qrandom"]) - if queue_media: - return (queue_media, "queue") + self.sources_obj["queue"].next() + if queue_media: return (queue_media, "queue") + current = self.sources_obj[self.current].get_current() or \ - self.sources_obj[self.current].next(\ - self.source_options["random"],self.source_options["repeat"]) + self.sources_obj[self.current].next(explicit = False) return (current, self.current) @format_rsp - def next(self): - queue_media = self.sources_obj["queue"].next(\ - self.source_options["qrandom"]) + def next(self, explicit = True): + queue_media = self.sources_obj["queue"].next(explicit) if queue_media: return (queue_media, "queue") - return (self.sources_obj[self.current].next(\ - self.source_options["random"],self.source_options["repeat"]),\ - self.current) + return (self.sources_obj[self.current].next(explicit),self.current) @format_rsp def previous(self): - return (self.sources_obj[self.current].previous(\ - self.source_options["random"],self.source_options["repeat"]), - self.current) + return (self.sources_obj[self.current].previous(),self.current) def queue_reset(self): self.sources_obj["queue"].reset() diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/sources/_medialist.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/sources/_medialist.py --- deejayd-0.7.2/deejayd/sources/_medialist.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/sources/_medialist.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,232 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import random + +__all__ = ["SimpleMediaList", "SortedMediaList", "UnsortedMediaList"] + +class SimpleMediaList(object): + + def __init__(self, list_id = 0): + self._media_id = 0 + self._order, self._content = [], {} + self.repeat = False + + self.list_id = list_id + self.time_length = 0 + + def __len__(self): + return len(self._order) + + def _set_media_id(self): + self._media_id += 1 + return self._media_id + + def _set_media_ans(self, id, pos = -1): + try: ans = self._content[id] + except KeyError: + return None + ans.update({"id": id, "pos": pos}) + return ans + + def get(self, start = 0, stop = None): + stop = stop or len(self._order) + return map(self._set_media_ans, self._order[start:stop],\ + range(start, stop)) + + def get_item(self, id): + try: return self._set_media_ans(id, self._order.index(id)) + except ValueError: + return None + + def get_item_first(self): + if self._order: + return self._set_media_ans(self._order[0], 0) + return None + + def get_item_last(self): + if self._order: + return self._set_media_ans(self._order[-1], len(self._order)-1) + return None + + def get_ids(self): + return self._order + + def reload_item_pos(self, media): + try: pos = self._order.index(media["id"]) + except TypeError: return + except ValueError: + pos = -1 + media["pos"] = pos + + def set(self, medias): + self._order, self._content = [], {} + self.time_length = 0 + self.add_media(medias) + + def add_media(self, medias, first_pos = None): + first_pos = first_pos or len(self._order) + + for index, m in enumerate(medias): + id = self._set_media_id() + self._order.insert(first_pos + index, id) + self._content[id] = m + # update medialist time length + try: length = int(m["length"]) + except (ValueError, KeyError, TypeError): + continue + self.time_length += length + self.list_id += 1 + + def clear(self): + self._order, self._content = [], {} + self.time_length = 0 + self.list_id += 1 + + def delete(self, ids, type = "id"): + if type == "pos": ids = [self._order[p] for p in ids] + missing_ids = [id for id in ids if id not in self._order] + if missing_ids: return False + + for id in ids: + self._order.remove(id) + # update time length + try: length = int(self._content[id]["length"]) + except (ValueError, KeyError, TypeError): continue + else: self.time_length -= length + del self._content[id] + self.list_id += 1 + return True + + def next(self, media): + try: + idx = self._order.index(media["id"]) + id = self._order[idx+1] + except ValueError: # id not found, try to find by media_id + id = None + if media: + for list_id, m in self._content.items(): + if m["media_id"] == media["media_id"]: + idx = self._order.index(m["id"]) + try: id = self._order[idx+1] + except IndexError: # end of medialist + return None + break + if id is None: return None + except (IndexError, KeyError, TypeError): + return None + return self._set_media_ans(id, idx+1) + + def previous(self, media): + try: + idx = self._order.index(media["id"]) + id = idx and self._order[idx-1] or None + except (ValueError, KeyError): + return None + return self._set_media_ans(id, idx-1) + + def find_id(self, media_id): + for id, m in self._content.items(): + if m["media_id"] == media_id: + return id + raise ValueError + + +class _MediaList(SimpleMediaList): + + # + # library changes action + # + def remove_media(self, media_id): + ans = False + for key, media in self._content.items(): + if media["media_id"] == media_id: + self._order.remove(key) + del self._content[key] + ans = True + try: length = int(media["length"]) + except (ValueError, KeyError, TypeError): + continue + self.time_length -= length + if ans: self.list_id += 1 + return ans + + def update_media(self, new_media): + ans = False + for key, m in self._content.items(): + if m["media_id"] == new_media["media_id"]: + self._content[key] = new_media + ans = True + if ans: self.list_id += 1 + return ans + + +class UnsortedMediaList(_MediaList): + + def move(self, ids, new_pos, type = "id"): + if type == "pos": ids = [self._order[p] for p in ids] + missing_ids = [id for id in ids if id not in self._order] + if missing_ids: return False + + s_list = [id for id in self._order[:new_pos] if id not in ids] + e_list = [id for id in self._order[new_pos:] if id not in ids] + self._order = s_list + ids + e_list + self.list_id += 1 + return True + + def shuffle(self, current = None): + if not self._order: return + random.shuffle(self._order) + if current and current["id"]: + try: self._order.remove(current["id"]) + except ValueError: pass + else: + self._order = [current["id"]] + self._order + self.list_id += 1 + + +class SortedMediaList(_MediaList): + + def __init__(self, list_id = 0): + super(SortedMediaList, self).__init__(list_id) + + # + # sort actions + # + def __compare_tag(self, id1, id2, tag, direction): + m1 = self._content[id1] + m2 = self._content[id2] + if m1[tag] < m2[tag]: + return direction == "ascending" and -1 or 1 + elif m1[tag] == m2[tag]: + return 0 + else: + return direction == "ascending" and 1 or -1 + + def sort(self, sorts): + + def compare(id1, id2): + for (tag, direction) in sorts: + result = self.__compare_tag(id1, id2, tag, direction) + if result != 0: return result + return 0 + + self._order.sort(cmp=compare) + self.list_id += 1 + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/sources/panel.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/sources/panel.py --- deejayd-0.7.2/deejayd/sources/panel.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/sources/panel.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,238 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from deejayd.mediafilters import * +from deejayd.sources._base import _BaseSortedLibSource, SourceError +from deejayd.ui import log + +class PanelSource(_BaseSortedLibSource): + SUBSCRIPTIONS = { + "playlist.update": "cb_playlist_update", + "playlist.listupdate": "cb_playlist_listupdate", + "mediadb.mupdate": "cb_library_changes", + } + base_medialist = "__panelcurrent__" + name = "panel" + source_signal = 'panel.update' + supported_panel_tags = [\ + ['genre','artist','album'],\ + ['genre','various_artist','album'],\ + ['artist','album'],\ + ['various_artist','album'],\ + ] + contains_tags = ('genre','artist','album','title','all') + sort_tags = ('genre','artist','album','title','rating','tracknumber') + default_sorts = DEFAULT_AUDIO_SORT + + def __init__(self, db, library, config): + super(PanelSource, self).__init__(db, library) + + # get panel tags + self.__panel_tags = config.getlist("panel", "panel_tags") + if self.__panel_tags not in self.supported_panel_tags: + log.err(_("You choose wrong panel tags, fallback to default")) + self.__panel_tags = ['genre','artist','album'] + + # get recorded panel medialist + filter = And() + try: ml_id = self.db.get_medialist_id(self.base_medialist, 'magic') + except ValueError: # medialist does not exist + self._sorts = [] + else: + # get filters + filter.filterlist = self.db.get_magic_medialist_filters(ml_id) + # get recorded sorts + self._sorts = list(self.db.get_magic_medialist_sorts(ml_id)) or [] + self.__filters_to_parms(filter) + + # custom attributes + self.__selected_mode = { + "type": self.db.get_state("panel-type"), + "value": self.db.get_state("panel-value")} + self.__update_active_list(self.__selected_mode["type"],\ + self.__selected_mode["value"]) + + def __filters_to_parms(self, filter = None): + # filter as AND(Search, Panel) with + # * Panel : And(OR(genre=value1, genre=value2), OR(artist=value1)...) + # * Search : OR(tag1 CONTAINS value, ) + self.__search, self.__panel, self.__filter = None, {}, filter + if filter != None: + for ft in filter.filterlist: + if ft.get_name() == "and": # panel + for panel_ft in ft.filterlist: + tag = panel_ft.filterlist[0].tag + if tag in self.__panel_tags: + self.__panel[tag] = panel_ft + elif ft.get_name() == "or" or ft.type == "basic": # search + self.__search = ft + + def __update_panel_filters(self): + # rebuild filter + self.__filter = And() + if self.__search: + self.__filter.filterlist.append(self.__search) + panel_filter = And() + panel_filter.filterlist = self.__panel.values() + self.__filter.filterlist.append(panel_filter) + + if self.__selected_mode["type"] == "panel": + sorts = self._sorts + self.__class__.default_sorts + medias = self.library.search(self.__filter, sorts) + self._media_list.set(medias) + self.__update_current() + self.dispatch_signame(self.__class__.source_signal) + + def __update_active_list(self, type, pl_id, raise_ex = False): + need_sort, sorts = False, self._sorts + self.__class__.default_sorts + if type == "playlist": + try: medias = self._get_playlist_content(pl_id) + except SourceError: # playlist does not exist, set to panel + if raise_ex: + raise SourceError(_("Playlist with id %s not found")\ + % str(pl_id)) + self.__selected_mode["type"] = "panel"; + medias = self.library.search(self.__filter, sorts) + else: + need_sort = True + elif type == "panel": + medias = self.library.search(self.__filter, sorts) + else: + raise TypeError + self._media_list.set(medias) + if need_sort: self._media_list.sort(self._sorts) + + def __update_current(self): + if self._current and self._current["id"] != -1: # update current id + media_id = self._current["media_id"] + try: + self._current["id"] = self._media_list.find_id(media_id) + except ValueError: + self._current["id"] = -1 + + def get_panel_tags(self): + return self.__panel_tags + + def set_panel_filters(self, tag, values): + if tag not in self.__panel_tags: + raise SourceError(_("Tag '%s' not supported") % tag) + if not values: + self.remove_panel_filters(tag) + return + filter = Or() + for value in values: + filter.combine(Equals(tag, value)) + if tag in self.__panel and self.__panel[tag].equals(filter): + return # do not need update + self.__panel[tag] = filter + + # remove filter for panels at the right of this tag + for tg in reversed(self.get_panel_tags()): + if tg == tag: break + try: del self.__panel[tg] + except KeyError: + pass + + self.__update_panel_filters() + + def remove_panel_filters(self, tag): + try: del self.__panel[tag] + except KeyError: + pass + self.__update_panel_filters() + + def clear_panel_filters(self): + self.__panel = {} + self.__update_panel_filters() + + def set_search_filter(self, tag, value): + if tag not in self.__class__.contains_tags: + raise SourceError(_("Tag '%s' not supported") % tag) + if tag == "all": + new_filter = Or() + for tg in ('title','genre','artist','album'): + new_filter.combine(Contains(tg, value)) + else: + new_filter = Contains(tag, value) + self.__search = new_filter + self.__panel = {} # remove old panel filter + self.__update_panel_filters() + + def clear_search_filter(self): + if self.__search: + self.__search = None + self.__update_panel_filters() + + def get_active_list(self): + return self.__selected_mode + + def set_active_list(self, type, pl_id): + if type == self.__selected_mode["type"]\ + and pl_id == self.__selected_mode["value"]: + return # we do not need to update panel + self.__update_active_list(type, pl_id, raise_ex = True) + self.__selected_mode = {"type": type, "value": pl_id} + self.__update_current() + self.dispatch_signame(self.__class__.source_signal) + + def get_content(self, start = 0, stop = None): + if self.__selected_mode["type"] == "panel": + return self._media_list.get(start, stop), self.__filter,\ + self._sorts + elif self.__selected_mode["type"] == "playlist": + return self._media_list.get(start, stop), None, self._sorts + + def close(self): + states = [ + (self._playorder.name, self.name+"-playorder"), + (str(self._media_list.list_id),self.__class__.name+"id"), + (self.__selected_mode["type"],"panel-type"), + (self.__selected_mode["value"],"panel-value"), + ] + if self.has_repeat: + states.append((self._media_list.repeat, self.name+"-repeat")) + self.db.set_state(states) + # save panel filters + filter_list = self.__filter.filterlist + ml_id = self.db.set_magic_medialist_filters(self.base_medialist,\ + filter_list) + # save panel sorts + self.db.set_magic_medialist_sorts(ml_id, self._sorts) + + # + # callback for deejayd signal + # + def cb_playlist_update(self, signal): + pl_id = int(signal.get_attr('pl_id')) + if self.__selected_mode["type"] == "playlist"\ + and pl_id == int(self.__selected_mode["value"]): + self.__update_active_list("playlist", pl_id, raise_ex = True) + self.dispatch_signame(self.__class__.source_signal) + + def cb_playlist_listupdate(self, signal): + if self.__selected_mode["type"] == "playlist": + pl_id = int(self.__selected_mode["value"]) + list = [int(id) \ + for (id, pl, type) in self.db.get_medialist_list() if not \ + pl.startswith("__") or not pl.endswith("__")] + if pl_id not in list: # fall back to panel + self.__update_active_list("panel", "", raise_ex = True) + self.__selected_mode = {"type": "panel", "value": ""} + self.dispatch_signame(self.__class__.source_signal) + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/sources/playlist.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/sources/playlist.py --- deejayd-0.7.2/deejayd/sources/playlist.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/sources/playlist.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -16,126 +16,24 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import os, random -from deejayd.sources._base import _BaseSource, MediaList, MediaNotFoundError -from deejayd.mediadb.library import NotFoundException +from deejayd.sources._base import _BaseAudioLibSource -def playlist_action(func): - def playlist_action_func(self, *__args, **__kw): - if __kw.has_key('playlist') and __kw['playlist']: - pls_name = __kw['playlist'] - del __kw['playlist'] - pls_obj = MediaList(self.db) - pls_obj.load_playlist(pls_name, self.library.get_root_path()) - else: - pls_obj = None - - __kw['playlist'] = pls_obj - rs = func(self, *__args, **__kw) - if pls_obj != None: - self.db.delete_medialist(pls_name) - self.db.save_medialist(pls_obj.get(), pls_name) - return rs - - return playlist_action_func - -class PlaylistSource(_BaseSource): - pls_name = "__djcurrent__" +class PlaylistSource(_BaseAudioLibSource): + SUBSCRIPTIONS = { + "mediadb.mupdate": "cb_library_changes", + } + base_medialist = "__djcurrent__" name = "playlist" + source_signal = 'player.plupdate' - def __init__(self,db,library): - _BaseSource.__init__(self,db) - - # Load current playlist - self.library = library - self._media_list = MediaList(self.db, self.get_recorded_id()) - self._media_list.load_playlist(self.pls_name, library.get_root_path()) - - def get_list(self): - list = [pl for (pl,) in self.db.get_medialist_list() if not \ - pl.startswith("__") or not pl.endswith("__")] - return list - - def load_playlist(self, playlists, pos = None): - for pls in playlists: - self._media_list.load_playlist(pls,\ - self.library.get_root_path(), pos) - self.dispatch_signame('player.plupdate') - - @playlist_action - def get_content(self,playlist = None): - media_list = playlist or self._media_list - return media_list.get() - - @playlist_action - def add_path(self,paths,playlist = None,pos = None): - media_list = playlist or self._media_list - - songs = [] - if isinstance(paths,str): - paths = [paths] - for path in paths: - try: songs.extend(self.library.get_all_files(path)) - except NotFoundException: - # perhaps it is file - try: songs.extend(self.library.get_file(path)) - except NotFoundException: raise MediaNotFoundError - - media_list.add_media(songs,pos) - if playlist: - self.dispatch_signame('playlist.update') - else: - self.dispatch_signame('player.plupdate') - - @playlist_action - def shuffle(self, playlist = None): - media_list = playlist or self._media_list - media_list.shuffle() - if playlist: - self.dispatch_signame('playlist.update') - else: - self.dispatch_signame('player.plupdate') - - @playlist_action - def move(self, id, new_pos, type = "id", playlist = None): - media_list = playlist or self._media_list - media_list.move(id, new_pos, type) - if playlist: - self.dispatch_signame('playlist.update') - else: - self.dispatch_signame('player.plupdate') - - @playlist_action - def clear(self, playlist = None): - media_list = playlist or self._media_list - if playlist == None: - self._current = None - media_list.clear() - if playlist: - self.dispatch_signame('playlist.update') - else: - self.dispatch_signame('player.plupdate') - - @playlist_action - def delete(self, id, type = "id", playlist = None): - media_list = playlist or self._media_list - media_list.delete(id, type) - if playlist: - self.dispatch_signame('playlist.update') - else: - self.dispatch_signame('player.plupdate') + def shuffle(self): + self._media_list.shuffle(self._current) + if self._current: self._current["pos"] = 0 + self.dispatch_signame(self.__class__.source_signal) def save(self, playlist_name): - self.db.delete_medialist(playlist_name) - self.db.save_medialist(self._media_list.get(), playlist_name) - self.dispatch_signame('playlist.update') - - def rm(self, playlist_name): - self.db.delete_medialist(playlist_name) - self.dispatch_signame('playlist.update') - - def close(self): - _BaseSource.close(self) - self.save(self.pls_name) + id = self.db.set_static_medialist(playlist_name, self._media_list.get()) + self.dispatch_signame('playlist.listupdate') + return {"playlist_id": id} # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/sources/_playorder.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/sources/_playorder.py --- deejayd-0.7.2/deejayd/sources/_playorder.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/sources/_playorder.py 2009-09-21 08:44:20.000000000 +0100 @@ -0,0 +1,164 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import random + +class Order(object): + + # Not called directly, but the default implementation of + # next_explicit and next_implicit both just call this. + def next(self, medialist, current): + raise NotImplementedError + + # Not called directly, but the default implementation of + # previous_explicit calls this. Right now there is no such thing + # as previous_implicit. + def previous(self, medialist, current): + raise NotImplementedError + + # Not called directly, but the default implementation of + # set_explicit calls this. Right now there is no such thing as + # set_implicit. + def set(self, medialist, media_id): + return medialist.get_item(media_id) + + # Called when the user presses a "Next" button. + def next_explicit(self, medialist, current): + return self.next(medialist, current) + + # Called when a media ends passively, e.g. it plays through. + def next_implicit(self, medialist, current): + return self.next(medialist, current) + + # Called when the user presses a "Previous" button. + def previous_explicit(self, medialist, current): + return self.previous(medialist, current) + + # Called when the user manually selects a media + # If desired the play order can override that + def set_explicit(self, medialist, media_id): + return self.set(medialist, media_id) + + def reset(self, medialist): + pass + +class OrderInOrder(Order): + name = "inorder" + + def next(self, medialist, current): + if current is None: + return medialist.get_item_first() + else: + next = medialist.next(current) + if next is None and medialist.repeat: + next = medialist.get_item_first() + return next + + def previous(self, medialist, current): + if len(medialist) == 0: + return None + elif current is None: + return medialist.get_item_last() + else: + previous = medialist.previous(current) + if previous is None and medialist.repeat: + previous = medialist.get_item_last() + return previous + +class OrderRemembered(Order): + # Shared class for all the shuffle modes that keep a memory + # of their previously played medias. + + def __init__(self): + self._played = [] + + def next(self, medialist, current): + if current is not None: + self._played.append(current["id"]) + + def previous(self, medialist, current): + try: id = self._played.pop() + except IndexError: return None + else: return medialist.get_item(id) + + def set(self, medialist, media_id): + self._played.append(media_id) + return medialist.get_item(media_id) + + def reset(self, medialist): + self._played = [] + +class OrderShuffle(OrderRemembered): + name = "random" + + def next(self, medialist, current): + super(OrderShuffle, self).next(medialist, current) + played = set(self._played) + medias = set([m["id"] for m in medialist.get()]) + remaining = medias.difference(played) + + if remaining: + return medialist.get_item(random.choice(list(remaining))) + elif medialist.repeat and not medialist.is_empty(): + del(self._played[:]) + return medialist.get_item(random.choice(medias)) + else: + del(self._played[:]) + return None + +class OrderWeighted(OrderRemembered): + name = "random-weighted" + + def next(self, medialist, current): + super(OrderWeighted, self).next(medialist, current) + played = set(self._played) + remaining = [(m["id"],int(m["rating"])) \ + for m in medialist.get() if m["id"] not in played] + + max_score = sum([r for (id,r) in remaining]) + choice = int(random.random() * max_score) + current = 0 + for id, rating in remaining: + current += rating + if current >= choice: + return medialist.get_item(id) + + if medialist.repeat and not medialist.is_empty(): + del(self._played[:]) + return medialist.get_item_first() + else: + del(self._played[:]) + return None + +class OrderOneMedia(OrderInOrder): + name = "onemedia" + + def next_implicit(self, medialist, current): + if medialist.repeat: + return current + else: + return None + +orders = { + "onemedia": OrderOneMedia(), + "inorder": OrderInOrder(), + "random": OrderShuffle(), + "random-weighted": OrderWeighted(), + } + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/sources/queue.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/sources/queue.py --- deejayd-0.7.2/deejayd/sources/queue.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/sources/queue.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -17,53 +17,28 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import random -from deejayd.mediadb.library import NotFoundException -from deejayd.sources._base import _BaseSource, MediaList, MediaNotFoundError +from deejayd.sources._base import _BaseAudioLibSource, SourceError -class QueueSource(_BaseSource): - queue_name = "__djqueue__" +class QueueSource(_BaseAudioLibSource): + base_medialist = "__djqueue__" name = "queue" + source_signal = 'queue.update' + available_playorder = ("inorder", "random") + has_repeat = False - def __init__(self, db, audio_library): - _BaseSource.__init__(self,db) - self._media_list = MediaList(db, self.get_recorded_id() + 1) - self._media_list.load_playlist(self.queue_name,\ - audio_library.get_root_path()) - self.audio_lib = audio_library - - def add_path(self, paths, pos = None): - medias = [] - for path in paths: - try: medias.extend(self.audio_lib.get_all_files(path)) - except NotFoundException: - try: medias.extend(self.audio_lib.get_file(path)) - except NotFoundException: raise MediaNotFoundError - - self._media_list.add_media(medias, pos) - self.dispatch_signame('queue.update') - - def delete(self, id): - _BaseSource.delete(self, id) - self.dispatch_signame('queue.update') - - def load_playlist(self, playlists, pos = None): - for pls in playlists: - self._media_list.load_playlist(pls,\ - self.audio_lib.get_root_path(), pos) - - def go_to(self,nb,type = "id"): - _BaseSource.go_to(self,nb,type) + def go_to(self, nb, type = "id"): + self._current = super(QueueSource, self).go_to(nb, type) if self._current != None: - self._media_list.delete(nb, type) + self._media_list.delete([self._current["id"],]) + self.dispatch_signame(self.source_signal) return self._current - def next(self,rd): - l = self._media_list.length() - pos = 0 - if rd and l > 0: - m = random.choice(self._media_list.get()) - pos = m["pos"] - self.go_to(pos,'pos') + def next(self, explicit = True): + self._current = None + super(QueueSource, self).next(explicit) + if self._current != None: + self._media_list.delete([self._current["id"],]) + self.dispatch_signame(self.source_signal) return self._current def previous(self,rd,rpt): @@ -73,9 +48,4 @@ def reset(self): self._current = None - def close(self): - _BaseSource.close(self) - self.db.delete_medialist(self.queue_name) - self.db.save_medialist(self._media_list.get(), self.queue_name) - # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/sources/video.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/sources/video.py --- deejayd-0.7.2/deejayd/sources/video.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/sources/video.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -18,48 +18,59 @@ import os from deejayd.mediadb.library import NotFoundException -from deejayd.sources._base import _BaseSource, SimpleMediaList, \ - MediaNotFoundError +from deejayd.sources._base import _BaseSortedLibSource, SourceError +from deejayd import mediafilters -class VideoSource(_BaseSource): +class VideoSource(_BaseSortedLibSource): + SUBSCRIPTIONS = { + "mediadb.mupdate": "cb_library_changes", + } name = "video" - list_name = "__videocurrent__" + base_medialist = "__videocurrent__" + source_signal = 'video.update' + sort_tags = ('title','rating','length') + default_sorts = mediafilters.DEFAULT_VIDEO_SORT def __init__(self, db, library): - _BaseSource.__init__(self, db) - self.library = library - self._media_list = SimpleMediaList(self.get_recorded_id()) - + super(VideoSource, self).__init__(db, library) # load saved - content = self.db.get_videolist(self.list_name) - medias = [] - for (pos, dir, fn, title, len, w, h, sub, id) in content: - medias.append({ - "filename": fn, "dir": dir, "length": len, "videowidth": w, - "videoheight": h, "external_subtitle": sub, - "type": "video", "title": title, "media_id":id, - "uri": "file://" + os.path.join(self.library.get_root_path(), \ - dir, fn), - }) - self._media_list.set(medias) + try: ml_id = self.db.get_medialist_id(self.base_medialist, 'static') + except ValueError: # medialist does not exist + self._sorts = [] + else: + self._sorts = list(self.db.get_magic_medialist_sorts(ml_id)) or [] + self._media_list.set(self._get_playlist_content(ml_id)) + self.set_sorts(self._sorts) def set(self, type, value): + need_sort = False if type == "directory": try: video_list = self.library.get_all_files(value) except NotFoundException: - raise MediaNotFoundError + raise SourceError(_("Directory %s not found") % value) + need_sort = True elif type == "search": - video_list = self.library.search(value) + sorts = self._sorts + self.__class__.default_sorts + video_list = self.library.search(\ + mediafilters.Contains("title",value), sorts) else: - raise ValueError + raise SourceError(_("type %s not supported") % type) self._media_list.set(video_list) - self.dispatch_signame('video.update') + if need_sort: + self._media_list.sort(self._sorts + self.default_sorts) + self.dispatch_signame(self.source_signal) + + def get_content(self, start = 0, stop = None): + return self._media_list.get(start, stop), None, self._sorts def close(self): - _BaseSource.close(self) - # record video list in the db - self.db.delete_medialist(self.list_name) - self.db.save_medialist(self._media_list.get(), self.list_name) + super(VideoSource, self).close() + # save panel sorts + try: ml_id = self.db.get_medialist_id(self.base_medialist, 'static') + except ValueError: # medialist does not exist + pass + else: + self.db.set_magic_medialist_sorts(ml_id, self._sorts) # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/sources/webradio.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/sources/webradio.py --- deejayd-0.7.2/deejayd/sources/webradio.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/sources/webradio.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -50,18 +50,12 @@ return uris -class WebradioList(SimpleMediaList): - - def _update_list_id(self): - self._list_id += 1 - - class WebradioSource(_BaseSource): name = "webradio" def __init__(self, db): _BaseSource.__init__(self, db) - self._media_list = WebradioList(self.get_recorded_id()) + self._media_list = SimpleMediaList(self.get_recorded_id()) # load recorded webradio wbs = self.db.get_webradios() @@ -88,14 +82,14 @@ self.dispatch_signame('webradio.listupdate') return True - def delete(self, id): - _BaseSource.delete(self, id) + def delete(self, ids): + _BaseSource.delete(self, ids) self.dispatch_signame('webradio.listupdate') def get_status(self): return [ - (self.name, self._media_list.get_list_id()), - (self.name+"length", self._media_list.length()) + (self.name, self._media_list.list_id), + (self.name+"length", len(self._media_list)) ] def close(self): @@ -103,7 +97,7 @@ # save webradio self.db.clear_webradios() values = [(w["pos"],w["title"],w["uri"])\ - for w in self._media_list.get()] + for w in self._media_list.get()] self.db.add_webradios(values) # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/ui/config.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/ui/config.py --- deejayd-0.7.2/deejayd/ui/config.py 2009-11-15 05:48:11.000000000 +0000 +++ deejayd-0.9.0/deejayd/ui/config.py 2009-09-21 08:44:21.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -28,7 +28,7 @@ def __init__(self): if DeejaydConfig.__config == None: - DeejaydConfig.__config = ConfigParser.ConfigParser() + DeejaydConfig.__config = ConfigParser.SafeConfigParser() default_config_path = os.path.abspath(os.path.dirname(__file__)) DeejaydConfig.__config.readfp(open(default_config_path\ @@ -57,5 +57,7 @@ else: return bind_addresses + def write(self, fp): + self.__config.write(fp) # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/ui/defaults.conf /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/ui/defaults.conf --- deejayd-0.7.2/deejayd/ui/defaults.conf 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/ui/defaults.conf 2009-09-21 08:44:21.000000000 +0100 @@ -7,8 +7,8 @@ log = error # Modes enabled in deejayd. Available modes are : -# playlist, webradio, video, dvd -activated_modes = playlist,webradio +# playlist, panel, webradio, video, dvd +activated_modes = playlist,panel,webradio # fullscreen mode fullscreen = yes @@ -18,6 +18,7 @@ # media_backend : choose the media backend for deejayd # possible values are : auto (whichever could be loaded, first trying with # xine), xine or gstreamer +# Caution : gstreamer backend does not support video playback media_backend = auto @@ -32,12 +33,10 @@ port = 6880 # Addresses to bind to, a list of ip addresses separated by ',' or 'all'. bind_addresses = localhost -# directory which contains static files for webui -htdocs_dir = /usr/share/deejayd/htdocs # temp directory where deejayd save rdf files for webui -rdf_dir = /tmp/deejayd_webui +tmp_dir = /tmp/deejayd_webui # Number of seconds between auto-refreshes of ui. -# set to 0 if you don't want to refresh webui. +# set to 0 if you don't want to refresh automatically the webui. refresh = 0 [database] @@ -59,6 +58,16 @@ video_directory = /var/lib/deejayd/video filesystem_charset = utf-8 +[panel] +# panel_tags : set panel tags for panel mode +# supported lists are +# * genre,artist,album (default) +# * artist,album +# * genre,various_artist,album +# * various_artist,album +# various_artist is equivalent to artist, except compilation albums are +# grouped inside "Various Artist" label +panel_tags = genre,artist,album [gstreamer] # Audio Ouput @@ -69,18 +78,17 @@ # valid only for alsa output #alsa_card = hw:2 -# Video Ouput -# Possible values are : auto, xv, x -video_output = auto - - [xine] # Audio Ouput # Possible values are : auto,alsa, oss... audio_output = auto +# Software Mixer Use +# set to true to use software mixer instead of hardware +software_mixer = false + # Video Ouput -# Possible values are : auto,Xv, .. +# Possible values are : auto,Xv,xshm .. video_output = auto # Video Display diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/ui/i18n.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/ui/i18n.py --- deejayd-0.7.2/deejayd/ui/i18n.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/ui/i18n.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -26,8 +26,12 @@ self.plural = lambda n: n > 1 gettext.GNUTranslations.__init__(self, *args, **kwargs) - def install(self): - __builtin__.__dict__["_"] = self.gettext - __builtin__.__dict__["ngettext"] = self.ngettext + def install(self, unicode = True): + if unicode: + __builtin__.__dict__["_"] = self.ugettext + __builtin__.__dict__["ngettext"] = self.ungettext + else: + __builtin__.__dict__["_"] = self.gettext + __builtin__.__dict__["ngettext"] = self.ngettext # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/ui/__init__.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/ui/__init__.py --- deejayd-0.7.2/deejayd/ui/__init__.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/ui/__init__.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/ui/log.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/ui/log.py --- deejayd-0.7.2/deejayd/ui/log.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/ui/log.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -17,8 +17,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import signal -import sys +import signal, sys, locale from twisted.python import log from deejayd.ui.config import DeejaydConfig @@ -78,19 +77,21 @@ def err(err, fatal = False): - log.msg(_("ERROR - %s") % err) + msg = _("ERROR - %s") % err + log.msg(msg.encode(locale.getpreferredencoding())) if fatal: sys.exit(err) def msg(msg): - log.msg(msg) + log.msg(msg.encode(locale.getpreferredencoding())) def info(msg): if log_level >= INFO: - log.msg(_("INFO - %s") % msg) + msg = _("INFO - %s") % msg + log.msg(msg.encode(locale.getpreferredencoding())) def debug(msg): if log_level >= DEBUG: - log.msg(_("DEBUG - %s") % msg) - + msg = _("DEBUG - %s") % msg + log.msg(msg.encode(locale.getpreferredencoding())) # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/utils.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/utils.py --- deejayd-0.7.2/deejayd/utils.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/utils.py 2009-09-21 08:44:21.000000000 +0100 @@ -0,0 +1,72 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import urllib +from deejayd.ui import log + +def quote_uri(path): + if type(path) is unicode: + path = path.encode('utf-8') + return "file://%s" % urllib.quote(path) + +def str_encode(data, charset = 'utf-8'): + if type(data) is unicode: return data + try: rs = data.decode(charset, "strict") + except UnicodeError: + log.err(_("%s string has wrong characters, skip it") %\ + data.decode(charset, "ignore").encode("utf-8","ignore")) + raise UnicodeError + return unicode(rs) + +def format_time(time): + """Turn a time value in seconds into hh:mm:ss or mm:ss.""" + if time >= 3600: # 1 hour + # time, in hours:minutes:seconds + return "%d:%02d:%02d" % (time // 3600, (time % 3600) // 60, time % 60) + else: + # time, in minutes:seconds + return "%d:%02d" % (time // 60, time % 60) + +def format_time_long(time): + """Turn a time value in seconds into x hours, x minutes, etc.""" + if time < 1: return _("No time information") + cutoffs = [ + (60, "%d seconds", "%d second"), + (60, "%d minutes", "%d minute"), + (24, "%d hours", "%d hour"), + (365, "%d days", "%d day"), + (None, "%d years", "%d year"), + ] + time_str = [] + for divisor, plural, single in cutoffs: + if time < 1: break + if divisor is None: time, unit = 0, time + else: time, unit = divmod(time, divisor) + if unit: time_str.append(ngettext(single, plural, unit) % unit) + time_str.reverse() + if len(time_str) > 2: time_str.pop() + return ", ".join(time_str) + + ngettext("%d second", "%d seconds", 1) + ngettext("%d minute", "%d minutes", 1) + ngettext("%d hour", "%d hours", 1) + ngettext("%d day", "%d days", 1) + ngettext("%d year", "%d years", 1) + + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/webui/commands.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/webui/commands.py --- deejayd-0.7.2/deejayd/webui/commands.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/webui/commands.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,535 +0,0 @@ -# Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer -# Alexandre Rossi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -from deejayd.ui.config import DeejaydConfig - -class ArgError(Exception): pass - -class _UnknownCommand: - method = "get" - command_args = [] - - def __init__(self, deejayd, answer): - self._deejayd = deejayd - self._answer = answer - self._args = {} - - def argument_validation(self, http_args): - for arg in self.command_args: - if arg['name'] in http_args: - # format http parms - value = http_args[arg['name']] - if "mult" in arg.keys() and arg["mult"]: - if not isinstance(value, list): value = [value] - self._args[arg['name']] = value - else: - if isinstance(value, list): value = value[0] - self._args[arg['name']] = value - value = [value] - - for v in value: - if arg['type'] == "string": - try: v.split() - except AttributeError: - raise ArgError(_("Arg %s (%s) is not a string") % \ - (arg['name'], str(v))) - - elif arg['type'] == "int": - try: v = int(v) - except (ValueError,TypeError): - raise ArgError(_("Arg %s (%s) is not a int") %\ - (arg['name'], str(v))) - - elif arg['type'] == "enum_str": - if v not in arg['values']: - raise ArgError(\ - _("Arg %s (%s) is not in the possible list")\ - % (arg['name'],str(v))) - - elif arg['type'] == "enum_int": - try: v = int(v) - except (ValueError,TypeError): - raise ArgError(_("Arg %s (%s) is not a int") %\ - (arg['name'], str(v))) - else: - if v not in arg['values']: - raise ArgError(\ - _(\ - "Arg %s (%s) is not in the possible list")\ - % (arg['name'],str(v))) - - elif arg['type'] == "regexp": - import re - if not re.compile(arg['value']).search(v): - raise ArgError(\ - _("Arg %s (%s) not match to the regular exp (%s)") % - (arg['name'],str(v),arg['value'])) - - elif arg['req']: - raise ArgError(_("Arg %s is mising") % arg['name']) - else: - self._args[arg['name']] = arg['default'] - - def default_result(self): - status = self._deejayd.get_status() - # player update - cur = None - cur_list = self._deejayd.get_current().get_medias() - if len(cur_list) == 1: - cur = cur_list[0] - self._answer.set_player(status, cur) - - # source update - self._answer.set_queue(status, self._deejayd) - if "playlist" in status.keys(): - self._answer.set_playlist(status, self._deejayd) - if "webradio" in status.keys(): - self._answer.set_webradio(status, self._deejayd) - if "dvd" in status.keys(): - self._answer.set_dvd(status, self._deejayd) - if "video" in status.keys(): - self._answer.set_video(status, self._deejayd) - - # video library update - stats = self._deejayd.get_stats() - if "video" in status.keys() and "video_library_update" in stats.keys(): - self._answer.set_videodir(int(stats["video_library_update"]),\ - self._deejayd) - - def execute(self): - raise NotImplementedError - -class Init(_UnknownCommand): - name = "init" - - def execute(self): - status = self._deejayd.get_status() - # available modes - av_modes = self._deejayd.get_mode() - self._answer.set_available_modes(av_modes) - - # current mode - self._answer.set_view_mode(status["mode"]) - - # locale string - strings = {"confirm": _('Are you sure ?'), - "missParm": _('It misses a parameter !'), - "replacePls": _('Do you want to replace this playlist ?')} - self._answer.set_locale_strings(strings) - - # config parms - config = DeejaydConfig() - refresh = config.get('webui','refresh') - self._answer.set_config({"refresh": refresh}) - - if "playlist" in status.keys(): - # audio files list - files_list = self._deejayd.get_audio_dir("") - self._answer.set_audiofile_list(files_list, "") - - # playlist list - pls_list = self._deejayd.get_playlist_list() - self._answer.set_playlist_list(pls_list.get_medias()) - -class Refresh(_UnknownCommand): - name = "refresh" - def execute(self): pass - -class SetMode(_UnknownCommand): - name = "setMode" - command_args = [{"name": "mode", "type": "string", "req": True}] - - def execute(self): - self._deejayd.set_mode(self._args["mode"]).get_contents() - self._answer.set_view_mode(self._args["mode"]) - -# -# Player controls -# -class PlayToggle(_UnknownCommand): - name = "playtoggle" - - def execute(self): - self._deejayd.play_toggle().get_contents() - -class GoTo(_UnknownCommand): - name = "goto" - command_args = [{"name": "id", "type": "regexp",\ - "value":"^\w{1,}|\w{1,}\.\w{1,}$","req": True}, - {"name": "id_type", "type": "string", "req": False, "default": "id"}, - {"name":"source", "type": "string", "req": False, "default": None}] - - def execute(self): - self._deejayd.go_to(self._args["id"], self._args["id_type"], \ - self._args["source"]).get_contents() - -class Stop(_UnknownCommand): - name = "stop" - - def execute(self): - self._deejayd.stop().get_contents() - -class Next(_UnknownCommand): - name = "next" - - def execute(self): - self._deejayd.next().get_contents() - -class Previous(_UnknownCommand): - name = "previous" - - def execute(self): - self._deejayd.previous().get_contents() - -class _Options(_UnknownCommand): - def execute(self): - status = self._deejayd.get_status() - val = status[self.__class__.name] == 1 and (0,) or (1,) - self._deejayd.set_option(self.__class__.name,val[0]).get_contents() - -class Random(_Options): - name = "random" - -class QueueRandom(_Options): - name = "qrandom" - -class Repeat(_Options): - name = "repeat" - -class Volume(_UnknownCommand): - name = "setVol" - command_args = [ - {"name":"volume", "type":"enum_int", "req":True, "values":range(0,101)}] - - def execute(self): - self._deejayd.set_volume(int(self._args["volume"])).get_contents() - -class Seek(_UnknownCommand): - name = "setTime" - command_args = [{"name": "time", "type": "int", "req": True}] - - def execute(self): - status = self._deejayd.get_status() - if status["state"] != "stop": - self._deejayd.seek(self._args["time"]) - -class PlayerOption(_UnknownCommand): - name = "setPlayerOption" - command_args = [{"name": "option_name", "type": "str", "req": True}, - {"name": "set_type", "type": "enum_str",\ - "values": ("up", "down", "value"), "req": False, "default": "value"}, - {"name": "option_value", "type": "int", "req": True}] - - def execute(self): - if self._args["set_type"] == "value": - val = int(self._args["option_value"]) - else: - current = self._deejayd.get_current().get_medias() - try: current = current[0] - except (IndexError, TypeError): return - if self._args["option_name"] not in current.keys(): - return - val = current[self._args["option_name"]] - if self._args["set_type"] == "up": - val += int(self._args["option_value"]) - else: val -= int(self._args["option_value"]) - - self._deejayd.set_player_option(self._args["option_name"], val).\ - get_contents() - -# -# Library commands -# -class _Library(_UnknownCommand): - def default_result(self): pass - -class AudioLibraryUpdate(_Library): - name = "audioUpdate" - - def execute(self): - rs = self._deejayd.update_audio_library() - self._answer.set_update_library(rs["audio_updating_db"], "audio") - -class VideoLibraryUpdate(_Library): - name = "videoUpdate" - - def execute(self): - rs = self._deejayd.update_video_library() - self._answer.set_update_library(rs["video_updating_db"], "video") - -class AudioUpdateCheck(_Library): - name = "audio_update_check" - command_args = [{"name": "id", "type": "int", "req": True}] - - def execute(self): - status = self._deejayd.get_status() - if "audio_updating_db" in status.keys() and \ - int(status["audio_updating_db"]) == int(self._args["id"]): - self._answer.set_update_library(self._args["id"], "audio") - else: - self._answer.set_update_library(self._args["id"], "audio", "0") - if "audio_updating_error" in status.keys(): - self._answer.set_error(status["audio_updating_error"]) - else: - self._answer.set_msg(_("The audio library has been updated")) - - files_list = self._deejayd.get_audio_dir() - self._answer.set_audiofile_list(files_list, "") - -class VideoUpdateCheck(_Library): - name = "video_update_check" - command_args = [{"name": "id", "type": "int", "req": True}] - - def execute(self): - status = self._deejayd.get_status() - if "video_updating_db" in status.keys() and \ - int(status["video_updating_db"]) == int(self._args["id"]): - self._answer.set_update_library(self._args["id"], "video") - else: - self._answer.set_update_library(self._args["id"], "video", "0") - if "video_updating_error" in status.keys(): - self._answer.set_error(status["video_updating_error"]) - else: - self._answer.set_msg(_("The video library has been updated")) - - stats = self._deejayd.get_stats() - self._answer.set_videodir(stats["video_library_update"],\ - self._deejayd) - -class GetAudioDir(_Library): - name = "getdir" - method = "post" - command_args = [{"name":"dir","type":"string","req":False,"default":""}] - - def execute(self): - files_list = self._deejayd.get_audio_dir(self._args["dir"]) - self._answer.set_audiofile_list(files_list, self._args["dir"]) - -class AudioSearch(_Library): - name = "search" - method = "post" - command_args = [{"name":"type", "type":"enum_str", - "values": ('all','title','genre','filename','artist', - 'album'),"req":True}, - {"name":"txt", "type":"string", "req":True}] - - def execute(self): - files_list = self._deejayd.audio_search(self._args["txt"], - self._args["type"]) - self._answer.set_audiofile_list(files_list) - -# -# Playlist commands -# -class PlaylistAdd(_UnknownCommand): - name = "playlistAdd" - method = "post" - command_args = [{"name":"path","type":"string","req":True,"mult": True},\ - {"name":"name","type":"string","req":False,"default":None}, - {"name":"pos","type":"int","req":False,"default":-1}] - - def execute(self): - pos = int(self._args["pos"]) - if pos == -1: pos = None - - pls = self._deejayd.get_playlist(self._args["name"]) - pls.add_songs(self._args["path"],pos).get_contents() - -class PlaylistRemove(_UnknownCommand): - name = "playlistRemove" - method = "post" - command_args = [{"name":"ids","type":"int","req":True,"mult":True},] - - def execute(self): - pls = self._deejayd.get_playlist() - pls.del_songs(self._args["ids"]).get_contents() - -class PlaylistLoad(_UnknownCommand): - name = "playlistLoad" - method = "post" - command_args = [{"name":"pls_name","type":"string","req":True,"mult":True},\ - {"name":"name","type":"string","req":False,"default":None}, - {"name":"pos","type":"int","req":True}] - - def execute(self): - pos = int(self._args["pos"]) - if pos == -1: pos = None - - pls = self._deejayd.get_playlist(self._args["name"]) - pls.loads(self._args["pls_name"],pos).get_contents() - -class PlaylistMove(_UnknownCommand): - name = "playlistMove" - method = "post" - command_args = [{"name":"ids","type":"int","req":True,"mult": True}, - {"name":"new_pos","type":"int","req":True}] - - def execute(self): - ids = [int(id) for id in self._args["ids"]] - pls = self._deejayd.get_playlist() - pls.move(ids, int(self._args["new_pos"])).get_contents() - -class PlaylistSave(_UnknownCommand): - name = "playlistSave" - method = "post" - command_args = [{"name":"name","type":"string","req":True}] - - def default_result(self): - pls_list = self._deejayd.get_playlist_list() - self._answer.set_playlist_list(pls_list.get_medias()) - - def execute(self): - pls = self._deejayd.get_playlist() - pls.save(self._args["name"]).get_contents() - self._answer.set_msg(_("Current playlist has been saved")) - -class PlaylistErase(_UnknownCommand): - name = "playlistErase" - method = "post" - command_args = [{"name":"name","type":"string","req":True,"mult":True}] - - def default_result(self): - pls_list = self._deejayd.get_playlist_list() - self._answer.set_playlist_list(pls_list.get_medias()) - - def execute(self): - self._deejayd.erase_playlist(self._args["name"]).get_contents() - -class PlaylistShuffle(_UnknownCommand): - name = "playlistShuffle" - - def execute(self): - pls = self._deejayd.get_playlist() - pls.shuffle().get_contents() - -class PlaylistClear(_UnknownCommand): - name = "playlistClear" - - def execute(self): - pls = self._deejayd.get_playlist() - pls.clear().get_contents() - -# -# Queue commands -# -class QueueAdd(_UnknownCommand): - name = "queueAdd" - method = "post" - command_args = [{"name":"path","type":"string","req":True,"mult":True},\ - {"name":"pos","type":"int","req":True}] - - def execute(self): - pos = int(self._args["pos"]) - if pos == -1: pos = None - - queue = self._deejayd.get_queue() - queue.add_medias(self._args["path"], pos).get_contents() - -class QueueLoad(_UnknownCommand): - name = "queueLoad" - method = "post" - command_args = [{"name":"pls_name","type":"string","req":True,"mult":True},\ - {"name":"pos","type":"int","req":True}] - - def execute(self): - pos = int(self._args["pos"]) - if pos == -1: pos = None - - queue = self._deejayd.get_queue() - queue.load_playlists(self._args["pls_name"],pos).get_contents() - -class QueueRemove(_UnknownCommand): - name = "queueRemove" - method = "post" - command_args = [{"name":"ids","type":"int","req":True,"mult":True},] - - def execute(self): - queue = self._deejayd.get_queue() - queue.del_songs(self._args["ids"]).get_contents() - -class QueueClear(_UnknownCommand): - name = "queueClear" - - def execute(self): - queue = self._deejayd.get_queue() - queue.clear().get_contents() - -# -# Webradio commands -# -class WebradioAdd(_UnknownCommand): - name = "webradioAdd" - method = "post" - command_args = [{"name":"name","type":"string","req":True},\ - {"name":"url","type":"string","req":True},] - - def execute(self): - wb = self._deejayd.get_webradios() - wb.add_webradio(self._args["name"], self._args["url"]).get_contents() - -class WebradioDelete(_UnknownCommand): - name = "webradioRemove" - method = "post" - command_args = [{"name":"ids","type":"int","req":True,"mult":True},] - - def execute(self): - wb = self._deejayd.get_webradios() - wb.delete_webradios(self._args["ids"]).get_contents() - -class WebradioClear(_UnknownCommand): - name = "webradioClear" - - def execute(self): - wb = self._deejayd.get_webradios() - wb.clear().get_contents() - -# -# Video commands -# -class SetVideo(_UnknownCommand): - name = "videoset" - method = "post" - command_args = [{"name":"value", "type":"str", "req":False, "default":""}, - {"name":"type","type":"enum_str","values":("directory","search"),\ - "req":False,"default":"directory"},] - - def execute(self): - video = self._deejayd.get_video() - video.set(self._args["value"], self._args["type"]).get_contents() - -# -# Dvd commands -# -class DvdLoad(_UnknownCommand): - name = "dvdLoad" - - def execute(self): - self._deejayd.dvd_reload().get_contents() - -########################################################################### -commands = {} - -import sys -thismodule = sys.modules[__name__] -for itemName in dir(thismodule): - try: - item = getattr(thismodule, itemName) - commands[item.name] = item - except AttributeError: - pass -# Build the list of available commands diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/webui/__init__.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/webui/__init__.py --- deejayd-0.7.2/deejayd/webui/__init__.py 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/webui/__init__.py 2009-09-21 08:44:20.000000000 +0100 @@ -1,5 +1,5 @@ # Deejayd, a media player daemon -# Copyright (C) 2007-2008 Mickael Royer +# Copyright (C) 2007-2009 Mickael Royer # Alexandre Rossi # # This program is free software; you can redistribute it and/or modify @@ -15,4 +15,130 @@ # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os,shutil +from twisted.web import static,server +from twisted.web.resource import Resource + +from deejayd.interfaces import DeejaydError +from deejayd.ui import log + +# jsonrpc import +from deejayd.webui.jsonrpc import JSONRPC +from deejayd.rpc.protocol import build_protocol, set_web_subhandler + +# xul parts +from deejayd.webui.xul import build as xul_build +# mobile parts +from deejayd.webui import mobile + + +class DeejaydWebError(DeejaydError): pass + +class DeejaydMainHandler(Resource): + + def getChild(self, name, request): + if name == '': return self + return Resource.getChild(self,name,request) + + def render_GET(self, request): + user_agent = request.getHeader("user-agent"); + root = request.prepath[-1] != '' and request.path + '/' or request.path + if user_agent.lower().find("mobile") != -1: + # redirect to specific mobile interface + request.redirect(root + 'm/') + return 'redirected' + else: # default xul interface + request.redirect(root + 'xul/') + return 'redirected' + + +class DeejaydXulHandler(Resource): + + def __init__(self, config): + Resource.__init__(self) + self.__config = config + + def getChild(self, name, request): + if name == '': return self + return Resource.getChild(self,name,request) + + def render_GET(self, request): + request.setHeader("Content-Type", "application/vnd.mozilla.xul+xml") + return xul_build(self.__config) + + +class DeejaydMobileHandler(Resource): + + def __init__(self, deejayd, config): + Resource.__init__(self) + self.__deejayd = deejayd + self.__config = config + + def getChild(self, name, request): + if name == '': return self + return Resource.getChild(self,name,request) + + def render_GET(self, request): + # Trailing slash is required for js script paths in the mobile webui, + # therefore we need to add it if it is missing, by issuing a redirect + # to the web browser. + if request.prepath[-1] != '': + request.redirect(request.path + '/') + return 'redirected' + + user_agent = request.getHeader("user-agent"); + return mobile.build_template(self.__deejayd, user_agent) + + +class SiteWithCustomLogging(server.Site): + + def _openLogFile(self, path): + self.log_file = log.LogFile(path, False) + self.log_file.set_reopen_signal(callback=self.__reopen_cb) + return self.log_file.fd + + def __reopen_cb(self, signal, frame): + self.log_file.reopen() + # Change the logfile fd from HTTPFactory internals. + self.logFile = self.log_file.fd + + +def init(deejayd_core, config, webui_logfile, htdocs_dir): + # create tmp directory + tmp_dir = config.get("webui","tmp_dir") + if os.path.isdir(tmp_dir): + try: shutil.rmtree(tmp_dir) + except (IOError, OSError): + raise DeejaydWebError(_("Unable to remove tmp directory %s") % \ + tmp_dir) + try: os.mkdir(tmp_dir) + except IOError: + raise DeejaydWebError(_("Unable to create tmp directory %s") % tmp_dir) + + if not os.path.isdir(htdocs_dir): + raise DeejaydWebError(_("Htdocs directory %s does not exists") % \ + htdocs_dir) + + # main handler + main_handler = DeejaydMainHandler() + # json-rpc handler + rpc_handler = JSONRPC(deejayd_core) + rpc_handler = build_protocol(deejayd_core, rpc_handler) + rpc_handler = set_web_subhandler(deejayd_core, tmp_dir, rpc_handler) + main_handler.putChild("rpc",rpc_handler) + # statics url + main_handler.putChild("tmp",static.File(tmp_dir)) + main_handler.putChild("static",static.File(htdocs_dir)) + + # xul part + xul_handler = DeejaydXulHandler(config) + main_handler.putChild("xul", xul_handler) + + # mobile part + mobile_handler = DeejaydMobileHandler(deejayd_core, config) + main_handler.putChild("m", mobile_handler) + + return SiteWithCustomLogging(main_handler, logPath=webui_logfile) + # vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/webui/jsonrpc.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/webui/jsonrpc.py --- deejayd-0.7.2/deejayd/webui/jsonrpc.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/webui/jsonrpc.py 2009-09-21 08:44:21.000000000 +0100 @@ -0,0 +1,130 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""A generic resource for publishing objects via JSON-RPC. +Requires simplejson; can be downloaded from +http://cheeseshop.python.org/pypi/simplejson +""" +from __future__ import nested_scopes + +# System Imports +import urlparse + +# Sibling Imports +from twisted.web import resource, server +from twisted.internet import defer, protocol, reactor +from twisted.web import http + +from deejayd.ui import log +from deejayd.rpc import Fault +from deejayd.rpc.jsonparsers import loads_request +from deejayd.rpc.jsonbuilders import JSONRPCResponse +from deejayd.rpc import protocol as deejayd_protocol + + +class Handler: + """Handle a JSON-RPC request and store the state for a request in progress. + + Override the run() method and return result using self.result, + a Deferred. + + We require this class since we're not using threads, so we can't + encapsulate state in a running function if we're going to have + to wait for results. + + For example, lets say we want to authenticate against twisted.cred, + run a LDAP query and then pass its result to a database query, all + as a result of a single JSON-RPC command. We'd use a Handler instance + to store the state of the running command. + """ + + def __init__(self, resource, *args): + self.resource = resource # the JSON-RPC resource we are connected to + self.result = defer.Deferred() + self.run(*args) + + def run(self, *args): + # event driven equivalent of 'raise UnimplementedError' + self.result.errback(NotImplementedError("Implement run() in subclasses")) + + +class JSONRPC(resource.Resource, deejayd_protocol.DeejaydHttpJSONRPC): + """A resource that implements JSON-RPC. + + Methods published can return JSON-RPC serializable results, Faults, + Binary, Boolean, DateTime, Deferreds, or Handler instances. + + By default methods beginning with 'jsonrpc_' are published. + + Sub-handlers for prefixed methods (e.g., system.listMethods) + can be added with putSubHandler. By default, prefixes are + separated with a '.'. Override self.separator to change this. + """ + + # Error codes for Twisted, if they conflict with yours then + # modify them at runtime. + NOT_FOUND = 8001 + FAILURE = 8002 + + isLeaf = 1 + + def __init__(self, deejayd): + super(JSONRPC, self).__init__() + self.subHandlers = {} + self.deejayd_core = deejayd + + def render(self, request): + request.content.seek(0, 0) + # Unmarshal the JSON-RPC data + content = request.content.read() + try: + parsed = loads_request(content) + args, functionPath = parsed['params'], parsed["method"] + function = self._getFunction(functionPath) + except Fault, f: + try: id = parsed["id"] + except: + id = None + self._cbRender(f, request, id) + else: + request.setHeader("content-type", "text/json") + defer.maybeDeferred(function, *args).addErrback( + self._ebRender, parsed["id"] + ).addCallback( + self._cbRender, request, parsed["id"] + ) + return server.NOT_DONE_YET + + def _cbRender(self, result, request, id): + if isinstance(result, Handler): + result = result.result + # build json answer + ans = JSONRPCResponse(result, id).to_json() + request.setHeader("content-length", str(len(ans))) + request.write(ans) + request.finish() + + def _ebRender(self, failure, id): + if isinstance(failure.value, Fault): + return failure.value + log.err(failure) + return Fault(self.FAILURE, "error") + +__all__ = ["JSONRPC", "Handler"] + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/webui/mobile.py /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/webui/mobile.py --- deejayd-0.7.2/deejayd/webui/mobile.py 1970-01-01 01:00:00.000000000 +0100 +++ deejayd-0.9.0/deejayd/webui/mobile.py 2009-09-21 08:44:21.000000000 +0100 @@ -0,0 +1,309 @@ +# Deejayd, a media player daemon +# Copyright (C) 2007-2009 Mickael Royer +# Alexandre Rossi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#from deejayd.utils import str_encode + +class IphoneBrowser(object): + + def is_true(self, user_agent): + if user_agent.lower().find("iphone") != -1: + return True + return False + + def header(self): + return """ + + + +""" + +class WebkitBrowser(object): + + def is_true(self, user_agent): + if user_agent.lower().find("applewebkit") != -1: + return True + return False + + def header(self): + return """ + +""" + +class DefaultBrowser(object): + + def is_true(self, user_agent): + return True + + def header(self): + return "" + +browsers = [IphoneBrowser(), WebkitBrowser(), DefaultBrowser()] + +def build_template(deejayd, user_agent): + global browsers + for bw in browsers: + if bw.is_true(user_agent): + browser = bw + break + + # build modelist + mode_list = deejayd.get_mode().get_contents() + av_modes = [k for (k, v) in mode_list.items() if int(v) == 1] + mode_title = { + "playlist": _("Playlist Mode"), + "panel": _("Panel Mode"), + "video": _("Video Mode"), + "webradio": _("Webradio Mode"), + "dvd": _("DVD Mode"), + } + modes = [] + button = """ +
+ %(title)s +
+ """ + for m in av_modes: + modes.append(button % {"mode": m, "title": mode_title[m]}) + + tpl = """ + + + + Deejayd Webui + + + + %(header)s + + + + + + + + + + + + + + +
+
+
+ %(current-mode)s +
+
+ %(no-playing-media)s +
+
+ %(refresh)s +
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + +""" % { + "header": browser.header(), + "now-playing": _("Now Playing"), + "no-playing-media": _("No Playing Media"), + "mode-list": _("Mode List"), + "current-mode": _("Current Mode"), + "modelist-content": "\n".join(modes), + "close": _("Close"), + "refresh": _("Refresh"), + # options + "in-order": _("In Order"), + "random": _("Random"), + "wrandom": _("Weighted Random"), + "one-media": _("One Media"), + "repeat": _("Repeat"), + "save-options": _("Save Options"), + "play-order": _("Play Order"), + # js localisation + "loading": _("Loading..."), + "load-files": _("Load Files"), + "audio-library": _("Audio Library"), + "video-library": _("Video Library"), + "search": _("Search"), + "pls-mode": _("Playlist Mode"), + "video-mode": _("Video Mode"), + "wb-mode": _("Webradio Mode"), + "panel-mode": _("Panel Mode"), + "dvd-mode": _("DVD Mode"), + "wb-name": _("Webradio Name"), + "wb-url": _("Webradio URL"), + "add": _("Add"), + "wb-add": _("Add a Webradio"), + "all": _("All"), + "various": _("Various Artist"), + "unknown": _("Unknown"), + "genre": _("Genre"), + "artist": _("Artist"), + "album": _("Album"), +} + return str(tpl.encode('utf-8')) + +# vim: ts=4 sw=4 expandtab diff -Nru /tmp/SuSgfU94U6/deejayd-0.7.2/deejayd/webui/templates/dvd.xml /tmp/LOo8qwLBV9/deejayd-0.9.0/deejayd/webui/templates/dvd.xml --- deejayd-0.7.2/deejayd/webui/templates/dvd.xml 2008-05-14 22:47:38.000000000 +0100 +++ deejayd-0.9.0/deejayd/webui/templates/dvd.xml 1970-01-01 01:00:00.000000000 +0100 @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - -