clippy-commits Mailing List for Clippy
Status: Pre-Alpha
Brought to you by:
edheldil
You can subscribe to this list here.
2008 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
(1) |
Dec
|
---|---|---|---|---|---|---|---|---|---|---|---|---|
2009 |
Jan
|
Feb
|
Mar
|
Apr
|
May
(1) |
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
2010 |
Jan
(3) |
Feb
|
Mar
(2) |
Apr
|
May
|
Jun
(2) |
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
From: <edh...@us...> - 2010-06-11 22:09:47
|
Revision: 13 http://clippy.svn.sourceforge.net/clippy/?rev=13&view=rev Author: edheldil Date: 2010-06-11 21:39:52 +0000 (Fri, 11 Jun 2010) Log Message: ----------- Added debug repository for testing network issues Added Paths: ----------- clippy/repositories/debug_src_repo.py Added: clippy/repositories/debug_src_repo.py =================================================================== --- clippy/repositories/debug_src_repo.py (rev 0) +++ clippy/repositories/debug_src_repo.py 2010-06-11 21:39:52 UTC (rev 13) @@ -0,0 +1,86 @@ +# -*-python-*- +# vim: set ts=4 sw=4 expandtab: + +import dircache +import mimetypes +import os +import os.path +import stat +import string + +from clippy import loader, repository +from clippy.repository import * + + +class DebugSrcRepository (repository.Repository): + def __init__ (self, id, config): + repository.Repository.__init__ (self, id, config) + + # FIXME: and what about Unicode??? + #self.fname_trans = string.maketrans ("/\\<>?*`\000", "________") + + # this should work with unicode + # FIXME: there are many more, like \001-\037, ... + #self.fname_trans = dict (zip (map (ord, u'\a\b\t\n\v\f\r/\\<>?*`'), u'abtnvfr_______')) + + + @staticmethod + def getCaps (): + return REPO_REMOTE | REPO_GET + + + @staticmethod + def getConfigParams (): + res = repository.Repository.getConfigParams () + res.extend ([ + ('name', 'Name', 'STR', 'Name ...', 'Debug Source Repository'), + ('dir', 'Dir', 'DIR', 'repo dir', os.path.expanduser ('~/clippy_files')), + ('read_size', 'Read Size', 'INT', 'Read data chunk size', 4096), + ]) + return res + + + def getQueryParams (self): + # list of tuples: (key, label, type, desc, default) + return [] + + + def connect (self): + dircache.reset () + + def getItems (self, query): + res = [] + dir = self.getConfig ('dir') + for f in dircache.listdir (dir): + id = os.stat (os.path.join (dir, f))[stat.ST_INO] + meta = { + 'id': str (id), + 'name': f, + 'filename': f, + 'type': mimetypes.guess_type (f) + } + res.append (meta) + + return res + + def getData (self, metadata): + filename = os.path.join (self.getConfig ('dir'), metadata['filename']) + fh = open (filename ,'rb') + read_size = self.getConfig ("read_size") + if read_size: + data = '' + while True: + print ".", + chunk = fh.read (read_size) + data += chunk + if len (chunk) != read_size: + break + else: + data = fh.read () + + fh.close () + + return data + + +loader.registerRepository ('debug_src_repo', DebugSrcRepository) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <edh...@us...> - 2010-06-11 21:40:20
|
Revision: 14 http://clippy.svn.sourceforge.net/clippy/?rev=14&view=rev Author: edheldil Date: 2010-06-11 21:40:13 +0000 (Fri, 11 Jun 2010) Log Message: ----------- Threads Modified Paths: -------------- clippy/config.py clippy/ui/assistant/app.py clippy/ui/assistant/win_main.py Modified: clippy/config.py =================================================================== --- clippy/config.py 2010-06-11 21:39:52 UTC (rev 13) +++ clippy/config.py 2010-06-11 21:40:13 UTC (rev 14) @@ -9,7 +9,7 @@ class Config (object): SYS_CONFIG_FILE = '/etc/clippy.cfg' CONFIG_DIR = os.path.expanduser ('~/.clippy') - CONFIG_FILE = os.path.join (CONFIG_DIR, 'clippy.cfg') + CONFIG_FILE = os.path.join (CONFIG_DIR, 'config') USER_REPO_DIR = os.path.join (CONFIG_DIR, 'repositories') Modified: clippy/ui/assistant/app.py =================================================================== --- clippy/ui/assistant/app.py 2010-06-11 21:39:52 UTC (rev 13) +++ clippy/ui/assistant/app.py 2010-06-11 21:40:13 UTC (rev 14) @@ -48,6 +48,8 @@ self.skip_targets = False self.skip_sources = False self.skip_confirm = False + + self.use_threads = True # FIXME: option for swapping order of source and target pages self.item_list_mode = 'both' # one of: src, tgt, both @@ -108,6 +110,13 @@ default = False, help = "skip download confirmation page") + oparser.add_option ("-u", + "--no-threads", + dest = "use_threads", + action = "store_false", + default = self.use_threads, + help = "don't thread networking") + # oparser.add_option ("-o", # "--option", # dest = "options", @@ -130,6 +139,7 @@ self.item_list_mode = opts.item_list_mode self.skip_confirm = opts.skip_confirm + self.use_threads = opts.use_threads def read_config (self): self.config.read_file (self.config.CONFIG_FILE, True) Modified: clippy/ui/assistant/win_main.py =================================================================== --- clippy/ui/assistant/win_main.py 2010-06-11 21:39:52 UTC (rev 13) +++ clippy/ui/assistant/win_main.py 2010-06-11 21:40:13 UTC (rev 14) @@ -6,6 +6,7 @@ import gtk import pango import string +import sys import thread import time @@ -284,7 +285,7 @@ def run_async (self, job_fns, idle_fn, end_fn): - if True: + if app.use_threads: jobs = [ worker.Job (job_fn) for job_fn in job_fns ] [ worker.input.put (job) for job in jobs ] gtk.idle_add (idle_fn, (jobs, end_fn)) @@ -325,9 +326,12 @@ print "Can't get source items:", e + self.throbber_phase = 0 + self.throbber_time = 0 self.run_async ((lambda: app.repo_target.getItems (qry), lambda: app.repo_source.getItems (qry)), self.update_item_list_idle, self.update_item_list_end) def update_item_list_end (self, results): + print tree = self['tree_items'] items = [] model = self.model_items @@ -378,7 +382,13 @@ tree.thaw_child_notify () def update_item_list_idle (self, data): - print 'x', + pics = "|/-\\" + if time.time () - self.throbber_time >0.3: + print 3*pics[self.throbber_phase] + "\r", + self.throbber_phase = (self.throbber_phase + 1) % len (pics) + sys.stdout.flush() + self.throbber_time = time.time () + jobs, end_fn = data if jobs[0].is_done () and jobs[1].is_done (): end_fn ([ job.result for job in jobs ]) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <edh...@us...> - 2010-03-05 10:08:17
|
Revision: 12 http://clippy.svn.sourceforge.net/clippy/?rev=12&view=rev Author: edheldil Date: 2010-03-05 10:08:12 +0000 (Fri, 05 Mar 2010) Log Message: ----------- Implemented simple asynchronous execution Modified Paths: -------------- clippy/ui/assistant/win_main.py Added Paths: ----------- clippy/worker.py Modified: clippy/ui/assistant/win_main.py =================================================================== --- clippy/ui/assistant/win_main.py 2010-03-05 10:07:27 UTC (rev 11) +++ clippy/ui/assistant/win_main.py 2010-03-05 10:08:12 UTC (rev 12) @@ -6,8 +6,10 @@ import gtk import pango import string +import thread +import time -from clippy import loader, repository, utils +from clippy import loader, repository, utils, worker from clippy.query import Query from clippy.ui import glfactory @@ -281,6 +283,20 @@ self.handle_tree_repos_cursor_changed (tree) + def run_async (self, job_fns, idle_fn, end_fn): + if True: + jobs = [ worker.Job (job_fn) for job_fn in job_fns ] + [ worker.input.put (job) for job in jobs ] + gtk.idle_add (idle_fn, (jobs, end_fn)) + #time.sleep (0.001) + thread.interrupt_main () + return jobs + else: + res = [ fn () for fn in job_fns ] + end_fn (res) + return None + + def update_item_list (self): tree = self['tree_items'] model = self.model_items @@ -288,7 +304,6 @@ # FIXME: should also disable sorting model.clear () - items = [] items_src = [] items_tgt = [] @@ -299,9 +314,6 @@ repo = app.repo_target qry = Query (repo) repo.connect () - items_tgt = repo.getItems (qry) - for item in items_tgt: - item['_origin'] = 'tgt' except Exception, e: print "Can't get target items:", e @@ -309,12 +321,24 @@ repo = app.repo_source qry = Query (repo) repo.connect () - items_src = repo.getItems (qry) - for item in items_src: - item['_origin'] = 'src' except Exception, e: print "Can't get source items:", e + + self.run_async ((lambda: app.repo_target.getItems (qry), lambda: app.repo_source.getItems (qry)), self.update_item_list_idle, self.update_item_list_end) + + def update_item_list_end (self, results): + tree = self['tree_items'] + items = [] + model = self.model_items + items_tgt = results[0] + items_src = results[1] + for item in items_tgt: + item['_origin'] = 'tgt' + + for item in items_src: + item['_origin'] = 'src' + # FIXME: this part should be in core if app.item_list_mode in ['both', 'tgt', 'merge']: @@ -353,7 +377,16 @@ #self['treecaches'].set_model (self.model_caches) tree.thaw_child_notify () + def update_item_list_idle (self, data): + print 'x', + jobs, end_fn = data + if jobs[0].is_done () and jobs[1].is_done (): + end_fn ([ job.result for job in jobs ]) + else: + gtk.idle_add (self.update_item_list_idle, data) + # update throbber + def handle_open_metadata (self, *args): try: win = app.windows['win_metadata'] Added: clippy/worker.py =================================================================== --- clippy/worker.py (rev 0) +++ clippy/worker.py 2010-03-05 10:08:12 UTC (rev 12) @@ -0,0 +1,36 @@ +# -*-python-*- +# vim: set ts=4 sw=4 expandtab: + +import threading +import Queue + +input = Queue.Queue () + +class Worker (threading.Thread): + def run (self): + while True: + job = input.get () + # Empty action causes the worker to terminate + if not job.action: + break + res = job.action () + job.set_result (1, res) + +class Job: + def __init__ (self, action): + self.status = 0 + self.action = action + self.result = None + + def set_result (self, status, result): + self.result = result + self.status = status + + def is_done (self): + return self.status != 0 + +wrk = Worker () +wrk.setDaemon (True) +wrk.start () + + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <edh...@us...> - 2010-03-05 10:07:38
|
Revision: 11 http://clippy.svn.sourceforge.net/clippy/?rev=11&view=rev Author: edheldil Date: 2010-03-05 10:07:27 +0000 (Fri, 05 Mar 2010) Log Message: ----------- More work on config refactoring Modified Paths: -------------- clippy/config.py clippy/loader.py clippy/repository.py clippy/ui/assistant/app.py clippy/ui/assistant/win_main.py Modified: clippy/config.py =================================================================== --- clippy/config.py 2010-01-21 22:30:46 UTC (rev 10) +++ clippy/config.py 2010-03-05 10:07:27 UTC (rev 11) @@ -63,7 +63,7 @@ def set (self, key, value): # Setting unknown keys is not allowed.,keys have to be added first # FIXME: meaningful exception? - self.config[key] + #self.config[key] self.config[key] = value # FIXME: set key in userconfig and make it dirty Modified: clippy/loader.py =================================================================== --- clippy/loader.py 2010-01-21 22:30:46 UTC (rev 10) +++ clippy/loader.py 2010-03-05 10:07:27 UTC (rev 11) @@ -2,8 +2,13 @@ # vim: set ts=4 sw=4 expandtab: import os.path +import sys +import traceback +from clippy import config +from clippy import repository + def registerRepository (id, klass): Loader.registerRepository (id, klass) @@ -25,6 +30,7 @@ self.loadRepositoryDefinitions (repo_dir, None) except: print "Skipping ", repo_dir + traceback.print_exc () pass def importRepositoryClasses (self, dir): @@ -44,13 +50,13 @@ # FIXME: this prevents developer to supply their # own config class. Rather pass config class factory as an argument to this function - config = config.Config () - config.read (os.path.join (dir, file)) - config = config.config # FIXME: ugly + cfg = config.ConfigFile () + cfg.read (os.path.join (dir, file)) + cfg = cfg.config # FIXME: ugly - id = config['id'] + id = cfg['id'] # FIXME: check that id is unique? - self.registerRepository (id, None, config) + self.registerRepository (id, None, cfg) #yield True #yield False @@ -83,7 +89,7 @@ def setupRepositoryClass (self, id): if not id: - print >>sys.stderr, "Skipping repo" + print >>sys.stderr, "Skipping repo: no id" return None klass, config = self.repository_map[id] @@ -96,15 +102,16 @@ try: klass_id = config['class'] except KeyError: - print >>sys.stderr, "Skipping repo" + print >>sys.stderr, "Skipping repo: no class" return None klass = self.setupRepositoryClass (klass_id) - if isinstance (Repository, klass): + if issubclass (klass, repository.Repository): self.repository_map[id][0] = klass return klass else: - print >>sys.stderr, "Skipping repo" + print "klass:", klass + print >>sys.stderr, "Skipping repo: klass no inst" @@ -118,7 +125,7 @@ self.setupRepositoryConfig (id, id) base = self.config.get ('repo.' + id + '.class') if base: - self.setupRepositoryConfig (app, id, base) + self.setupRepositoryConfig (id, base) def getRepositories (self): return self.repository_map Modified: clippy/repository.py =================================================================== --- clippy/repository.py 2010-01-21 22:30:46 UTC (rev 10) +++ clippy/repository.py 2010-03-05 10:07:27 UTC (rev 11) @@ -58,6 +58,11 @@ return self.config.get ('repo.' + self.id + '.' + key) def setConfig (self, key, value): + # FIXME: this is ugly, because the keys are set either + # globally, which screws other instances of the class, + # or locally, which means that app.config is not + # universal and omniscient any more. + #self.objconfig.set (key, value) self.config.set ('repo.' + self.id + '.' + key, value) def getQueryParams (self): Modified: clippy/ui/assistant/app.py =================================================================== --- clippy/ui/assistant/app.py 2010-01-21 22:30:46 UTC (rev 10) +++ clippy/ui/assistant/app.py 2010-03-05 10:07:27 UTC (rev 11) @@ -67,7 +67,7 @@ # FIXME: maybe cli args and config should be called first #print "Init Core" - self.loader = loader.Loader () + self.loader = loader.Loader (self.config) self.loader.loadRepositories (utils.USER_REPO_DIR) # Load Prepare UI for launch @@ -137,7 +137,7 @@ def setup (self): self.glade_file = os.path.expanduser (self.config.get ('ui.assistant.glade_file', self.GLADE_FILE)) - + self.loader.setupRepositories () #print "Loading UI" self.wtree = gtk.glade.XML (self.glade_file) @@ -164,7 +164,7 @@ x, y = window.get_position () w, h = window.get_size () geometry = "%dx%d+%d+%d" %(w, h, x, y) - self.config.set ('ui.%s.geometry'%window_name, geometry) + self.config.set ('ui.assistant.%s.geometry'%window_name, geometry) # FIXME: account for fullscreen def progress_fn (self, msg, percent): @@ -262,7 +262,7 @@ column = gtk.TreeViewColumn ("Value", text_renderer, text = 1) tree.append_column (column) - self.restore_geometry (self, 'win_metadata') + app.restore_geometry (self, 'win_metadata') def update (self, meta): Modified: clippy/ui/assistant/win_main.py =================================================================== --- clippy/ui/assistant/win_main.py 2010-01-21 22:30:46 UTC (rev 10) +++ clippy/ui/assistant/win_main.py 2010-03-05 10:07:27 UTC (rev 11) @@ -270,7 +270,7 @@ for id in app.loader.getRepositories (): klass, config = app.loader.getRepositoryClass (id) if klass.getCaps () & caps_mask: - repo = klass (id, config) + repo = app.loader.getRepository (id) else: continue model.append ([repo]) @@ -278,6 +278,7 @@ tree.thaw_child_notify () tree.get_selection ().select_path (0) tree.grab_focus () + self.handle_tree_repos_cursor_changed (tree) def update_item_list (self): @@ -312,7 +313,7 @@ for item in items_src: item['_origin'] = 'src' except Exception, e: - print "Can't get target items:", e + print "Can't get source items:", e # FIXME: this part should be in core @@ -362,6 +363,9 @@ if self.get_current_page () == self.PAGE_ITEMS: tree = self['tree_items'] + else: + # FIXME: better would be to open repo's metadata window + return selection = tree.get_selection () model, iter = selection.get_selected () @@ -615,6 +619,7 @@ print "TARGETS" if app.repo_target is None: self.update_repo_list (self.PAGE_TARGETS) + print app.repo_target elif page == self.PAGE_SOURCES: print "SOURCES" if app.repo_source is None: This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <edh...@us...> - 2010-01-21 22:30:54
|
Revision: 10 http://clippy.svn.sourceforge.net/clippy/?rev=10&view=rev Author: edheldil Date: 2010-01-21 22:30:46 +0000 (Thu, 21 Jan 2010) Log Message: ----------- Work on refactoring config Modified Paths: -------------- clippy/config.py clippy/loader.py clippy/repository.py clippy/ui/assistant/app.py clippy/ui/shell/app.py clippy/utils.py Modified: clippy/config.py =================================================================== --- clippy/config.py 2010-01-07 23:35:31 UTC (rev 9) +++ clippy/config.py 2010-01-21 22:30:46 UTC (rev 10) @@ -19,19 +19,23 @@ self.user_config = None self.createConfig () - # FIXME: is it a good idea? #atexit.register (self.write_user_config, self.CONFIG_FILE) - def read_file (self, filename): + + def read_file (self, filename, user_config=False): cfg = ConfigFile () cfg.read (filename) self.addConfig (cfg) + if user_config: + self.user_config = cfg + def write_user_config (self): if self.user_config and self.dirty: self.user_config.write () + def createConfig (self): try: os.mkdir (self.CONFIG_DIR) @@ -43,8 +47,6 @@ #self.config['ui.win_main.geometry'] = '500x350+20+20' - - def get (self, key, default = None): # FIXME: get rid of default value? try: @@ -65,19 +67,23 @@ self.config[key] = value # FIXME: set key in userconfig and make it dirty + def add (self, key, value): if not key in self.config: self.config[key] = value + def createKey (self, key, description, default): self.config[key] = default + def addConfig (self, cfg): if isinstance (cfg, ConfigFile): cfg = cfg.config for key, value in cfg.items (): self.add (key, value) + # def getConfigTree (self, prefix): # if not prefix.endswith ('.'): # raise ValueError ("Prefix does not end with '.': " + prefix) Modified: clippy/loader.py =================================================================== --- clippy/loader.py 2010-01-07 23:35:31 UTC (rev 9) +++ clippy/loader.py 2010-01-21 22:30:46 UTC (rev 10) @@ -14,8 +14,8 @@ repository_map = {} - def __init__ (self): - pass + def __init__ (self, config): + self.config = config def loadRepositories (self, repo_dir): import clippy.repositories @@ -50,21 +50,7 @@ id = config['id'] # FIXME: check that id is unique? - klass = config['class'] - klass = self.getRepositoryClass (klass) - - # NOTE: the effect is that config keys defined in main config file - # shadow those in a repo definition file and in a class. - #for key, value in config.items (): - # if key == 'id' or key == 'class': - # continue - # - # key = 'repo.' + id + '.' + key - # if not key in self.config: - # #self.config[key] = value - # pass - - self.registerRepository (id, klass, config) + self.registerRepository (id, None, config) #yield True #yield False @@ -73,28 +59,73 @@ def registerRepository (id, klass, config = None): # This method is called from repository modules - # get repo's config params. - # merge forward duplicate config keys, i.e. (name, dir, name) becomes (name, dir) (FIXME: just merge?) - # add config params to global config if they are not there yet # create instance and give it id and some ref to config # repo's __init__() sets attrs based on repo.<id>.xxxx + if not id in Loader.repository_map: + Loader.repository_map[id] = [klass, config] + def setupRepositoryConfig (self, id, klass_id): + klass, config = self.repository_map[klass_id] + if klass is None: + return + + if config is not None: + for key in config: + self.config.add ('repo.' + id + '.' + key, config[key]) + params = klass.getConfigParams () - if config is None: - config = {} for param in reversed (params): key = param[0] - if not config.has_key (key): - config[key] = param[4] + if key == 'id': + continue + self.config.add ('repo.' + id + '.' + key, param[4]) - Loader.repository_map[id] = (klass, config) + def setupRepositoryClass (self, id): + if not id: + print >>sys.stderr, "Skipping repo" + return None + + klass, config = self.repository_map[id] + if klass: + return klass + + # Mark this repo as "in progress", to prevent circular dependencies + self.repository_map[id][0] = True + + try: + klass_id = config['class'] + except KeyError: + print >>sys.stderr, "Skipping repo" + return None + + klass = self.setupRepositoryClass (klass_id) + if isinstance (Repository, klass): + self.repository_map[id][0] = klass + return klass + else: + print >>sys.stderr, "Skipping repo" + + + + def setupRepositories (self): + for id in self.repository_map: + self.setupRepositoryClass (id) + + # FIXME: delete all repos without a class + + for id in self.repository_map: + self.setupRepositoryConfig (id, id) + base = self.config.get ('repo.' + id + '.class') + if base: + self.setupRepositoryConfig (app, id, base) + def getRepositories (self): return self.repository_map def getRepository (self, id): klass, config = self.getRepositoryClass (id) - repo = klass (id, config.copy ()) + repo = klass (id, self.config) return repo Modified: clippy/repository.py =================================================================== --- clippy/repository.py 2010-01-07 23:35:31 UTC (rev 9) +++ clippy/repository.py 2010-01-21 22:30:46 UTC (rev 10) @@ -55,10 +55,10 @@ def getConfig (self, key): # FIXME: recursive search in ancestors - return self.config[key] + return self.config.get ('repo.' + self.id + '.' + key) def setConfig (self, key, value): - self.config[key] = value + self.config.set ('repo.' + self.id + '.' + key, value) def getQueryParams (self): # list of tuples: (key, label, type, desc, default) Modified: clippy/ui/assistant/app.py =================================================================== --- clippy/ui/assistant/app.py 2010-01-07 23:35:31 UTC (rev 9) +++ clippy/ui/assistant/app.py 2010-01-21 22:30:46 UTC (rev 10) @@ -9,7 +9,7 @@ import gtk import gtk.glade -from clippy import loader, utils +from clippy import loader, utils, config from clippy.ui import glfactory from clippy.ui.assistant import win_main from clippy.ui.assistant import win_repo_config @@ -20,7 +20,10 @@ ################################################# ################################################# -class ClippyAssistant: +class ClippyAssistant (object): + # NOTE: this would mean that the glade file would have to be with python files, not in /usr/share where it belongs + GLADE_FILE = os.path.join (os.path.dirname (__file__) , 'ui.glade') + column_hash = { 'name': ( 'Name', 'str'), 'id': ( 'ID', 'str'), @@ -60,6 +63,8 @@ self.win_main = None #win_main.app = app + self.config = config.Config () + # FIXME: maybe cli args and config should be called first #print "Init Core" self.loader = loader.Loader () @@ -70,6 +75,7 @@ def main (self): self.parse_args () + self.read_config () self.setup () self.run () @@ -102,6 +108,13 @@ default = False, help = "skip download confirmation page") +# oparser.add_option ("-o", +# "--option", +# dest = "options", +# action = "store_multiple", +# default = [], +# help = "set cfg file option") + (opts, args) = oparser.parse_args () @@ -118,9 +131,12 @@ self.skip_confirm = opts.skip_confirm + def read_config (self): + self.config.read_file (self.config.CONFIG_FILE, True) + self.config.read_file (self.config.SYS_CONFIG_FILE) def setup (self): - self.glade_file = os.path.expanduser (self.getConfig ('ui.glade_file')) + self.glade_file = os.path.expanduser (self.config.get ('ui.assistant.glade_file', self.GLADE_FILE)) #print "Loading UI" @@ -137,7 +153,7 @@ gtk.main () def restore_geometry (self, window, window_name): - geometry = self.getConfig ('ui.%s.geometry' %window_name) + geometry = self.config.get ('ui.assistant.%s.geometry' %window_name, '') mo = re.match (r'(\d+)x(\d+)([+-]\d+)([+-]\d+)', geometry) if mo is not None: geometry = map (int, mo.group (3, 4, 1, 2)) @@ -148,7 +164,7 @@ x, y = window.get_position () w, h = window.get_size () geometry = "%dx%d+%d+%d" %(w, h, x, y) - self.setConfig ('ui.%s.geometry'%window_name, geometry) + self.config.set ('ui.%s.geometry'%window_name, geometry) # FIXME: account for fullscreen def progress_fn (self, msg, percent): @@ -167,17 +183,15 @@ # NOTE: this method should be overridden in a child class - def getConfig (self, key): - # FIXME: should be in default config? - cfg = { - 'ui.glade_file': os.path.join (os.path.dirname (__file__) , 'ui.glade'), # NOTE: glade file has to be with python files, not in /usr/share - 'ui.win_main.geometry': '500x350+30+30', - } - - return cfg[key] +# def getConfig (self, key): +# # FIXME: should be in default config? +# cfg = { +# 'ui.glade_file': os.path.join (os.path.dirname (__file__) , 'ui.glade'), # NOTE: glade file has to be with python files, not in /usr/share +# 'ui.win_main.geometry': '500x350+30+30', +# } +# +# return cfg[key] - def setConfig (self, key, value): - pass # def open_url (self, url): # if app.browser.find ('%s') >= 0: Modified: clippy/ui/shell/app.py =================================================================== --- clippy/ui/shell/app.py 2010-01-07 23:35:31 UTC (rev 9) +++ clippy/ui/shell/app.py 2010-01-21 22:30:46 UTC (rev 10) @@ -6,6 +6,7 @@ import readline import sys import traceback +from clippy.config import Config from clippy.loader import Loader from clippy.query import Query import clippy.utils as utils @@ -14,8 +15,11 @@ HISTORY_FILE = os.path.join (utils.CONFIG_DIR, "cclippy.history") def __init__ (self): - self.loader = Loader () + self.config = Config () + # FIXME: read cfg files ... + self.loader = Loader (self.config) self.loader.loadRepositories (utils.USER_REPO_DIR) + self.loader.setupRepositories () self._readline_init () self.encoding = 'ascii' @@ -150,8 +154,8 @@ for (i, id) in enumerate (sorted (self.loader.getRepositories ())): # FIXME: this is rather ugly klass, config = self.loader.getRepositoryClass (id) - name = config['name'] - desc = config['desc'] + name = self.config.get ('repo.' + id + '.name') + desc = self.config.get ('repo.' + id + '.desc') print '[%d]\t%s - %s\n\t\t%s' %(i, id, name, desc) else: try: Modified: clippy/utils.py =================================================================== --- clippy/utils.py 2010-01-07 23:35:31 UTC (rev 9) +++ clippy/utils.py 2010-01-21 22:30:46 UTC (rev 10) @@ -9,17 +9,7 @@ CONFIG_FILE = os.path.join (CONFIG_DIR, 'clippy.cfg') USER_REPO_DIR = os.path.join (CONFIG_DIR, 'repositories') -if False: - if config is None: - self.config = config.Config () - else: - self.config = config - #self.createConfig () - self.config.read (self.CONFIG_FILE) - # FIXME: is it a good idea? - atexit.register (self.writeConfigFile, self.CONFIG_FILE) - XFER_START = 0 XFER_FILE_START = 1 XFER_FILE_UPDATE = 2 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <edh...@us...> - 2010-01-07 23:35:48
|
Revision: 9 http://clippy.svn.sourceforge.net/clippy/?rev=9&view=rev Author: edheldil Date: 2010-01-07 23:35:31 +0000 (Thu, 07 Jan 2010) Log Message: ----------- Refactoring repo configuration Replaced core with repo loader New repo for art.gnome.org Simplified assistant ui fiels a bit Modified Paths: -------------- Makefile clippy/config.py clippy/repositories/dia_shapes_repo.py clippy/repositories/ghns_repo.py clippy/repositories/local_dir_repo.py clippy/repositories/ocal_repo.py clippy/repositories/rest_repo.py clippy/repository.py clippy/ui/__init__.py clippy/ui/assistant/app.py clippy/ui/assistant/ui.py clippy/ui/assistant/win_main.py clippy/ui/shell/app.py clippy/utils.py setup.py Added Paths: ----------- clippy/loader.py clippy/repositories/art_gnome_org_repo.py Removed Paths: ------------- clippy/core.py Modified: Makefile =================================================================== --- Makefile 2010-01-01 21:14:58 UTC (rev 8) +++ Makefile 2010-01-07 23:35:31 UTC (rev 9) @@ -10,7 +10,8 @@ all: clean: - -rm *~ *.pyc *.core *.tmp MANIFEST clippy/*~ clippy/*.pyc clippy/*/*~ clippy/*/*.pyc clippy/*/*/*~ clippy/*/*/*.pyc + -rm *~ *.pyc *.core *.tmp MANIFEST clippy/*~ clippy/*.pyc clippy/*/*~ clippy/*/*.pyc clippy/*/*/*~ clippy/*/*/*.pyc + -rm -rf build tmp cleanrepo: -rm ./tout/* @@ -21,6 +22,9 @@ bdist: python ./setup.py bdist +install: + python ./setup.py install --prefix=tmp + dist: tar cvzf $(FULLNAME).tar.gz $(SOURCES) $(DATA) Modified: clippy/config.py =================================================================== --- clippy/config.py 2010-01-01 21:14:58 UTC (rev 8) +++ clippy/config.py 2010-01-07 23:35:31 UTC (rev 9) @@ -15,12 +15,23 @@ def __init__ (self): self.config = {} + self.dirty = False + self.user_config = None self.createConfig () + # FIXME: is it a good idea? - atexit.register (self.writeConfigFile, self.CONFIG_FILE) + #atexit.register (self.write_user_config, self.CONFIG_FILE) + def read_file (self, filename): + cfg = ConfigFile () + cfg.read (filename) + self.addConfig (cfg) + def write_user_config (self): + if self.user_config and self.dirty: + self.user_config.write () + def createConfig (self): try: os.mkdir (self.CONFIG_DIR) @@ -33,44 +44,7 @@ - def read (self, filename, config = None): - if config is None: - config = self.config - - try: - fh = open (filename, 'r') - except IOError: - return False - for line in fh: - line = line.strip () - if line == '' or line.startswith ('#'): - continue - - key, value = line.split ('=', 1) - key = key.strip () - value = value.strip () - - config[key] = value - - fh.close () - return True - - - def write (self, filename): - try: - fh = open (filename + '.tmp', 'w') - - for key in sorted (self.config): - fh.write ("%s = %s\n" %(key, str (self.config[key]))) - - fh.flush () - fh.close () - os.rename (filename + '.tmp', filename) - except IOError, e: - print >>sys.stderr, "Error writing config file %s:" %filename, e - - def get (self, key, default = None): # FIXME: get rid of default value? try: @@ -89,11 +63,20 @@ # FIXME: meaningful exception? self.config[key] self.config[key] = value + # FIXME: set key in userconfig and make it dirty + def add (self, key, value): + if not key in self.config: + self.config[key] = value def createKey (self, key, description, default): self.config[key] = default + def addConfig (self, cfg): + if isinstance (cfg, ConfigFile): + cfg = cfg.config + for key, value in cfg.items (): + self.add (key, value) # def getConfigTree (self, prefix): # if not prefix.endswith ('.'): @@ -107,3 +90,47 @@ # return res +class ConfigFile (object): + def __init__ (self, **kw): + self.config = {} + self.filename = None + + def read (self, filename): + try: + fh = open (filename, 'r') + except IOError: + return False + + self.filename = filename + + for line in fh: + line = line.strip () + if line == '' or line.startswith ('#'): + continue + + key, value = line.split ('=', 1) + key = key.strip () + value = value.strip () + + self.config[key] = value + + fh.close () + return True + + + def write (self, filename=None): + if not filename: + filename = self.filename + + try: + fh = open (filename + '.tmp', 'w') + + for key in sorted (self.config): + fh.write ("%s = %s\n" %(key, str (self.config[key]))) + + fh.flush () + fh.close () + os.rename (filename + '.tmp', filename) + except IOError, e: + print >>sys.stderr, "Error writing config file %s:" %filename, e + Deleted: clippy/core.py =================================================================== --- clippy/core.py 2010-01-01 21:14:58 UTC (rev 8) +++ clippy/core.py 2010-01-07 23:35:31 UTC (rev 9) @@ -1,105 +0,0 @@ -# -*-python-*- -# vim: set ts=4 sw=4 expandtab: - -import os.path - -clippy_core = None - -def registerRepository (id, klass): - clippy_core.registerRepository (id, klass) - - -class Loader (object): - - repository_list = [] - repository_map = {} - - - def __init__ (self): - global clippy_core - clippy_core = self - - def loadRepositories (self, repo_dir): - import clippy.repositories - self.importRepositoryClasses (os.path.dirname (clippy.repositories.__file__)) - #self.importRepositoryClasses (self.USER_REPO_DIR) - try: - self.loadRepositoryDefinitions (repo_dir, None) - except: - print "Skipping ", repo_dir - pass - - def importRepositoryClasses (self, dir): - for file in sorted (os.listdir (dir)): - file, ext = os.path.splitext (file) - if not ext.lower () in ['.py', '.pyc', '.pyo', '.pyw']: - continue - - # FIXME: or use execfile()? - exec 'import clippy.repositories.' + file - - - def loadRepositoryDefinitions (self, dir, progress_fn): - for file in sorted (os.listdir (dir)): - if not file.endswith ('.cfg'): - continue - - # FIXME: this prevents developer to supply their - # own config class. Rather pass config class factory as an argument to this function - config = config.Config () - config.read (os.path.join (dir, file)) - config = config.config # FIXME: ugly - - id = config['id'] - # FIXME: check that id is unique? - klass = config['class'] - klass = self.getRepositoryClass (klass) - - # NOTE: the effect is that config keys defined in main config file - # shadow those in a repo definition file and in a class. - #for key, value in config.items (): - # if key == 'id' or key == 'class': - # continue - # - # key = 'repo.' + id + '.' + key - # if not key in self.config: - # #self.config[key] = value - # pass - - self.registerRepository (id, klass, config) - #yield True - #yield False - - - def registerRepository (self, id, klass, config = None): - # This method is called from repository modules - - # get repo's config params. - # merge forward duplicate config keys, i.e. (name, dir, name) becomes (name, dir) (FIXME: just merge?) - # add config params to global config if they are not there yet - # create instance and give it id and some ref to config - # repo's __init__() sets attrs based on repo.<id>.xxxx - - params = klass.getConfigParams () - if config is None: - config = {} - for param in reversed (params): - key = param[0] - if not config.has_key (key): - config[key] = param[4] - - Loader.repository_map[id] = (klass, config) - - def getRepositories (self): - return self.repository_map - - def getRepository (self, id): - klass, config = self.getRepositoryClass (id) - repo = klass (id, config.copy ()) - return repo - - - def getRepositoryClass (self, id): - return self.repository_map[id] - - Copied: clippy/loader.py (from rev 8, clippy/core.py) =================================================================== --- clippy/loader.py (rev 0) +++ clippy/loader.py 2010-01-07 23:35:31 UTC (rev 9) @@ -0,0 +1,104 @@ +# -*-python-*- +# vim: set ts=4 sw=4 expandtab: + +import os.path + + +def registerRepository (id, klass): + Loader.registerRepository (id, klass) + + +class Loader (object): + + repository_list = [] + repository_map = {} + + + def __init__ (self): + pass + + def loadRepositories (self, repo_dir): + import clippy.repositories + self.importRepositoryClasses (os.path.dirname (clippy.repositories.__file__)) + #self.importRepositoryClasses (self.USER_REPO_DIR) + try: + self.loadRepositoryDefinitions (repo_dir, None) + except: + print "Skipping ", repo_dir + pass + + def importRepositoryClasses (self, dir): + for file in sorted (os.listdir (dir)): + file, ext = os.path.splitext (file) + if not ext.lower () in ['.py', '.pyc', '.pyo', '.pyw']: + continue + + # FIXME: or use execfile()? + exec 'import clippy.repositories.' + file + + + def loadRepositoryDefinitions (self, dir, progress_fn): + for file in sorted (os.listdir (dir)): + if not file.endswith ('.cfg'): + continue + + # FIXME: this prevents developer to supply their + # own config class. Rather pass config class factory as an argument to this function + config = config.Config () + config.read (os.path.join (dir, file)) + config = config.config # FIXME: ugly + + id = config['id'] + # FIXME: check that id is unique? + klass = config['class'] + klass = self.getRepositoryClass (klass) + + # NOTE: the effect is that config keys defined in main config file + # shadow those in a repo definition file and in a class. + #for key, value in config.items (): + # if key == 'id' or key == 'class': + # continue + # + # key = 'repo.' + id + '.' + key + # if not key in self.config: + # #self.config[key] = value + # pass + + self.registerRepository (id, klass, config) + #yield True + #yield False + + + @staticmethod + def registerRepository (id, klass, config = None): + # This method is called from repository modules + + # get repo's config params. + # merge forward duplicate config keys, i.e. (name, dir, name) becomes (name, dir) (FIXME: just merge?) + # add config params to global config if they are not there yet + # create instance and give it id and some ref to config + # repo's __init__() sets attrs based on repo.<id>.xxxx + + params = klass.getConfigParams () + if config is None: + config = {} + for param in reversed (params): + key = param[0] + if not config.has_key (key): + config[key] = param[4] + + Loader.repository_map[id] = (klass, config) + + def getRepositories (self): + return self.repository_map + + def getRepository (self, id): + klass, config = self.getRepositoryClass (id) + repo = klass (id, config.copy ()) + return repo + + + def getRepositoryClass (self, id): + return self.repository_map[id] + + Added: clippy/repositories/art_gnome_org_repo.py =================================================================== --- clippy/repositories/art_gnome_org_repo.py (rev 0) +++ clippy/repositories/art_gnome_org_repo.py 2010-01-07 23:35:31 UTC (rev 9) @@ -0,0 +1,184 @@ +# -*-python-*- +# vim: set ts=4 sw=4 expandtab: + +import mimetypes +import os +import os.path +import re +import urllib2 + +from clippy import loader, repository, query +from clippy.repository import * + + +class ArtGnomeOrgRepository (repository.Repository): + def __init__ (self, id, config): + repository.Repository.__init__ (self, id, config) + self.base_url = 'http://art.gnome.org/backgrounds' + #self.search_url = self.base_url + '/tags' + + #self.link_re = re.compile ('<a href="(' + self.base_url + '/files/([^/]+)/([0-9]+))">([^<]+)</a>') + + #self.dload_re = re.compile ('<p><a href="(' + self.base_url + '/download/[^/]+/([0-9]+))" id="cc_downloadbutton" type="([^"]+)" title="([^"]+)"><span>([^<]*)</span></a>') + self.name_re = re.compile ('^<b>([^<>]+)</b>$') + self.author_re = re.compile ('^<span class="item-detail">by <script type=.*;</script>([^<>]*)<script type=\'text/javascript\'>document.write') + self.license_re = re.compile ('^<br><span class="item-detail"><a href="([^<>"]+)">([^<>]+)</a></span>$') + self.preview_re = re.compile ('^<img width="\\d+" height="\\d+" alt="Preview" src=\'(/images/thumbnails/.*)\'>$') + self.link_re = re.compile ('^<option value="(/[^<>"]+/(\\d+)/([^<>"/]+))">$') + self.type_re = re.compile ('^(\\d+x\\d+|scalable)\\s*</option>$') + + @staticmethod + def getCaps (): + return REPO_REMOTE | REPO_GET | REPO_HAS_PAGES + + @staticmethod + def getConfigParams (): + res = repository.Repository.getConfigParams () + res.extend ([ + ('name', 'Name', 'STR', 'Name...', 'art.Gnome.org (AGO) repository'), + ]) + return res + + def getQueryParams (self): + return [ + #('tag', 'Tag', 'TAG', 'tag desc...', 'backgrounds'), + ('page', 'Page', 'NUM', 'page desc...', 0), + ] + + def connect (self): +# username = config['username'] +# password = config['password'] +# +# auth_handler = urllib2.HTTPBasicAuthHandler () +# auth_handler.add_password ( +# realm = self.auth_realm, +# uri = self.auth_uri, +# user = username, +# passwd = password) +# # ...and install it globally so it can be used with urlopen. +# opener = urllib2.build_opener (auth_handler) +# urllib2.install_opener (opener) + self.tags = self.getTags () + + def disconnect (self): + self.tags = None + + + def getTags (self): + res = [] + return res + f = urllib2.urlopen (self.search_url) + for line in f: + if line.find ('class="cc_tag_link"') >= 0: + pos = line.index ('>') + pos2 = line.index ('<', pos) + tag = line[pos+1:pos2] + res.append (tag) + + return res + + + def getItems (self, query): + res = [] + + url = self.base_url + if query.page > 1: + url += '?page=' + query.page + + f = urllib2.urlopen (url) + line = f.readline () + while line: + line = line.strip () + + if not line.startswith ('<div class="list-item">'): + line = f.readline () + continue + + line = f.readline ().strip () + mo = self.name_re.search (line) + if not mo: + print 'Line does not match line_re:', line + continue + name = mo.group (1) + + f.readline () + + line = f.readline ().strip () + mo = self.author_re.search (line) + if not mo: + print 'Line does not match author_re:', line + continue + author = mo.group (1) + + line = f.readline ().strip () + mo = self.license_re.search (line) + if not mo: + print 'Line does not match license_re:', line + continue + license_url = mo.group (1) + license = mo.group (2) + + f.readline () + line = f.readline ().strip () + + mo = self.preview_re.search (line) + if not mo: + print 'Line does not match preview_re:', line + continue + + preview = mo.group (1) + + f.readline () + f.readline () + f.readline () + f.readline () + f.readline () + f.readline () + line = f.readline ().strip () + + while line != '</select>': + mo = self.link_re.search (line) + if not mo: + print 'Line does not match link_re:', line + break + + url = 'http://art.gnome.org/download?d=' + mo.group (1) + id = mo.group (2) + filename = mo.group (3) + + line = f.readline ().strip () + + mo = self.type_re.search (line) + if not mo: + print 'Line does not match type_re:', line + break + + type = mo.group (1) + + meta = { + 'id': id, + 'name': name, + 'filename': filename, + 'author': author, + 'license': license, + 'license_url': license_url, + 'type': type, + 'preview': 'http://art.gnome.org' + preview, + 'url': url, + } + res.append (meta) + line = f.readline ().strip () + + + f.close () + return res + + + def getData (self, metadata): + f = urllib2.urlopen (metadata['url']) + + data = f.read () + return data + + +loader.registerRepository ('art_gnome_org_repo', ArtGnomeOrgRepository) Modified: clippy/repositories/dia_shapes_repo.py =================================================================== --- clippy/repositories/dia_shapes_repo.py 2010-01-01 21:14:58 UTC (rev 8) +++ clippy/repositories/dia_shapes_repo.py 2010-01-07 23:35:31 UTC (rev 9) @@ -8,7 +8,7 @@ import urllib2 from xml.dom.minidom import parse, parseString -from clippy import core, repository +from clippy import loader, repository class DiaShapesRepository (repository.Repository): @@ -66,4 +66,4 @@ return data -core.registerRepository ('dia_shapes_repo', DiaShapesRepository) +loader.registerRepository ('dia_shapes_repo', DiaShapesRepository) Modified: clippy/repositories/ghns_repo.py =================================================================== --- clippy/repositories/ghns_repo.py 2010-01-01 21:14:58 UTC (rev 8) +++ clippy/repositories/ghns_repo.py 2010-01-07 23:35:31 UTC (rev 9) @@ -7,7 +7,7 @@ import urllib2 from xml.dom.minidom import parse, parseString -from clippy import core, repository +from clippy import loader, repository class GHNSRepository (repository.Repository): @@ -116,4 +116,4 @@ return data -core.registerRepository ('ghns_repo', GHNSRepository) +loader.registerRepository ('ghns_repo', GHNSRepository) Modified: clippy/repositories/local_dir_repo.py =================================================================== --- clippy/repositories/local_dir_repo.py 2010-01-01 21:14:58 UTC (rev 8) +++ clippy/repositories/local_dir_repo.py 2010-01-07 23:35:31 UTC (rev 9) @@ -7,7 +7,7 @@ import stat import string -from clippy import core, repository +from clippy import loader, repository from clippy.repository import * @@ -85,4 +85,4 @@ fh.close () -core.registerRepository ('local_dir_repo', LocalDirRepository) +loader.registerRepository ('local_dir_repo', LocalDirRepository) Modified: clippy/repositories/ocal_repo.py =================================================================== --- clippy/repositories/ocal_repo.py 2010-01-01 21:14:58 UTC (rev 8) +++ clippy/repositories/ocal_repo.py 2010-01-07 23:35:31 UTC (rev 9) @@ -6,7 +6,7 @@ import re import urllib2 -from clippy import core, repository, query +from clippy import loader, repository, query from clippy.repository import * @@ -111,4 +111,4 @@ return data -core.registerRepository ('ocal_repo', OCALRepository) +loader.registerRepository ('ocal_repo', OCALRepository) Modified: clippy/repositories/rest_repo.py =================================================================== --- clippy/repositories/rest_repo.py 2010-01-01 21:14:58 UTC (rev 8) +++ clippy/repositories/rest_repo.py 2010-01-07 23:35:31 UTC (rev 9) @@ -5,7 +5,7 @@ import urllib2 from xml.dom.minidom import parse, parseString -from clippy import core, repository +from clippy import loader, repository from clippy.repository import * @@ -92,4 +92,4 @@ return data -core.registerRepository ('rest_repo', RestRepository) +loader.registerRepository ('rest_repo', RestRepository) Modified: clippy/repository.py =================================================================== --- clippy/repository.py 2010-01-01 21:14:58 UTC (rev 8) +++ clippy/repository.py 2010-01-07 23:35:31 UTC (rev 9) @@ -1,6 +1,5 @@ # -*-python-*- # vim: set ts=4 sw=4 expandtab: -from clippy import core REPO_LOCAL = 0 REPO_CACHE = 0 @@ -23,7 +22,6 @@ class Repository (object): def __init__ (self, id, config): - self.core = core.clippy_core self.id = id self.config = config self.name = self.getConfig ('name') Modified: clippy/ui/__init__.py =================================================================== --- clippy/ui/__init__.py 2010-01-01 21:14:58 UTC (rev 8) +++ clippy/ui/__init__.py 2010-01-07 23:35:31 UTC (rev 9) @@ -1,4 +1,5 @@ # -*-python-*- -from assistant.ui import ClippyAssistant +#from shell.ui import ClippyShell +#from assistant.ui import ClippyAssistant #from commander.app import ClippyCommander Modified: clippy/ui/assistant/app.py =================================================================== --- clippy/ui/assistant/app.py 2010-01-01 21:14:58 UTC (rev 8) +++ clippy/ui/assistant/app.py 2010-01-07 23:35:31 UTC (rev 9) @@ -1,22 +1,93 @@ #!/usr/bin/env python +# vim: set ts=4 sw=4 expandtab: from optparse import OptionParser -from clippy.ui import ClippyAssistant +import os +import os.path +import re -oparser = OptionParser () -oparser.add_option ("-s", +import gtk +import gtk.glade + +from clippy import loader, utils +from clippy.ui import glfactory +from clippy.ui.assistant import win_main +from clippy.ui.assistant import win_repo_config + +################################################# +app = None + +################################################# +################################################# + +class ClippyAssistant: + column_hash = { + 'name': ( 'Name', 'str'), + 'id': ( 'ID', 'str'), + 'desc': ( 'Desc', 'str'), + 'type': ( 'Type', 'str'), + 'filename': ( 'Filename', 'str'), + 'selection': ('S', 'bool'), + } + + COL_HEADER = 0 + COL_TYPE = 1 + + def __init__ (self): + global app + + app = self + win_main.app = self + + self.repo_source = None + self.repo_target = None + + self.skip_targets = False + self.skip_sources = False + self.skip_confirm = False + # FIXME: option for swapping order of source and target pages + + self.item_list_mode = 'both' # one of: src, tgt, both + + self.icon_dir = '.' + self.icons = {} + + self.columns = [ 'name', 'desc' ] + self.columns_item = { '': [ 'name', 'id', 'type', 'selection', 'filename' ] } + + # Map w/ references to already created windows + self.windows = {} + self.win_main = None + #win_main.app = app + + # FIXME: maybe cli args and config should be called first + #print "Init Core" + self.loader = loader.Loader () + self.loader.loadRepositories (utils.USER_REPO_DIR) + + # Load Prepare UI for launch + ###self.setup () + + def main (self): + self.parse_args () + self.setup () + self.run () + + def parse_args (self): + oparser = OptionParser () + oparser.add_option ("-s", "--source", dest = "source", help = "use source repository REPOID and skip source selection page", metavar = "REPOID") -oparser.add_option ("-t", + oparser.add_option ("-t", "--target", dest = "target", help = "use target repository REPOID and skip target selection page", metavar = "REPOID") -oparser.add_option ("-i", + oparser.add_option ("-i", "--item-list-mode", dest = "item_list_mode", help = "item list mode MODE", @@ -24,28 +95,199 @@ metavar = "MODE") -oparser.add_option ("-y", + oparser.add_option ("-y", "--skip-confirm", dest = "skip_confirm", action = "store_true", default = False, help = "skip download confirmation page") -(opts, args) = oparser.parse_args () + (opts, args) = oparser.parse_args () -app = ClippyAssistant () -if opts.target is not None: - r = app.core.getRepository (opts.target) - app.setTargetRepository (r) + if opts.target is not None: + r = self.loader.getRepository (opts.target) + self.setTargetRepository (r) -if opts.source is not None: - r = app.core.getRepository (opts.source) - app.setSourceRepository (r) + if opts.source is not None: + r = self.loader.getRepository (opts.source) + self.setSourceRepository (r) -if opts.item_list_mode is not None: - app.item_list_mode = opts.item_list_mode + if opts.item_list_mode is not None: + self.item_list_mode = opts.item_list_mode -app.skip_confirm = opts.skip_confirm + self.skip_confirm = opts.skip_confirm -app.run () + + def setup (self): + self.glade_file = os.path.expanduser (self.getConfig ('ui.glade_file')) + + + #print "Loading UI" + self.wtree = gtk.glade.XML (self.glade_file) + self.factory = myFactory (self.wtree) + + #self.icons['XXXX'] = gtk.gdk.pixbuf_new_from_file (os.path.join (app.icon_dir, 'xxxx.png')) + self.win_main = self.factory ("win_main") + #self.win_main.setup () + self.win_main.show () + + + def run (self): + gtk.main () + + def restore_geometry (self, window, window_name): + geometry = self.getConfig ('ui.%s.geometry' %window_name) + mo = re.match (r'(\d+)x(\d+)([+-]\d+)([+-]\d+)', geometry) + if mo is not None: + geometry = map (int, mo.group (3, 4, 1, 2)) + window.resize (geometry[2], geometry[3]) + window.move (geometry[0], geometry[1]) + + def save_geometry (self, window, window_name): + x, y = window.get_position () + w, h = window.get_size () + geometry = "%dx%d+%d+%d" %(w, h, x, y) + self.setConfig ('ui.%s.geometry'%window_name, geometry) + # FIXME: account for fullscreen + + def progress_fn (self, msg, percent): + #self.splash_window.update_progress (msg, percent) + pass + + + def setSourceRepository (self, repo): + self.repo_source = repo + self.skip_sources = True + + + def setTargetRepository (self, repo): + self.repo_target = repo + self.skip_targets = True + + + # NOTE: this method should be overridden in a child class + def getConfig (self, key): + # FIXME: should be in default config? + cfg = { + 'ui.glade_file': os.path.join (os.path.dirname (__file__) , 'ui.glade'), # NOTE: glade file has to be with python files, not in /usr/share + 'ui.win_main.geometry': '500x350+30+30', + } + + return cfg[key] + + def setConfig (self, key, value): + pass + +# def open_url (self, url): +# if app.browser.find ('%s') >= 0: +# os.system (app.browser %url) +# else: +# os.system (app.browser + " '" + url + "'") +# +# +# def help (self, topic): +# self.open_url (app.help_url + '/' + topic + '.html') +# + +# def run_dialog (self, win_name, update_args = (), closers = ()): +# try: win = self.windows[win_name] +# except: win = self.windows[win_name] = self.factory (win_name) +# +# if callable (update_args): +# update_args (win) +# else: +# try: +# apply (win.update, update_args) +# except AttributeError: +# print "Warning: %s has no update() fn" %win_name +# +# while True: +# res = win.run () +# if res in (gtk.RESPONSE_OK, gtk.RESPONSE_CLOSE, gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT) + closers: +# # FIXME: maybe it would be better to let the caller do win.hide () +# win.hide () +# return res, win +# +# if res == gtk.RESPONSE_HELP: +# self.help (win_name) + + +################################################# +#class win_save (glfactory.glObject): +# def init_widget (self): +# model = gtk.ListStore (int, str, str) +# self['treesaveformats'].set_model (model) +# +# for fmt in app.formats: +# model.append (i, fmt[0], fmt[1]) +# +# renderer = gtk.CellRendererText () +# +# column = gtk.TreeViewColumn ('ext', renderer, text = 1) +# self['treesaveformats'].append_column (column) +# +# column = gtk.TreeViewColumn ('Name', renderer, text = 2) +# self['treesaveformats'].append_column (column) +# +# def on_destroy (self, widget): +# gtk.main_quit () + + +################################################# +class win_metadata (glfactory.glObject): + def init_widget (self): + self.model = gtk.ListStore (str, str) + tree = self['tree_metadata'] + tree.set_model (self.model) + + text_renderer = gtk.CellRendererText () + + column = gtk.TreeViewColumn ("Key", text_renderer, text = 0) + tree.append_column (column) + + column = gtk.TreeViewColumn ("Value", text_renderer, text = 1) + tree.append_column (column) + self.restore_geometry (self, 'win_metadata') + + + def update (self, meta): + self.model.clear () + for key in sorted (meta.keys ()): + self.model.append ([key, str (meta[key])]) + + def on_but_metadata_close_clicked (self, *args): + self.hide () + + def on_delete_event (self, *args): + self.hide () + return 1 + + +################################################# +class win_about (glfactory.glObject): + def update (self): + pass + + +################################################# +class myFactory (glfactory.glFactory): + classes = { + 'win_main': win_main.win_main, + 'win_metadata': win_metadata, + 'win_repo_config': win_repo_config.win_repo_config, + 'win_repo_query': win_repo_config.win_repo_query, + #'win_about': win_about, + } + + +################################################# +if __name__ == '__main__': + # FIXME: need to setup import root to ../.. + app = ClippyAssistant () + app.main () + + +################################################# +# End of file app.py + Modified: clippy/ui/assistant/ui.py =================================================================== --- clippy/ui/assistant/ui.py 2010-01-01 21:14:58 UTC (rev 8) +++ clippy/ui/assistant/ui.py 2010-01-07 23:35:31 UTC (rev 9) @@ -9,7 +9,7 @@ import gtk import gtk.glade -from clippy import core, utils +from clippy import loader, utils from clippy.ui import glfactory from clippy.ui.assistant import win_main from clippy.ui.assistant import win_repo_config @@ -36,8 +36,8 @@ def __init__ (self): global ui - ui = self - win_main.ui = self + app = self + win_main.app = self self.repo_source = None self.repo_target = None @@ -61,8 +61,8 @@ #win_main.app = app #print "Init Core" - self.core = core.Loader () - self.core.loadRepositories (utils.USER_REPO_DIR) + self.loader = loader.Loader () + self.loader.loadRepositories (utils.USER_REPO_DIR) # Load Prepare UI for launch self.setup () Modified: clippy/ui/assistant/win_main.py =================================================================== --- clippy/ui/assistant/win_main.py 2010-01-01 21:14:58 UTC (rev 8) +++ clippy/ui/assistant/win_main.py 2010-01-07 23:35:31 UTC (rev 9) @@ -7,13 +7,13 @@ import pango import string -from clippy import core, repository +from clippy import loader, repository, utils from clippy.query import Query from clippy.ui import glfactory ################################################# -ui = None +app = None name_lookup = '' @@ -51,7 +51,7 @@ return ".." if col == 'selection': - if ui.win_main.selection.has_key (id (obj)): + if app.win_main.selection.has_key (id (obj)): return True else: return False @@ -71,8 +71,8 @@ if not obj: return False # FIXME: why do the null objects occur at all???? - if ui.filter: - for f in ui.filter: + if app.filter: + for f in app.filter: if not f (obj): return False @@ -119,7 +119,7 @@ 'on_menu_repo_query_activate': self.handle_open_repo_query, 'on_menu_item_metadata_activate': self.handle_open_metadata, } - ui.wtree.signal_autoconnect (signal_dict) + app.wtree.signal_autoconnect (signal_dict) self.set_forward_page_func (self.forward_fn) self.set_page_complete (self.get_nth_page (self.PAGE_INTRO), True) @@ -158,7 +158,7 @@ # FIXME: should not be here self.selection = {} - ui.restore_geometry (self, 'win_main') + app.restore_geometry (self, 'win_main') def setup_list (self, page): @@ -166,17 +166,17 @@ coltypes = [] if page == self.PAGE_SOURCES: - columns += ui.columns + columns += app.columns self.model_sources = model = gtk.ListStore (object) self.model_sources_filtered = filtered = model.filter_new () tree = self['tree_sources'] elif page == self.PAGE_TARGETS: - columns += ui.columns + columns += app.columns self.model_targets = model = gtk.ListStore (object) self.model_targets_filtered = filtered = model.filter_new () tree = self['tree_targets'] else: # items - columns += ui.columns_item[''] + columns += app.columns_item[''] self.model_items = model = gtk.ListStore (object) self.model_items_filtered = filtered = model.filter_new () tree = self['tree_items'] @@ -187,8 +187,8 @@ coltypes.append (object) continue - desc = ui.column_hash[c] - col_type = desc[ui.COL_TYPE] + desc = app.column_hash[c] + col_type = desc[app.COL_TYPE] if col_type == 'img': coltypes.append (gtk.gdk.Pixbuf) elif col_type == 'bool': @@ -233,9 +233,9 @@ i = 1 for c in columns[1:]: - desc = ui.column_hash[c] - col_label = desc[ui.COL_HEADER] - col_type = desc[ui.COL_TYPE] + desc = app.column_hash[c] + col_label = desc[app.COL_HEADER] + col_type = desc[app.COL_TYPE] if col_type == 'img': column = gtk.TreeViewColumn (col_label, img_renderer, pixbuf = i) elif col_type == 'bool': @@ -267,8 +267,8 @@ # FIXME: should also disable sorting model.clear () - for id in ui.core.getRepositories (): - klass, config = ui.core.getRepositoryClass (id) + for id in app.loader.getRepositories (): + klass, config = app.loader.getRepositoryClass (id) if klass.getCaps () & caps_mask: repo = klass (id, config) else: @@ -295,7 +295,7 @@ # FIXME: some list modes do not need source or target items, so do not load them try: - repo = ui.repo_target + repo = app.repo_target qry = Query (repo) repo.connect () items_tgt = repo.getItems (qry) @@ -305,7 +305,7 @@ print "Can't get target items:", e try: - repo = ui.repo_source + repo = app.repo_source qry = Query (repo) repo.connect () items_src = repo.getItems (qry) @@ -316,13 +316,13 @@ # FIXME: this part should be in core - if ui.item_list_mode in ['both', 'tgt', 'merge']: + if app.item_list_mode in ['both', 'tgt', 'merge']: items.extend (items_tgt) - if ui.item_list_mode in ['both', 'src', 'merge']: + if app.item_list_mode in ['both', 'src', 'merge']: items.extend (items_src) - if ui.item_list_mode in ['merge']: + if app.item_list_mode in ['merge']: item_hash = {} for item in items: if item_hash.has_key (item['name']): @@ -334,7 +334,7 @@ items = item_hash.values () - if ui.item_list_mode in ['new']: + if app.item_list_mode in ['new']: item_hash = {} for item in items_tgt: item_hash[item['name']] = item @@ -343,7 +343,7 @@ if not item_hash.has_key (item['name']): items.append (item) - print "MODE", ui.item_list_mode + print "MODE", app.item_list_mode items = sorted (items, None, lambda item: item['name']) #self.model_items.append (None) for item in items: @@ -355,10 +355,10 @@ def handle_open_metadata (self, *args): try: - win = ui.windows['win_metadata'] + win = app.windows['win_metadata'] except: - win = ui.factory ('win_metadata') - ui.windows['win_metadata'] = win + win = app.factory ('win_metadata') + app.windows['win_metadata'] = win if self.get_current_page () == self.PAGE_ITEMS: tree = self['tree_items'] @@ -374,15 +374,15 @@ def handle_open_repo_config (self, *args): try: - win = ui.windows['win_repo_config'] + win = app.windows['win_repo_config'] except: - win = ui.factory ('win_repo_config') - ui.windows['win_repo_config'] = win + win = app.factory ('win_repo_config') + app.windows['win_repo_config'] = win if self.get_current_page () == self.PAGE_SOURCES: - repo = ui.repo_source + repo = app.repo_source else: - repo = ui.repo_target + repo = app.repo_target win.update (repo) win.show () @@ -390,23 +390,23 @@ def handle_open_repo_query (self, *args): try: - win = ui.windows['win_repo_query'] + win = app.windows['win_repo_query'] except: - win = ui.factory ('win_repo_query') - ui.windows['win_repo_query'] = win + win = app.factory ('win_repo_query') + app.windows['win_repo_query'] = win if self.get_current_page () == self.PAGE_SOURCES: - repo = ui.repo_source + repo = app.repo_source query = self.query_source else: - repo = ui.repo_target + repo = app.repo_target query = self.query_target win.update (repo, query) win.show () def handle_preferences (self): - res, win = ui.run_dialog ('win_preferences') + res, win = app.run_dialog ('win_preferences') if res == gtk.RESPONSE_OK: # FIXME: should we recompute cache.found for the new nickname??? self.update () @@ -414,20 +414,20 @@ ## win.hide () def handle_open_url (self, url): - if ui.browser.find ('%s') >= 0: - os.system (ui.browser %url) + if app.browser.find ('%s') >= 0: + os.system (app.browser %url) else: - os.system (ui.browser + " '" + url + "'") + os.system (app.browser + " '" + url + "'") def handle_open_help (self, topic): - self.handle_open_url (ui.help_url + '/' + topic + '.html') + self.handle_open_url (app.help_url + '/' + topic + '.html') def menu_preferences (self, *args): self.handle_preferences () def menu_about (self, *args): - res, win = ui.run_dialog ('win_about') + res, win = app.run_dialog ('win_about') @@ -467,11 +467,11 @@ repo = model.get_value (iter, 0) page = self.get_current_page () if page == self.PAGE_TARGETS: - ui.repo_target = repo - ui.repo_source = None # do new source repos filter + app.repo_target = repo + app.repo_source = None # do new source repos filter self.set_page_complete (self.get_nth_page (self.PAGE_TARGETS), True) elif page == self.PAGE_SOURCES: - ui.repo_source = repo + app.repo_source = repo self.selection = None # FIXME: invalidate items self.set_page_complete (self.get_nth_page (self.PAGE_SOURCES), True) @@ -537,7 +537,7 @@ #if s != None: # app.core.setConfig ('ui.split_position', str (s)) - ui.save_geometry (self, 'win_main') + app.save_geometry (self, 'win_main') def on_tree_sources_button_press_event (self, widget, ev): @@ -545,7 +545,7 @@ return if ev.button == 3: - popup = ui.wtree.get_widget ('menu_repo') + popup = app.wtree.get_widget ('menu_repo') popup.popup (None, None, None, 0, 0) #elif ev.type == gtk.gdk._2BUTTON_PRESS: @@ -558,7 +558,7 @@ return if ev.button == 3: - popup = ui.wtree.get_widget ('menu_repo') + popup = app.wtree.get_widget ('menu_repo') popup.popup (None, None, None, 0, 0) #elif ev.type == gtk.gdk._2BUTTON_PRESS: @@ -570,7 +570,7 @@ return if ev.button == 3: - popup = ui.wtree.get_widget ('menu_item') + popup = app.wtree.get_widget ('menu_item') popup.popup (None, None, None, 0, 0) #elif ev.type == gtk.gdk._2BUTTON_PRESS: @@ -598,11 +598,11 @@ print "FWD", args page = args[0] next = page + 1 - if next == self.PAGE_TARGETS and ui.skip_targets: + if next == self.PAGE_TARGETS and app.skip_targets: next = next + 1 - if next == self.PAGE_SOURCES and ui.skip_sources: + if next == self.PAGE_SOURCES and app.skip_sources: next = next + 1 - if next == self.PAGE_CONFIRM and ui.skip_confirm: + if next == self.PAGE_CONFIRM and app.skip_confirm: next = next + 1 return next @@ -613,11 +613,11 @@ print "PREPARE" , page if page == self.PAGE_TARGETS: print "TARGETS" - if ui.repo_target is None: + if app.repo_target is None: self.update_repo_list (self.PAGE_TARGETS) elif page == self.PAGE_SOURCES: print "SOURCES" - if ui.repo_source is None: + if app.repo_source is None: self.update_repo_list (self.PAGE_SOURCES) elif page == self.PAGE_ITEMS: print "ITEMS" @@ -633,11 +633,33 @@ print "PROGRESS" self.log_buf.delete (self.log_buf.get_start_iter (), self.log_buf.get_end_iter ()) #self['vb_page_download'].show () - gtk.idle_add (ui.core.xfer (ui.repo_source, ui.repo_target, self.selection, self.progress_fn, self.log_fn).next) + gtk.idle_add (utils.xfer (app.repo_source, app.repo_target, self.selection, self.progress_fn).next) #ui.core.xfer (ui.repo_source, ui.repo_target, self.selection, self.progress_fn, self.log_fn) - def progress_fn (self, total, total_msg, file, file_msg): + + def progress_fn (self, state, **kw): + if state == utils.XFER_START: + print "Starting transfer..." + elif state == utils.XFER_FILE_START: + self['progress_file'].set_fraction (0) + self['progress_file'].set_text (kw['name']) + self.log_buf.insert (self.log_buf.get_end_iter (), kw['name'] + ' ... ') + elif state == utils.XFER_FILE_UPDATE: + pass + elif state == utils.XFER_FILE_OK: + self['progress_file'].set_fraction (1.0) + self.log_buf.insert_with_tags_by_name (self.log_buf.get_end_iter (), "OK\n", "ok") + elif state == utils.XFER_FILE_ERROR: + self.log_buf.insert_with_tags_by_name (self.log_buf.get_end_iter (), "ERROR\n", "error") + elif state == utils.XFER_END: + print 'Transfer complete' + else: + pass + + self['text_log'].scroll_to_mark (self.log_buf.get_insert (), 0.05, True, 0.0, 1.0) + + def progressx_fn (self, total, total_msg, file, file_msg): self['progress_file'].set_fraction (file) self['progress_file'].set_text (file_msg) self['progress_total'].set_fraction (total) Modified: clippy/ui/shell/app.py =================================================================== --- clippy/ui/shell/app.py 2010-01-01 21:14:58 UTC (rev 8) +++ clippy/ui/shell/app.py 2010-01-07 23:35:31 UTC (rev 9) @@ -4,9 +4,9 @@ import atexit import os.path import readline +import sys import traceback - -from clippy.core import Loader +from clippy.loader import Loader from clippy.query import Query import clippy.utils as utils @@ -14,11 +14,17 @@ HISTORY_FILE = os.path.join (utils.CONFIG_DIR, "cclippy.history") def __init__ (self): - self.core = Loader () - self.core.loadRepositories (utils.USER_REPO_DIR) + self.loader = Loader () + self.loader.loadRepositories (utils.USER_REPO_DIR) self._readline_init () self.encoding = 'ascii' + def quit (self): + try: + readline.write_history_file (self.HISTORY_FILE) + except IOError, e: + print >>sys.stderr, "W: Can't write history file `%s':" %self.HISTORY_FILE, e + def _readline_init (self): """Enable history and tab completion for the command line.""" @@ -29,7 +35,7 @@ except IOError: pass - atexit.register (readline.write_history_file, self.HISTORY_FILE) + atexit.register (self.quit) ###readline.set_pre_input_hook (self._readline_hook) def _readline_hook (): @@ -119,7 +125,11 @@ print "Source: ", source_name print "Target: ", target_name - cmd = raw_input ('Cmd: ') + try: + cmd = raw_input ('Cmd: ') + except EOFError: + print + cmd = 'q' try: cmd, arg = cmd.split (' ', 1) @@ -137,21 +147,21 @@ elif cmd == 's' or cmd == 't': if arg is None: - for (i, id) in enumerate (sorted (self.core.getRepositories ())): + for (i, id) in enumerate (sorted (self.loader.getRepositories ())): # FIXME: this is rather ugly - klass, config = self.core.getRepositoryClass (id) + klass, config = self.loader.getRepositoryClass (id) name = config['name'] desc = config['desc'] print '[%d]\t%s - %s\n\t\t%s' %(i, id, name, desc) else: try: arg = int (arg) - arg = sorted (self.core.getRepositories ())[arg] + arg = sorted (self.loader.getRepositories ())[arg] except: pass #try: - repo = self.core.getRepository (arg) + repo = self.loader.getRepository (arg) #except Exception, e: # print e # continue @@ -245,7 +255,9 @@ else: print "Unknown command:", cmd + print "Quit..." + def main (): app = Shell () app.run_shell () Modified: clippy/utils.py =================================================================== --- clippy/utils.py 2010-01-01 21:14:58 UTC (rev 8) +++ clippy/utils.py 2010-01-07 23:35:31 UTC (rev 9) @@ -2,6 +2,7 @@ # vim: set ts=4 sw=4 expandtab: import os.path +import traceback SYS_CONFIG_FILE = '/etc/clippy.cfg' CONFIG_DIR = os.path.expanduser ('~/.clippy') @@ -44,6 +45,7 @@ target.putData (obj, data) except Exception, e: progress_fn (XFER_FILE_ERROR, source=source, target=target, progress=progress, name=obj['name'], error=unicode (e)) + traceback.print_exc () # FIXME: delete t,p file yield True continue Modified: setup.py =================================================================== --- setup.py 2010-01-01 21:14:58 UTC (rev 8) +++ setup.py 2010-01-07 23:35:31 UTC (rev 9) @@ -9,11 +9,13 @@ author = 'Jarda Benkovsky', author_email = 'edh...@us...', url = 'http://www.eowyn.cz/clippy', - scripts = ['cclippy.py'], - packages = ['clippy', 'clippy.repositories', 'clippy.ui.assistant', 'clippy.ui.commander'], + license = 'GPL', + scripts = ['cclippy', 'gclippy'], + #packages = ['clippy', 'clippy.repositories', 'clippy.ui.shell', 'clippy.ui.assistant', 'clippy.ui.commander'], + packages = ['clippy', 'clippy.repositories', 'clippy.ui.shell', 'clippy.ui.assistant'], data_files = [('share/clippy/assistant', ['clippy/ui/assistant/ui.glade', 'logo.png', 'icon.png']), ('share/clippy/icons', glob.glob ('icons/*.png')), - ('share/doc/clippy', ['TODO']), - ('share/doc/gclippy/help', glob.glob ('help/*.html') + glob.glob ('help/*.css') + glob.glob ('help/*.png')), + ('share/doc/clippy', ['README', 'TODO']), + ('share/doc/clippy/help', glob.glob ('help/*.html') + glob.glob ('help/*.css') + glob.glob ('help/*.png')), ]) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <edh...@us...> - 2010-01-01 21:15:15
|
Revision: 8 http://clippy.svn.sourceforge.net/clippy/?rev=8&view=rev Author: edheldil Date: 2010-01-01 21:14:58 +0000 (Fri, 01 Jan 2010) Log Message: ----------- Refactored Replace core with just repo loader Modified Paths: -------------- Makefile clippy/core.py clippy/repositories/dia_shapes_repo.py clippy/repositories/ghns_repo.py clippy/repositories/local_dir_repo.py clippy/repositories/ocal_repo.py clippy/repositories/rest_repo.py clippy/repository.py clippy/ui/assistant/ui.py clippy/ui/assistant/win_main.py Added Paths: ----------- cclippy clippy/config.py clippy/ui/shell/ clippy/ui/shell/__init__.py clippy/ui/shell/app.py clippy/utils.py gclippy Removed Paths: ------------- cclippy.py Modified: Makefile =================================================================== --- Makefile 2009-05-12 14:33:54 UTC (rev 7) +++ Makefile 2010-01-01 21:14:58 UTC (rev 8) @@ -10,8 +10,11 @@ all: clean: - -rm *.pyc *.core *.tmp MANIFEST clippy/*.pyc clippy/repositories/*.pyc clippy/ui/*.pyc clippy/ui/*/*.pyc + -rm *~ *.pyc *.core *.tmp MANIFEST clippy/*~ clippy/*.pyc clippy/*/*~ clippy/*/*.pyc clippy/*/*/*~ clippy/*/*/*.pyc +cleanrepo: + -rm ./tout/* + sdist: python ./setup.py sdist Added: cclippy =================================================================== --- cclippy (rev 0) +++ cclippy 2010-01-01 21:14:58 UTC (rev 8) @@ -0,0 +1,2 @@ +#!/bin/sh +exec python -m clippy.ui.shell.app Property changes on: cclippy ___________________________________________________________________ Added: svn:executable + * Deleted: cclippy.py =================================================================== --- cclippy.py 2009-05-12 14:33:54 UTC (rev 7) +++ cclippy.py 2010-01-01 21:14:58 UTC (rev 8) @@ -1,221 +0,0 @@ -#!/usr/bin/env python - -import atexit -import os.path -import readline - -from clippy.core import ClippyCore -from clippy.query import Query - -class CClippy (object): - HISTORY_FILE = os.path.join (ClippyCore.CONFIG_DIR, "cclippy.history") - - def __init__ (self): - self.core = ClippyCore () - self._readline_init () - - def run (self): - repo = self.core.repositories[0] - - repo_config = self.core.getConfigTree ('repo.' + repo.id + '.') - - repo.connect (repo_config) - objlist = repo.search (None) - #for obj in objlist: - # print obj['id'], obj['name'] - - data = repo.getObject (objlist[0]) - - inst.handleData (objlist[0], data) - - def _readline_init (self): - """Enable history and tab completion for the command line.""" - - readline.parse_and_bind ("tab: complete") - try: - readline.read_history_file (self.HISTORY_FILE) - except IOError: - pass - - atexit.register (readline.write_history_file, self.HISTORY_FILE) - ###readline.set_pre_input_hook (self._readline_hook) - - def _readline_hook (): - readline.insert_text ('') - readline.redisplay () - - def help (self): - print """ -Commands: - s - set source repository - t - set target repository - ls - list source file - lt - list target files - qs - list or set source repo query params - qt - list or set target repo query params - cs - list or set source repo config params - ct - list or set target repo config params - g - get specified ids - p - put specified ids - d - delete specified ids - i - metadata info on specified ids - apply - transfer previously selected files - h - this help - q - quit program -""" - - def run_shell (self): - source = None - source_query = None - target = None - target_query = None - items = [] - selection = {} - connected = False - - print "\ntype <?> or <h> for help, <q> for quit\n" - while True: - if source is not None: - source_name = source.name - else: - source_name = '' - - if target is not None: - target_name = target.name - else: - target_name = '' - - print "Source: ", source_name - print "Target: ", target_name - - cmd = raw_input ('Cmd: ') - - try: - cmd, arg = cmd.split (' ', 1) - except: - arg = None - - if cmd == 'h' or cmd == '?': - self.help () - - elif cmd == 'q': - break - - elif cmd == 's' or cmd == 't': - if arg is None: - for (i, id) in enumerate (sorted (self.core.getRepositories ())): - # FIXME: this is rather ugly - name = self.core.getConfig ('repo.' + id + '.name') - desc = self.core.getConfig ('repo.' + id + '.desc') - print '[%d]\t%s - %s\n\t\t%s' %(i, id, name, desc) - else: - try: - arg = int (arg) - arg = sorted (self.core.getRepositories ())[arg] - except: - pass - - try: - repo = self.core.getRepository (arg) - except Exception, e: - print e - continue - - qry = Query (repo) - if cmd == 's': - source = repo - source_query = qry - selection = {} - else: - target = repo - target_query = qry - - elif cmd == 'ls' or cmd == 'lt': - if cmd == 'ls': - repo = source - qry = source_query - source.connect () - items = source.getItems (source_query) - else: - repo = target - qry = target_query - target.connect () - - # FIXME: use cache if permitted - for meta in repo.getItems (qry): - if meta['id'] in selection: - sel_sign = '+' - else: - sel_sign = ' ' - print sel_sign, meta['id'], meta['name'].encode ('ascii', 'replace') - - elif cmd == 'qs' or cmd == 'qt': - if cmd == 'qs': - qry = source_query - else: - qry = target_query - - if arg is None: - for param in qry.repo.getQueryParams (): - print param[0], ':', qry.getValue (param[0]) - else: - key, value = arg.split (' ', 1) - qry.setValue (key, value) - # FIXME: getItems () - - elif cmd == 'cs' or cmd == 'ct': - if cmd == 'cs': - repo = source - else: - repo = target - - if arg is None: - for param in repo.getConfigParams (): - value = self.core.getConfig ('repo.' + repo.id + '.' + param[0]) - print param[0], ':', value - else: - key, value = arg.split (' ', 1) - self.core.setConfig ('repo.' + repo.id + '.' + key, value) - - # FIXME: we have to reload repo now, because the config params - # are usually only read at class instantiation - - # get - elif cmd == 'g': - ids = arg.split (' ') - for id in ids: - new_sel = filter (lambda m: m['id'] == id, items) - for m in new_sel: - selection[m['id']] = m - - # info - elif cmd == 'i': - ids = arg.split (' ') - new_sel = filter (lambda m: m['id'] in ids, items) - for m in new_sel: - for key, value in m.items (): - print key + ':', value - - - #put - elif cmd == 'p': - pass - - # delete - elif cmd == 'd': - pass - - elif cmd == 'apply': - ids = selection.keys () - ids.sort () - for id in ids: - meta = selection[id] - print "Getting", id, meta['name'].encode ('ascii', 'replace') - data = source.getData (meta) - target.putData (meta, data) - - else: - print "Unknown command:", cmd - -app = CClippy () -app.run_shell () Added: clippy/config.py =================================================================== --- clippy/config.py (rev 0) +++ clippy/config.py 2010-01-01 21:14:58 UTC (rev 8) @@ -0,0 +1,109 @@ +# -*-python-*- +# vim: set ts=4 sw=4 expandtab: + +import atexit +import os.path + + + +class Config (object): + SYS_CONFIG_FILE = '/etc/clippy.cfg' + CONFIG_DIR = os.path.expanduser ('~/.clippy') + CONFIG_FILE = os.path.join (CONFIG_DIR, 'clippy.cfg') + USER_REPO_DIR = os.path.join (CONFIG_DIR, 'repositories') + + + def __init__ (self): + self.config = {} + self.createConfig () + + # FIXME: is it a good idea? + atexit.register (self.writeConfigFile, self.CONFIG_FILE) + + + def createConfig (self): + try: + os.mkdir (self.CONFIG_DIR) + os.mkdir (self.USER_REPO_DIR) + except: + pass + + # FIXME: see ui.assistant.ui getConfig() + #self.config['ui.win_main.geometry'] = '500x350+20+20' + + + + def read (self, filename, config = None): + if config is None: + config = self.config + + try: + fh = open (filename, 'r') + except IOError: + return False + + for line in fh: + line = line.strip () + if line == '' or line.startswith ('#'): + continue + + key, value = line.split ('=', 1) + key = key.strip () + value = value.strip () + + config[key] = value + + fh.close () + return True + + + def write (self, filename): + try: + fh = open (filename + '.tmp', 'w') + + for key in sorted (self.config): + fh.write ("%s = %s\n" %(key, str (self.config[key]))) + + fh.flush () + fh.close () + os.rename (filename + '.tmp', filename) + except IOError, e: + print >>sys.stderr, "Error writing config file %s:" %filename, e + + + def get (self, key, default = None): + # FIXME: get rid of default value? + try: + return self.config[key] + except: + return default + + + def getRepoConfig (self, repo, key, default = None): + # FIXME: moved to Repository? + return self.getConfig ('repo.' + repo.id + '.' + key, default) + + + def set (self, key, value): + # Setting unknown keys is not allowed.,keys have to be added first + # FIXME: meaningful exception? + self.config[key] + self.config[key] = value + + + def createKey (self, key, description, default): + self.config[key] = default + + +# def getConfigTree (self, prefix): +# if not prefix.endswith ('.'): +# raise ValueError ("Prefix does not end with '.': " + prefix) +# +# res = {} +# for key, value in self.config.items (): +# if key.startswith (prefix): +# res[key[len (prefix):]] = value +# +# return res + + Modified: clippy/core.py =================================================================== --- clippy/core.py 2009-05-12 14:33:54 UTC (rev 7) +++ clippy/core.py 2010-01-01 21:14:58 UTC (rev 8) @@ -1,8 +1,7 @@ # -*-python-*- +# vim: set ts=4 sw=4 expandtab: -import atexit import os.path -import urlparse clippy_core = None @@ -10,10 +9,7 @@ clippy_core.registerRepository (id, klass) -class ClippyCore (object): - CONFIG_DIR = os.path.expanduser ('~/.clippy') - CONFIG_FILE = os.path.join (CONFIG_DIR, 'clippy.cfg') - USER_REPO_DIR = os.path.join (CONFIG_DIR, 'repositories') +class Loader (object): repository_list = [] repository_map = {} @@ -23,107 +19,37 @@ global clippy_core clippy_core = self - self.config = {} - try: - os.mkdir (self.CONFIG_DIR) - os.mkdir (self.USER_REPO_DIR) - except: - pass - - self.readConfigFile (self.CONFIG_FILE) - # FIXME: is it a good idea? - atexit.register (self.writeConfigFile, self.CONFIG_FILE) - + def loadRepositories (self, repo_dir): import clippy.repositories self.importRepositoryClasses (os.path.dirname (clippy.repositories.__file__)) #self.importRepositoryClasses (self.USER_REPO_DIR) - self.loadRepositoryDefinitions (self.USER_REPO_DIR) - - - def readConfigFile (self, filename, config = None): - if config is None: - config = self.config - try: - fh = open (filename, 'r') - except IOError: - return False - - for line in fh: - line = line.strip () - if line == '' or line.startswith ('#'): - continue - - key, value = line.split ('=', 1) - key = key.strip () - value = value.strip () - - config[key] = value - - fh.close () - return True - - - def writeConfigFile (self, filename): - fh = open (filename + '.tmp', 'w') - - for key in sorted (self.config): - fh.write ("%s = %s\n" %(key, str (self.config[key]))) - - fh.flush () - fh.close () - os.rename (filename + '.tmp', filename) - - - def getConfig (self, key, default = None): - # FIXME: get rid of default value? - try: - return self.config[key] + self.loadRepositoryDefinitions (repo_dir, None) except: - return default + print "Skipping ", repo_dir + pass - - def getRepoConfig (self, repo, key, default = None): - # FIXME: moved to Repository? - return self.getConfig ('repo.' + repo.id + '.' + key, default) - - - def setConfig (self, key, value): - # Setting unknown keys is not allowed.,keys have to be added first - # FIXME: meaningful exception? - self.config[key] - self.config[key] = value - - -# def getConfigTree (self, prefix): -# if not prefix.endswith ('.'): -# raise ValueError ("Prefix does not end with '.': " + prefix) -# -# res = {} -# for key, value in self.config.items (): -# if key.startswith (prefix): -# res[key[len (prefix):]] = value -# -# return res - - def importRepositoryClasses (self, dir): for file in sorted (os.listdir (dir)): file, ext = os.path.splitext (file) - if not ext.lower () in ['.py', '.pyc']: + if not ext.lower () in ['.py', '.pyc', '.pyo', '.pyw']: continue # FIXME: or use execfile()? exec 'import clippy.repositories.' + file - def loadRepositoryDefinitions (self, dir): + def loadRepositoryDefinitions (self, dir, progress_fn): for file in sorted (os.listdir (dir)): if not file.endswith ('.cfg'): continue - config = {} - self.readConfigFile (os.path.join (dir, file), config) + # FIXME: this prevents developer to supply their + # own config class. Rather pass config class factory as an argument to this function + config = config.Config () + config.read (os.path.join (dir, file)) + config = config.config # FIXME: ugly + id = config['id'] # FIXME: check that id is unique? klass = config['class'] @@ -131,18 +57,21 @@ # NOTE: the effect is that config keys defined in main config file # shadow those in a repo definition file and in a class. - for key, value in config.items (): - if key == 'id' or key == 'class': - continue - - key = 'repo.' + id + '.' + key - if not key in self.config: - self.config[key] = value + #for key, value in config.items (): + # if key == 'id' or key == 'class': + # continue + # + # key = 'repo.' + id + '.' + key + # if not key in self.config: + # #self.config[key] = value + # pass - self.registerRepository (id, klass) + self.registerRepository (id, klass, config) + #yield True + #yield False - def registerRepository (self, id, klass): + def registerRepository (self, id, klass, config = None): # This method is called from repository modules # get repo's config params. @@ -152,19 +81,21 @@ # repo's __init__() sets attrs based on repo.<id>.xxxx params = klass.getConfigParams () + if config is None: + config = {} for param in reversed (params): - key = 'repo.' + id + '.' +param[0] - if not key in self.config: - self.config[key] = param[4] + key = param[0] + if not config.has_key (key): + config[key] = param[4] - ClippyCore.repository_map[id] = klass + Loader.repository_map[id] = (klass, config) def getRepositories (self): return self.repository_map def getRepository (self, id): - klass = self.getRepositoryClass (id) - repo = klass (id) + klass, config = self.getRepositoryClass (id) + repo = klass (id, config.copy ()) return repo @@ -172,30 +103,3 @@ return self.repository_map[id] - def xfer (self, source, target, selection, progress_fn, log_fn): - if progress_fn is None: - progress_fn = lambda a, b, c, d: 0 - if log_fn is None: - log_fn = lambda a, b: 0 - - progress_fn (0.0, "", 0.0, "") - total_files = len (selection) - for i, obj in enumerate (selection.values ()): - i = i+ 0.0 - log_fn (0, "Downloading " + obj['name']) - progress_fn (i/total_files, "", 0.0, obj['name']) - - try: - data = source.getData (obj) - target.putData (obj, data) - except Exception, e: - log_fn (2, "Error\n " +str (e)) - # FIXME: delete t,p file - yield True - continue - - progress_fn ((i+1)/total_files, "", 1.0, obj['name']) - log_fn (1, "OK") - yield True - - yield False Modified: clippy/repositories/dia_shapes_repo.py =================================================================== --- clippy/repositories/dia_shapes_repo.py 2009-05-12 14:33:54 UTC (rev 7) +++ clippy/repositories/dia_shapes_repo.py 2010-01-01 21:14:58 UTC (rev 8) @@ -1,5 +1,6 @@ # -*-python-*- +import cStringIO import mimetypes import os import os.path @@ -11,8 +12,8 @@ class DiaShapesRepository (repository.Repository): - def __init__ (self, id): - repository.Repository.__init__ (self, id) + def __init__ (self, id, config): + repository.Repository.__init__ (self, id, config) self.sheets_url = "http://dia-installer.de/sheets.xml" @@ -48,7 +49,10 @@ def getItems (self, query): f = urllib2.urlopen (self.sheets_url) - dom = parse (f) + txt = f.read () + f.close () + mbuf = cStringIO.StringIO (txt) + dom = parse (mbuf) objects = map (self.getNodeMetadata, dom.getElementsByTagName ('sheet')) dom.unlink () f.close () Modified: clippy/repositories/ghns_repo.py =================================================================== --- clippy/repositories/ghns_repo.py 2009-05-12 14:33:54 UTC (rev 7) +++ clippy/repositories/ghns_repo.py 2010-01-01 21:14:58 UTC (rev 8) @@ -11,8 +11,8 @@ class GHNSRepository (repository.Repository): - def __init__ (self, id): - repository.Repository.__init__ (self, id) + def __init__ (self, id, config): + repository.Repository.__init__ (self, id, config) self.category_url = self.getConfig ('category_url') self.category_re = re.compile ('<td>([^<]+)</td><td>([^<]+)</td><td>([^<]+-providers.xml)</td>') Modified: clippy/repositories/local_dir_repo.py =================================================================== --- clippy/repositories/local_dir_repo.py 2009-05-12 14:33:54 UTC (rev 7) +++ clippy/repositories/local_dir_repo.py 2010-01-01 21:14:58 UTC (rev 8) @@ -12,9 +12,8 @@ class LocalDirRepository (repository.Repository): - def __init__ (self, id): - repository.Repository.__init__ (self, id) - self.dir = self.getConfig ('dir') + def __init__ (self, id, config): + repository.Repository.__init__ (self, id, config) # FIXME: and what about Unicode??? #self.fname_trans = string.maketrans ("/\\<>?*`\000", "________") @@ -57,8 +56,9 @@ def getItems (self, query): res = [] - for f in dircache.listdir (self.dir): - id = os.stat (os.path.join (self.dir, f))[stat.ST_INO] + dir = self.getConfig ('dir') + for f in dircache.listdir (dir): + id = os.stat (os.path.join (dir, f))[stat.ST_INO] meta = { 'id': str (id), 'name': f, @@ -70,7 +70,7 @@ return res def getData (self, metadata): - filename = os.path.join (self.dir, metadata['filename']) + filename = os.path.join (self.getConfig ('dir'), metadata['filename']) fh = open (filename ,'rb') data = fh.read () fh.close () @@ -79,7 +79,7 @@ def putData (self, metadata, data): # FIXME: check filename for ../, etc - filename = os.path.join (self.dir, self.cleanFilename (metadata['filename'])) + filename = os.path.join (self.getConfig ('dir'), self.cleanFilename (metadata['filename'])) fh = open (filename ,'wb') fh.write (data) fh.close () Modified: clippy/repositories/ocal_repo.py =================================================================== --- clippy/repositories/ocal_repo.py 2009-05-12 14:33:54 UTC (rev 7) +++ clippy/repositories/ocal_repo.py 2010-01-01 21:14:58 UTC (rev 8) @@ -11,8 +11,8 @@ class OCALRepository (repository.Repository): - def __init__ (self, id): - repository.Repository.__init__ (self, id) + def __init__ (self, id, config): + repository.Repository.__init__ (self, id, config) self.base_url = 'http://openclipart.org/media' self.search_url = self.base_url + '/tags' Modified: clippy/repositories/rest_repo.py =================================================================== --- clippy/repositories/rest_repo.py 2009-05-12 14:33:54 UTC (rev 7) +++ clippy/repositories/rest_repo.py 2010-01-01 21:14:58 UTC (rev 8) @@ -10,8 +10,8 @@ class RestRepository (repository.Repository): - def __init__ (self, id): - repository.Repository.__init__ (self, id) + def __init__ (self, id, config): + repository.Repository.__init__ (self, id, config) self.categories_url = 'http://api.opendesktop.org/v1/content/categories' self.search_url = 'http://api.opendesktop.org/v1/content/data?categories=1' self.auth_uri = 'http://api.opendesktop.org/v1/content/' Modified: clippy/repository.py =================================================================== --- clippy/repository.py 2009-05-12 14:33:54 UTC (rev 7) +++ clippy/repository.py 2010-01-01 21:14:58 UTC (rev 8) @@ -1,5 +1,5 @@ # -*-python-*- - +# vim: set ts=4 sw=4 expandtab: from clippy import core REPO_LOCAL = 0 @@ -22,18 +22,16 @@ REPO_PARAM_BOOL = 2 class Repository (object): - def __init__ (self, id): + def __init__ (self, id, config): self.core = core.clippy_core self.id = id + self.config = config self.name = self.getConfig ('name') self.desc = self.getConfig ('desc') self.types = [] - def getConfig (self, key, default = None): - return self.core.getConfig ('repo.' + self.id + '.' + key, default) - @staticmethod def getCaps (): return 0 @@ -57,7 +55,13 @@ def getProvidedTypes (self): return [] + def getConfig (self, key): + # FIXME: recursive search in ancestors + return self.config[key] + def setConfig (self, key, value): + self.config[key] = value + def getQueryParams (self): # list of tuples: (key, label, type, desc, default) return [] Modified: clippy/ui/assistant/ui.py =================================================================== --- clippy/ui/assistant/ui.py 2009-05-12 14:33:54 UTC (rev 7) +++ clippy/ui/assistant/ui.py 2010-01-01 21:14:58 UTC (rev 8) @@ -1,4 +1,5 @@ # -*-python-*- +# vim: set ts=4 sw=4 expandtab: ################################################# import os @@ -8,7 +9,7 @@ import gtk import gtk.glade -from clippy import core +from clippy import core, utils from clippy.ui import glfactory from clippy.ui.assistant import win_main from clippy.ui.assistant import win_repo_config @@ -60,23 +61,17 @@ #win_main.app = app #print "Init Core" - self.core = core.ClippyCore () + self.core = core.Loader () + self.core.loadRepositories (utils.USER_REPO_DIR) # Load Prepare UI for launch self.setup () def setup (self): - self.glade_file = os.path.expanduser (self.getConfig ('glade_file')) + self.glade_file = os.path.expanduser (self.getConfig ('ui.glade_file')) - geometry = self.getConfig ('geometry') - mo = re.match (r'(\d+)x(\d+)([+-]\d+)([+-]\d+)', geometry) - if mo is not None: - self.geometry = map (int, mo.group (3, 4, 1, 2)) - else: - self.geometry = None - #print "Loading UI" self.wtree = gtk.glade.XML (self.glade_file) self.factory = myFactory (self.wtree) @@ -90,7 +85,21 @@ def run (self): gtk.main () + def restore_geometry (self, window, window_name): + geometry = self.getConfig ('ui.%s.geometry' %window_name) + mo = re.match (r'(\d+)x(\d+)([+-]\d+)([+-]\d+)', geometry) + if mo is not None: + geometry = map (int, mo.group (3, 4, 1, 2)) + window.resize (geometry[2], geometry[3]) + window.move (geometry[0], geometry[1]) + def save_geometry (self, window, window_name): + x, y = window.get_position () + w, h = window.get_size () + geometry = "%dx%d+%d+%d" %(w, h, x, y) + self.setConfig ('ui.%s.geometry'%window_name, geometry) + # FIXME: account for fullscreen + def progress_fn (self, msg, percent): #self.splash_window.update_progress (msg, percent) pass @@ -110,12 +119,14 @@ def getConfig (self, key): # FIXME: should be in default config? cfg = { - 'glade_file': os.path.join (os.path.dirname (__file__) , 'ui.glade'), # NOTE: glade file has to be with python files, not in /usr/share - 'geometry': '500x350+30+30', + 'ui.glade_file': os.path.join (os.path.dirname (__file__) , 'ui.glade'), # NOTE: glade file has to be with python files, not in /usr/share + 'ui.win_main.geometry': '500x350+30+30', } return cfg[key] + def setConfig (self, key, value): + pass # def open_url (self, url): # if app.browser.find ('%s') >= 0: @@ -186,6 +197,7 @@ column = gtk.TreeViewColumn ("Value", text_renderer, text = 1) tree.append_column (column) + ui.restore_geometry (self, 'win_metadata') def update (self, meta): Modified: clippy/ui/assistant/win_main.py =================================================================== --- clippy/ui/assistant/win_main.py 2009-05-12 14:33:54 UTC (rev 7) +++ clippy/ui/assistant/win_main.py 2010-01-01 21:14:58 UTC (rev 8) @@ -1,4 +1,5 @@ # -*-python-*- +# vim: set ts=4 sw=4 expandtab: ################################################# import os @@ -157,9 +158,7 @@ # FIXME: should not be here self.selection = {} - if ui.geometry != None: - self.resize (ui.geometry[2], ui.geometry[3]) - self.move (ui.geometry[0], ui.geometry[1]) + ui.restore_geometry (self, 'win_main') def setup_list (self, page): @@ -268,11 +267,10 @@ # FIXME: should also disable sorting model.clear () - for id in ui.core.getRepositories (): - repo_class = ui.core.getRepositoryClass (id) - if repo_class.getCaps () & caps_mask: - repo = repo_class (id) + klass, config = ui.core.getRepositoryClass (id) + if klass.getCaps () & caps_mask: + repo = klass (id, config) else: continue model.append ([repo]) @@ -539,11 +537,7 @@ #if s != None: # app.core.setConfig ('ui.split_position', str (s)) - x, y = self.get_position () - w, h = self.get_size () - geometry = "%dx%d+%d+%d" %(w, h, x, y) - ui.core.setConfig ('ui.geometry', geometry) - # FIXME: account for fullscreen + ui.save_geometry (self, 'win_main') def on_tree_sources_button_press_event (self, widget, ev): Copied: clippy/ui/shell/app.py (from rev 7, cclippy.py) =================================================================== --- clippy/ui/shell/app.py (rev 0) +++ clippy/ui/shell/app.py 2010-01-01 21:14:58 UTC (rev 8) @@ -0,0 +1,256 @@ +#!/usr/bin/env python +# vim: set ts=4 sw=4 expandtab: + +import atexit +import os.path +import readline +import traceback + +from clippy.core import Loader +from clippy.query import Query +import clippy.utils as utils + +class Shell (object): + HISTORY_FILE = os.path.join (utils.CONFIG_DIR, "cclippy.history") + + def __init__ (self): + self.core = Loader () + self.core.loadRepositories (utils.USER_REPO_DIR) + self._readline_init () + self.encoding = 'ascii' + + + def _readline_init (self): + """Enable history and tab completion for the command line.""" + + readline.parse_and_bind ("tab: complete") + try: + readline.read_history_file (self.HISTORY_FILE) + except IOError: + pass + + atexit.register (readline.write_history_file, self.HISTORY_FILE) + ###readline.set_pre_input_hook (self._readline_hook) + + def _readline_hook (): + readline.insert_text ('') + readline.redisplay () + + def help (self): + print """ +Commands: + s - set source repository + t - set target repository + ls - list source file + lt - list target files + qs - list or set source repo query params + qt - list or set target repo query params + cs - list or set source repo config params + ct - list or set target repo config params + g - get specified ids + p - put specified ids + d - delete specified ids + i - metadata info on specified ids + apply - transfer previously selected files + h - this help + q - quit program +""" + + def list_items (self, repo, qry, selection): + try: + repo.connect () + items = repo.getItems (qry) + except Exception, e: + traceback.print_exc () + print + return [] + + + # FIXME: use cache if permitted + for meta in items: + if meta['id'] in selection: + sel_sign = '+' + else: + sel_sign = ' ' + print sel_sign, meta['id'], meta['name'].encode (self.encoding, 'replace') + + return items + + + def progress_fn (self, state, **kw): + if state == utils.XFER_START: + print "Starting transfer..." + elif state == utils.XFER_FILE_START: + print kw['name'], + elif state == utils.XFER_FILE_UPDATE: + pass + elif state == utils.XFER_FILE_OK: + print 'OK' + elif state == utils.XFER_FILE_ERROR: + print 'ERROR' + elif state == utils.XFER_END: + print 'Transfer complete' + else: + pass + + + + def run_shell (self): + source = None + source_query = None + target = None + target_query = None + items = [] + selection = {} + connected = False + + print "\ntype <?> or <h> for help, <q> for quit\n" + while True: + if source is not None: + source_name = source.name + else: + source_name = '' + + if target is not None: + target_name = target.name + else: + target_name = '' + + print "Source: ", source_name + print "Target: ", target_name + + cmd = raw_input ('Cmd: ') + + try: + cmd, arg = cmd.split (' ', 1) + except: + arg = None + + if arg == '': + arg = None + + if cmd == 'h' or cmd == '?': + self.help () + + elif cmd == 'q': + break + + elif cmd == 's' or cmd == 't': + if arg is None: + for (i, id) in enumerate (sorted (self.core.getRepositories ())): + # FIXME: this is rather ugly + klass, config = self.core.getRepositoryClass (id) + name = config['name'] + desc = config['desc'] + print '[%d]\t%s - %s\n\t\t%s' %(i, id, name, desc) + else: + try: + arg = int (arg) + arg = sorted (self.core.getRepositories ())[arg] + except: + pass + + #try: + repo = self.core.getRepository (arg) + #except Exception, e: + # print e + # continue + + qry = Query (repo) + if cmd == 's': + source = repo + source_query = qry + selection = {} + else: + target = repo + target_query = qry + + elif cmd == 'ls' or cmd == 'lt': + if cmd == 'ls': + items = self.list_items (source, source_query, selection) + else: + self.list_items (target, target_query, selection) + + + elif cmd == 'qs' or cmd == 'qt': + if cmd == 'qs': + qry = source_query + else: + qry = target_query + + if arg is None: + for param in qry.repo.getQueryParams (): + print param[0], ':', qry.getValue (param[0]) + else: + key, value = arg.split (' ', 1) + qry.setValue (key, value) + # FIXME: getItems () + + + elif cmd == 'cs' or cmd == 'ct': + if cmd == 'cs': + repo = source + else: + repo = target + + if arg is None: + for param in repo.getConfigParams (): + value = repo.getConfig (param[0]) + print param[0], ':', value + else: + key, value = arg.split (' ', 1) + repo.setConfig (key, value) + + # FIXME: we have to reload repo now, because the config params + # are usually only read at class instantiation + + # get + elif cmd == 'g': + if arg is None or items is None: + items = self.list_items (source, source_query, selection) + + else: + ids = arg.split (' ') + for id in ids: + new_sel = filter (lambda m: m['id'] == id, items) + for m in new_sel: + selection[m['id']] = m + + # info + elif cmd == 'i': + if arg is None or items is None: + items = self.list_items (source, source_query, selection) + + else: + ids = arg.split (' ') + new_sel = filter (lambda m: m['id'] in ids, items) + for m in new_sel: + for key, value in m.items (): + print key + ':', value + + + #put + elif cmd == 'p': + pass + + # delete + elif cmd == 'd': + pass + + elif cmd == 'apply': + gen = utils.xfer (source, target, selection, self.progress_fn) + while gen.next (): + pass + + else: + print "Unknown command:", cmd + + +def main (): + app = Shell () + app.run_shell () + + +if __name__ == '__main__': + main () + Added: clippy/utils.py =================================================================== --- clippy/utils.py (rev 0) +++ clippy/utils.py 2010-01-01 21:14:58 UTC (rev 8) @@ -0,0 +1,56 @@ +# -*-python-*- +# vim: set ts=4 sw=4 expandtab: + +import os.path + +SYS_CONFIG_FILE = '/etc/clippy.cfg' +CONFIG_DIR = os.path.expanduser ('~/.clippy') +CONFIG_FILE = os.path.join (CONFIG_DIR, 'clippy.cfg') +USER_REPO_DIR = os.path.join (CONFIG_DIR, 'repositories') + +if False: + if config is None: + self.config = config.Config () + else: + self.config = config + #self.createConfig () + + self.config.read (self.CONFIG_FILE) + # FIXME: is it a good idea? + atexit.register (self.writeConfigFile, self.CONFIG_FILE) + +XFER_START = 0 +XFER_FILE_START = 1 +XFER_FILE_UPDATE = 2 +XFER_FILE_OK = 3 +XFER_FILE_ERROR = 4 +XFER_END = 5 + + +def xfer (source, target, selection, progress_fn): + if progress_fn is None: + progress_fn = lambda s, **kw: 0 + + progress_fn (XFER_START, source=source, target=target) + files = len (selection) + oks = 0 + errors = 0 + bytes = 0 + for i, obj in enumerate (selection.values ()): + progress=(i+0.0)/files + progress_fn (XFER_FILE_START, source=source, target=target, progress=progress, name=obj['name']) + try: + data = source.getData (obj) + target.putData (obj, data) + except Exception, e: + progress_fn (XFER_FILE_ERROR, source=source, target=target, progress=progress, name=obj['name'], error=unicode (e)) + # FIXME: delete t,p file + yield True + continue + + progress_fn (XFER_FILE_OK, source=source, target=target, progress=(i+1.0)/files, name=obj['name']) + # FIXME: remove the tranferred file from selection + yield True + + progress_fn (XFER_END) + yield False Added: gclippy =================================================================== --- gclippy (rev 0) +++ gclippy 2010-01-01 21:14:58 UTC (rev 8) @@ -0,0 +1,2 @@ +#!/bin/sh +exec python -m clippy.ui.assistant.app Property changes on: gclippy ___________________________________________________________________ Added: svn:executable + * This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <edh...@us...> - 2009-05-12 14:34:01
|
Revision: 7 http://clippy.svn.sourceforge.net/clippy/?rev=7&view=rev Author: edheldil Date: 2009-05-12 14:33:54 +0000 (Tue, 12 May 2009) Log Message: ----------- - Added GTKAssistant client - too many changes Modified Paths: -------------- Makefile TODO clippy/core.py clippy/repositories/ghns_repo.py clippy/repositories/local_dir_repo.py clippy/repositories/ocal_repo.py clippy/repositories/rest_repo.py clippy/repository.py setup.py Added Paths: ----------- COPYING README clippy/repositories/dia_shapes_repo.py clippy/ui/ clippy/ui/__init__.py clippy/ui/assistant/ clippy/ui/assistant/__init__.py clippy/ui/assistant/app.py clippy/ui/assistant/ui.glade clippy/ui/assistant/ui.py clippy/ui/assistant/win_main.py clippy/ui/assistant/win_repo_config.py clippy/ui/glfactory.py Added: COPYING =================================================================== --- COPYING (rev 0) +++ COPYING 2009-05-12 14:33:54 UTC (rev 7) @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. Modified: Makefile =================================================================== --- Makefile 2008-11-13 17:18:35 UTC (rev 6) +++ Makefile 2009-05-12 14:33:54 UTC (rev 7) @@ -10,7 +10,7 @@ all: clean: - -rm *.pyc *.core *.tmp MANIFEST clippy/*.pyc clippy/repositories/*.pyc + -rm *.pyc *.core *.tmp MANIFEST clippy/*.pyc clippy/repositories/*.pyc clippy/ui/*.pyc clippy/ui/*/*.pyc sdist: python ./setup.py sdist Added: README =================================================================== --- README (rev 0) +++ README 2009-05-12 14:33:54 UTC (rev 7) @@ -0,0 +1,34 @@ +==== Clippy ==== + +=== API === + +class ClippyCore +getRepositoryClass (id) +getRepository (id) +getRepositories () +xfer (source_repository, target_repository, objects, progress_fn, log_fn) + +=== Clippy Assistant API === + +class ClippyAssistant + +== Methods == + +setSourceRepository (repository) + +setTargetRepository (repository) + +run () + +== Attributes == + +core + reference to ClippyCore - FIXME: get rid of it, use it as a base class instead + +skip_confirm = False +columns +columns_item +TODO: item display mode: source only, source and target mixed, source without target, ... +TODO: default sort + + Modified: TODO =================================================================== --- TODO 2008-11-13 17:18:35 UTC (rev 6) +++ TODO 2009-05-12 14:33:54 UTC (rev 7) @@ -1,31 +1,74 @@ -- error reporting - exceptions or error codes? -- how should the insts and repos do i18 stuff? .desktop files? -- check whether target file does not exist -- cleanup after failed transfers/installs -- possibly: the installers should read data directly from repository to their location, to avoid copying files from repoclient's tmp dir to the final destination or spooling large files to memory -- how to handle archives? e.g. KHotNewStuff has some fonts in zip archives, with README and license file. Probably display the text files when installing? -- How to handle download pages like in opendesktop.org? Ignore the sucking repo or invoke web browser? -- should configParams and queryParams be merged? -- add COPYING and license boilerplates to files - probably LGPL to clippy/*, GPL to cclippy -- allow searching for recent additions or with the help of page search form (see registry.gimp.org) -- sorting - GHNS has support for sorting in the *-providers.xml files +==== Open Issues ==== -Repos to add: -(sources) -http://registry.gimp.org/ -http://openfontlibrary.org -(sinks) -dia -blender -tuxpaint -gimp -inkscape +=== Core, Data Model === -Suggested reading: -GHNS and KNewStuff library http://techbase.kde.org/Development/Tutorials/Introduction_to_Get_Hot_New_Stuff -http://www.kde-look.org/help/ghns.php + * IMPORTANT: rework and finalize the config system: + * repos have config in main config and their own config -> confusing + * allow to redefine config system by calling app + + * should configParams and queryParams be merged? + * add tags to repositories for easy filtering -OCAL Helper +=== Repository Handling === -eric4 editor's plugin repository + * how to handle archives? e.g. KHotNewStuff has some fonts in zip archives, with README and license file. Probably display the text files when installing? + * how to handle download pages like in opendesktop.org? Ignore the sucking repo or invoke web browser? + * allow searching for recent additions or with the help of page search form (see registry.gimp.org) + * sorting - GHNS has support for sorting in the *-providers.xml files + +=== File Transfer === + + * check whether target file does not exist + * cleanup after failed transfers/installs + * possibly: the installers should read data directly from repository to their location, to avoid copying files from repoclient's tmp dir to the final destination or spooling large files to memory + * use threads to download more files at once + +==== Repositories ==== + + * local_dir_repo- add unzip, un... capability, needs lots of checking for security sake + + +=== Error reporting, debugging, messages === + + * error reporting - exceptions or error codes? + * return list of actions done to calling app + * report/debug network problems + * centralize message handling + +=== Miscellaneous === + + * double clicking a repository (and hence direct transition to a next page) causes the page back button to behave weirdly + * i18n and l15n: How? .desktop files? + * add COPYING and license boilerplates to files - probably LGPL to clippy/*, GPL to cclippy + * use GTK style file + * setup script, compare with existing python projects (gramps, ...) + * documentation + * homepage + + + +==== New Repositories ==== +=== Sources === + * http://registry.gimp.org/ + * http://openfontlibrary.org + * http://dia-installer.de/shapes.html + +=== Targets === + * dia + * blender + * tuxpaint + * gimp + * inkscape + + + +==== Suggested Reading ==== + + * GHNS and KNewStuff library http://techbase.kde.org/Development/Tutorials/Introduction_to_Get_Hot_New_Stuff http://www.kde-look.org/help/ghns.php + + * OCAL Helper + * diashapes tool + * eric4 editor's plugin repository + * gnomesword2 module manager + * firefox extension manager/updater Modified: clippy/core.py =================================================================== --- clippy/core.py 2008-11-13 17:18:35 UTC (rev 6) +++ clippy/core.py 2009-05-12 14:33:54 UTC (rev 7) @@ -2,8 +2,8 @@ import atexit import os.path +import urlparse - clippy_core = None def registerRepository (id, klass): @@ -70,6 +70,7 @@ for key in sorted (self.config): fh.write ("%s = %s\n" %(key, str (self.config[key]))) + fh.flush () fh.close () os.rename (filename + '.tmp', filename) @@ -161,7 +162,6 @@ def getRepositories (self): return self.repository_map - def getRepository (self, id): klass = self.getRepositoryClass (id) repo = klass (id) @@ -170,3 +170,32 @@ def getRepositoryClass (self, id): return self.repository_map[id] + + + def xfer (self, source, target, selection, progress_fn, log_fn): + if progress_fn is None: + progress_fn = lambda a, b, c, d: 0 + if log_fn is None: + log_fn = lambda a, b: 0 + + progress_fn (0.0, "", 0.0, "") + total_files = len (selection) + for i, obj in enumerate (selection.values ()): + i = i+ 0.0 + log_fn (0, "Downloading " + obj['name']) + progress_fn (i/total_files, "", 0.0, obj['name']) + + try: + data = source.getData (obj) + target.putData (obj, data) + except Exception, e: + log_fn (2, "Error\n " +str (e)) + # FIXME: delete t,p file + yield True + continue + + progress_fn ((i+1)/total_files, "", 1.0, obj['name']) + log_fn (1, "OK") + yield True + + yield False Added: clippy/repositories/dia_shapes_repo.py =================================================================== --- clippy/repositories/dia_shapes_repo.py (rev 0) +++ clippy/repositories/dia_shapes_repo.py 2009-05-12 14:33:54 UTC (rev 7) @@ -0,0 +1,65 @@ +# -*-python-*- + +import mimetypes +import os +import os.path +import re +import urllib2 +from xml.dom.minidom import parse, parseString + +from clippy import core, repository + + +class DiaShapesRepository (repository.Repository): + def __init__ (self, id): + repository.Repository.__init__ (self, id) + self.sheets_url = "http://dia-installer.de/sheets.xml" + + + @staticmethod + def getCaps (): + return repository.REPO_REMOTE | repository.REPO_GET + + @staticmethod + def getConfigParams (): + res = repository.Repository.getConfigParams () + res.extend ([ + ('name', 'Name', 'STR', 'Name...', 'Dia Shapes at dia-installer.de repository'), + ]) + return res + + def getQueryParams (self): + return [ + ] + + + def getNodeMetadata (self, node): + metadata = {} + metadata['name'] = node.getAttribute ('name') + metadata['description'] = node.getAttribute ('descriprion') + metadata['creator'] = node.getAttribute ('creator') + metadata['website'] = node.getAttribute ('website') + metadata['download'] = node.getAttribute ('download') + + metadata['id'] = metadata['name'] + metadata['filename'] = os.path.basename (metadata['download']) + return metadata + + + def getItems (self, query): + f = urllib2.urlopen (self.sheets_url) + dom = parse (f) + objects = map (self.getNodeMetadata, dom.getElementsByTagName ('sheet')) + dom.unlink () + f.close () + return objects + + + def getData (self, metadata): + f = urllib2.urlopen (metadata['download']) + + data = f.read () + return data + + +core.registerRepository ('dia_shapes_repo', DiaShapesRepository) Modified: clippy/repositories/ghns_repo.py =================================================================== --- clippy/repositories/ghns_repo.py 2008-11-13 17:18:35 UTC (rev 6) +++ clippy/repositories/ghns_repo.py 2009-05-12 14:33:54 UTC (rev 7) @@ -14,22 +14,27 @@ def __init__ (self, id): repository.Repository.__init__ (self, id) - self.category_url = 'http://www.kde-look.org/help/ghns.php' + self.category_url = self.getConfig ('category_url') self.category_re = re.compile ('<td>([^<]+)</td><td>([^<]+)</td><td>([^<]+-providers.xml)</td>') - self.providers_url = 'http://download.kde.org/khotnewstuff/%s-providers.xml' + self.providers_url = self.getConfig ('providers_url') + self.repo_url = self.getConfig ('repo_url') + #self.repo_url = 'http://download.kde.org/khotnewstuff/fonts/fonts.xml' # if repo_url is not None, do not parse providers.xml file and go to repo_url directly - #self.repo_url = 'http://download.kde.org/khotnewstuff/fonts/fonts.xml' - self.repo_url = None + #self.repo_url = None - def getCaps (self): - return repository.REPO_REMOTE + @staticmethod + def getCaps (): + return repository.REPO_REMOTE | repository.REPO_GET @staticmethod def getConfigParams (): res = repository.Repository.getConfigParams () res.extend ([ ('name', 'Name', 'STR', 'Name...', 'KDE GetHotNewStuff (GHNS) repository'), + ('category_url', 'Category URL', 'STR', 'If empty, use providers file instead', 'http://www.kde-look.org/help/ghns.php'), + ('providers_url', 'Providers URL', 'STR', 'If empty, use providers file instead', 'http://download.kde.org/khotnewstuff/%s-providers.xml'), + ('repo_url', 'Repository URL', 'STR', 'If empty, use providers file instead', ''), ]) return res @@ -53,7 +58,10 @@ def connect (self): - self.categories = self.getCategories () + if self.category_url: + self.categories = self.getCategories () + else: + self.categories = [] def getNodeText (self, nodelist): Modified: clippy/repositories/local_dir_repo.py =================================================================== --- clippy/repositories/local_dir_repo.py 2008-11-13 17:18:35 UTC (rev 6) +++ clippy/repositories/local_dir_repo.py 2009-05-12 14:33:54 UTC (rev 7) @@ -24,8 +24,9 @@ self.fname_trans = dict (zip (map (ord, u'\a\b\t\n\v\f\r/\\<>?*`'), u'abtnvfr_______')) - def getCaps (self): - return REPO_LOCAL | REPO_DONT_CACHE + @staticmethod + def getCaps (): + return REPO_LOCAL | REPO_DONT_CACHE | REPO_GET | REPO_PUT @staticmethod Modified: clippy/repositories/ocal_repo.py =================================================================== --- clippy/repositories/ocal_repo.py 2008-11-13 17:18:35 UTC (rev 6) +++ clippy/repositories/ocal_repo.py 2009-05-12 14:33:54 UTC (rev 7) @@ -23,7 +23,7 @@ @staticmethod def getCaps (): - return REPO_REMOTE | REPO_HAS_TAGS | REPO_HAS_PAGES + return REPO_REMOTE | REPO_GET | REPO_HAS_TAGS | REPO_HAS_PAGES @staticmethod def getConfigParams (): Modified: clippy/repositories/rest_repo.py =================================================================== --- clippy/repositories/rest_repo.py 2008-11-13 17:18:35 UTC (rev 6) +++ clippy/repositories/rest_repo.py 2009-05-12 14:33:54 UTC (rev 7) @@ -6,6 +6,7 @@ from xml.dom.minidom import parse, parseString from clippy import core, repository +from clippy.repository import * class RestRepository (repository.Repository): @@ -17,6 +18,10 @@ self.auth_realm = 'your valid user account' @staticmethod + def getCaps (): + return REPO_REMOTE | REPO_GET + + @staticmethod def getConfigParams (): res = repository.Repository.getConfigParams () res.extend ([ Modified: clippy/repository.py =================================================================== --- clippy/repository.py 2008-11-13 17:18:35 UTC (rev 6) +++ clippy/repository.py 2009-05-12 14:33:54 UTC (rev 7) @@ -12,7 +12,15 @@ REPO_HAS_PAGES = 8 REPO_DONT_CACHE = 16 REPO_REQUIRES_AUTH = 32 +REPO_GET = 64 +REPO_PUT = 128 +REPO_DELETE = 256 +# Parametr +REPO_PARAM_STR = 0 +REPO_PARAM_TAG = 1 +REPO_PARAM_BOOL = 2 + class Repository (object): def __init__ (self, id): self.core = core.clippy_core @@ -26,7 +34,8 @@ return self.core.getConfig ('repo.' + self.id + '.' + key, default) - def getCaps (self): + @staticmethod + def getCaps (): return 0 @staticmethod @@ -38,6 +47,17 @@ ] + # NOTE: these methods can't be static, because different repos of the same class + # can support different mime types + def getAcceptedTypes (self): + # list of tuples (mimetype, encoding, specialtype) + # specialtype could be st.like Wallpaper, XMMS-skin, etc. + return [] + + def getProvidedTypes (self): + return [] + + def getQueryParams (self): # list of tuples: (key, label, type, desc, default) return [] Added: clippy/ui/__init__.py =================================================================== --- clippy/ui/__init__.py (rev 0) +++ clippy/ui/__init__.py 2009-05-12 14:33:54 UTC (rev 7) @@ -0,0 +1,4 @@ +# -*-python-*- +from assistant.ui import ClippyAssistant +#from commander.app import ClippyCommander + Added: clippy/ui/assistant/app.py =================================================================== --- clippy/ui/assistant/app.py (rev 0) +++ clippy/ui/assistant/app.py 2009-05-12 14:33:54 UTC (rev 7) @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +from optparse import OptionParser +from clippy.ui import ClippyAssistant + +oparser = OptionParser () +oparser.add_option ("-s", + "--source", + dest = "source", + help = "use source repository REPOID and skip source selection page", + metavar = "REPOID") + +oparser.add_option ("-t", + "--target", + dest = "target", + help = "use target repository REPOID and skip target selection page", + metavar = "REPOID") + +oparser.add_option ("-i", + "--item-list-mode", + dest = "item_list_mode", + help = "item list mode MODE", + default = "merge", + metavar = "MODE") + + +oparser.add_option ("-y", + "--skip-confirm", + dest = "skip_confirm", + action = "store_true", + default = False, + help = "skip download confirmation page") + +(opts, args) = oparser.parse_args () + +app = ClippyAssistant () + +if opts.target is not None: + r = app.core.getRepository (opts.target) + app.setTargetRepository (r) + +if opts.source is not None: + r = app.core.getRepository (opts.source) + app.setSourceRepository (r) + +if opts.item_list_mode is not None: + app.item_list_mode = opts.item_list_mode + +app.skip_confirm = opts.skip_confirm + +app.run () Property changes on: clippy/ui/assistant/app.py ___________________________________________________________________ Added: svn:executable + * Added: clippy/ui/assistant/ui.glade =================================================================== --- clippy/ui/assistant/ui.glade (rev 0) +++ clippy/ui/assistant/ui.glade 2009-05-12 14:33:54 UTC (rev 7) @@ -0,0 +1,2448 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> +<!--Generated with glade3 3.4.5 on Tue May 5 14:51:18 2009 --> +<glade-interface> + <widget class="GtkAssistant" id="win_main"> + <property name="border_width">10</property> + <property name="title" translatable="yes">Clippy</property> + <property name="modal">True</property> + <child> + <widget class="GtkLabel" id="lab_page_intro"> + <property name="visible">True</property> + <property name="label" translatable="yes">Clippy serves to download ...</property> + </widget> + <packing> + <property name="page_type">GTK_ASSISTANT_PAGE_INTRO</property> + <property name="title">Welcome to Clippy</property> + </packing> + </child> + <child> + <widget class="GtkVBox" id="vb_page_target"> + <property name="visible">True</property> + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <child> + <widget class="GtkViewport" id="viewport1"> + <property name="visible">True</property> + <property name="resize_mode">GTK_RESIZE_QUEUE</property> + <child> + <widget class="GtkTreeView" id="tree_targets"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_clickable">True</property> + </widget> + </child> + </widget> + </child> + </widget> + </child> + </widget> + <packing> + <property name="title">Select local (target) repository</property> + </packing> + </child> + <child> + <widget class="GtkVBox" id="vb_page_source"> + <property name="visible">True</property> + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow2"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <child> + <widget class="GtkViewport" id="viewport2"> + <property name="visible">True</property> + <property name="resize_mode">GTK_RESIZE_QUEUE</property> + <child> + <widget class="GtkTreeView" id="tree_sources"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_clickable">True</property> + </widget> + </child> + </widget> + </child> + </widget> + </child> + </widget> + <packing> + <property name="title">Select remote (source) repository</property> + </packing> + </child> + <child> + <widget class="GtkVBox" id="vb_page_items"> + <property name="visible">True</property> + <child> + <widget class="GtkHBox" id="hbox3"> + <property name="visible">True</property> + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow3"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <child> + <widget class="GtkViewport" id="viewport3"> + <property name="visible">True</property> + <property name="resize_mode">GTK_RESIZE_QUEUE</property> + <child> + <widget class="GtkTreeView" id="tree_items"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_clickable">True</property> + </widget> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkVBox" id="vbox4"> + <property name="visible">True</property> + <child> + <widget class="GtkButton" id="but_add"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="response_id">0</property> + <child> + <widget class="GtkImage" id="image4"> + <property name="visible">True</property> + <property name="stock">gtk-add</property> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkButton" id="but_delete"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="response_id">0</property> + <child> + <widget class="GtkImage" id="image5"> + <property name="visible">True</property> + <property name="stock">gtk-delete</property> + </widget> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="but_info"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="response_id">0</property> + <child> + <widget class="GtkImage" id="image6"> + <property name="visible">True</property> + <property name="stock">gtk-info</property> + </widget> + </child> + </widget> + <packing> + <property name="position">2</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + </widget> + <packing> + <property name="title">Set items to download or delete</property> + </packing> + </child> + <child> + <widget class="GtkVBox" id="vb_page_confirm"> + <property name="visible">True</property> + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow4"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <child> + <widget class="GtkViewport" id="viewport4"> + <property name="visible">True</property> + <property name="resize_mode">GTK_RESIZE_QUEUE</property> + <child> + <widget class="GtkTextView" id="text_confirm"> + <property name="visible">True</property> + <property name="can_focus">True</property> + </widget> + </child> + </widget> + </child> + </widget> + </child> + <child> + <placeholder/> + </child> + </widget> + <packing> + <property name="page_type">GTK_ASSISTANT_PAGE_CONFIRM</property> + <property name="title">Confirm changes</property> + </packing> + </child> + <child> + <widget class="GtkVBox" id="vb_page_download"> + <property name="visible">True</property> + <child> + <widget class="GtkFrame" id="frame1"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <child> + <widget class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <property name="left_padding">12</property> + <child> + <widget class="GtkProgressBar" id="progress_file"> + <property name="visible">True</property> + <property name="text" translatable="yes"></property> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label8"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>File</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <widget class="GtkFrame" id="frame2"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <child> + <widget class="GtkAlignment" id="alignment2"> + <property name="visible">True</property> + <property name="left_padding">12</property> + <child> + <widget class="GtkProgressBar" id="progress_total"> + <property name="visible">True</property> + <property name="text" translatable="yes"></property> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label9"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Total</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow5"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <child> + <widget class="GtkViewport" id="viewport5"> + <property name="visible">True</property> + <property name="resize_mode">GTK_RESIZE_QUEUE</property> + <child> + <widget class="GtkTextView" id="text_log"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">False</property> + </widget> + </child> + </widget> + </child> + </widget> + <packing> + <property name="position">2</property> + </packing> + </child> + </widget> + <packing> + <property name="page_type">GTK_ASSISTANT_PAGE_PROGRESS</property> + <property name="title">Downloading ...</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/... [truncated message content] |
From: <edh...@us...> - 2008-11-13 17:18:41
|
Revision: 6 http://clippy.svn.sourceforge.net/clippy/?rev=6&view=rev Author: edheldil Date: 2008-11-13 17:18:35 +0000 (Thu, 13 Nov 2008) Log Message: ----------- Fixed incorrect use of join() Modified Paths: -------------- cclippy.py Modified: cclippy.py =================================================================== --- cclippy.py 2008-11-13 00:23:50 UTC (rev 5) +++ cclippy.py 2008-11-13 17:18:35 UTC (rev 6) @@ -175,8 +175,11 @@ print param[0], ':', value else: key, value = arg.split (' ', 1) - self.core.setConfig ('.'.join ('repo', repo.id, key), value) + self.core.setConfig ('repo.' + repo.id + '.' + key, value) + # FIXME: we have to reload repo now, because the config params + # are usually only read at class instantiation + # get elif cmd == 'g': ids = arg.split (' ') This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |