66
65
TORRENTTEMPLATE = None
67
66
SUMMARYTEMPLATE = None
69
HIDETORRENTDETAIL = False
68
HIDETORRENTDETAIL = False
77
76
sessionstate = None
78
77
sessionstatefound = False
81
80
STATE_DOWNLOADING = 4
87
86
def __init__(self, options):
91
90
self.options = options
92
91
self.logger = logging.getLogger(app_name+"."+module_name)
93
92
self.loadConfigData()
95
94
if DELUGE_AVAIL == True:
97
96
# sort out the server option
98
97
self.config.SERVER = self.config.SERVER.replace("localhost", "127.0.0.1")
100
99
# create the uri for the rpc connection
101
100
self.uri = "http://"+self.config.SERVER+":"+str(self.config.PORT)
103
102
# create the rpc and client objects
104
103
sclient.set_core_uri(self.uri)
107
106
# attempt to get the required data
108
107
self.sessionstate = sclient.get_session_state()
109
108
self.sessionstatefound = True
111
110
self.sessionstatefound = False
114
113
self.sessionstatefound = False
117
116
except Exception,e:
118
117
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
120
119
def loadConfigData(self):
123
122
self.config = DelugeConfig()
125
124
if self.options.config != None:
126
125
# load the config based on options passed in from the main app
127
126
configfilepath = self.options.config
129
128
# load plugin config from home directory of the user
130
129
configfilepath = os.path.join(os.path.expanduser('~'), ".config/"+app_name+"/"+module_name+".config")
132
131
if os.path.exists(configfilepath):
134
133
self.logger.info("Loading config settings from \"%s\""%configfilepath)
136
135
for line in fileinput.input(os.path.expanduser(configfilepath)):
137
136
line = line.strip()
138
137
if len(line) > 0 and line[0:1] != "#": # ignore commented lines or empty ones
140
139
name = line.split("=")[0].strip().upper() # config setting name on the left of =
141
140
value = line.split("=")[1].split("#")[0].strip() # config value on the right of = (minus any trailing comments)
143
142
if len(value) > 0:
144
143
if name == "SERVER":
145
self.config.SERVER = self.getTypedValue(value, "string")
144
self.config.SERVER = getTypedValue(value, "string")
146
145
elif name == "PORT":
147
self.config.PORT = self.getTypedValue(value, "integer")
146
self.config.PORT = getTypedValue(value, "integer")
148
147
elif name == "HEADERTEMPLATE":
149
self.config.HEADERTEMPLATE = self.getTypedValue(value, "string")
148
self.config.HEADERTEMPLATE = getTypedValue(value, "string")
150
149
elif name == "TORRENTTEMPLATE":
151
self.config.TORRENTTEMPLATE = self.getTypedValue(value, "string")
150
self.config.TORRENTTEMPLATE = getTypedValue(value, "string")
152
151
elif name == "SUMMARYTEMPLATE":
153
self.config.SUMMARYTEMPLATE = self.getTypedValue(value, "string")
152
self.config.SUMMARYTEMPLATE = getTypedValue(value, "string")
154
153
elif name == "SHOWSUMMARY":
155
self.config.SHOWSUMMARY = self.getTypedValue(value, "boolean")
154
self.config.SHOWSUMMARY = getTypedValue(value, "boolean")
156
155
elif name == "HIDETORRENTDETAIL":
157
self.config.HIDETORRENTDETAIL = self.getTypedValue(value, "boolean")
156
self.config.HIDETORRENTDETAIL = getTypedValue(value, "boolean")
158
157
elif name == "ACTIVEONLY":
159
self.config.ACTIVEONLY = self.getTypedValue(value, "boolean")
158
self.config.ACTIVEONLY = getTypedValue(value, "boolean")
160
159
elif name == "LIMIT":
161
self.config.LIMIT = self.getTypedValue(value, "integer")
160
self.config.LIMIT = getTypedValue(value, "integer")
163
162
self.logger.error("Unknown option in config file: " + name)
165
164
self.logger.info("Config data file %s not found, using defaults and setting up config file for next time" % configfilepath)
167
166
userconfigpath = os.path.join(os.path.expanduser('~'), ".config/"+app_name+"/")
168
167
configsource = os.path.join(app_path, "config/"+module_name+".config")
170
169
if os.path.exists(userconfigpath) == False:
171
170
os.makedirs(userconfigpath)
175
174
except Exception, e:
176
175
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
178
def getTypedValue(self, value, expectedtype):
181
if len(value.strip(" ")) == 0:
184
elif value.lower() == "true":
185
if expectedtype == "boolean":
188
self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
190
elif value.lower() == "false":
191
if expectedtype == "boolean":
194
self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
196
elif self.isNumeric(value) == True:
197
if expectedtype == "integer":
200
self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
205
except (TypeError, ValueError):
206
self.logger.error("Cannot convert '%s' to expected type of '%s'"%(value,expectedtype))
209
177
def getTorrentTemplateOutput(self, template, name, state, stateicon, totaldone, totalsize, progress, nofiles, downloadrate, uploadrate, eta, currentpeers, currentseeds, totalpeers, totalseeds, ratio):
213
181
output = template
215
183
output = output.replace("[name]",name)
216
184
output = output.replace("[state]",state)
217
185
output = output.replace("[stateicon]",stateicon)
225
193
output = output.replace("[currentpeers]",currentpeers)
226
194
output = output.replace("[currentseeds]",currentseeds)
227
195
output = output.replace("[totalpeers]",totalpeers)
228
output = output.replace("[totalseeds]",totalseeds)
196
output = output.replace("[totalseeds]",totalseeds)
229
197
output = output.replace("[ratio]",ratio)
231
199
# get rid of any excess crlf's and add just one
232
200
output = output.rstrip(" \n")
233
201
output = output + "\n"
237
205
except Exception,e:
238
206
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
241
209
def getSummaryTemplateOutput(self, template, notorrents, totalprogress, totaldone, totalsize, totaldownloadrate, totaluploadrate, totaleta, currentpeers, currentseeds, totalpeers, totalseeds, totalratio):
245
213
output = template
247
215
output = output.replace("[notorrents]",notorrents)
295
263
headertemplate = inputfile.read()
297
265
inputfile.close()
299
267
# define header in output
300
268
output = headertemplate
302
270
if self.sessionstatefound == True:
304
272
self.logger.info("Session state data is available, proceeding...")
306
274
torrentDataList = []
307
275
torrentItemList = ["num_peers","num_seeds","name","state","total_done","total_size","total_wanted","progress","files","eta","download_payload_rate","upload_payload_rate","total_peers","total_seeds","ratio"]
310
278
# summary variables
311
279
summary_notorrent = 0
312
280
summary_totaldone = 0
358
326
torrenttemplate = inputfile.read()
360
328
inputfile.close()
362
330
self.logger.info("Retrieving torrent session state data...")
364
332
#get list of torrents and display the 1ist.
365
333
#sessionstate = sclient.get_session_state()
366
334
if len(self.sessionstate) > 0:
368
336
self.logger.info("Processing %s torrent(s)..."%str(len(self.sessionstate)))
370
338
for torrent in self.sessionstate:
372
340
torrent_status = sclient.get_torrent_status(torrent, torrentItemList)
374
342
if torrent_status != None:
376
344
if self.config.ACTIVEONLY == True:
380
348
# check for activity
381
349
if "num_peers" in torrent_status:
382
350
if torrent_status["num_peers"] > 0:
385
353
if "num_seeds" in torrent_status:
386
354
if torrent_status["num_seeds"] > 0:
389
357
# output details if all required or if active
390
358
if self.config.ACTIVEONLY == False or active == True:
392
360
if "name" in torrent_status:
393
name = self.getHTMLText(torrent_status["name"])
361
name = getHTMLText(torrent_status["name"])
397
365
if "state" in torrent_status:
398
366
state = torrent_status["state"]
400
368
if state == "Downloading":
401
369
statecode = self.STATE_DOWNLOADING
402
370
elif state == "Seeding":
407
375
statecode = self.STATE_PAUSED
409
377
statecode = self.STATE_UNKNOWN
412
380
state = "Unknown"
413
381
statecode = self.STATE_UNKNOWN
415
383
if "total_done" in torrent_status:
416
384
totaldone = fsize(torrent_status["total_done"])
417
385
summary_totaldone = summary_totaldone + int(torrent_status["total_done"])
419
totaldone = "??.? KiB"
387
totaldone = "??.? KiB"
421
389
if "total_size" in torrent_status:
422
390
totalsize = fsize(torrent_status["total_wanted"])
423
391
summary_totalsize = summary_totalsize + int(torrent_status["total_wanted"])
425
totalsize = "??.? KiB"
393
totalsize = "??.? KiB"
427
395
if "progress" in torrent_status:
428
396
progress = str(round(torrent_status["progress"],2))+"%"
432
400
if "files" in torrent_status:
433
401
nofiles = str(len(torrent_status["files"]))
437
405
if "eta" in torrent_status:
438
406
eta = torrent_status["eta"]
440
408
if eta > highesteta:
443
411
eta = ftime(torrent_status["eta"])
447
415
if "download_payload_rate" in torrent_status:
448
416
downloadrate = fspeed(torrent_status["download_payload_rate"])
449
417
summary_totaldownloadrate = summary_totaldownloadrate + float(torrent_status["download_payload_rate"])
451
downloadrate = "?.? KiB/s"
419
downloadrate = "?.? KiB/s"
453
421
if "upload_payload_rate" in torrent_status:
454
422
uploadrate = fspeed(torrent_status["upload_payload_rate"])
455
423
summary_totaluploadrate = summary_totaluploadrate + float(torrent_status["upload_payload_rate"])
457
uploadrate = "?.? KiB/s"
425
uploadrate = "?.? KiB/s"
459
427
if "num_peers" in torrent_status:
460
428
currentpeers = str(torrent_status["num_peers"])
461
429
summary_currentpeers = summary_currentpeers + int(torrent_status["num_peers"])
463
431
currentpeers = "?"
465
433
if "num_seeds" in torrent_status:
466
434
currentseeds = str(torrent_status["num_seeds"])
467
435
summary_currentseeds = summary_currentseeds + int(torrent_status["num_seeds"])
469
437
currentseeds = "?"
471
439
if "total_peers" in torrent_status:
472
440
totalpeers = str(torrent_status["total_peers"])
473
441
summary_totalpeers = summary_totalpeers + int(torrent_status["total_peers"])
477
445
if "total_seeds" in torrent_status:
478
446
totalseeds = str(torrent_status["total_seeds"])
479
447
summary_totalseeds = summary_totalseeds + int(torrent_status["total_seeds"])
483
451
if "ratio" in torrent_status:
484
452
ratio = str(round(torrent_status["ratio"],3)).ljust(5,"0")
488
456
summary_notorrent = summary_notorrent + 1
490
458
# add torrent data to list
491
459
torrentData = TorrentData(name, state, statecode, totaldone, totalsize, progress, nofiles, downloadrate, uploadrate, eta, currentpeers, currentseeds, totalpeers, totalseeds, ratio)
492
460
torrentDataList.append(torrentData)
495
463
self.logger.info("No torrent status data available")
497
465
if summary_notorrent > 0:
499
467
if self.config.SHOWSUMMARY == True:
501
469
# sort out summary data for output
502
470
summary_notorrent = str(summary_notorrent)
503
471
summary_totalprogress = str(round((float(summary_totaldone) / float(summary_totalsize)) *100,2))+"%"
504
472
summary_totaldone = fsize(summary_totaldone)
505
summary_totalsize = fsize(summary_totalsize)
473
summary_totalsize = fsize(summary_totalsize)
506
474
summary_totaldownloadrate = fspeed(summary_totaldownloadrate)
507
475
summary_totaluploadrate = fspeed(summary_totaluploadrate)
508
476
summary_totaleta = ftime(highesteta)
511
479
summary_totalpeers = str(summary_totalpeers)
512
480
summary_totalseeds = str(summary_totalseeds)
513
481
summary_totalratio = "?.???"
515
483
output = output + self.getSummaryTemplateOutput(summarytemplate, summary_notorrent, summary_totalprogress, summary_totaldone, summary_totalsize, summary_totaldownloadrate, summary_totaluploadrate, summary_totaleta, summary_currentpeers, summary_currentseeds, summary_totalpeers, summary_totalseeds, summary_totalratio)
516
484
#return output.encode("utf-8")
519
487
if self.config.HIDETORRENTDETAIL == False:
521
489
# torrent details
524
492
# sort list, eta based
525
493
torrentDataList.sort(reverse = True)
527
495
# output torrent data using the template
528
for torrentData in torrentDataList:
496
for torrentData in torrentDataList:
530
498
# keep a tally of torrent output, if past the limit then exit
531
499
if self.config.LIMIT <> 0:
532
500
outputCount = outputCount + 1
533
501
if outputCount > self.config.LIMIT:
536
504
stateicon = self.getImageSrcForStateCode(torrentData.statecode)
538
506
#output = output + self.getTorrentTemplateOutput(torrenttemplate, torrentData.name, torrentData.state, torrentData.totaldone, torrentData.totalsize, torrentData.progress, torrentData.nofiles, torrentData.downloadrate, torrentData.uploadrate, torrentData.eta, torrentData.currentpeers, torrentData.currentseeds, torrentData.totalpeers, torrentData.totalseeds, torrentData.ratio)+"\n"
539
507
output = output + self.getTorrentTemplateOutput(torrenttemplate, torrentData.name, torrentData.state, stateicon, torrentData.totaldone, torrentData.totalsize, torrentData.progress, torrentData.nofiles, torrentData.downloadrate, torrentData.uploadrate, torrentData.eta, torrentData.currentpeers, torrentData.currentseeds, torrentData.totalpeers, torrentData.totalseeds, torrentData.ratio)+"\n"
541
509
return output.encode("utf-8")
544
512
output = output + "<p>No torrent info to display</p>"
545
513
return output.encode("utf-8")
548
516
self.logger.info("No torrents found")
549
517
output = output + "<p>No torrents found</p>"
553
521
self.logger.info("No session state data is available, nothing to output...")
554
522
output = output + "<p>No session state data is available, nothing to output...</p>"
555
523
return output.encode("utf-8")
557
525
except Exception,e:
558
526
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
560
def getHTMLText(self,text):
563
for char in text: #html:
565
htmlentities.append(char)
567
htmlentities.append('&%s;' % codepoint2name[ord(char)])
568
html = "".join(htmlentities)
570
html = html.replace("\n","<br>\n") # switch out new line for html breaks
575
528
def getImageSrcForStateCode(self, statecode):
576
529
imagesrc = "file://%s/images/delugeicons/%s.png"%(app_path, str(statecode))
579
def getCleanText(self,html):
582
text = text.replace("\n","") # remove new lines from html
583
text = text.replace("'","'") # workaround for shitty xml codes not compliant with html
584
text = text.replace("<br>","\n") # switch out html breaks for new line
585
text = re.sub('<(.|\n)+?>','',text) # remove any html tags
586
text = re.sub('&(%s);' % '|'.join(name2codepoint), lambda m: chr(name2codepoint[m.group(1)]), text)
591
def isNumeric(self, string):
593
dummy = float(string)
598
532
def getHTML(options):
599
533
output = Output(options)
600
534
html = output.getOutput()
605
539
if __name__ == "__main__":
607
541
parser = OptionParser()
608
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")
542
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")
609
543
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.")
610
544
parser.add_option("--template", dest="template", type="string", metavar="FILE", help=u"Override the template for the plugin, default or config based template ignored.")
611
545
parser.add_option("--verbose", dest="verbose", default=False, action="store_true", help=u"Outputs verbose info to the terminal")
612
546
parser.add_option("--version", dest="version", default=False, action="store_true", help=u"Displays the version of the script.")
613
parser.add_option("--logfile", dest="logfile", type="string", metavar="FILE", help=u"If a filepath is set, the script logs to the filepath.")
547
parser.add_option("--logfile", dest="logfile", type="string", metavar="FILE", help=u"If a filepath is set, the script logs to the filepath.")
615
549
(options, args) = parser.parse_args()
617
551
output = Output(options)
618
552
html = output.getOutput()