1 from xml
.dom
import Node
, minidom
3 import rox
, os
, pango
, sys
, textwrap
, traceback
, subprocess
, time
, urlparse
4 from rox
import g
, tasks
, loading
9 from implementation
import ImplementationProperties
10 from xmltools
import *
12 from zeroinstall
.zerostore
import unpack
15 RESPONSE_SAVE_AND_TEST
= 1
17 gladefile
= os
.path
.join(rox
.app_dir
, '0publish-gui.glade')
20 tree
= gtk
.glade
.XML(gladefile
, 'no_file_specified')
21 box
= tree
.get_widget('no_file_specified')
22 tree
.get_widget('new_button').grab_focus()
26 chooser
= g
.FileChooserDialog('Choose a location for the new feed',
27 None, g
.FILE_CHOOSER_ACTION_SAVE
)
28 chooser
.set_current_name('MyProg.xml')
29 chooser
.add_button(g
.STOCK_CANCEL
, g
.RESPONSE_CANCEL
)
30 chooser
.add_button(g
.STOCK_NEW
, g
.RESPONSE_OK
)
32 chooser
= g
.FileChooserDialog('Choose the feed to edit',
33 None, g
.FILE_CHOOSER_ACTION_OPEN
)
34 chooser
.add_button(g
.STOCK_CANCEL
, g
.RESPONSE_CANCEL
)
35 chooser
.add_button(g
.STOCK_OPEN
, g
.RESPONSE_OK
)
38 chooser
.set_default_response(g
.RESPONSE_OK
)
39 if chooser
.run() != g
.RESPONSE_OK
:
41 path
= chooser
.get_filename()
43 return FeedEditor(path
)
46 emptyFeed
= """<?xml version='1.0'?>
47 <interface xmlns="%s">
50 """ % (XMLNS_INTERFACE
)
52 class FeedEditor(loading
.XDSLoader
):
53 def __init__(self
, pathname
):
54 loading
.XDSLoader
.__init
__(self
, None)
56 self
.pathname
= pathname
58 self
.wTree
= gtk
.glade
.XML(gladefile
, 'main')
59 self
.window
= self
.wTree
.get_widget('main')
60 self
.window
.connect('destroy', rox
.toplevel_unref
)
61 self
.xds_proxy_for(self
.window
)
63 help = gtk
.glade
.XML(gladefile
, 'main_help')
64 help_box
= help.get_widget('main_help')
65 help_box
.set_default_size(g
.gdk
.screen_width() / 4,
66 g
.gdk
.screen_height() / 4)
67 help_box
.connect('delete-event', lambda box
, ev
: True)
68 help_box
.connect('response', lambda box
, resp
: box
.hide())
71 if resp
== g
.RESPONSE_HELP
:
73 elif resp
== RESPONSE_SAVE_AND_TEST
:
75 elif resp
== RESPONSE_SAVE
:
79 self
.window
.connect('response', resp
)
82 keys
= signing
.get_secret_keys()
83 key_menu
= self
.wTree
.get_widget('feed_key')
84 key_model
= g
.ListStore(str, str)
85 key_menu
.set_model(key_model
)
86 cell
= g
.CellRendererText()
87 cell
.set_property('ellipsize', pango
.ELLIPSIZE_MIDDLE
)
88 key_menu
.pack_start(cell
)
89 key_menu
.add_attribute(cell
, 'text', 1)
91 key_model
.append((None, '(unsigned)'))
95 self
.impl_model
= g
.TreeStore(str, object)
96 impl_tree
= self
.wTree
.get_widget('impl_tree')
97 impl_tree
.set_model(self
.impl_model
)
98 text
= g
.CellRendererText()
99 column
= g
.TreeViewColumn('', text
)
100 column
.add_attribute(text
, 'text', 0)
101 impl_tree
.append_column(column
)
103 impl_tree
.get_selection().set_mode(g
.SELECTION_BROWSE
)
105 if os
.path
.exists(self
.pathname
):
106 data
, _
, self
.key
= signing
.check_signature(self
.pathname
)
107 self
.doc
= minidom
.parseString(data
)
110 self
.doc
= minidom
.parseString(emptyFeed
)
112 key_menu
.set_active(0)
114 #self.attr_model = g.ListStore(str, str)
115 #attributes = self.wTree.get_widget('attributes')
116 #attributes.set_model(self.attr_model)
117 #text = g.CellRendererText()
118 #for title in ['Attribute', 'Value']:
119 # column = g.TreeViewColumn(title, text)
120 # attributes.append_column(column)
122 self
.wTree
.get_widget('add_implementation').connect('clicked', lambda b
: self
.add_version())
123 self
.wTree
.get_widget('add_archive').connect('clicked', lambda b
: self
.add_archive())
124 self
.wTree
.get_widget('edit_properties').connect('clicked', lambda b
: self
.edit_version())
125 self
.wTree
.get_widget('remove').connect('clicked', lambda b
: self
.remove_version())
126 impl_tree
.connect('row-activated', lambda tv
, path
, col
: self
.edit_version(path
))
128 self
.wTree
.get_widget('notebook').next_page()
130 def add_version(self
):
131 ImplementationProperties(self
)
133 def edit_version(self
, path
= None, element
= None):
134 assert not (path
and element
)
139 element
= self
.get_selected()
141 element
= self
.impl_model
[path
][1]
143 ImplementationProperties(self
, element
)
145 def update_fields(self
):
146 root
= self
.doc
.documentElement
149 value
= singleton_text(root
, name
)
151 self
.wTree
.get_widget('feed_' + name
).set_text(value
)
156 for icon
in children(root
, 'icon'):
157 if icon
.getAttribute('type') == 'image/png':
158 href
= icon
.getAttribute('href')
159 self
.wTree
.get_widget('feed_icon').set_text(href
)
162 description
= singleton_text(root
, 'description') or ''
163 paragraphs
= [format_para(p
) for p
in description
.split('\n\n')]
164 buffer = self
.wTree
.get_widget('feed_description').get_buffer()
165 buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
166 buffer.insert_at_cursor('\n'.join(paragraphs
))
168 key_menu
= self
.wTree
.get_widget('feed_key')
169 model
= key_menu
.get_model()
173 if line
[0] == self
.key
:
177 model
.append((self
.key
, 'Missing key (%s)' % self
.key
))
178 key_menu
.set_active(i
)
180 key_menu
.set_active(0)
182 self
.update_version_model()
184 def add_archives(self
, impl_element
, iter):
185 for child
in child_elements(impl_element
):
186 if child
.namespaceURI
!= XMLNS_INTERFACE
: continue
187 if child
.localName
== 'archive':
188 self
.impl_model
.append(iter, ['Archive ' + child
.getAttribute('href'), child
])
190 self
.impl_model
.append(iter, ['<%s>' % child
.localName
, child
])
192 def update_version_model(self
):
193 self
.impl_model
.clear()
194 impl_tree
= self
.wTree
.get_widget('impl_tree')
197 def add_impls(elem
, iter, attrs
):
198 """Add all groups, implementations and requirements in elem"""
200 for x
in child_elements(elem
):
201 if x
.namespaceURI
!= XMLNS_INTERFACE
: continue
203 if x
.localName
== 'requires':
204 req_iface
= x
.getAttribute('interface')
205 new
= self
.impl_model
.append(iter, ['Requires %s' % req_iface
, x
])
207 if x
.localName
not in ('implementation', 'group'): continue
209 new_attrs
= attrs
.copy()
210 attributes
= x
.attributes
211 for i
in range(attributes
.length
):
212 a
= attributes
.item(i
)
213 new_attrs
[str(a
.name
)] = a
.value
215 if x
.localName
== 'implementation':
216 version
= new_attrs
.get('version', '(missing version number)')
217 new
= self
.impl_model
.append(iter, ['Version %s' % version
, x
])
218 self
.add_archives(x
, new
)
219 elif x
.localName
== 'group':
220 new
= self
.impl_model
.append(iter, ['Group', x
])
221 to_expand
.append(self
.impl_model
.get_path(new
))
222 add_impls(x
, new
, new_attrs
)
224 add_impls(self
.doc
.documentElement
, None, attrs
= {})
226 for path
in to_expand
:
227 impl_tree
.expand_row(path
, False)
235 # Spawn a grandchild and exit
236 subprocess
.Popen(['0launch', '--gui', self
.pathname
])
239 traceback
.print_exc()
242 pid
, status
= os
.waitpid(child
, 0)
245 raise Exception('Failed to run 0launch - status code %d' % status
)
247 def update_doc(self
):
248 root
= self
.doc
.documentElement
249 def update(name
, required
= False, attrs
= {}, value_attr
= None):
250 widget
= self
.wTree
.get_widget('feed_' + name
)
251 if isinstance(widget
, g
.TextView
):
252 buffer = widget
.get_buffer()
253 text
= buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
254 paras
= ['\n'.join(textwrap
.wrap(para
, 80)) for para
in text
.split('\n') if para
.strip()]
255 value
= '\n' + '\n\n'.join(paras
)
257 value
= widget
.get_text()
258 elems
= list(children(root
, name
, attrs
= attrs
))
263 elem
= create_element(root
, name
,
264 before
= ['group', 'implementation', 'requires'])
266 elem
.setAttribute(x
, attrs
[x
])
268 elem
.setAttribute(value_attr
, value
)
271 set_data(elem
, value
)
274 raise Exception('Missing required field "%s"' % name
)
279 update('summary', True)
280 update('description', True)
282 update('icon', attrs
= {'type': 'image/png'}, value_attr
= 'href')
284 uri
= self
.wTree
.get_widget('feed_url').get_text()
286 root
.setAttribute('uri', uri
)
287 elif root
.hasAttribute('uri'):
288 root
.removeAttribute('uri')
290 key_menu
= self
.wTree
.get_widget('feed_key')
291 key_model
= key_menu
.get_model()
292 self
.key
= key_model
[key_menu
.get_active()][0]
294 def save(self
, callback
= None):
297 sign
= signing
.sign_xml
299 sign
= signing
.sign_unsigned
300 data
= self
.doc
.toxml() + '\n'
302 gen
= sign(self
.pathname
, data
, self
.key
, callback
)
303 # May require interaction to get the pass-phrase, so run in the background...
307 def add_archive(self
):
308 archive
.AddArchiveBox(self
)
310 def xds_load_from_file(self
, path
):
311 archive
.AddArchiveBox(self
, local_archive
= path
)
313 def remove_version(self
, path
= None):
314 elem
= self
.get_selected()
316 self
.update_version_model()
318 def get_selected(self
):
319 tree
= self
.wTree
.get_widget('impl_tree')
320 sel
= tree
.get_selection()
321 model
, iter = sel
.get_selected()
323 raise Exception('Select something first!')
324 return model
[iter][1]
326 def find_implementation(self
, id):
327 def find_impl(parent
):
328 for x
in child_elements(parent
):
329 if x
.namespaceURI
!= XMLNS_INTERFACE
: continue
330 if x
.localName
== 'group':
333 elif x
.localName
== 'implementation':
334 if x
.getAttribute('id') == id:
336 return find_impl(self
.doc
.documentElement
)