Merge lp:~keirangtp/cardapio/web-plugins into lp:cardapio

Proposed by Paweł Bara
Status: Merged
Merged at revision: 363
Proposed branch: lp:~keirangtp/cardapio/web-plugins
Merge into: lp:cardapio
Diff against target: 2568 lines (+1158/-317)
16 files modified
src/cardapio.py (+131/-137)
src/plugins/amazon.py (+268/-0)
src/plugins/bing.py (+7/-19)
src/plugins/ebay.py (+251/-0)
src/plugins/google.py (+9/-20)
src/plugins/google_localized.py (+9/-21)
src/plugins/pidgin.py (+218/-0)
src/plugins/software_center.py (+3/-11)
src/plugins/tomboy.py (+206/-0)
src/plugins/tracker.py (+3/-11)
src/plugins/tracker_fts.py (+10/-18)
src/plugins/virtualbox.py (+3/-3)
src/plugins/wikipedia.py (+15/-19)
src/plugins/yahoo.py (+9/-23)
src/plugins/you_tube.py (+7/-17)
src/plugins/zg_recent_documents.py (+9/-18)
To merge this branch: bzr merge lp:~keirangtp/cardapio/web-plugins
Reviewer Review Type Date Requested Status
Thiago Teixeira Approve
Review via email: mp+31681@code.launchpad.net

Description of the change

Amazon plugin
Tomboy plugin

To post a comment you must log in.
lp:~keirangtp/cardapio/web-plugins updated
336. By Pawel Bara

updated Tomboys category icon

337. By Pawel Bara

found real search method in Tomboys DBus API (d-feet for the rescue) ;)

338. By Pawel Bara

bugfix: keyword searches not working in Bing

339. By Pawel Bara

merging trunk

340. By Pawel Bara

initial eBay plugin (localization on is its way)

341. By Pawel Bara

merged trunk and localized (finished) eBay plugin

342. By Pawel Bara

deleted tomboy plugin for merging purpose (will work on it some more)

Revision history for this message
Paweł Bara (keirangtp) wrote :

new Amazon plugin, new eBay plugin and a thread-safe version of passing current search string to Cardapio in Bing, Yahoo, Wikipedia and YouTube plugins

lp:~keirangtp/cardapio/web-plugins updated
343. By Pawel Bara

fallback search more item in eBay when the API call totally fails + finally got Tomboy plugin working well!

344. By Pawel Bara

tomboy: return immediately after passing empty search results to Cardapio

345. By Pawel Bara

minor documentation upgrades in Tomboys plugin

346. By Pawel Bara

new finished plugin: talk to your pidgin buddies!

347. By Pawel Bara

fixes bug 614710 - a little change in API of plugins: Cardapio passes result limit (count) to them

348. By Pawel Bara

minor corrections finalizing bug 614710

Revision history for this message
Thiago Teixeira (tvst) wrote :

This will be in the bzr trunk and PPA later tonight. Thanks for the help!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/cardapio.py'
2--- src/cardapio.py 2010-08-05 21:09:45 +0000
3+++ src/cardapio.py 2010-08-12 17:20:35 +0000
4@@ -63,7 +63,7 @@
5
6 except:
7 # assume that if gnomeapplet is not found then the user is running Cardapio
8- # without Gnome (maybe with './cardapio show', for example).
9+ # without Gnome (maybe with './cardapio show', for example).
10 print('Info: gnomeapplet Python module not present')
11
12 try:
13@@ -136,14 +136,14 @@
14 version = '0.9.136'
15
16 core_plugins = [
17- 'applications',
18- 'google',
19- 'google_localized',
20+ 'applications',
21+ 'google',
22+ 'google_localized',
23 'pinned',
24- 'places',
25+ 'places',
26 'software_center',
27- 'tracker',
28- 'tracker_fts',
29+ 'tracker',
30+ 'tracker_fts',
31 'zg_recent_documents',
32 ]
33
34@@ -216,20 +216,20 @@
35 self.setup_dbus()
36 self.setup_base_ui() # must be the first ui-related method to be called
37 self.setup_plugins()
38- self.build_ui()
39- self.setup_ui_from_all_settings()
40+ self.build_ui()
41+ self.setup_ui_from_all_settings()
42
43 self.schedule_search_with_all_plugins('')
44
45 if not hidden: self.show()
46
47- # this is useful so that the user can edit the config file on first-run
48+ # this is useful so that the user can edit the config file on first-run
49 # without need to quit cardapio first:
50 self.save_config_file()
51
52 if gnome_program_init is not None:
53 gnome_program_init('', self.version) # Prints a warning to the screen. Ignore it.
54- client = gnome_ui_master_client()
55+ client = gnome_ui_master_client()
56 client.connect('save-yourself', self.quit)
57
58
59@@ -262,9 +262,9 @@
60 self.bus = dbus.SessionBus()
61 dbus.service.Object.__init__(self, self.bus, Cardapio.bus_obj_str)
62
63-
64+
65 def get_plugin_class(self, basename):
66- """
67+ """
68 Returns the CardapioPlugin class from the plugin at plugins/basename.py.
69 If it fails, it returns a string decribing the error.
70 """
71@@ -277,7 +277,7 @@
72
73 plugin_class = plugin_module.CardapioPlugin
74
75- if plugin_class.plugin_api_version != CardapioPluginInterface.plugin_api_version:
76+ if plugin_class.plugin_api_version != CardapioPluginInterface.plugin_api_version:
77 return 'Incorrect API version'
78
79 return plugin_class
80@@ -325,7 +325,7 @@
81 }
82
83 plugin_dirs = [
84- os.path.join(cardapio_path, 'plugins'),
85+ os.path.join(cardapio_path, 'plugins'),
86 os.path.join(DesktopEntry.xdg_config_home, 'Cardapio', 'plugins')
87 ]
88
89@@ -374,7 +374,7 @@
90 basename = str(basename)
91 plugin_class = self.get_plugin_class(basename)
92
93- if type(plugin_class) is str:
94+ if type(plugin_class) is str:
95 logging.error('[plugin: %s] %s' % (basename, plugin_class))
96 self.settings['active plugins'].remove(basename)
97 continue
98@@ -425,7 +425,7 @@
99 levels of messages can be used by setting one of is_debug, is_warning, is_error:
100
101 debug - Used for any debugging message, including any messages that may
102- be privacy sensitive. Messages set with the flag is_debug=True
103+ be privacy sensitive. Messages set with the flag is_debug=True
104 will *not* be logged unless the user enters debug mode.
105
106 info - This is the default level when you don't set any of the flags.
107@@ -438,13 +438,13 @@
108 allow the plugin to function at all.
109 """
110
111- if is_error:
112+ if is_error:
113 write = logging.error
114
115- elif is_warning:
116+ elif is_warning:
117 write = logging.warning
118
119- elif is_debug:
120+ elif is_debug:
121 write = logging.debug
122
123 else:
124@@ -494,7 +494,7 @@
125
126 self.config_folder_path = os.path.join(DesktopEntry.xdg_config_home, 'Cardapio')
127
128- if not os.path.exists(self.config_folder_path):
129+ if not os.path.exists(self.config_folder_path):
130 os.mkdir(self.config_folder_path)
131
132 elif not os.path.isdir(self.config_folder_path):
133@@ -537,14 +537,14 @@
134 self.settings = {}
135 s = {}
136
137- try:
138+ try:
139 s = json.load(config_file)
140
141 except Exception, exception:
142 logging.error('Could not read config file:')
143 logging.error(exception)
144
145- finally:
146+ finally:
147 config_file.close()
148
149 default_side_pane_items = []
150@@ -552,20 +552,20 @@
151 if os.path.exists(path):
152 default_side_pane_items.append(
153 {
154- 'name' : _('Ubuntu Software Center'),
155+ 'name' : _('Ubuntu Software Center'),
156 'icon name' : 'softwarecenter',
157- 'tooltip' : _('Lets you choose from thousands of free applications available for Ubuntu'),
158+ 'tooltip' : _('Lets you choose from thousands of free applications available for Ubuntu'),
159 'type' : 'raw',
160- 'command' : 'software-center',
161+ 'command' : 'software-center',
162 })
163
164 default_side_pane_items.append(
165 {
166- 'name' : _('Help and Support'),
167+ 'name' : _('Help and Support'),
168 'icon name' : 'help-contents',
169- 'tooltip' : _('Get help with %(distro_name)s') % {'distro_name':Cardapio.distro_name},
170+ 'tooltip' : _('Get help with %(distro_name)s') % {'distro_name':Cardapio.distro_name},
171 'type' : 'raw',
172- 'command' : 'gnome-help',
173+ 'command' : 'gnome-help',
174 })
175
176 self.read_config_option(s, 'window size' , None ) # format: [px, px]
177@@ -585,10 +585,10 @@
178 self.read_config_option(s, 'keybinding' , '<Super>space' ) # the user should use gtk.accelerator_parse('<Super>space') to see if the string is correct!
179 self.read_config_option(s, 'applet label' , Cardapio.distro_name ) # string
180 self.read_config_option(s, 'applet icon' , 'start-here' , override_empty_str = True) # string (either a path to the icon, or an icon name)
181- self.read_config_option(s, 'pinned items' , [] )
182+ self.read_config_option(s, 'pinned items' , [] )
183 self.read_config_option(s, 'side pane items' , default_side_pane_items )
184- self.read_config_option(s, 'active plugins' , ['pinned', 'places', 'applications', 'tracker', 'google', 'software_center'])
185- self.read_config_option(s, 'plugin settings' , {} )
186+ self.read_config_option(s, 'active plugins' , ['pinned', 'places', 'applications', 'tracker', 'google', 'software_center'])
187+ self.read_config_option(s, 'plugin settings' , {} )
188
189 # these are a bit of a hack:
190 self.read_config_option(s, 'handler for ftp paths' , r"nautilus '%s'" ) # a command line using %s
191@@ -635,7 +635,7 @@
192 self.settings[key] = val
193 else:
194 self.settings[key] = user_settings[key]
195- else:
196+ else:
197 self.settings[key] = val
198
199 if force_update_from_version is not None:
200@@ -709,12 +709,12 @@
201 self.plugin_checkbox_column = self.get_object('PluginCheckboxColumn')
202 self.view_mode_button = self.get_object('ViewModeButton')
203
204- # HACK: fix names of widgets to allow theming
205+ # HACK: fix names of widgets to allow theming
206 # (glade doesn't seem to properly add names to widgets anymore...)
207 for widget in self.builder.get_objects():
208
209 # skip the about dialog or the app name will be overwritten!
210- if widget == self.about_dialog: continue
211+ if widget == self.about_dialog: continue
212
213 if 'set_name' in dir(widget):
214 widget.set_name(gtk.Buildable.get_name(widget))
215@@ -767,10 +767,10 @@
216 <menuitem name="Item 5" verb="AboutDistro" label="%s" pixtype="none"/>
217 </popup>
218 ''' % (
219- _('_Properties'),
220- _('_Edit Menus'),
221- _('_About Cardapio'),
222- _('_About Gnome'),
223+ _('_Properties'),
224+ _('_Edit Menus'),
225+ _('_About Cardapio'),
226+ _('_About Gnome'),
227 _('_About %(distro_name)s') % {'distro_name' : Cardapio.distro_name}
228 )
229
230@@ -799,7 +799,7 @@
231 self.safe_cardapio_proxy.handle_search_error = self.plugin_handle_search_error
232 self.safe_cardapio_proxy.ask_for_reload_permission = self.plugin_ask_for_reload_permission
233
234- self.build_plugin_database()
235+ self.build_plugin_database()
236 self.activate_plugins_from_settings()
237
238
239@@ -810,7 +810,7 @@
240
241 panel = self.panel_button.get_toplevel().window
242
243- if panel is None:
244+ if panel is None:
245 return gtk.icon_size_lookup(gtk.ICON_SIZE_LARGE_TOOLBAR)[0]
246
247 panel_width, panel_height = panel.get_size()
248@@ -836,7 +836,7 @@
249
250 def setup_panel_button(self):
251 """
252- Sets up the look and feel of the Cardapio applet button
253+ Sets up the look and feel of the Cardapio applet button
254 """
255
256 self.panel_button.set_label(self.settings['applet label'])
257@@ -923,13 +923,13 @@
258
259 button = self.add_button(_('All'), None, self.category_pane, tooltip = _('Show all categories'), button_type = Cardapio.CATEGORY_BUTTON)
260 button.connect('clicked', self.on_all_sections_sidebar_button_clicked)
261- self.all_sections_sidebar_button = button
262+ self.all_sections_sidebar_button = button
263 self.set_sidebar_button_active(button, True)
264 self.all_sections_sidebar_button.set_sensitive(False)
265
266 button = self.add_button(_('All'), None, self.system_category_pane, tooltip = _('Show all categories'), button_type = Cardapio.CATEGORY_BUTTON)
267 button.connect('clicked', self.on_all_sections_sidebar_button_clicked)
268- self.all_system_sections_sidebar_button = button
269+ self.all_system_sections_sidebar_button = button
270 self.set_sidebar_button_active(button, True)
271 self.all_system_sections_sidebar_button.set_sensitive(False)
272
273@@ -1076,7 +1076,7 @@
274 is_core = (basename in self.core_plugins)
275 is_required = (basename in self.required_plugins)
276
277- if is_required : title = '<b>%s</b>' % name
278+ if is_required : title = '<b>%s</b>' % name
279
280 icon_pixbuf = self.get_icon_pixbuf(plugin_info['category icon'], icon_size, 'package-x-generic')
281
282@@ -1085,7 +1085,7 @@
283 self.update_plugin_description()
284 self.options_dialog.show()
285
286-
287+
288 def close_options_dialog(self, *dummy):
289 """
290 Hides the Options Dialog
291@@ -1118,7 +1118,7 @@
292
293 model, iter_ = self.get_object('PluginTreeView').get_selection().get_selected()
294
295- if iter_ is None:
296+ if iter_ is None:
297 is_core = True
298 plugin_info = {'name': '', 'version': '', 'author': '', 'description': ''}
299
300@@ -1137,7 +1137,7 @@
301
302 # make sure the label doesn't resize the window!
303 if width > 1:
304- label.set_size_request(width - self.scrollbar_width - 20, -1)
305+ label.set_size_request(width - self.scrollbar_width - 20, -1)
306
307 # The -20 is a hack because some themes add extra padding that I need to
308 # account for. Since I don't know where that padding is comming from, I
309@@ -1181,13 +1181,13 @@
310
311 pthinfo = treeview.get_path_at_pos(int(event.x), int(event.y))
312
313- if pthinfo is None:
314+ if pthinfo is None:
315 treeview.window.set_cursor(None)
316 return
317
318 path, col, cellx, celly = pthinfo
319
320- if col == self.plugin_checkbox_column:
321+ if col == self.plugin_checkbox_column:
322 treeview.window.set_cursor(None)
323 else:
324 treeview.window.set_cursor(self.drag_allowed_cursor)
325@@ -1205,7 +1205,7 @@
326
327 if basename in self.required_plugins: return
328
329- self.plugin_tree_model.set_value(iter_, 3, not cell.get_active())
330+ self.plugin_tree_model.set_value(iter_, 3, not cell.get_active())
331 self.apply_plugins_from_option_window()
332
333
334@@ -1231,7 +1231,7 @@
335 y = event.y_root - window_y
336 window_width, window_height = self.window.get_size()
337 resize_margin = 10
338-
339+
340 if x < resize_margin:
341
342 if y < resize_margin:
343@@ -1260,7 +1260,7 @@
344
345 else:
346 edge = gtk.gdk.WINDOW_EDGE_SOUTH
347-
348+
349 x = int(event.x_root)
350 y = int(event.y_root)
351
352@@ -1336,7 +1336,7 @@
353 # So by ignoring this focus-out we actually make sure that Cardapio
354 # will be hidden after all. Silly.
355
356- if self.panel_applet is not None and (0 <= cursor_x <= applet_w and 0 <= cursor_y <= applet_h):
357+ if self.panel_applet is not None and (0 <= cursor_x <= applet_w and 0 <= cursor_y <= applet_h):
358 return
359
360 # If the last app was opened in the background, make sure Cardapio
361@@ -1364,7 +1364,7 @@
362
363 def on_mainwindow_delete_event(self, widget, event):
364 """
365- What happens when the user presses Alt-F4? If in panel mode,
366+ What happens when the user presses Alt-F4? If in panel mode,
367 nothing. If in launcher mode, this terminates Cardapio.
368 """
369
370@@ -1591,7 +1591,7 @@
371 button = self.add_app_button(filename, icon_name, self.subfolders_section_contents, 'xdg', command, tooltip = command, app_list = None)
372
373 if count:
374- self.subfolders_section_slab.show()
375+ self.subfolders_section_slab.show()
376 self.set_section_has_entries(self.subfolders_section_slab)
377 self.no_results_to_show = False
378
379@@ -1628,7 +1628,7 @@
380
381 for plugin_keyword in self.keyword_to_plugin_mapping:
382 if plugin_keyword.find(keyword) == 0:
383- keyword_exists = True
384+ keyword_exists = True
385 keyword = plugin_keyword
386 break
387
388@@ -1695,7 +1695,7 @@
389 try:
390 # TODO: make plugins run in a separate thread
391 self.show_plugin_loading_text(plugin)
392- plugin.search(text, long_search = True)
393+ plugin.search(text, self.settings['long search results limit'])
394
395 except Exception, exception:
396 self.plugin_write_to_log(plugin, 'Plugin search query failed to execute', is_error = True)
397@@ -1705,7 +1705,7 @@
398
399 for plugin in self.active_plugin_instances:
400
401- if plugin.search_delay_type != delay_type or plugin.show_only_with_keyword:
402+ if plugin.search_delay_type != delay_type or plugin.show_only_with_keyword:
403 continue
404
405 if plugin.hide_from_sidebar and len(text) < self.settings['min search string length']:
406@@ -1716,7 +1716,7 @@
407 try:
408 # TODO: make plugins run in a separate thread
409 self.show_plugin_loading_text(plugin)
410- plugin.search(text)
411+ plugin.search(text, self.settings['search results limit'])
412
413 except Exception, exception:
414 self.plugin_write_to_log(plugin, 'Plugin search query failed to execute', is_error = True)
415@@ -1944,7 +1944,7 @@
416
417 if self.is_search_entry_empty():
418 self.hide_all_transitory_sections()
419- return
420+ return
421
422 first_app_widget = self.get_first_visible_app()
423 if first_app_widget is not None:
424@@ -1973,9 +1973,9 @@
425
426 else:
427 first_app_widget = self.get_first_visible_app()
428- if first_app_widget is not None:
429+ if first_app_widget is not None:
430 self.window.set_focus(first_app_widget)
431-
432+
433
434 elif event.keyval == gtk.gdk.keyval_from_name('Escape'):
435
436@@ -2095,7 +2095,7 @@
437 if orientation == gnomeapplet.ORIENT_DOWN:
438 window_x = panel_x + applet_x
439 window_y = panel_y + applet_y + applet_height
440-
441+
442 # bottom
443 elif orientation == gnomeapplet.ORIENT_UP:
444 window_x = panel_x + applet_x
445@@ -2105,7 +2105,7 @@
446 elif orientation == gnomeapplet.ORIENT_RIGHT:
447 window_x = panel_x + applet_x + applet_width
448 window_y = panel_y + applet_y
449-
450+
451 # right
452 elif orientation == gnomeapplet.ORIENT_LEFT:
453 window_x = panel_x + applet_x - window_width
454@@ -2131,7 +2131,7 @@
455 Resize Cardapio according to the user preferences
456 """
457
458- if self.settings['window size'] is not None:
459+ if self.settings['window size'] is not None:
460 self.window.resize(*self.settings['window size'])
461
462 if self.settings['splitter position'] > 0:
463@@ -2282,7 +2282,7 @@
464 window.present_with_time(int(time.time()))
465
466 # for metacity, this is required!!
467- window.window.focus()
468+ window.window.focus()
469
470
471 def on_panel_button_pressed(self, widget, event):
472@@ -2504,13 +2504,13 @@
473
474 def on_volume_monitor_changed(self, drive):
475 """
476- Handler for when volumes are mounted or ejected
477+ Handler for when volumes are mounted or ejected
478 """
479
480 self.clear_pane(self.places_section_contents)
481 self.build_places_list()
482
483-
484+
485 def get_folder_name_and_path(self, folder_path):
486 """
487 Returns a folder's name and path from its full filename
488@@ -2519,12 +2519,12 @@
489 path = folder_path.strip(' \n\r\t')
490
491 res = folder_path.split(os.path.sep)
492- if res:
493+ if res:
494 name = res[-1].strip(' \n\r\t').replace('%20', ' ')
495 if name: return name, path
496
497 # TODO: handle remote folders like nautilus does (i.e. '/home on ftp.myserver.net')
498- name = path.replace('%20', ' ')
499+ name = path.replace('%20', ' ')
500 return name, path
501
502
503@@ -2535,7 +2535,7 @@
504 """
505
506 res = folder_path.split(' ')
507- if len(res) > 1:
508+ if len(res) > 1:
509 name = ' '.join(res[1:]).strip(' \n\r\t')
510 path = res[0]
511 return name, path
512@@ -2568,8 +2568,8 @@
513
514 text = self.search_entry.get_text().strip().lower()
515
516- no_results = True
517-
518+ no_results = True
519+
520 for app in self.settings[list_name]:
521
522 # fixing a misspelling from the old config files...
523@@ -2626,7 +2626,7 @@
524 [
525 _('Log Out...'),
526 _('Log out of this session to log in as a different user'),
527- 'system-log-out',
528+ 'system-log-out',
529 'gnome-session-save --logout-dialog',
530 self.right_session_pane,
531 ],
532@@ -2688,18 +2688,18 @@
533 sidebar_button.hide()
534 section_slab.hide()
535 self.section_list[section_slab] = {
536- 'has entries': False,
537- 'category': sidebar_button,
538- 'contents': section_contents,
539+ 'has entries': False,
540+ 'category': sidebar_button,
541+ 'contents': section_contents,
542 'name': title_str,
543 'is system section': system_menu,
544 }
545
546 else:
547 self.section_list[section_slab] = {
548- 'has entries': True,
549- 'category': sidebar_button,
550- 'contents': section_contents,
551+ 'has entries': True,
552+ 'category': sidebar_button,
553+ 'contents': section_contents,
554 'name': title_str,
555 'is system section': system_menu,
556 }
557@@ -2810,7 +2810,7 @@
558 self.add_session_slab()
559 self.add_system_slab()
560 #self.build_system_list() # TODO: use gnomecc.menu
561-
562+
563 elif basename == 'places':
564 self.add_places_slab()
565
566@@ -2863,15 +2863,15 @@
567
568 if command_type != 'callback' and command_type != 'raw':
569
570- if command_type == 'app':
571+ if command_type == 'app':
572 button.drag_source_set(
573- gtk.gdk.BUTTON1_MASK,
574- [('text/uri-list', 0, 0)],
575+ gtk.gdk.BUTTON1_MASK,
576+ [('text/uri-list', 0, 0)],
577 gtk.gdk.ACTION_COPY)
578- else:
579+ else:
580 button.drag_source_set(
581- gtk.gdk.BUTTON1_MASK,
582- [('text/uri-list', 0, 0)],
583+ gtk.gdk.BUTTON1_MASK,
584+ [('text/uri-list', 0, 0)],
585 gtk.gdk.ACTION_LINK)
586
587 button.connect('drag-begin', self.on_app_button_drag_begin)
588@@ -2934,7 +2934,7 @@
589 align = gtk.Alignment(0, 0.5)
590 align.add(hbox)
591
592- if tooltip:
593+ if tooltip:
594 tooltip = self.unescape(tooltip)
595 button.set_tooltip_text(tooltip)
596
597@@ -2988,7 +2988,7 @@
598
599 # TODO: speed this up!
600
601- if not icon_value:
602+ if not icon_value:
603 icon_value = fallback_icon
604
605 icon_pixbuf = None
606@@ -3037,7 +3037,7 @@
607 # try generic mimetype
608 gen_type = icon_name.split('-')[0]
609 icon_name = gen_type + '-x-generic'
610- if self.icon_theme.has_icon(icon_name):
611+ if self.icon_theme.has_icon(icon_name):
612 return icon_name
613
614 return None
615@@ -3064,7 +3064,7 @@
616 for icon_name in icons:
617 if self.icon_theme.has_icon(icon_name):
618 return icon_name
619-
620+
621 return None
622
623
624@@ -3087,7 +3087,7 @@
625
626 def read_gtk_theme_info(self):
627 """
628- Reads colors and other info from the GTK theme so that the app better
629+ Reads colors and other info from the GTK theme so that the app better
630 adapt to any custom theme
631 """
632
633@@ -3201,8 +3201,8 @@
634 """
635
636 self.clicked_app = widget.app_info
637-
638- if widget.app_info['type'] == 'callback':
639+
640+ if widget.app_info['type'] == 'callback':
641 self.pin_menuitem.hide()
642 self.unpin_menuitem.hide()
643 self.add_side_pane_menuitem.hide()
644@@ -3217,12 +3217,12 @@
645 self.app_menu_separator.show()
646
647 for command in [app['command'] for app in self.settings['pinned items']]:
648- if command == widget.app_info['command']:
649+ if command == widget.app_info['command']:
650 already_pinned = True
651 break
652
653 for command in [app['command'] for app in self.settings['side pane items']]:
654- if command == widget.app_info['command']:
655+ if command == widget.app_info['command']:
656 already_on_side_pane = True
657 break
658
659@@ -3288,7 +3288,7 @@
660
661 i = 0
662
663- for item_info in self.clicked_app['context menu']:
664+ for item_info in self.clicked_app['context menu']:
665
666 menu_item = gtk.ImageMenuItem(item_info['name'], True)
667 menu_item.set_tooltip_text(item_info['tooltip'])
668@@ -3322,7 +3322,7 @@
669 elif alloc.y + alloc.height > scroller_position + page_size:
670 self.scroll_adjustment.set_value(alloc.y + alloc.height - page_size)
671
672-
673+
674 def on_app_button_drag_begin(self, button, drag_context):
675 """
676 Set up drag action (not much goes on here...)
677@@ -3335,7 +3335,7 @@
678 def on_app_button_data_get(self, button, drag_context, selection_data, info, time):
679 """
680 Prepare the data that will be sent to the other app when the drag-and-drop
681- operation is done
682+ operation is done
683 """
684
685 command = button.app_info['command']
686@@ -3344,7 +3344,7 @@
687 if command_type == 'app':
688 command = 'file://' + command
689
690- elif command_type == 'xdg':
691+ elif command_type == 'xdg':
692
693 path_type, dummy = urllib2.splittype(command)
694 if path_type is None: command = 'file://' + command
695@@ -3400,7 +3400,7 @@
696 path = DesktopEntry.DesktopEntry(command).getExec()
697
698 # Strip parts of the path that contain %<a-Z>
699-
700+
701 path_parts = path.split()
702
703 for i in xrange(len(path_parts)):
704@@ -3439,11 +3439,11 @@
705 response = self.show_executable_file_dialog(path)
706
707 # if "Run in Terminal"
708- if response == 1:
709+ if response == 1:
710 return self.launch_raw_in_terminal(path, hide)
711
712 # if "Display"
713- elif response == 2:
714+ elif response == 2:
715 pass
716
717 # if "Run"
718@@ -3451,7 +3451,7 @@
719 return self.launch_raw(path, hide)
720
721 # if "Cancel"
722- else:
723+ else:
724 return
725
726 elif path_type in ['ftp', 'sftp', 'smb']:
727@@ -3540,7 +3540,7 @@
728 else:
729 widget = self.all_sections_sidebar_button
730
731- self.set_sidebar_button_active(widget, True)
732+ self.set_sidebar_button_active(widget, True)
733
734 if self.is_search_entry_empty():
735 widget.set_sensitive(False)
736@@ -3604,11 +3604,11 @@
737 if self.plugins_still_searching > 0:
738 return
739
740- if self.no_results_to_show:
741+ if self.no_results_to_show:
742 self.show_no_results_text()
743
744- return
745-
746+ return
747+
748 if self.section_list[self.selected_section]['has entries']:
749 self.selected_section.show()
750 self.hide_no_results_text()
751@@ -3629,7 +3629,7 @@
752 self.hide_section(self.system_section_slab, fully_hide)
753 self.hide_section(self.sidepane_section_slab, fully_hide)
754 self.hide_section(self.uncategorized_section_slab, fully_hide)
755-
756+
757 self.hide_transitory_plugin_sections(fully_hide)
758
759
760@@ -3713,7 +3713,7 @@
761
762
763 class CardapioPluginInterface:
764- # for documentation, see: https://answers.launchpad.net/cardapio/+faq/1172
765+ # for documentation, see: https://answers.launchpad.net/cardapio/+faq/1172
766
767 author = ''
768 name = ''
769@@ -3724,7 +3724,7 @@
770 help_text = ''
771 version = ''
772
773- plugin_api_version = 1.37
774+ plugin_api_version = 1.38
775
776 search_delay_type = 'local search update delay'
777
778@@ -3749,7 +3749,7 @@
779 For example, the Tracker plugin sets self.loaded to False if Tracker is not
780 installed in the system.
781
782- The constructor is given a single parameter, which is an object used to
783+ The constructor is given a single parameter, which is an object used to
784 communicate with Cardapio. This object has the following members:
785
786 - settings - this is a dict containing the same things that you will
787@@ -3759,17 +3759,17 @@
788 log file, like this: write_to_log(self, 'hi there')
789
790 - handle_search_result - a function to which you should pass the
791- search results when you have them (see more info below, in the
792+ search results when you have them (see more info below, in the
793 search() method)
794
795 - handle_search_error - a function to which you should pass an error
796- message if the search fails (see more info below, in the
797+ message if the search fails (see more info below, in the
798 search() method)
799
800 - ask_for_reload_permission - a function that should be used whenever
801 the plugin wants to reload its database. Not all plugins have
802 internal databases, though, so this is not always applicable. This
803- is used, for example, with the software_center plugin. (see
804+ is used, for example, with the software_center plugin. (see
805 on_reload_permission_granted below for more info)
806
807 Note: DO NOT WRITE ANYTHING IN THE settings DICT!!
808@@ -3787,44 +3787,38 @@
809 pass
810
811
812- def search(self, text, long_search = False):
813+ def search(self, text, result_limit):
814 """
815 REQUIRED
816
817 This method gets called when a new text string is entered in the search
818- field. It also takes an argument indicating whether this is a "long search" or
819- not. This indicates the amount of items that the plugin should return to
820- Cardapio:
821+ field. It also takes an argument indicating the maximum number of
822+ results Cardapio's expecting. The plugin should always provide as many
823+ results as it can but their number cannot exceed the given limit!
824
825- * if long_search == True:
826- --> # of items = cardapio_proxy.settings['search results limit']
827-
828- * if long_search == False:
829- --> # of items = cardapio_proxy.settings['long search results limit']
830-
831 One of the following functions should be called from this method
832 (of from a thread spawned by this method):
833
834 * if all goes well:
835- --> handle_search_result(plugin, results, original_query)
836+ --> handle_search_result(plugin, results, original_query)
837
838 * if there is an error
839- --> handle_search_error(plugin, text)
840+ --> handle_search_error(plugin, text)
841
842 The arguments to these functions are:
843
844- * plugin - this plugin instance (that is, it should always
845+ * plugin - this plugin instance (that is, it should always
846 be "self", without quotes)
847 * text - some text to be inserted in Cardapio's log.
848 * results - an array of dict items as described below.
849 * original_query - the search query that this corresponds to. The
850- plugin should save the query received by the
851+ plugin should save the query received by the
852 search() method and pass it back to Cardapio.
853
854 item = {
855 'name' : _('Music'),
856 'tooltip' : _('Show your Music folder'),
857- 'icon name' : 'text-x-generic',
858+ 'icon name' : 'text-x-generic',
859 'type' : 'xdg',
860 'command' : '~/Music',
861 'context menu' : None
862@@ -3860,9 +3854,9 @@
863 def on_reload_permission_granted(self):
864 """
865 NOT REQUIRED
866-
867+
868 Whenever a plugin wishes to rebuild some sort of internal database,
869- if this takes more than a couple of milliseconds it is advisable to
870+ if this takes more than a couple of milliseconds it is advisable to
871 first ask Cardapio for permission. This is how this works:
872
873 1) Plugin calls cardapio_proxy.ask_for_reload_permission(self)
874@@ -3870,11 +3864,11 @@
875 Cardapio then decides at what time it is best to give the plugin the
876 reload permission. Usually this can take up to 10s, to allow several
877 plugins to reload at the same time. Then, Cardapio shows the "Data has
878- changed" window.
879-
880+ changed" window.
881+
882 2) Cardapio calls on_reload_permission_granted to tell the plugin that
883- it can reload its database
884-
885+ it can reload its database
886+
887 When done, the "Data has changed" window is hidden.
888 """
889 pass
890
891=== added file 'src/plugins/amazon.py'
892--- src/plugins/amazon.py 1970-01-01 00:00:00 +0000
893+++ src/plugins/amazon.py 2010-08-12 17:20:35 +0000
894@@ -0,0 +1,268 @@
895+import json
896+import gio
897+import urllib
898+
899+import base64
900+import hashlib
901+import hmac
902+import time
903+
904+from xml.etree import ElementTree
905+
906+from locale import getdefaultlocale
907+
908+class CardapioPlugin(CardapioPluginInterface):
909+
910+ """
911+ Amazon plugin based on it's Product Advertising API. We provide
912+ results from all of the shop's categories.
913+
914+ API's documentation can be found at:
915+ http://docs.amazonwebservices.com/AWSECommerceService/2009-11-01/DG/
916+
917+ If there's a localized API version, the plugin will use it. Amazon includes
918+ CA, GB, JP, FR and DE versions.
919+ As a fallback strategy, we use the United States version.
920+
921+ The Cardapio's result limit is not fully respected. Amazon always
922+ returns paginated results with 10 elements per page. Because of the
923+ asynchronous nature of Cardapio's searching, we cannot use the
924+ pagination feature. We need to stick to the first page of results
925+ and that's why this plugin will always return at most 10 items.
926+
927+ All of the plugin's web requests are asynchronous and cancellable.
928+ """
929+
930+ # Cardapio's variables
931+ author = 'Pawel Bara'
932+ name = _('Amazon')
933+ description = _('Search for results in Amazon')
934+ version = '0.9b'
935+
936+ url = ''
937+ help_text = ''
938+
939+ default_keyword = 'amazon'
940+
941+ plugin_api_version = 1.38
942+
943+ search_delay_type = 'remote search update delay'
944+
945+ category_name = _('Amazon Results')
946+ category_tooltip = _('Results found in Amazon')
947+
948+ category_icon = 'system-search'
949+ fallback_icon = ''
950+
951+ hide_from_sidebar = True
952+
953+ def __init__(self, cardapio_proxy):
954+ cardapio_proxy.write_to_log(self, 'initializing Amazon plugin')
955+
956+ self.cardapio = cardapio_proxy
957+
958+ self.cancellable = gio.Cancellable()
959+
960+ # my API keys
961+ self.aws_access_key = 'AKIAIW35CYEJ653CJJHQ'
962+ self.aws_secret_access_key = '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'
963+
964+ # basic API's arguments (search in all categories)
965+ self.api_base_args = {
966+ 'Service' : 'AWSECommerceService',
967+ 'Version' : '2009-11-01',
968+ 'Operation' : 'ItemSearch',
969+ 'SearchIndex' : 'All',
970+ 'ResponseGroup' : 'Small',
971+ 'AWSAccessKeyId': self.aws_access_key
972+ }
973+
974+ # try to get a locale specific URL for Amazon
975+ self.locale_url = self.get_locale_url()
976+
977+ # Amazon's base URLs (search and search more variations)
978+ self.api_base_url = 'http://' + self.locale_url + '/onca/xml?{0}'
979+ self.web_base_url = 'http://www.amazon.com/s?url=search-alias%3Daps&{0}'
980+
981+ self.loaded = True
982+
983+ def get_locale_url(self):
984+ """
985+ Tries to get a locale specific base URL for Amazon's API according to
986+ http://docs.amazonwebservices.com/AWSECommerceService/2009-11-01/DG/
987+
988+ If there's none, uses ".com" as a fallback strategy.
989+ """
990+
991+ locale_dict = {
992+ 'ca' : 'ecs.amazonaws.ca',
993+ 'de' : 'ecs.amazonaws.de',
994+ 'fr' : 'ecs.amazonaws.fr',
995+ 'jp' : 'ecs.amazonaws.jp',
996+ 'uk' : 'ecs.amazonaws.co.uk'
997+ }
998+
999+ default = 'ecs.amazonaws.com'
1000+
1001+ # get and parse the language code
1002+ lang_code = getdefaultlocale()[0]
1003+
1004+ if lang_code is None:
1005+ return default
1006+
1007+ lang = lang_code[:2].lower()
1008+ dialect = lang_code[3:].lower()
1009+
1010+ # try to find a mapping...
1011+ key = None
1012+ if lang == 'en':
1013+ if dialect == 'gb':
1014+ key = 'uk'
1015+ elif dialect == 'ca':
1016+ key = 'ca'
1017+ elif lang == 'fr':
1018+ key = 'fr'
1019+ elif lang == 'de':
1020+ key = 'de'
1021+ elif lang == 'ja':
1022+ key = 'jp'
1023+
1024+ return locale_dict.get(key, default)
1025+
1026+ def search(self, text, result_limit):
1027+ if len(text) == 0:
1028+ return
1029+
1030+ self.cardapio.write_to_log(self, 'searching for {0} using Amazon'.format(text), is_debug = True)
1031+
1032+ self.cancellable.reset()
1033+
1034+ # prepare final API URL
1035+ final_url = self.api_base_url.format(self.prepare_amazon_rest_url(text))
1036+
1037+ self.cardapio.write_to_log(self, 'final API URL: {0}'.format(final_url), is_debug = True)
1038+
1039+ # asynchronous and cancellable IO call
1040+ self.current_stream = gio.File(final_url)
1041+ self.current_stream.load_contents_async(self.show_search_results,
1042+ cancellable = self.cancellable,
1043+ user_data = (text, result_limit))
1044+
1045+ def prepare_amazon_rest_url(self, text):
1046+ """
1047+ Prepares a RESTful URL according to Amazon's strict querying policies.
1048+ Deals with the variable part of the URL only (the one after the '?').
1049+ """
1050+
1051+ # additional required API arguments
1052+ copy_args = self.api_base_args.copy()
1053+ copy_args['Keywords'] = text
1054+ copy_args['Timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
1055+
1056+ # turn the argument map into a list of encoded request parameter strings
1057+ query_list = map(
1058+ lambda (k, v): (k + "=" + urllib.quote(v)),
1059+ copy_args.items()
1060+ )
1061+
1062+ # sort the list (by parameter name)
1063+ query_list.sort()
1064+
1065+ # turn the list into a partial URL string
1066+ query_string = "&".join(query_list)
1067+
1068+ # prepare a string on which we will base the AWS signature
1069+ string_to_sign = """GET
1070+{0}
1071+/onca/xml
1072+{1}""".format(self.locale_url, query_string)
1073+
1074+ # create HMAC for the string (using SHA-256 and our secret API key)
1075+ hm = hmac.new(key = self.aws_secret_access_key,
1076+ msg = string_to_sign,
1077+ digestmod = hashlib.sha256)
1078+ # final step... convert the HMAC to base64, then encode it
1079+ signature = urllib.quote(base64.b64encode(hm.digest()))
1080+
1081+ return query_string + '&Signature=' + signature
1082+
1083+ def show_search_results(self, gdaemonfile, result, user_data):
1084+ """
1085+ Callback to asynchronous IO (Amazon's API call).
1086+ """
1087+
1088+ text = user_data[0]
1089+ result_limit = user_data[1]
1090+
1091+ # watch out for connection problems
1092+ try:
1093+ xml_body = self.current_stream.load_contents_finish(result)[0]
1094+
1095+ # watch out for empty input
1096+ if len(xml_body) == 0:
1097+ return
1098+
1099+ root = ElementTree.fromstring(xml_body)
1100+
1101+ # strip the namespaces from all the parsed items
1102+ for el in root.getiterator():
1103+ ns_pos = el.tag.find('}')
1104+ if ns_pos != -1:
1105+ el.tag = el.tag[(ns_pos + 1):]
1106+ except Exception as ex:
1107+ self.cardapio.handle_search_error(self, 'error while obtaining data: {0}'.format(str(ex)))
1108+ return
1109+
1110+ # decode the result
1111+ try:
1112+ items = []
1113+
1114+ is_valid = root.find('Items/Request/IsValid')
1115+ total_results = root.find('Items/TotalResults')
1116+
1117+ # if we have a valid response with any results...
1118+ if (not is_valid is None) and is_valid != 'False' and (not total_results is None) and total_results != '0':
1119+ # remember them all
1120+ for i, item in enumerate(root.findall('Items/Item')):
1121+
1122+ # the number of results cannot be limited using Amazon's API...
1123+ if i == result_limit:
1124+ break
1125+
1126+ i_attributes = item.find('ItemAttributes')
1127+ url = item.find('DetailPageURL').text
1128+
1129+ items.append({
1130+ 'name' : i_attributes.find('Title').text + ' [' + i_attributes.find('ProductGroup').text + ']',
1131+ 'tooltip' : url,
1132+ 'icon name' : 'text-html',
1133+ 'type' : 'xdg',
1134+ 'command' : url,
1135+ 'context menu' : None
1136+ })
1137+
1138+ # always add 'Search more...' item
1139+ search_more_args = { 'field-keywords' : text }
1140+
1141+ items.append({
1142+ 'name' : _('Show additional results'),
1143+ 'tooltip' : _('Show additional search results in your web browser'),
1144+ 'icon name' : 'system-search',
1145+ 'type' : 'xdg',
1146+ # TODO: cardapio later unquotes this and then quotes it again;
1147+ # it's screwing my quotation
1148+ 'command' : self.web_base_url.format(urllib.urlencode(search_more_args)),
1149+ 'context menu' : None
1150+ })
1151+
1152+ # pass the results to Cardapio
1153+ self.cardapio.handle_search_result(self, items, text)
1154+
1155+ except KeyError:
1156+ self.cardapio.handle_search_error(self, "Incorrect Amazon's JSON structure")
1157+
1158+ def cancel(self):
1159+ self.cardapio.write_to_log(self, 'cancelling a recent Amazon search (if any)', is_debug = True)
1160+
1161+ if not self.cancellable.is_cancelled():
1162+ self.cancellable.cancel()
1163
1164=== modified file 'src/plugins/bing.py'
1165--- src/plugins/bing.py 2010-08-04 20:24:22 +0000
1166+++ src/plugins/bing.py 2010-08-12 17:20:35 +0000
1167@@ -22,7 +22,7 @@
1168 url = ''
1169 help_text = ''
1170
1171- plugin_api_version = 1.37
1172+ plugin_api_version = 1.38
1173
1174 search_delay_type = 'remote search update delay'
1175
1176@@ -43,18 +43,10 @@
1177
1178 self.cancellable = gio.Cancellable()
1179
1180- # Bing's API arguments (my AppID and a request for a web search with
1181- # maximum four results)
1182+ # Bing's API arguments (my AppID and a request for a web search)
1183 self.api_base_args = {
1184 'Appid' : '237CBC82BB8C3F7F5F19F6A77B0D38A59E8F8C2C',
1185- 'sources' : 'web',
1186- 'web.count': self.cardapio.settings['search results limit'],
1187- }
1188-
1189- self.api_base_args_long = {
1190- 'Appid' : '237CBC82BB8C3F7F5F19F6A77B0D38A59E8F8C2C',
1191- 'sources' : 'web',
1192- 'web.count': self.cardapio.settings['long search results limit'],
1193+ 'sources' : 'web'
1194 }
1195
1196 # Bing's base URLs (search and search more variations)
1197@@ -63,22 +55,18 @@
1198
1199 self.loaded = True
1200
1201- def search(self, text, long_search = False):
1202+ def search(self, text, result_limit):
1203
1204 if len(text) == 0:
1205 return
1206
1207- self.current_query = text
1208-
1209 self.cardapio.write_to_log(self, 'searching for {0} using Bing'.format(text), is_debug = True)
1210
1211 self.cancellable.reset()
1212
1213 # prepare final API URL
1214- if long_search:
1215- current_args = self.api_base_args_long.copy()
1216- else:
1217- current_args = self.api_base_args.copy()
1218+ current_args = self.api_base_args.copy()
1219+ current_args['web.count'] = result_limit
1220
1221 current_args['query'] = text
1222 final_url = self.api_base_url.format(urllib.urlencode(current_args))
1223@@ -143,7 +131,7 @@
1224 })
1225
1226 # pass the results to Cardapio
1227- self.cardapio.handle_search_result(self, items, self.current_query)
1228+ self.cardapio.handle_search_result(self, items, text)
1229
1230 except KeyError:
1231 self.cardapio.handle_search_error(self, "Incorrect Bing's JSON structure")
1232
1233=== added file 'src/plugins/ebay.py'
1234--- src/plugins/ebay.py 1970-01-01 00:00:00 +0000
1235+++ src/plugins/ebay.py 2010-08-12 17:20:35 +0000
1236@@ -0,0 +1,251 @@
1237+import json
1238+import gio
1239+import urllib
1240+
1241+from glib import GError
1242+from locale import getdefaultlocale
1243+
1244+class CardapioPlugin(CardapioPluginInterface):
1245+ """
1246+ eBay search plugin based on it's Finding API documented at:
1247+ http://developer.ebay.com/products/finding/
1248+
1249+ Please note, that this API limits the number of calls to 5000 per IP
1250+ and day.
1251+
1252+ All calls are localized, meaning that they are using eBay version local
1253+ to the user. The specific version being used is derived from the user's
1254+ computer locale.
1255+
1256+ For a list of locale supported by eBay check:
1257+ http://developer.ebay.com/DevZone/finding/Concepts/SiteIDToGlobalID.html
1258+ The listing there means that the plugin is localized for more than 20
1259+ countries. :) Nevertheless, we must have a fallback strategy and we use
1260+ the US version in this role.
1261+
1262+ All of the plugin's web requests are asynchronous and cancellable.
1263+ """
1264+
1265+ # Cardapio's variables
1266+ author = 'Pawel Bara'
1267+ name = _('eBay')
1268+ description = _('Search for items on eBay')
1269+ version = '0.9b'
1270+
1271+ url = ''
1272+ help_text = ''
1273+
1274+ plugin_api_version = 1.38
1275+
1276+ search_delay_type = 'remote search update delay'
1277+
1278+ default_keyword = 'ebay'
1279+
1280+ category_name = _('eBay Results')
1281+ category_tooltip = _('Items found on eBay')
1282+
1283+ category_icon = 'system-search'
1284+ fallback_icon = ''
1285+
1286+ hide_from_sidebar = True
1287+
1288+ def __init__(self, cardapio_proxy):
1289+ cardapio_proxy.write_to_log(self, 'initializing eBay plugin')
1290+
1291+ self.cardapio = cardapio_proxy
1292+
1293+ self.cancellable = gio.Cancellable()
1294+
1295+ # eBay's API arguments (my API key, 'find' operation, JSON response format,
1296+ # and locale information)
1297+ self.api_base_args = {
1298+ 'SECURITY-APPNAME' : 'Cardapio-9704-40b3-8e17-cfad62dd6c45',
1299+ 'OPERATION-NAME' : 'findItemsByKeywords',
1300+ 'RESPONSE-DATA-FORMAT' : 'JSON',
1301+ 'GLOBAL-ID' : self.get_global_id()
1302+ }
1303+
1304+ # eBay's base URLs (search and a fallback search more variations)
1305+ self.api_base_url = 'http://svcs.ebay.com/services/search/FindingService/v1?{0}'
1306+ self.web_base_url = 'http://shop.ebay.com/?{0}'
1307+
1308+ self.loaded = True
1309+
1310+ def get_global_id(self):
1311+ """
1312+ Tries to get a locale specific GLOBAL-ID argument for eBay's API.
1313+ For more information check those two websites:
1314+ http://developer.ebay.com/DevZone/finding/Concepts/SiteIDToGlobalID.html
1315+ http://developer.ebay.com/DevZone/finding/CallRef/Enums/GlobalIdList.html
1316+
1317+ We use 'EBAY-US' as a fallback strategy.
1318+ """
1319+
1320+ default = 'EBAY-US'
1321+
1322+ # get and parse the language code
1323+ lang_code = getdefaultlocale()[0]
1324+
1325+ if lang_code is None:
1326+ return default
1327+
1328+ lang = lang_code[:2].lower()
1329+ dialect = lang_code[3:].lower()
1330+
1331+ # try to find a mapping...
1332+ result = None
1333+ if lang == 'en':
1334+ if dialect == 'gb':
1335+ result = 'EBAY-GB'
1336+ elif dialect == 'ca':
1337+ result = 'EBAY-ENCA'
1338+ elif dialect == 'ie':
1339+ result = 'EBAY-IE'
1340+ elif dialect == 'in':
1341+ result = 'EBAY-IN'
1342+ elif dialect == 'my':
1343+ result = 'EBAY-MY'
1344+ elif dialect == 'ph':
1345+ result = 'EBAY-PH'
1346+ elif dialect == 'sg':
1347+ result = 'EBAY-SG'
1348+ elif dialect == 'au':
1349+ result = 'EBAY-AU'
1350+ elif lang == 'fr':
1351+ if dialect == 'be':
1352+ result = 'EBAY-FRBE'
1353+ elif dialect == 'ca':
1354+ result = 'EBAY-FRCA'
1355+ else:
1356+ result = 'EBAY-FR'
1357+ elif lang == 'de':
1358+ if dialect == 'at':
1359+ result = 'EBAY-AT'
1360+ elif dialect == 'ch':
1361+ result = 'EBAY-CH'
1362+ else:
1363+ result = 'EBAY-DE'
1364+ elif lang == 'it':
1365+ result = 'EBAY-IT'
1366+ elif lang == 'pl':
1367+ result = 'EBAY-PL'
1368+ elif lang == 'es':
1369+ result = 'EBAY-ES'
1370+ elif lang == 'nl':
1371+ if dialect == 'be':
1372+ result = 'EBAY-NLBE'
1373+ else:
1374+ result = 'EBAY-NL'
1375+ elif lang == 'zh':
1376+ result = 'EBAY-HK'
1377+ elif lang == 'sv':
1378+ result = 'EBAY-SE'
1379+
1380+ return default if result is None else result
1381+
1382+ def search(self, text, result_limit):
1383+ if len(text) == 0:
1384+ return
1385+
1386+ self.cardapio.write_to_log(self, 'searching for {0} on eBay'.format(text), is_debug = True)
1387+
1388+ self.cancellable.reset()
1389+
1390+ # prepare final API URL (items per page and search keyword)
1391+ current_args = self.api_base_args.copy()
1392+ current_args['paginationInput.entriesPerPage'] = result_limit
1393+ current_args['keywords'] = text
1394+
1395+ final_url = self.api_base_url.format(urllib.urlencode(current_args))
1396+
1397+ self.cardapio.write_to_log(self, 'final API URL: {0}'.format(final_url), is_debug = True)
1398+
1399+ # asynchronous and cancellable IO call
1400+ self.current_stream = gio.File(final_url)
1401+ self.current_stream.load_contents_async(self.show_search_results,
1402+ cancellable = self.cancellable,
1403+ user_data = text)
1404+
1405+ def show_search_results(self, gdaemonfile, result, text):
1406+ """
1407+ Callback to asynchronous IO (eBay's API call).
1408+ """
1409+
1410+ # watch out for connection problems
1411+ try:
1412+ json_body = self.current_stream.load_contents_finish(result)[0]
1413+
1414+ # watch out for empty input
1415+ if len(json_body) == 0:
1416+ return
1417+
1418+ response = json.loads(json_body)
1419+ except (ValueError, GError) as ex:
1420+ self.cardapio.handle_search_error(self, 'error while obtaining data: {0}'.format(str(ex)))
1421+ return
1422+
1423+ # decode the result
1424+ try:
1425+ items = []
1426+
1427+ response_body = response['findItemsByKeywordsResponse'][0]
1428+
1429+ # if we made a successful call...
1430+ if response_body['ack'][0] == 'Success':
1431+ search_result = response_body['searchResult'][0]
1432+
1433+ # and we have any results...
1434+ if int(search_result['@count']) > 0:
1435+
1436+ # remember them all
1437+ for ebay_item in search_result['item']:
1438+ ebay_item_url = ebay_item['viewItemURL'][0]
1439+
1440+ items.append({
1441+ 'name' : ebay_item['title'][0],
1442+ 'tooltip' : ebay_item_url,
1443+ 'icon name' : 'text-html',
1444+ 'type' : 'xdg',
1445+ 'command' : ebay_item_url,
1446+ 'context menu' : None
1447+ })
1448+
1449+ # on a succesful call, add the 'Search more...' item (URL from the response)
1450+ items.append({
1451+ 'name' : _('Show additional results'),
1452+ 'tooltip' : _('Show additional search results in your web browser'),
1453+ 'icon name' : 'system-search',
1454+ 'type' : 'xdg',
1455+ 'command' : response_body['itemSearchURL'][0],
1456+ 'context menu' : None
1457+ })
1458+
1459+ # if the API call failed, add the generic 'search more' item
1460+ if len(items) == 0:
1461+ search_more_args = {
1462+ '_nkw' : text,
1463+ '_sacat' : 'See-All-Categories'
1464+ }
1465+
1466+ items.append({
1467+ 'name' : _('Show additional results'),
1468+ 'tooltip' : _('Show additional search results in your web browser'),
1469+ 'icon name' : 'system-search',
1470+ 'type' : 'xdg',
1471+ # TODO: cardapio later unquotes this and then quotes it again;
1472+ # it's screwing my quotation
1473+ 'command' : self.web_base_url.format(urllib.urlencode(search_more_args)),
1474+ 'context menu' : None
1475+ })
1476+
1477+ # pass the results to Cardapio
1478+ self.cardapio.handle_search_result(self, items, text)
1479+
1480+ except KeyError:
1481+ self.cardapio.handle_search_error(self, "Incorrect eBay's JSON structure")
1482+
1483+ def cancel(self):
1484+ self.cardapio.write_to_log(self, 'cancelling a recent eBay search (if any)', is_debug = True)
1485+
1486+ if not self.cancellable.is_cancelled():
1487+ self.cancellable.cancel()
1488
1489=== modified file 'src/plugins/google.py'
1490--- src/plugins/google.py 2010-08-04 20:24:22 +0000
1491+++ src/plugins/google.py 2010-08-12 17:20:35 +0000
1492@@ -12,7 +12,7 @@
1493 help_text = ''
1494 version = '1.37'
1495
1496- plugin_api_version = 1.37
1497+ plugin_api_version = 1.38
1498
1499 search_delay_type = 'remote search update delay'
1500
1501@@ -42,20 +42,7 @@
1502 if google_interface_language_format == 'zh-HK':
1503 google_interface_language_format = 'zh-CN'
1504
1505- # The google search API only supports two sizes for the result list,
1506- # that is: small (4 results) or large (8 results). So this plugin
1507- # chooses the most appropriate given the 'search results limit' user
1508- # preference.
1509-
1510- if self.c.settings['search results limit'] >= 8:
1511- self.query_url = r'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=large&q=%s'
1512- else:
1513- self.query_url = r'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=small&q=%s'
1514-
1515- if self.c.settings['long search results limit'] >= 8:
1516- self.query_url_long = r'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=large&q=%s'
1517- else:
1518- self.query_url_long = r'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=small&q=%s'
1519+ self.query_url = r'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz={0}&q={1}'
1520
1521 self.search_controller = gio.Cancellable()
1522
1523@@ -72,7 +59,7 @@
1524 self.loaded = True
1525
1526
1527- def search(self, text, long_search = False):
1528+ def search(self, text, result_limit):
1529
1530 # TODO: I'm sure this is not the best way of doing remote procedure
1531 # calls, but I can't seem to find anything that is this easy to use and
1532@@ -84,10 +71,12 @@
1533 self.current_query = text
1534 text = urllib2.quote(text)
1535
1536- if long_search:
1537- query = self.query_url_long % text
1538- else:
1539- query = self.query_url % text
1540+ # The google search API only supports two sizes for the result list,
1541+ # that is: small (4 results) or large (8 results). So this plugin
1542+ # chooses the most appropriate given the 'search results limit' user
1543+ # preference.
1544+
1545+ query = self.query_url.format('large' if result_limit >= 8 else 'small', text)
1546
1547 self.stream = gio.File(query)
1548
1549
1550=== modified file 'src/plugins/google_localized.py'
1551--- src/plugins/google_localized.py 2010-08-04 20:24:22 +0000
1552+++ src/plugins/google_localized.py 2010-08-12 17:20:35 +0000
1553@@ -12,7 +12,7 @@
1554 help_text = ''
1555 version = '1.37'
1556
1557- plugin_api_version = 1.37
1558+ plugin_api_version = 1.38
1559
1560 search_delay_type = 'remote search update delay'
1561
1562@@ -46,21 +46,7 @@
1563 if google_interface_language_format[:2] == 'zh':
1564 google_results_language_format = google_interface_language_format
1565
1566-
1567- # The google search API only supports two sizes for the result list,
1568- # that is: small (4 results) or large (8 results). So this plugin
1569- # chooses the most appropriate given the 'search results limit' user
1570- # preference.
1571-
1572- if self.c.settings['search results limit'] >= 8:
1573- self.query_url = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=large&lr=lang_%s&q=%%s' % google_results_language_format
1574- else:
1575- self.query_url = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=small&lr=lang_%s&q=%%s' % google_results_language_format
1576-
1577- if self.c.settings['long search results limit'] >= 8:
1578- self.query_url_long = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=large&lr=lang_%s&q=%%s' % google_results_language_format
1579- else:
1580- self.query_url_long = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=small&lr=lang_%s&q=%%s' % google_results_language_format
1581+ self.query_url = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz={0}&lr=lang_%s&q={1}' % google_results_language_format
1582
1583 self.search_controller = gio.Cancellable()
1584
1585@@ -77,7 +63,7 @@
1586 self.loaded = True
1587
1588
1589- def search(self, text, long_search = False):
1590+ def search(self, text, result_limit):
1591
1592 # TODO: I'm sure this is not the best way of doing remote procedure
1593 # calls, but I can't seem to find anything that is this easy to use and
1594@@ -89,10 +75,12 @@
1595 self.current_query = text
1596 text = urllib2.quote(text)
1597
1598- if long_search:
1599- query = self.query_url_long % text
1600- else:
1601- query = self.query_url % text
1602+ # The google search API only supports two sizes for the result list,
1603+ # that is: small (4 results) or large (8 results). So this plugin
1604+ # chooses the most appropriate given the 'search results limit' user
1605+ # preference.
1606+
1607+ query = self.query_url.format('large' if result_limit >= 8 else 'small', text)
1608
1609 self.stream = gio.File(query)
1610
1611
1612=== added file 'src/plugins/pidgin.py'
1613--- src/plugins/pidgin.py 1970-01-01 00:00:00 +0000
1614+++ src/plugins/pidgin.py 2010-08-12 17:20:35 +0000
1615@@ -0,0 +1,218 @@
1616+from dbus.exceptions import DBusException
1617+
1618+class CardapioPlugin(CardapioPluginInterface):
1619+
1620+ """
1621+ Pidgin plugin based on it's D-Bus interface. Documentation:
1622+ http://developer.pidgin.im/wiki/DbusHowto
1623+
1624+ The plugin looks for online buddies and provides the user with
1625+ possibility to start a conversation with any of them. All active
1626+ accounts are considered. We match buddies by their alias (case
1627+ insensitive).
1628+
1629+ Please note that the plugin only works when Pidgin is on. You don't
1630+ need to turn Pidgin on before starting Cardapio or before initializing
1631+ the plugin. You just need to turn it on before performing a Cardapio
1632+ search.
1633+ """
1634+
1635+ # Cardapio's variables
1636+ author = 'Pawel Bara'
1637+ name = _('Pidgin')
1638+ description = _('Search for online Pidgin buddies')
1639+ version = '0.9b'
1640+
1641+ url = ''
1642+ help_text = ''
1643+
1644+ default_keyword = 'pidgin'
1645+
1646+ plugin_api_version = 1.38
1647+
1648+ search_delay_type = 'local search update delay'
1649+
1650+ category_name = _('Pidgin Buddies')
1651+ category_tooltip = _('Your online Pidgin buddies')
1652+
1653+ category_icon = 'pidgin'
1654+ fallback_icon = ''
1655+
1656+ hide_from_sidebar = True
1657+
1658+ def __init__(self, cardapio_proxy):
1659+ cardapio_proxy.write_to_log(self, 'initializing Pidgin plugin')
1660+
1661+ self.cardapio = cardapio_proxy
1662+
1663+ # Pidgin's D-Bus constants
1664+ self.dpidgin_bus_name = 'im.pidgin.purple.PurpleService'
1665+ self.dpidgin_object_path = '/im/pidgin/purple/PurpleObject'
1666+ self.dpidgin_iface_name = 'im.pidgin.purple.PurpleInterface'
1667+
1668+ try:
1669+ self.bus = dbus.SessionBus()
1670+ # we track Pidgin's on / off status
1671+ self.bus.watch_name_owner(self.dpidgin_bus_name, self.on_dbus_name_change)
1672+
1673+ self.loaded = True
1674+
1675+ except DBusException as ex:
1676+ self.cardapio.write_to_log(self, 'Pidgin plugin initialization error: {0}'.format(str(ex)), is_error = True)
1677+ self.loaded = False
1678+
1679+ def on_dbus_name_change(self, connection_name):
1680+ """
1681+ This method effectively tracks down the events of Pidgin app starting
1682+ and shutting down. When the app shuts down, this callback nullifies our
1683+ Pidgin's proxy and when the app starts, this callback sets the valid
1684+ proxy again.
1685+ """
1686+
1687+ if len(connection_name) == 0:
1688+ self.pidgin = None
1689+ else:
1690+ bus_object = self.bus.get_object(connection_name, self.dpidgin_object_path)
1691+ self.pidgin = dbus.Interface(bus_object, self.dpidgin_iface_name)
1692+
1693+ def search(self, text, result_limit):
1694+ if len(text) == 0:
1695+ return
1696+
1697+ # send empty results to Cardapio if Pidgin's off
1698+ if self.pidgin is None:
1699+ self.cardapio.handle_search_result(self, [], text)
1700+ return
1701+
1702+ self.cardapio.write_to_log(self, 'searching for Pidgin buddies with name like {0}'.format(text), is_debug = True)
1703+
1704+ # prepare a parametrized callback that remembers the current search text and
1705+ # the result limit
1706+ callback = DBusGatherBuddiesCallback(self.pidgin, self.finalize_search,
1707+ text, result_limit)
1708+
1709+ # let's start by getting all of the user's active accounts
1710+ self.pidgin.PurpleAccountsGetAllActive(reply_handler = callback.handle_search_result,
1711+ error_handler = self.handle_search_error)
1712+
1713+ def finalize_search(self, buddies, text):
1714+ """
1715+ DBusGatherBuddiesCallback invokes this when it finishes gathering
1716+ single search results. This method finalizes the search, then
1717+ passes the result to Cardapio.
1718+ """
1719+
1720+ items = []
1721+
1722+ # for every buddy...
1723+ for buddy in buddies:
1724+
1725+ # 'start conversation' callback wrapper
1726+ conversation_callback = DBusTalkToBuddyCallback(self.pidgin, buddy[0], buddy[1])
1727+
1728+ # add 'talk to this buddy' item
1729+ items.append({
1730+ 'name' : buddy[2] + ' ({0})'.format(buddy[1]),
1731+ 'tooltip' : _('Talk to this buddy'),
1732+ 'icon name' : 'pidgin',
1733+ 'type' : 'callback',
1734+ 'command' : conversation_callback.start_conversation,
1735+ 'context menu' : None
1736+ })
1737+
1738+ self.cardapio.handle_search_result(self, items, text)
1739+
1740+ def handle_search_error(self, error):
1741+ """
1742+ Error callback to asynchronous Pidgin's D-Bus call.
1743+ """
1744+
1745+ self.cardapio.handle_search_error(self, 'Pidgin search error: {0}'.format(str(error)))
1746+
1747+class DBusGatherBuddiesCallback:
1748+ """
1749+ DBusGatherBuddiesCallback serves as a parametrized wrapper over
1750+ the asynchronous callback to Pidgin's PurpleAccountsGetAllActive
1751+ call.
1752+ """
1753+
1754+ def __init__(self, pidgin, result_callback, text, result_limit):
1755+ self.pidgin = pidgin
1756+ self.result_callback = result_callback
1757+
1758+ self.text = text
1759+ self.result_limit = result_limit
1760+
1761+ def handle_search_result(self, accounts):
1762+ """
1763+ Callback to asynchronous Pidgin's PurpleAccountsGetAllActive
1764+ call. It gathers results and then passes those back to the
1765+ main plugin class through the result_callback method.
1766+ """
1767+
1768+ # gather all online buddies
1769+ self.result_callback(self.gather_buddies(accounts), self.text)
1770+
1771+ def gather_buddies(self, accounts):
1772+ """
1773+ Gathers all of the user's online Pidgin buddies in a form of list
1774+ containing tuples.
1775+ """
1776+
1777+ # return empty results if Pidgin's off
1778+ if self.pidgin is None:
1779+ return []
1780+
1781+ buddies = []
1782+
1783+ # for every active account...
1784+ for account in accounts:
1785+
1786+ # and every buddy associated with this active account...
1787+ for buddy in self.pidgin.PurpleFindBuddies(account, ''):
1788+
1789+ # obey the result limit!
1790+ if len(buddies) == self.result_limit:
1791+ return buddies
1792+
1793+ # we remember only those buddies who are online now
1794+ if self.pidgin.PurpleBuddyIsOnline(buddy):
1795+
1796+ buddy_alias = self.pidgin.PurpleBuddyGetAlias(buddy)
1797+ buddy_name = self.pidgin.PurpleBuddyGetName(buddy)
1798+
1799+ # if buddies alias contains (case insensitive) the search
1800+ # keyword, add him to the result list
1801+ if buddy_alias.lower().count(self.text.lower()) > 0:
1802+ buddies.append((account, buddy_name, buddy_alias))
1803+
1804+ return buddies
1805+
1806+
1807+class DBusTalkToBuddyCallback:
1808+ """
1809+ DBusTalkToBuddyCallback serves as a parametrized wrapper over
1810+ the asynchronous callback to Pidgin's PurpleConversationNew
1811+ call.
1812+ """
1813+
1814+ def __init__(self, pidgin, account, buddy):
1815+ self.pidgin = pidgin
1816+
1817+ self.account = account
1818+ self.buddy = buddy
1819+
1820+ def start_conversation(self, search_text):
1821+ """
1822+ Starts a conversation for the account and buddy name with which this
1823+ callback was created. Ignores the callback parameter (search_text)
1824+ from Cardapio.
1825+ """
1826+
1827+ # try to avoid errors if Pidgin's off
1828+ if self.pidgin is None:
1829+ return
1830+
1831+ # starting a conversation... the number 1 means 'InstantMessage
1832+ # conversation'
1833+ self.pidgin.PurpleConversationNew(1, self.account, self.buddy)
1834\ No newline at end of file
1835
1836=== modified file 'src/plugins/software_center.py'
1837--- src/plugins/software_center.py 2010-08-02 07:22:55 +0000
1838+++ src/plugins/software_center.py 2010-08-12 17:20:35 +0000
1839@@ -32,7 +32,7 @@
1840 help_text = ''
1841 version = '1.12'
1842
1843- plugin_api_version = 1.37
1844+ plugin_api_version = 1.38
1845
1846 search_delay_type = 'local search update delay'
1847 default_keyword = 'softwarecenter'
1848@@ -53,9 +53,6 @@
1849
1850 self.c = cardapio_proxy
1851 self.loaded = False
1852-
1853- self.num_search_results = self.c.settings['search results limit']
1854- self.num_search_results_long = self.c.settings['long search results limit']
1855
1856 if import_error:
1857 self.c.write_to_log(self, 'Could not import certain modules', is_error = True)
1858@@ -103,7 +100,7 @@
1859 self.loaded = True # set to true if everything goes well
1860
1861
1862- def search(self, text, long_search = False):
1863+ def search(self, text, result_limit):
1864
1865 self.current_query = text
1866
1867@@ -116,14 +113,9 @@
1868 enquire.set_sort_by_value_then_relevance(XAPIAN_VALUE_POPCON)
1869 matches = enquire.get_mset(0, len(self.db))
1870
1871- if long_search:
1872- num_search_results = self.num_search_results_long
1873- else:
1874- num_search_results = self.num_search_results
1875-
1876 i = 0
1877 for m in matches:
1878- if not i < num_search_results : break
1879+ if not i < result_limit : break
1880
1881 doc = m[xapian.MSET_DOCUMENT]
1882 pkgname = self.db.get_pkgname(doc)
1883
1884=== added file 'src/plugins/tomboy.py'
1885--- src/plugins/tomboy.py 1970-01-01 00:00:00 +0000
1886+++ src/plugins/tomboy.py 2010-08-12 17:20:35 +0000
1887@@ -0,0 +1,206 @@
1888+from dbus.exceptions import DBusException
1889+
1890+class CardapioPlugin(CardapioPluginInterface):
1891+
1892+ """
1893+ Tomboy plugin based on it's D-Bus interface. Documentation:
1894+ http://arstechnica.com/open-source/news/2007/09/using-the-tomboy-d-bus-interface.ars
1895+
1896+ The plugin looks for notes with titles and contents similar to the search string.
1897+ If it can't find any, it provides user with the handy 'create a note with this
1898+ title' link.
1899+
1900+ Please note that the plugin only works when Tomboy is on. You don't need to
1901+ turn Tomboy on before starting Cardapio or before initializing the plugin.
1902+ You just need to turn it on before performing a Cardapio search.
1903+ """
1904+
1905+ # Cardapio's variables
1906+ author = 'Pawel Bara'
1907+ name = _('Tomboy')
1908+ description = _('Search for Tomboy notes')
1909+ version = '0.9b'
1910+
1911+ url = ''
1912+ help_text = ''
1913+
1914+ default_keyword = 'tomboy'
1915+
1916+ plugin_api_version = 1.38
1917+
1918+ search_delay_type = 'local search update delay'
1919+
1920+ category_name = _('Tomboy Results')
1921+ category_tooltip = _('Your Tomboy notes')
1922+
1923+ category_icon = 'tomboy'
1924+ fallback_icon = ''
1925+
1926+ hide_from_sidebar = True
1927+
1928+ def __init__(self, cardapio_proxy):
1929+ cardapio_proxy.write_to_log(self, 'initializing Tomboy plugin')
1930+
1931+ self.cardapio = cardapio_proxy
1932+
1933+ # Tomboy's D-Bus constants
1934+ self.dtomboy_bus_name = 'org.gnome.Tomboy'
1935+ self.dtomboy_object_path = '/org/gnome/Tomboy/RemoteControl'
1936+ self.dtomboy_iface_name = 'org.gnome.Tomboy.RemoteControl'
1937+
1938+ try:
1939+ self.bus = dbus.SessionBus()
1940+ # we track Tomboy's on / off status
1941+ self.bus.watch_name_owner(self.dtomboy_bus_name, self.on_dbus_name_change)
1942+
1943+ self.loaded = True
1944+
1945+ except DBusException as ex:
1946+ self.cardapio.write_to_log(self, 'Tomboy plugin initialization error: {0}'.format(str(ex)), is_error = True)
1947+ self.loaded = False
1948+
1949+ def on_dbus_name_change(self, connection_name):
1950+ """
1951+ This method effectively tracks down the events of Tomboy app starting
1952+ and shutting down. When the app shuts down, this callback nullifies our
1953+ Tomboy's proxy and when the app starts, the callback sets the valid
1954+ proxy again.
1955+ """
1956+
1957+ if len(connection_name) == 0:
1958+ self.tomboy = None
1959+ else:
1960+ bus_object = self.bus.get_object(connection_name, self.dtomboy_object_path)
1961+ self.tomboy = dbus.Interface(bus_object, self.dtomboy_iface_name)
1962+
1963+ def search(self, text, result_limit):
1964+ if len(text) == 0:
1965+ return
1966+
1967+ # send empty results to Cardapio if Tomboy's off
1968+ if self.tomboy is None:
1969+ self.cardapio.handle_search_result(self, [], text)
1970+ return
1971+
1972+ self.cardapio.write_to_log(self, 'searching for Tomboy notes with topic like {0}'.format(text), is_debug = True)
1973+
1974+ # prepare a parametrized callback that remembers the current search text and
1975+ # the result limit
1976+ callback = DBusSearchNotesCallback(self.tomboy, self.finalize_search,
1977+ text, result_limit)
1978+
1979+ # we ask for a case insensitive search
1980+ self.tomboy.SearchNotes(text.lower(), False, reply_handler = callback.handle_search_result,
1981+ error_handler = self.handle_search_error)
1982+
1983+ def finalize_search(self, items, text):
1984+ """
1985+ DBusSearchNotesCallback invokes this when it finishes gathering
1986+ single search results. This method finalizes the search, then
1987+ passes the results to Cardapio.
1988+ """
1989+
1990+ # if there are no results, we'll add the 'create a note with this
1991+ # title' link
1992+ if len(items) == 0:
1993+ items.append({
1994+ 'name' : _('Create this note'),
1995+ 'tooltip' : _('Create a new note with this title in Tomboy'),
1996+ 'icon name' : 'tomboy',
1997+ 'type' : 'callback',
1998+ 'command' : self.tomboy_create_note,
1999+ 'context menu' : None
2000+ })
2001+
2002+ # the 'search more' option is always present
2003+ items.append({
2004+ 'name' : _('Show additional notes'),
2005+ 'tooltip' : _('Show additional notes in Tomboy'),
2006+ 'icon name' : 'tomboy',
2007+ 'type' : 'callback',
2008+ 'command' : self.tomboy_find_more,
2009+ 'context menu' : None
2010+ })
2011+
2012+ self.cardapio.handle_search_result(self, items, text)
2013+
2014+ def handle_search_error(self, error):
2015+ """
2016+ Error callback to asynchronous Tomboy's D-Bus call.
2017+ """
2018+
2019+ self.cardapio.handle_search_error(self, 'Tomboy search error: {0}'.format(str(error)))
2020+
2021+ def tomboy_create_note(self, text):
2022+ """
2023+ Creates a new note with the given title, then displays it to
2024+ the user.
2025+ """
2026+
2027+ # try to avoid errors if Tomboy's off
2028+ if self.tomboy is None:
2029+ return
2030+
2031+ new_note = self.tomboy.CreateNamedNote(text)
2032+ self.tomboy.DisplayNote(new_note)
2033+
2034+ def tomboy_find_more(self, text):
2035+ """
2036+ Opens Tomboy's 'Search more' window.
2037+ """
2038+
2039+ # try to avoid errors if Tomboy's off
2040+ if self.tomboy is None:
2041+ return
2042+
2043+ self.tomboy.DisplaySearchWithText(text)
2044+
2045+class DBusSearchNotesCallback:
2046+ """
2047+ DBusSearchNotesCallback serves as a parametrized wrapper over
2048+ the asynchronous callback to Tomboy's SearchNotes call.
2049+ """
2050+
2051+ def __init__(self, tomboy, result_callback, text, result_limit):
2052+ self.tomboy = tomboy
2053+ self.result_callback = result_callback
2054+
2055+ self.text = text
2056+ self.result_limit = result_limit
2057+
2058+ def handle_search_result(self, result):
2059+ """
2060+ Callback to asynchronous Tomboy's SearchNotes call. It gathers
2061+ results and then passes those back to the main plugin class
2062+ through the result_callback method.
2063+ """
2064+
2065+ # pass empty results to the main class if Tomboy's off
2066+ if self.tomboy is None:
2067+ self.result_callback([], self.text)
2068+ return
2069+
2070+ items = []
2071+
2072+ # if we have any results...
2073+ i = 0
2074+ for note in result:
2075+
2076+ # exit after gathering enough results
2077+ if i == self.result_limit:
2078+ break
2079+ i += 1
2080+
2081+ # add 'open this note' item
2082+ items.append({
2083+ 'name' : self.tomboy.GetNoteTitle(note),
2084+ 'tooltip' : _('Open this note'),
2085+ 'icon name' : 'tomboy',
2086+ 'type' : 'xdg',
2087+ 'command' : note,
2088+ 'context menu' : None
2089+ })
2090+
2091+ # pass all of the gathered items and the current search
2092+ # text to the main plugin class
2093+ self.result_callback(items, self.text)
2094
2095=== modified file 'src/plugins/tracker.py'
2096--- src/plugins/tracker.py 2010-08-02 07:22:55 +0000
2097+++ src/plugins/tracker.py 2010-08-12 17:20:35 +0000
2098@@ -10,7 +10,7 @@
2099 help_text = ''
2100 version = '1.37'
2101
2102- plugin_api_version = 1.37
2103+ plugin_api_version = 1.38
2104
2105 search_delay_type = 'local search update delay'
2106
2107@@ -37,9 +37,6 @@
2108 self.loaded = False
2109 return
2110
2111- self.search_results_limit = self.c.settings['search results limit']
2112- self.search_results_limit_long = self.c.settings['long search results limit']
2113-
2114 self.action_command = r"tracker-search-tool '%s'"
2115 self.action = {
2116 'name' : _('Show additional results'),
2117@@ -53,16 +50,11 @@
2118 self.loaded = True
2119
2120
2121- def search(self, text, long_search = False):
2122+ def search(self, text, result_limit):
2123
2124 self.current_query = text
2125 text = urllib2.quote(text).lower()
2126
2127- if long_search:
2128- search_results_limit = self.search_results_limit_long
2129- else:
2130- search_results_limit = self.search_results_limit
2131-
2132 self.tracker.SparqlQuery(
2133 """
2134 SELECT ?uri ?mime
2135@@ -76,7 +68,7 @@
2136 ORDER BY ASC(?uri)
2137 LIMIT %d
2138 """
2139- % (text, search_results_limit),
2140+ % (text, result_limit),
2141 dbus_interface='org.freedesktop.Tracker1.Resources',
2142 reply_handler=self.prepare_and_handle_search_result,
2143 error_handler=self.handle_search_error
2144
2145=== modified file 'src/plugins/tracker_fts.py'
2146--- src/plugins/tracker_fts.py 2010-08-04 17:04:13 +0000
2147+++ src/plugins/tracker_fts.py 2010-08-12 17:20:35 +0000
2148@@ -10,7 +10,7 @@
2149 help_text = ''
2150 version = '1.371'
2151
2152- plugin_api_version = 1.37
2153+ plugin_api_version = 1.38
2154
2155 search_delay_type = 'local search update delay'
2156
2157@@ -22,7 +22,7 @@
2158 hide_from_sidebar = True
2159
2160
2161- def __init__(self, cardapio_proxy):
2162+ def __init__(self, cardapio_proxy):
2163
2164 self.c = cardapio_proxy
2165
2166@@ -31,13 +31,10 @@
2167
2168 if bus.request_name('org.freedesktop.Tracker1') == dbus.bus.REQUEST_NAME_REPLY_IN_QUEUE:
2169 tracker_object = bus.get_object('org.freedesktop.Tracker1', '/org/freedesktop/Tracker1/Resources')
2170- self.tracker = dbus.Interface(tracker_object, 'org.freedesktop.Tracker1.Resources')
2171+ self.tracker = dbus.Interface(tracker_object, 'org.freedesktop.Tracker1.Resources')
2172 else:
2173 self.loaded = False
2174- return
2175-
2176- self.search_results_limit = self.c.settings['search results limit']
2177- self.search_results_limit_long = self.c.settings['long search results limit']
2178+ return
2179
2180 self.action_command = r'tracker-search-tool %s'
2181 self.action = {
2182@@ -52,20 +49,15 @@
2183 self.loaded = True
2184
2185
2186- def search(self, text, long_search = False):
2187+ def search(self, text, result_limit):
2188
2189 self.current_query = text
2190 text = urllib2.quote(text).lower()
2191
2192- if long_search:
2193- search_results_limit = self.search_results_limit_long
2194- else:
2195- search_results_limit = self.search_results_limit
2196-
2197 self.tracker.SparqlQuery(
2198 """
2199 SELECT ?uri ?mime
2200- WHERE {
2201+ WHERE {
2202 ?item a nie:InformationElement;
2203 fts:match "%s";
2204 nie:url ?uri;
2205@@ -73,8 +65,8 @@
2206 tracker:available true.
2207 }
2208 LIMIT %d
2209- """
2210- % (text, search_results_limit),
2211+ """
2212+ % (text, result_limit),
2213 dbus_interface='org.freedesktop.Tracker1.Resources',
2214 reply_handler=self.prepare_and_handle_search_result,
2215 error_handler=self.handle_search_error
2216@@ -90,10 +82,10 @@
2217
2218 def prepare_and_handle_search_result(self, results):
2219
2220- formatted_results = []
2221+ formatted_results = []
2222
2223 for result in results:
2224-
2225+
2226 dummy, canonical_path = urllib2.splittype(result[0])
2227 parent_name, child_name = os.path.split(canonical_path)
2228 icon_name = result[1]
2229
2230=== modified file 'src/plugins/virtualbox.py'
2231--- src/plugins/virtualbox.py 2010-08-02 07:22:55 +0000
2232+++ src/plugins/virtualbox.py 2010-08-12 17:20:35 +0000
2233@@ -19,7 +19,7 @@
2234 help_text = ''
2235 version = '1.22'
2236
2237- plugin_api_version = 1.37
2238+ plugin_api_version = 1.38
2239
2240 search_delay_type = None
2241
2242@@ -70,8 +70,8 @@
2243
2244 self.loaded = True
2245
2246-
2247- def search(self, text, long_search = False):
2248+ # TODO: DOES NOT RESPECT result_limit's AUTHORITA'!
2249+ def search(self, text, result_limit):
2250
2251 self.current_query = text
2252
2253
2254=== modified file 'src/plugins/wikipedia.py'
2255--- src/plugins/wikipedia.py 2010-08-02 07:22:55 +0000
2256+++ src/plugins/wikipedia.py 2010-08-12 17:20:35 +0000
2257@@ -29,7 +29,7 @@
2258 url = ''
2259 help_text = ''
2260
2261- plugin_api_version = 1.37
2262+ plugin_api_version = 1.38
2263
2264 search_delay_type = 'remote search update delay'
2265
2266@@ -54,13 +54,7 @@
2267 # maximum four results, formatted as json)
2268 self.api_base_args = {
2269 'action': 'opensearch',
2270- 'format': 'json',
2271- 'limit' : str(self.cardapio.settings['search results limit']),
2272- }
2273- self.api_base_args_long = {
2274- 'action': 'opensearch',
2275- 'format': 'json',
2276- 'limit' : str(self.cardapio.settings['long search results limit']),
2277+ 'format': 'json'
2278 }
2279
2280 # Wikipedia's base URLs (search and show details variations)
2281@@ -69,23 +63,19 @@
2282
2283 self.loaded = True
2284
2285- def search(self, text, long_search = False):
2286+ def search(self, text, result_limit):
2287 if len(text) == 0:
2288 return
2289
2290- self.current_query = text
2291-
2292 self.cardapio.write_to_log(self, 'searching for {0} in Wikipedia'.format(text), is_debug = True)
2293
2294 self.cancellable.reset()
2295
2296 # prepare final API URL
2297- if long_search:
2298- current_args = self.api_base_args_long.copy()
2299- else:
2300- current_args = self.api_base_args.copy()
2301-
2302+ current_args = self.api_base_args.copy()
2303+ current_args['limit'] = result_limit
2304 current_args['search'] = text
2305+
2306 final_url = self.api_base_url.format(urllib.urlencode(current_args))
2307
2308 self.cardapio.write_to_log(self, 'final API URL: {0}'.format(final_url), is_debug = True)
2309@@ -93,9 +83,10 @@
2310 # asynchronous and cancellable IO call
2311 self.current_stream = gio.File(final_url)
2312 self.current_stream.load_contents_async(self.show_search_results,
2313- cancellable = self.cancellable)
2314+ cancellable = self.cancellable,
2315+ user_data = text)
2316
2317- def show_search_results(self, gdaemonfile, result):
2318+ def show_search_results(self, gdaemonfile, result, text):
2319 """
2320 Callback to asynchronous IO (Wikipedia's API call).
2321 """
2322@@ -120,6 +111,11 @@
2323 # response[1] because the response looks like: [text, [result_list]]
2324 # append results (if any)
2325 for item in response[1]:
2326+ # TODO: wikipedia sometimes returns item names encoded in unicode (try
2327+ # searching for 'aaaaaaaaaaaaa' for example); we use those names as part
2328+ # of a URL so we need to encode the special characters; unfortunately,
2329+ # Python's 2.* urllib.quote throws an exception when it's given unicode
2330+ # argument - what now?
2331 item_url = self.web_base_url.format(urllib.quote(item))
2332 items.append({
2333 'name' : item,
2334@@ -131,7 +127,7 @@
2335 })
2336
2337 # pass the results to Cardapio
2338- self.cardapio.handle_search_result(self, items, self.current_query)
2339+ self.cardapio.handle_search_result(self, items, text)
2340
2341 except KeyError:
2342 self.cardapio.handle_search_error(self, "Incorrect Wikipedia's JSON structure")
2343
2344=== modified file 'src/plugins/yahoo.py'
2345--- src/plugins/yahoo.py 2010-08-02 07:22:55 +0000
2346+++ src/plugins/yahoo.py 2010-08-12 17:20:35 +0000
2347@@ -31,7 +31,7 @@
2348 url = ''
2349 help_text = ''
2350
2351- plugin_api_version = 1.37
2352+ plugin_api_version = 1.38
2353
2354 search_delay_type = 'remote search update delay'
2355
2356@@ -66,44 +66,30 @@
2357 'appid' : 'TuNKmOzV34GRC9mrBNZMgr.vY1xPMLMH9U3PsOYkg8WvYnFawnB5gKd4GsrUbqluzg--',
2358 'format' : 'json',
2359 'style' : 'raw',
2360- 'count' : self.cardapio.settings['search results limit'],
2361- 'lang' : language,
2362- 'region' : region
2363- }
2364-
2365- self.api_base_args_long = {
2366- 'appid' : 'TuNKmOzV34GRC9mrBNZMgr.vY1xPMLMH9U3PsOYkg8WvYnFawnB5gKd4GsrUbqluzg--',
2367- 'format' : 'json',
2368- 'style' : 'raw',
2369- 'count' : self.cardapio.settings['long search results limit'],
2370 'lang' : language,
2371 'region' : region
2372 }
2373
2374 # Yahoo's base URLs (search and search more variations)
2375- self.api_base_url = 'http://boss.yahooapis.com/ysearch/web/v1/{0}?' + urllib.urlencode(self.api_base_args)
2376- self.api_base_url_long = 'http://boss.yahooapis.com/ysearch/web/v1/{0}?' + urllib.urlencode(self.api_base_args_long)
2377+ self.api_base_url = 'http://boss.yahooapis.com/ysearch/web/v1/{0}?{1}'
2378 self.web_base_url = 'http://search.yahoo.com/search?{0}'
2379
2380 self.loaded = True
2381
2382- def search(self, text, long_search = False):
2383+ def search(self, text, result_limit):
2384 if len(text) == 0:
2385 return
2386
2387- self.current_query = text
2388-
2389 self.cardapio.write_to_log(self, 'searching for {0} using Yahoo'.format(text), is_debug = True)
2390
2391 self.cancellable.reset()
2392
2393 # prepare final API URL
2394- if long_search:
2395- api_base_url = self.api_base_url_long
2396- else:
2397- api_base_url = self.api_base_url
2398-
2399- final_url = api_base_url.format(urllib.quote(text, ''))
2400+ current_args = self.api_base_args.copy()
2401+ current_args['count'] = result_limit
2402+
2403+ final_url = self.api_base_url.format(urllib.quote(text, ''), urllib.urlencode(current_args))
2404+
2405 self.cardapio.write_to_log(self, 'final API URL: {0}'.format(final_url), is_debug = True)
2406
2407 # asynchronous and cancellable IO call
2408@@ -164,7 +150,7 @@
2409 })
2410
2411 # pass the results to Cardapio
2412- self.cardapio.handle_search_result(self, items, self.current_query)
2413+ self.cardapio.handle_search_result(self, items, text)
2414
2415 except KeyError:
2416 self.cardapio.handle_search_error(self, "Incorrect Yahoo's JSON structure")
2417
2418=== modified file 'src/plugins/you_tube.py'
2419--- src/plugins/you_tube.py 2010-08-02 07:22:55 +0000
2420+++ src/plugins/you_tube.py 2010-08-12 17:20:35 +0000
2421@@ -22,7 +22,7 @@
2422 url = ''
2423 help_text = ''
2424
2425- plugin_api_version = 1.37
2426+ plugin_api_version = 1.38
2427
2428 search_delay_type = 'remote search update delay'
2429
2430@@ -45,12 +45,7 @@
2431
2432 # YouTube's API arguments (format and maximum result count)
2433 self.api_base_args = {
2434- 'alt' : 'json',
2435- 'max-results': self.cardapio.settings['search results limit'],
2436- }
2437- self.api_base_args_long = {
2438- 'alt' : 'json',
2439- 'max-results': self.cardapio.settings['long search results limit'],
2440+ 'alt' : 'json'
2441 }
2442
2443 # YouTube's base URLs (search and search more variations)
2444@@ -59,25 +54,20 @@
2445
2446 self.loaded = True
2447
2448- def search(self, text, long_search = False):
2449+ def search(self, text, result_limit):
2450
2451 if len(text) == 0:
2452 return
2453
2454- self.current_query = text
2455-
2456 self.cardapio.write_to_log(self, 'searching for {0} using YouTube'.format(text), is_debug = True)
2457
2458 self.cancellable.reset()
2459
2460 # prepare final API URL
2461- if long_search:
2462- api_base_args = self.api_base_args_long
2463- else:
2464- api_base_args = self.api_base_args
2465-
2466- current_args = api_base_args.copy()
2467+ current_args = self.api_base_args.copy()
2468+ current_args['max-results'] = result_limit
2469 current_args['q'] = text
2470+
2471 final_url = self.api_base_url.format(urllib.urlencode(current_args))
2472
2473 self.cardapio.write_to_log(self, 'final API URL: {0}'.format(final_url), is_debug = True)
2474@@ -150,7 +140,7 @@
2475 })
2476
2477 # pass the results to Cardapio
2478- self.cardapio.handle_search_result(self, items, self.current_query)
2479+ self.cardapio.handle_search_result(self, items, text)
2480
2481 except KeyError:
2482 self.cardapio.handle_search_error(self, "Incorrect YouTube's JSON structure")
2483
2484=== modified file 'src/plugins/zg_recent_documents.py'
2485--- src/plugins/zg_recent_documents.py 2010-08-02 07:22:55 +0000
2486+++ src/plugins/zg_recent_documents.py 2010-08-12 17:20:35 +0000
2487@@ -21,7 +21,7 @@
2488 help_text = ''
2489 version = '0.97b'
2490
2491- plugin_api_version = 1.37
2492+ plugin_api_version = 1.38
2493
2494 search_delay_type = 'local search update delay'
2495
2496@@ -40,8 +40,6 @@
2497 self.c = cardapio_proxy
2498
2499 self.loaded = False
2500- self.num_search_results = self.c.settings['search results limit']
2501- self.num_search_results_long = self.c.settings['long search results limit']
2502
2503 if 'ZeitgeistClient' not in globals():
2504 self.c.write_to_log(self, 'Could not import Zeitgeist', is_error = True)
2505@@ -89,33 +87,26 @@
2506 self.loaded = True
2507
2508
2509- def __del__(self):
2510-
2511- pass
2512-
2513-
2514- def search(self, text, long_search = False):
2515+ def search(self, text, result_limit):
2516
2517 self.current_query = text
2518
2519 text = text.lower()
2520 self.search_query = text
2521+ # TODO: this is thread unsafe. correct this using a wrapper like
2522+ # for example Tomboy's plugin does
2523+ self.result_limit = result_limit
2524
2525 if text:
2526 self.event_template.actor = 'application://' + text + '*'
2527 else:
2528 self.event_template.actor = ''
2529
2530- if long_search:
2531- num_search_results = self.num_search_results_long
2532- else:
2533- num_search_results = self.num_search_results
2534-
2535 self.zg.find_events_for_templates(
2536 [self.event_template],
2537 self.handle_search_result,
2538 timerange = self.time_range,
2539- num_events = num_search_results,
2540+ num_events = result_limit,
2541 result_type = datamodel.ResultType.MostRecentSubjects
2542 )
2543
2544@@ -133,7 +124,7 @@
2545 fts_results, count = self.fts.Search(
2546 self.search_query + '*',
2547 self.time_range,
2548- [], 0, self.num_search_results, 2)
2549+ [], 0, self.result_limit, 2)
2550
2551 except Exception, exception:
2552 print exception
2553@@ -148,14 +139,14 @@
2554
2555 for event in all_events:
2556
2557- if len(urls_seen) >= self.num_search_results: break
2558+ if len(urls_seen) >= self.result_limit: break
2559
2560 for subject in event.get_subjects():
2561
2562 dummy, canonical_path = urllib2.splittype(subject.uri)
2563 parent_name, child_name = os.path.split(canonical_path)
2564
2565- if len(urls_seen) >= self.num_search_results: break
2566+ if len(urls_seen) >= self.result_limit: break
2567 if canonical_path in urls_seen: continue
2568 urls_seen.add(canonical_path)
2569

Subscribers

People subscribed via source and target branches