inital attempt at combining client and core into single package
[tore.git] / torc / torc.py
blob39d5e8fe469556e286bd51a75585c4bb95deb906
1 #!/usr/bin/python
2 from tord.pyro import *
3 from tord.common import *
4 import os.path
5 import pytx, traceback, sys
7 def log(x):
8 sys.stderr.write(str(x))
10 def test(f):
11 def wrapper(*args, **kwargs):
12 try: return f(*args, **kwargs)
13 except:
14 traceback.print_exc()
15 log("error: " + str(f))
16 return wrapper
18 class Msg(pytx.List):
19 def __init__(self, core, flags = 0):
20 pytx.List.__init__(self, flags)
21 self.core = core
22 self.size = 5
23 self.count = 0
25 def msg(self, text):
26 self.add(self.count, text)
27 self.count += 1;
28 self.scroll(1)
29 self.draw()
31 def size_cb(self, s, d):
32 return self.size
34 class Status(pytx.List):
35 def __init__(self, core, flags = 0):
36 pytx.List.__init__(self, flags)
37 self.core = core
39 def peers(x):
40 if x > 999: return 'inf'
41 return str(x)
43 def speed(x):
44 if x < 0: return 'inf'
45 return fspeed(x)
47 self.keys = ["download_rate", "download_limit", "upload_rate", "upload_limit", "num_peers", "dht_nodes"]
49 self.format = {
50 "download_rate": lambda x: speed(x),
51 "download_limit": lambda x: speed(x),
52 "upload_rate": lambda x: speed(x),
53 "upload_limit": lambda x: speed(x),
54 "num_peers": lambda x: peers(x),
55 "dht_nodes": lambda x: peers(x),
58 def update(self, active=0):
59 data = self.core.get_session_status(self.keys)
60 status = [self.format[k](v) for k, v in zip(self.keys, data)]
61 text = "Download[%6s|%6s] Upload[%6s|%6s] Peers[%3s] DHT[%3s]" % tuple(status)
62 self.add(0, text)
64 def idle_cb(self):
65 self.update()
66 self.draw()
68 def size_cb(self, s, d):
69 return 1
71 class Torrents:
72 def __init__(self):
73 self.name = 'torrents'
74 self.keys = ['state', 'download_payload_rate', 'upload_payload_rate', 'progress', 'eta', 'total_wanted', 'num_peers', 'name']
75 self.func = lambda c, k, i, a, s: c.get_torrents_status(i, k, a)
77 self._bindings = {
78 'f' : lambda l,x: l.view_change('files', x),
79 'c' : lambda l,x: l.view_change('config_core', x),
80 '-' : lambda l,x: l.core.set_config({"max_upload_speed":20}),
81 'w' : lambda l,x: l.core.set_torrents_state("write", [x]),
82 'd' : lambda l,x: l.core.set_torrents_state("remove", [x]),
83 'r' : lambda l,x: l.core.set_torrents_state("resume", [x]),
84 'R' : lambda l,x: l.core.set_torrents_state("resume"),
85 'p' : lambda l,x: l.core.set_torrents_state("pause", [x]),
86 'P' : lambda l,x: l.core.set_torrents_state("pause"),
87 't' : lambda l,x: l.core.set_torrents_state("toggle", [x]),
88 'D' : lambda l,x: l.core.remove(x, True),
89 'y' : lambda l,x: l.columns_swap('progress', '_progress'),
90 'u' : lambda l,x: l.refresh(reset=True, clear=True, update=True, sync=True),
93 class Files:
94 def __init__(self):
95 self.name = "files"
96 self.keys = ['id', 'priority', 'progress', 'path']
97 self._bindings = {
98 '[' : lambda l,x: l.core.torrent_prioritize_files(self.id, [x], -1),
99 ']' : lambda l,x: l.core.torrent_prioritize_files(self.id, [x], 1),
100 ',' : lambda l,x: self.select(l,x,-1),
101 '.' : lambda l,x: self.select(l,x,1),
102 'u' : lambda l,x: l.refresh(update=True)
105 self.files = {}
107 def func(self, c, k, i, a, s):
108 files = c.torrent_get_files(k,s)
109 #if s not in self.files:
110 # self.files[s] = self.File(files)
111 #return self.files[s].get(files)
112 return files
114 def select(self, l, x, b):
115 remove = self.files[self.id].update(x, b)
116 if remove:
117 log (("remove:", [i for i in remove]))
118 for r in remove:
119 l.remove(r)
120 l.refresh(sync = True, update = True)
122 class ConfigCore:
123 def __init__(self):
124 self.name = 'config_core'
125 self.keys = ['key', 'value']
126 self.func = lambda c, k, i, a, s: c.get_config()
127 self._bindings = {
130 class Column:
131 def __init__(self, func, title, t_width, nt_width, align, resize):
132 self.func = func
133 self.title = title
134 self.t_width = t_width
135 self.nt_width = nt_width
136 self.align = align
137 self.resize = resize
139 def format(self, text, width):
140 if self.align == 0:
141 return text.rjust(width)
142 if self.align == 1:
143 return text.ljust(width)
145 def format_data(self, data, width):
146 return self.format(self.func(data), width)
148 def format_title(self, width):
149 return self.format(self.title, width)
151 def format_width(self, title):
152 if title: return self.t_width
153 return self.nt_width
155 class Columns:
156 def __init__(self):
157 self.cols = {}
159 def add_column(self, key, func, title, t_width, nt_width, align, resize):
160 self.cols[key] = Column(func, title, t_width, nt_width, align, resize)
162 def columns_swap(self, a, b):
163 self.cols[a], self.cols[b] = self.cols[b], self.cols[a]
164 self.refresh(reset=True)
166 def column_widths(self, keys, title):
167 return [self.cols[k].format_width(title) for k in keys]
169 def column_titles(self, keys, widths):
170 return [self.cols[k].format_title(w) for k, w in zip(keys, widths)]
172 def column_data(self, data, widths, keys):
173 return [self.cols[k].format_data(d,w) for d, w, k in zip(data, widths, keys)]
175 class Views:
176 def __init__(self):
177 self.views = {}
178 self.view = None
180 def view_add(self, view):
181 if not len(self.views): self.view = view
182 self.views[view.name] = view
183 self.view.id = None
185 def view_change(self, view = 'torrents', selected = None):
186 self.view = self.views[view]
187 self.view.id = selected
188 self.refresh(reset=True, clear=True, update=True)
190 def get_keys(self):
191 return self.view.keys
193 def view_data(self, core, ids, active):
194 return self.view.func(core, self.get_keys(), ids, active, self.view.id)
196 def view_bindings(self, obj, ch, id):
197 if chr(ch) in self.view._bindings:
198 self.view._bindings[chr(ch)](obj, id)
199 return True
201 class List(Columns, Views, pytx.List):
202 def __init__(self, core, get_core, flags):
203 pytx.List.__init__(self, flags)
204 Columns.__init__(self)
205 Views.__init__(self)
206 self.core = core
207 self.get_core = get_core
209 self.bindings = {
210 'g' : lambda x: self.view_change(),
211 'S' : lambda x: self.toggle_title(),
212 's' : lambda x: self.toggle_separator(),
213 'q' : lambda x: self.quit(),
216 self.ids = {}
217 self.hashs = {}
219 @test
220 def quit(self):
221 pytx.emergency()
223 @test
224 def refresh(self, clear = False, reset = False, sync = False, update = False, active = False):
225 if clear: self.clear()
226 if reset: self.reset()
227 if sync or reset: self.sync()
228 if update: self.update(self.get_core(), active)
229 self.sort()
230 self.draw()
232 @test
233 def toggle_title(self):
234 self.flag_toggle(pytx.TITLES)
235 self.refresh(reset=True)
237 @test
238 def toggle_separator(self):
239 self.flag_toggle(pytx.SEPARATOR)
240 self.refresh(reset=True)
242 @test
243 def toggle_acending(self):
244 self.refresh(update=True)
246 @test
247 def get_id(self, hash):
248 if hash not in self.ids:
249 self.ids[hash] = len(self.ids)
250 self.hashs[self.ids[hash]] = hash
251 return self.ids[hash]
253 @test
254 def get_hash(self, hash):
255 return self.hashs[hash]
257 @test
258 def update(self, core, ids=None, active=False):
259 for id, row in self.view_data(core, ids, active):
260 self.add(self.get_id(id), row)
262 @test
263 def text_cb(self, data):
264 return self.column_data(data, self.widths, self.get_keys())
266 @test
267 def bind_cb(self, ch, key):
268 try:
269 if chr(ch) in self.bindings: self.bindings[chr(ch)](key); return
270 if self.view_bindings(self, ch, self.get_hash(key)): return
271 except KeyError:
272 return
273 if 0 < ch-48 < len(self.get_keys()) + 1: self.sort(ch-49)
274 if ch-48 == 0: self.sort(asc = not self.sort_asc_get())
275 self.refresh()
277 @test
278 def idle_cb(self):
279 self.update(self.core, ids = None, active = True)
280 self.sort()
281 self.draw()
283 @test
284 def rsze_cb(self):
285 title = self.flag_get(pytx.TITLES)
286 keys = self.get_keys()
287 self.widths_set(self.column_widths(keys, title))
288 self.titles_set(self.column_titles(keys, self.widths))
289 self.refresh()
291 @test
292 def size_cb(self, s, d):
293 return d-6
295 class TXTor:
296 def __init__(self):
297 pytx.init()
299 def setup(self):
300 def state(x):
301 return (x[1] and "p" or " ") + fstate(x[0])[0]
303 def peers(x):
304 if x > 999: return 'inf'
305 return str(x)
307 def priority(x):
308 if x < 0: return '-'
309 return str(x)
311 def bar(x):
312 return fpcnt_bar(x, 12)
314 l = List(self.core, self.get_core, pytx.FOCUS|pytx.SCROLL|pytx.SEPARATOR|pytx.TITLES|pytx.HIGHLIGHT)
315 l.view_add(Torrents())
316 l.view_add(Files())
317 l.view_add(ConfigCore())
319 l.add_column("state", state, 'state', 5, 2, 0, False)
320 l.add_column("eta", ftime, 'eta', 7, 7, 0, False)
321 l.add_column("progress", bar, 'progress', 12, 12, 0, False)
322 l.add_column("_progress", fpcnt, 'progress', 8, 8, 0, False)
323 l.add_column("upload_payload_rate", fspeed, 'up', 6, 6, 0, False)
324 l.add_column("download_payload_rate", fspeed, 'down', 6, 6, 0, False)
325 l.add_column("total_wanted", fsize, 'size', 6, 6, 0, False)
326 l.add_column("num_peers", peers, 'peers', 5, 3, 0, False)
327 l.add_column("name", str, 'name', 0, 0, 1, True)
328 l.add_column("priority", priority, 'priority', 8, 1, 0, True)
329 l.add_column("path", str, 'path', 0, 0, 1, True)
330 l.add_column("id", str, 'id', 3, 3, 0, True)
331 l.add_column("queue", str, 'queue', 5, 3, 0, True)
333 b = pytx.Box(pytx.VERT, pytx.ROOT|pytx.BORDER)
334 m = Msg(self.core)
335 s = Status(self.core)
336 b.add(l);
337 b.add(m);
338 b.add(s)
339 pytx.adjust()
341 self.refresh = l.refresh
342 sys.stderr = FakeIO(m.msg, m.draw)
343 return b,l,m,s
345 def go(self):
346 self.core = self.get_core()
347 b,t,m,s = self.setup()
349 self.events = {
350 "update": t.update,
351 "remove": lambda c,x: t.refresh(reset=True, clear=True, update=True, sync=True),
354 self.refresh(update=True, reset=True)
355 s.update()
356 b.draw()
357 log(self.uri)
358 pytx.operate()
360 @test
361 def message(self, subject, msg):
362 alert, msg = msg
363 log("%-8s: %s" %( str(alert), str(msg)) )
364 if subject in self.events:
365 self.events[subject](self.get_core(), [msg])
366 self.refresh()
368 def shutdown(self):
369 pytx.free()
370 pytx.emergency()
372 def main()
373 try:
374 args = uri = publish = None
375 ''' we are going over the internet '''
376 if len(sys.argv) == 3:
377 if ping(sys.argv[1]):
378 uri, publish = sys.argv[1], sys.argv[2]
379 ''' we are disambiguating network servers '''
380 elif len(sys.argv) == 2: args = sys.argv[1]
381 if not uri: uri = get_daemon_uri(args)
382 daemon = get_daemon(False, True, publish)
383 ui = PyroClient(TXTor, uri)
384 run(ui, daemon, True)
385 finally:
386 if 'ui' in dir(): shutdown(ui, daemon)
388 if __name__ == "__main__":
389 main()