~m-buck/+junk/gtk-desktop-info

« back to all changes in this revision

Viewing changes to plugin_exaile.py

  • Committer: Mark Buck (Kaivalagi)
  • Date: 2009-06-19 17:13:00 UTC
  • Revision ID: m_buck@hotmail.com-20090619171300-5cbhr90xwg62z27y
Added --backgroundblend and --backgroundcolour options for visual seperation of output from wallpaper if required, Fixed song length output in the rhythmbox plugin when songs are an hour long or more, Added copy option to right click, enabling the copying of html content to the clipboard for testing, Moved common functions into a plugin_common module

Show diffs side-by-side

added added

removed removed

Lines of Context:
6
6
#
7
7
#  Author: Kaivalagi
8
8
# Created: 23/11/2008
9
 
from htmlentitydefs import name2codepoint, codepoint2name
10
9
from optparse import OptionParser
11
 
import traceback
 
10
from plugin_common import getHTMLText, getTypedValue
12
11
import codecs
 
12
import fileinput
 
13
import logging
13
14
import os
 
15
import re
14
16
import shutil
 
17
import traceback
15
18
import urllib
16
 
import re
17
 
import logging
18
 
import fileinput
19
19
 
20
20
try:
21
21
    import dbus
42
42
 
43
43
class ExaileConfig:
44
44
    HEADERTEMPLATE = None
45
 
    TEMPLATE = None       
 
45
    TEMPLATE = None
46
46
    STATUSTEXT = "Playing,Paused,Stopped"
47
47
    NOUNKNOWNOUTPUT = False
48
 
    
 
48
 
49
49
class MusicData:
50
50
    def __init__(self,status,coverart,title,album,length,artist,tracknumber,genre,year,filename,current_position_percent,current_position,rating,volume):
51
51
        self.status = status
57
57
        self.tracknumber = tracknumber
58
58
        self.genre = genre
59
59
        self.year = year
60
 
        self.filename = filename        
 
60
        self.filename = filename
61
61
        self.current_position_percent = current_position_percent
62
62
        self.current_position = current_position
63
63
        self.rating = rating
64
64
        self.volume = volume
65
 
        
 
65
 
66
66
class Output:
67
 
    
 
67
 
68
68
    output = u""
69
69
    error = u""
70
70
    musicData = None
75
75
        self.loadConfigData()
76
76
 
77
77
    def loadConfigData(self):
78
 
        try:         
 
78
        try:
79
79
 
80
80
            self.config = ExaileConfig()
81
 
            
 
81
 
82
82
            if self.options.config != None:
83
83
                # load the config based on options passed in from the main app
84
84
                configfilepath = self.options.config
85
85
            else:
86
86
                # load plugin config from home directory of the user
87
87
                configfilepath = os.path.join(os.path.expanduser('~'), ".config/"+app_name+"/"+module_name+".config")
88
 
                            
 
88
 
89
89
            if os.path.exists(configfilepath):
90
 
                
 
90
 
91
91
                self.logger.info("Loading config settings from \"%s\""%configfilepath)
92
92
 
93
 
                
 
93
 
94
94
                for line in fileinput.input(os.path.expanduser(configfilepath)):
95
95
                    line = line.strip()
96
96
                    if len(line) > 0 and line[0:1] != "#": # ignore commented lines or empty ones
97
 
    
 
97
 
98
98
                        name = line.split("=")[0].strip().upper() # config setting name on the left of =
99
99
                        value = line.split("=")[1].split("#")[0].strip() # config value on the right of = (minus any trailing comments)
100
 
                                                     
 
100
 
101
101
                        if len(value) > 0:
102
102
                            if name == "HEADERTEMPLATE":
103
 
                                self.config.HEADERTEMPLATE = self.getTypedValue(value, "string")
 
103
                                self.config.HEADERTEMPLATE = getTypedValue(value, "string")
104
104
                            if name == "TEMPLATE":
105
 
                                self.config.TEMPLATE = self.getTypedValue(value, "string")
 
105
                                self.config.TEMPLATE = getTypedValue(value, "string")
106
106
                            elif name == "STATUSTEXT":
107
 
                                self.config.STATUSTEXT = self.getTypedValue(value, "string")
 
107
                                self.config.STATUSTEXT = getTypedValue(value, "string")
108
108
                            elif name == "NOUNKNOWNOUTPUT":
109
 
                                self.config.NOUNKNOWNOUTPUT = self.getTypedValue(value, "boolean")                                                                                                                            
 
109
                                self.config.NOUNKNOWNOUTPUT = getTypedValue(value, "boolean")
110
110
                            else:
111
 
                                self.logger.error("Unknown option in config file: " + name)               
 
111
                                self.logger.error("Unknown option in config file: " + name)
112
112
            else:
113
113
                self.logger.info("Config data file %s not found, using defaults and setting up config file for next time" % configfilepath)
114
 
                
 
114
 
115
115
                userconfigpath = os.path.join(os.path.expanduser('~'), ".config/"+app_name+"/")
116
116
                configsource = os.path.join(app_path, "config/"+module_name+".config")
117
 
                
 
117
 
118
118
                if os.path.exists(userconfigpath) == False:
119
119
                    os.makedirs(userconfigpath)
120
120
 
123
123
        except Exception, e:
124
124
            self.logger.error(e.__str__()+"\n"+traceback.format_exc())
125
125
 
126
 
    def getTypedValue(self, value, expectedtype):
127
 
        
128
 
        try:
129
 
            if len(value.strip(" ")) == 0:
130
 
                return None
131
 
            
132
 
            elif value.lower() == "true":
133
 
                if expectedtype == "boolean":
134
 
                    return True
135
 
                else:
136
 
                    self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
137
 
                    
138
 
            elif value.lower() == "false":
139
 
                if expectedtype == "boolean":
140
 
                    return False
141
 
                else:
142
 
                    self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
143
 
                    
144
 
            elif self.isNumeric(value) == True:
145
 
                if expectedtype == "integer":
146
 
                    return int(value)
147
 
                else:
148
 
                    self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
149
 
                    
150
 
            else:
151
 
                return value
152
 
 
153
 
        except (TypeError, ValueError):
154
 
            self.logger.error("Cannot convert '%s' to expected type of '%s'"%(value,expectedtype))
155
 
            return value
156
 
        
157
126
    def testDBus(self, bus, interface):
158
127
        obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
159
128
        dbus_iface = dbus.Interface(obj, 'org.freedesktop.DBus')
160
129
        avail = dbus_iface.ListNames()
161
130
        return interface in avail
162
 
        
 
131
 
163
132
    def getOutputData(self, datatype, statustext, nounknownoutput):
164
133
        output = u""
165
 
        
 
134
 
166
135
        if DBUS_AVAIL == True:
167
 
            
 
136
 
168
137
            if nounknownoutput == True:
169
138
                unknown_time = ""
170
139
                unknown_number = ""
177
146
                unknown_string = "Unknown"
178
147
                unknown_coverart = "file://"+urllib.quote(os.path.join(app_path,"images/"+module_name+".png"))
179
148
                unknown_rating = "file://"+urllib.quote(os.path.join(app_path,"images/ratingicons/0.png"))
180
 
            
 
149
 
181
150
            try:
182
 
                    
 
151
 
183
152
                bus = dbus.SessionBus()
184
153
                if self.musicData == None:
185
 
                    
 
154
 
186
155
                    if self.testDBus(bus, 'org.exaile.DBusInterface'):
187
 
                        
 
156
 
188
157
                        try:
189
 
                        
 
158
 
190
159
                            self.logger.info("Setting up dbus interface")
191
 
                            
 
160
 
192
161
                            remote_object = bus.get_object("org.exaile.DBusInterface","/DBusInterfaceObject")
193
162
                            iface = dbus.Interface(remote_object, "org.exaile.DBusInterface")
194
 
                            
 
163
 
195
164
                            self.logger.info("Calling dbus interface for music data")
196
 
                                
 
165
 
197
166
                            # grab the data for use
198
 
                            
 
167
 
199
168
                            status = self.getStatusText(iface.status(), statustext)
200
 
                            
 
169
 
201
170
                            coverart = iface.get_cover_path()
202
 
                            
 
171
 
203
172
                            # if cover art found then use it, otherwise use default coverart image for this plugin
204
173
                            if len(coverart) > 0 and coverart.find("nocover.png") == -1:
205
174
                                coverart ="file://"+urllib.quote(coverart.encode("utf-8"))
206
175
                            else:
207
176
                                coverart = unknown_coverart
208
 
                                
 
177
 
209
178
                            length = iface.get_length()
210
179
                            title = iface.get_title()
211
180
                            album = iface.get_album()
213
182
                            genre = iface.get_track_attr("genre")
214
183
                            year = iface.get_track_attr("year")
215
184
                            tracknumber = iface.get_track_attr("track")
216
 
                            filename = iface.get_track_attr("filename")  
217
 
                                
 
185
                            filename = iface.get_track_attr("filename")
 
186
 
218
187
                            if len(length) > 0:
219
188
                                current_position_percent = str(int(iface.current_position()))
220
189
                                length_minutes, length_seconds = map(int,length.split(":"))
223
192
                            else:
224
193
                                current_position_percent = "0"
225
194
                                current_position = "0:00"
226
 
                                
 
195
 
227
196
                            volume = str(int(iface.get_volume().split(".")[0]))
228
197
                            rating = str(int(iface.get_rating()))
229
 
                                
 
198
 
230
199
                            self.musicData = MusicData(status,coverart,title,album,length,artist,tracknumber,genre,year,filename,current_position_percent,current_position,rating,volume)
231
 
                                
 
200
 
232
201
                        except Exception, e:
233
202
                            self.logger.error("Issue calling the dbus service:"+e.__str__()+"\n"+traceback.format_exc())
234
 
    
 
203
 
235
204
                if self.musicData != None:
236
 
                    
 
205
 
237
206
                    self.logger.info("Preparing output for datatype:"+datatype)
238
 
    
 
207
 
239
208
                    if datatype == "ST": #status
240
209
                        if self.musicData.status == None or len(self.musicData.status) == 0:
241
210
                            output = None
242
211
                        else:
243
 
                            output = self.getHTMLText(self.musicData.status)
244
 
    
 
212
                            output = getHTMLText(self.musicData.status)
 
213
 
245
214
                    elif datatype == "CA": #coverart
246
215
                        if self.musicData.coverart == None or len(self.musicData.coverart) == 0:
247
216
                            output = None
248
217
                        else:
249
218
                            output = self.musicData.coverart
250
 
                                                    
 
219
 
251
220
                    elif datatype == "TI": #title
252
221
                        if self.musicData.title == None or len(self.musicData.title) == 0:
253
222
                            output = None
254
223
                        else:
255
 
                            output = self.getHTMLText(self.musicData.title)
256
 
                            
 
224
                            output = getHTMLText(self.musicData.title)
 
225
 
257
226
                    elif datatype == "AL": #album
258
227
                        if self.musicData.album == None or len(self.musicData.album) == 0:
259
228
                            output = None
260
229
                        else:
261
 
                            output = self.getHTMLText(self.musicData.album)
262
 
                            
 
230
                            output = getHTMLText(self.musicData.album)
 
231
 
263
232
                    elif datatype == "AR": #artist
264
233
                        if self.musicData.artist == None or len(self.musicData.artist) == 0:
265
234
                            output = None
266
235
                        else:
267
 
                            output = self.getHTMLText(self.musicData.artist)
268
 
                            
 
236
                            output = getHTMLText(self.musicData.artist)
 
237
 
269
238
                    elif datatype == "GE": #genre
270
239
                        if self.musicData.title == genre or len(self.musicData.genre) == 0:
271
240
                            output = None
272
241
                        else:
273
 
                            output = self.getHTMLText(self.musicData.genre)
274
 
                            
 
242
                            output = getHTMLText(self.musicData.genre)
 
243
 
275
244
                    elif datatype == "YR": #year
276
245
                        if self.musicData.year == None or len(self.musicData.year) == 0:
277
246
                            output = None
278
247
                        else:
279
248
                            output = self.musicData.year
280
 
    
 
249
 
281
250
                    elif datatype == "TN": #tracknumber
282
251
                        if self.musicData.tracknumber == None or len(self.musicData.tracknumber) == 0:
283
252
                            output = None
284
253
                        else:
285
254
                            output = self.musicData.tracknumber
286
 
                                                    
 
255
 
287
256
                    elif datatype == "FN": #filename
288
257
                        if self.musicData.filename == None or len(self.musicData.filename) == 0:
289
258
                            output = None
290
259
                        else:
291
 
                            output = self.getHTMLText(self.musicData.filename)
292
 
    
 
260
                            output = getHTMLText(self.musicData.filename)
 
261
 
293
262
                    elif datatype == "LE": # length
294
263
                        if self.musicData.length == None or len(self.musicData.length) == 0:
295
264
                            output = None
296
265
                        else:
297
266
                            output = self.musicData.length
298
 
                            
 
267
 
299
268
                    elif datatype == "PP": #current position in percent
300
269
                        if self.musicData.current_position_percent == None or len(self.musicData.current_position_percent) == 0:
301
270
                            output = None
302
271
                        else:
303
272
                            output = self.musicData.current_position_percent
304
 
                            
 
273
 
305
274
                    elif datatype == "PT": #current position in time
306
275
                        if self.musicData.current_position == None or len(self.musicData.current_position) == 0:
307
276
                            output = None
308
277
                        else:
309
278
                            output = self.musicData.current_position
310
 
                            
 
279
 
311
280
                    elif datatype == "VO": #volume
312
281
                        if self.musicData.volume == None or len(self.musicData.volume) == 0:
313
282
                            output = None
314
283
                        else:
315
284
                            output = self.musicData.volume
316
 
                            
 
285
 
317
286
                    elif datatype == "RT": #rating
318
287
                        if self.musicData.rating == None or self.isNumeric(self.musicData.rating) == False:
319
288
                            output = None
326
295
                    else:
327
296
                        self.logger.error("Unknown datatype provided: " + datatype)
328
297
                        return u""
329
 
        
 
298
 
330
299
            except Exception, e:
331
300
                self.logger.error(e.__str__()+"\n"+traceback.format_exc())
332
301
 
341
310
                    output = unknown_rating
342
311
                else:
343
312
                    output = unknown_string
344
 
            
 
313
 
345
314
            return output
346
 
        
 
315
 
347
316
    def getStatusText(self, status, statustext):
348
 
        
349
 
        if status != None:        
 
317
 
 
318
        if status != None:
350
319
            statustextparts = statustext.split(",")
351
 
            
 
320
 
352
321
            if status == "playing":
353
322
                return statustextparts[0]
354
323
            elif status == "paused":
355
324
                return statustextparts[1]
356
325
            elif status == "stopped":
357
326
                return statustextparts[2]
358
 
            
 
327
 
359
328
        else:
360
329
            return status
361
330
 
362
331
 
363
332
    def getTemplateItemOutput(self, template_text):
364
 
        
 
333
 
365
334
        # keys to template data
366
335
        DATATYPE_KEY = "datatype"
367
336
        STATUSTEXT_KEY = "statustext"
368
337
        NOUNKNOWNOUTPUT_KEY = "nounknownoutput"
369
 
        
 
338
 
370
339
        datatype = None
371
340
        statustext = self.config.STATUSTEXT #default to command line option
372
341
        nounknownoutput = self.config.NOUNKNOWNOUTPUT #default to command line option
373
 
        
 
342
 
374
343
        for option in template_text.split('--'):
375
344
            if len(option) == 0 or option.isspace():
376
345
                continue
377
 
            
 
346
 
378
347
            # not using split here...it can't assign both key and value in one call, this should be faster
379
348
            x = option.find('=')
380
349
            if (x != -1):
385
354
            else:
386
355
                key = option.strip()
387
356
                value = None
388
 
            
 
357
 
389
358
            try:
390
359
                if key == DATATYPE_KEY:
391
 
                    datatype = self.getTypedValue(value, "string")
 
360
                    datatype = getTypedValue(value, "string")
392
361
                elif key == STATUSTEXT_KEY:
393
 
                    statustext = self.getTypedValue(value, "string")
 
362
                    statustext = getTypedValue(value, "string")
394
363
                elif key == NOUNKNOWNOUTPUT_KEY:
395
364
                    nounknownoutput = True
396
365
                else:
399
368
            except (TypeError, ValueError):
400
369
                self.logger.error("Cannot convert option argument to number: " + option)
401
370
                return u""
402
 
                
 
371
 
403
372
        if datatype != None:
404
373
            return self.getOutputData(datatype, statustext, nounknownoutput)
405
374
        else:
411
380
        output = u""
412
381
        end = False
413
382
        a = 0
414
 
        
 
383
 
415
384
        # a and b are indexes in the template string
416
385
        # moving from left to right the string is processed
417
386
        # b is index of the opening bracket and a of the closing bracket
418
387
        # everything between b and a is a template that needs to be parsed
419
388
        while not end:
420
389
            b = template.find('[', a)
421
 
            
 
390
 
422
391
            if b == -1:
423
392
                b = len(template)
424
393
                end = True
425
 
            
 
394
 
426
395
            # if there is something between a and b, append it straight to output
427
396
            if b > a:
428
397
                output += template[a : b]
433
402
                    # skip the bracket in the input string and continue from the beginning
434
403
                    a = b + 1
435
404
                    continue
436
 
                    
 
405
 
437
406
            if end:
438
407
                break
439
 
            
 
408
 
440
409
            a = template.find(']', b)
441
 
            
 
410
 
442
411
            if a == -1:
443
412
                self.logger.error("Missing terminal bracket (]) for a template item")
444
413
                return u""
445
 
            
 
414
 
446
415
            # if there is some template text...
447
416
            if a > b + 1:
448
417
                output += self.getTemplateItemOutput(template[b + 1 : a])
449
 
            
 
418
 
450
419
            a = a + 1
451
420
 
452
421
        return output
453
 
    
 
422
 
454
423
    def getOutput(self):
455
424
 
456
425
        if self.options.noheader == True:
475
444
            headertemplate = inputfile.read()
476
445
        finally:
477
446
            inputfile.close()
478
 
                    
 
447
 
479
448
        if self.options.template != None:
480
449
            templatefilepath = self.options.template
481
450
            self.logger.info("Using custom template file '%s'"%templatefilepath)
485
454
        else:
486
455
            templatefilepath = app_path+"/templates/exaile.template"
487
456
            self.logger.info("Using default template")
488
 
             
 
457
 
489
458
        # load the file
490
459
        try:
491
460
            inputfile = codecs.open(os.path.expanduser(templatefilepath), encoding='utf-8')
495
464
            template = inputfile.read()
496
465
        finally:
497
466
            inputfile.close()
498
 
        
 
467
 
499
468
        output = headertemplate
500
469
        output = output + self.getOutputFromTemplate(template)
501
470
 
503
472
 
504
473
    def getHTMLText(self,text):
505
474
        try:
506
 
            htmlentities = []               
 
475
            text = u""+text
 
476
        except:
 
477
            pass
 
478
 
 
479
        try:
 
480
            htmlentities = []
507
481
            for char in text: #html:
508
482
                if ord(char) < 128:
509
483
                    htmlentities.append(char)
510
484
                else:
511
485
                    htmlentities.append('&%s;' % codepoint2name[ord(char)])
512
486
            html = "".join(htmlentities)
513
 
            
 
487
 
514
488
            html = html.replace("\n","<br>\n") # switch out new line for html breaks
515
 
            return html            
 
489
            return html
516
490
        except:
517
491
            return text
518
492
 
524
498
            text = text.replace("<br>","\n") # switch out html breaks for new line
525
499
            text = re.sub('<(.|\n)+?>','',text) # remove any html tags
526
500
            text =  re.sub('&(%s);' % '|'.join(name2codepoint), lambda m: chr(name2codepoint[m.group(1)]), text)
527
 
            return text            
 
501
            return text
528
502
        except:
529
503
            return html
530
 
    
 
504
 
531
505
    def isNumeric(self,value):
532
506
        try:
533
507
            temp = int(value)
534
508
            return True
535
509
        except:
536
510
            return False
537
 
  
 
511
 
538
512
def getHTML(options):
539
513
    output = Output(options)
540
514
    html = output.getOutput()
545
519
if __name__ == "__main__":
546
520
 
547
521
    parser = OptionParser()
548
 
    parser.add_option("--noheader", dest="noheader", default=False, action="store_true", help=u"Turn off header output. This will override any header template setting to be nothing")        
 
522
    parser.add_option("--noheader", dest="noheader", default=False, action="store_true", help=u"Turn off header output. This will override any header template setting to be nothing")
549
523
    parser.add_option("--headertemplate", dest="headertemplate", type="string", metavar="FILE", help=u"Override the header template for the plugin, default or config based template ignored.")
550
524
    parser.add_option("--template", dest="template", type="string", metavar="FILE", help=u"Override the template for the plugin, default or config based template ignored.")
551
525
    parser.add_option("--verbose", dest="verbose", default=False, action="store_true", help=u"Outputs verbose info to the terminal")
552
526
    parser.add_option("--version", dest="version", default=False, action="store_true", help=u"Displays the version of the script.")
553
 
    parser.add_option("--logfile", dest="logfile", type="string", metavar="FILE", help=u"If a filepath is set, the script logs to the filepath.")                
554
 
    
 
527
    parser.add_option("--logfile", dest="logfile", type="string", metavar="FILE", help=u"If a filepath is set, the script logs to the filepath.")
 
528
 
555
529
    (options, args) = parser.parse_args()
556
 
        
 
530
 
557
531
    output = Output(options)
558
532
    html = output.getOutput()
559
533
    del output