From 8679b5e1510e859b696e6400731b0ba957dbdf4c Mon Sep 17 00:00:00 2001 From: epage Date: Wed, 28 Oct 2009 01:06:27 +0000 Subject: [PATCH] Updating from Dialcentral/ejpi git-svn-id: file:///svnroot/quicknote/trunk@59 bb7704e3-badb-4cfa-9ab3-9374dc87eaa2 --- Makefile | 38 +- data/quicknote.desktop | 2 +- src/constants.py | 4 + src/gtk_toolbox.py | 361 ++++++++++++++++-- src/hildonize.py | 731 +++++++++++++++++++++++++++++++++++ src/libhistory.py | 5 + src/libkopfzeile.py | 9 +- src/libnotizen.py | 13 +- src/libquicknote.py | 25 +- src/libspeichern.py | 13 +- src/libsqldialog.py | 5 +- src/libsync.py | 47 +-- src/quicknote.py | 21 +- support/builddeb.py | 64 +++- support/py2deb.py | 989 ++++++++++++++++++++++++++++++++++++++++++++++++ support/todo.py | 104 +++++ 16 files changed, 2323 insertions(+), 108 deletions(-) create mode 100755 src/hildonize.py create mode 100644 support/py2deb.py create mode 100755 support/todo.py diff --git a/Makefile b/Makefile index c83ef96..9ede64b 100644 --- a/Makefile +++ b/Makefile @@ -13,23 +13,28 @@ POTFILES=$(wildcard src/quicknoteclasses/*.py) UNIT_TEST=nosetests --with-doctest -w . SYNTAX_TEST=support/test_syntax.py +STYLE_TEST=../../Python/tools/pep8.py --ignore=W191,E501 LINT_RC=./support/pylint.rc LINT=pylint --rcfile=$(LINT_RC) PROFILE_GEN=python -m cProfile -o .profile PROFILE_VIEW=python -m pstats .profile +TODO_FINDER=support/todo.py +CTAGS=ctags-exuberant -.PHONY: all run profile test lint clean distclean install update_po build_mo +.PHONY: all run profile debug test lint tags todo clean distclean install update_po build_mo -all: build_mo - python2.5 setup.py build +all: test -run: +run: $(OBJ) $(PROGRAM) -profile: +profile: $(OBJ) $(PROFILE_GEN) $(PROGRAM) $(PROFILE_VIEW) +debug: $(OBJ) + $(DEBUGGER) $(PROGRAM) + test: $(OBJ) $(UNIT_TEST) @@ -72,20 +77,31 @@ build: $(OBJ) build_mo lint: $(OBJ) $(foreach file, $(SOURCE), $(LINT) $(file) ; ) -clean: +tags: $(TAG_FILE) + +todo: $(TODO_FILE) + +clean: rm -rf ./locale - rm -rf $(OBJ) + rm -Rf $(OBJ) rm -Rf $(BUILD_PATH) - python2.5 setup.py clean --all + rm -Rf $(TODO_FILE) -distclean: clean +distclean: + rm -Rf $(OBJ) + rm -Rf $(BUILD_PATH) + rm -Rf $(TAG_FILE) find $(SOURCE_PATH) -name "*.*~" | xargs rm -f find $(SOURCE_PATH) -name "*.swp" | xargs rm -f find $(SOURCE_PATH) -name "*.bak" | xargs rm -f find $(SOURCE_PATH) -name ".*.swp" | xargs rm -f -install: build_mo - python2.5 setup.py install --root $(DESTDIR) +$(TAG_FILE): $(OBJ) + mkdir -p $(dir $(TAG_FILE)) + $(CTAGS) -o $(TAG_FILE) $(SOURCE) + +$(TODO_FILE): $(SOURCE) + @- $(TODO_FINDER) $(SOURCE) > $(TODO_FILE) %.pyc: %.py $(SYNTAX_TEST) $< diff --git a/data/quicknote.desktop b/data/quicknote.desktop index 84ed6e2..6cc9b00 100644 --- a/data/quicknote.desktop +++ b/data/quicknote.desktop @@ -3,7 +3,7 @@ Encoding=UTF-8 Name=Quicknote Comment=Quicknote Type=Application -Exec=/usr/bin/quicknote.py +Exec=/usr/bin/run-standalone.sh /usr/bin/quicknote.py Icon=quicknote X-Window-Icon=quicknote X-Window-Icon-Dimmed=quicknote diff --git a/src/constants.py b/src/constants.py index 38bfd6f..a5f94e3 100644 --- a/src/constants.py +++ b/src/constants.py @@ -1,4 +1,8 @@ +import os + __pretty_app_name__ = "Quicknote" __app_name__ = "quicknote" __version__ = "0.7.8" +__build__ = 1 +_data_path_ = os.path.join(os.path.expanduser("~"), ".quicknote") __app_magic__ = 0xdeadbeef diff --git a/src/gtk_toolbox.py b/src/gtk_toolbox.py index 1490c3d..a2a2e2a 100644 --- a/src/gtk_toolbox.py +++ b/src/gtk_toolbox.py @@ -2,16 +2,76 @@ from __future__ import with_statement +import os +import errno import sys -import traceback +import time +import itertools import functools import contextlib -import warnings +import logging +import threading +import Queue import gobject import gtk +_moduleLogger = logging.getLogger("gtk_toolbox") + + +def get_screen_orientation(): + width, height = gtk.gdk.get_default_root_window().get_size() + if width < height: + return gtk.ORIENTATION_VERTICAL + else: + return gtk.ORIENTATION_HORIZONTAL + + +def orientation_change_connect(handler, *args): + """ + @param handler(orientation, *args) -> None(?) + """ + initialScreenOrientation = get_screen_orientation() + orientationAndArgs = list(itertools.chain((initialScreenOrientation, ), args)) + + def _on_screen_size_changed(screen): + newScreenOrientation = get_screen_orientation() + if newScreenOrientation != orientationAndArgs[0]: + orientationAndArgs[0] = newScreenOrientation + handler(*orientationAndArgs) + + rootScreen = gtk.gdk.get_default_root_window() + return gtk.connect(rootScreen, "size-changed", _on_screen_size_changed) + + +@contextlib.contextmanager +def flock(path, timeout=-1): + WAIT_FOREVER = -1 + DELAY = 0.1 + timeSpent = 0 + + acquired = False + + while timeSpent <= timeout or timeout == WAIT_FOREVER: + try: + fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR) + acquired = True + break + except OSError, e: + if e.errno != errno.EEXIST: + raise + time.sleep(DELAY) + timeSpent += DELAY + + assert acquired, "Failed to grab file-lock %s within timeout %d" % (path, timeout) + + try: + yield fd + finally: + os.unlink(path) + + @contextlib.contextmanager def gtk_lock(): gtk.gdk.threads_enter() @@ -80,6 +140,262 @@ def synchronous_gtk_message(original_func): return immediate_func +def autostart(func): + """ + >>> @autostart + ... def grep_sink(pattern): + ... print "Looking for %s" % pattern + ... while True: + ... line = yield + ... if pattern in line: + ... print line, + >>> g = grep_sink("python") + Looking for python + >>> g.send("Yeah but no but yeah but no") + >>> g.send("A series of tubes") + >>> g.send("python generators rock!") + python generators rock! + >>> g.close() + """ + + @functools.wraps(func) + def start(*args, **kwargs): + cr = func(*args, **kwargs) + cr.next() + return cr + + return start + + +@autostart +def printer_sink(format = "%s"): + """ + >>> pr = printer_sink("%r") + >>> pr.send("Hello") + 'Hello' + >>> pr.send("5") + '5' + >>> pr.send(5) + 5 + >>> p = printer_sink() + >>> p.send("Hello") + Hello + >>> p.send("World") + World + >>> # p.throw(RuntimeError, "Goodbye") + >>> # p.send("Meh") + >>> # p.close() + """ + while True: + item = yield + print format % (item, ) + + +@autostart +def null_sink(): + """ + Good for uses like with cochain to pick up any slack + """ + while True: + item = yield + + +@autostart +def comap(function, target): + """ + >>> p = printer_sink() + >>> cm = comap(lambda x: x+1, p) + >>> cm.send((0, )) + 1 + >>> cm.send((1.0, )) + 2.0 + >>> cm.send((-2, )) + -1 + """ + while True: + try: + item = yield + mappedItem = function(*item) + target.send(mappedItem) + except Exception, e: + _moduleLogger.exception("Forwarding exception!") + target.throw(e.__class__, str(e)) + + +def _flush_queue(queue): + while not queue.empty(): + yield queue.get() + + +@autostart +def queue_sink(queue): + """ + >>> q = Queue.Queue() + >>> qs = queue_sink(q) + >>> qs.send("Hello") + >>> qs.send("World") + >>> qs.throw(RuntimeError, "Goodbye") + >>> qs.send("Meh") + >>> qs.close() + >>> print [i for i in _flush_queue(q)] + [(None, 'Hello'), (None, 'World'), (, 'Goodbye'), (None, 'Meh'), (, None)] + """ + while True: + try: + item = yield + queue.put((None, item)) + except Exception, e: + queue.put((e.__class__, str(e))) + except GeneratorExit: + queue.put((GeneratorExit, None)) + raise + + +def decode_item(item, target): + if item[0] is None: + target.send(item[1]) + return False + elif item[0] is GeneratorExit: + target.close() + return True + else: + target.throw(item[0], item[1]) + return False + + +def nonqueue_source(queue, target): + isDone = False + while not isDone: + item = queue.get() + isDone = decode_item(item, target) + while not queue.empty(): + queue.get_nowait() + + +def threaded_stage(target, thread_factory = threading.Thread): + messages = Queue.Queue() + + run_source = functools.partial(nonqueue_source, messages, target) + thread = thread_factory(target=run_source) + thread.setDaemon(True) + thread.start() + + # Sink running in current thread + return queue_sink(messages) + + +class LoginWindow(object): + + def __init__(self, widgetTree): + """ + @note Thread agnostic + """ + self._dialog = widgetTree.get_widget("loginDialog") + self._parentWindow = widgetTree.get_widget("mainWindow") + self._serviceCombo = widgetTree.get_widget("serviceCombo") + self._usernameEntry = widgetTree.get_widget("usernameentry") + self._passwordEntry = widgetTree.get_widget("passwordentry") + + self._serviceList = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING) + self._serviceCombo.set_model(self._serviceList) + cell = gtk.CellRendererText() + self._serviceCombo.pack_start(cell, True) + self._serviceCombo.add_attribute(cell, 'text', 1) + self._serviceCombo.set_active(0) + + widgetTree.get_widget("loginbutton").connect("clicked", self._on_loginbutton_clicked) + widgetTree.get_widget("logins_close_button").connect("clicked", self._on_loginclose_clicked) + + def request_credentials(self, + parentWindow = None, + defaultCredentials = ("", "") + ): + """ + @note UI Thread + """ + if parentWindow is None: + parentWindow = self._parentWindow + + self._serviceCombo.hide() + self._serviceList.clear() + + self._usernameEntry.set_text(defaultCredentials[0]) + self._passwordEntry.set_text(defaultCredentials[1]) + + try: + self._dialog.set_transient_for(parentWindow) + self._dialog.set_default_response(gtk.RESPONSE_OK) + response = self._dialog.run() + if response != gtk.RESPONSE_OK: + raise RuntimeError("Login Cancelled") + + username = self._usernameEntry.get_text() + password = self._passwordEntry.get_text() + self._passwordEntry.set_text("") + finally: + self._dialog.hide() + + return username, password + + def request_credentials_from(self, + services, + parentWindow = None, + defaultCredentials = ("", "") + ): + """ + @note UI Thread + """ + if parentWindow is None: + parentWindow = self._parentWindow + + self._serviceList.clear() + for serviceIdserviceName in services: + self._serviceList.append(serviceIdserviceName) + self._serviceCombo.set_active(0) + self._serviceCombo.show() + + self._usernameEntry.set_text(defaultCredentials[0]) + self._passwordEntry.set_text(defaultCredentials[1]) + + try: + self._dialog.set_transient_for(parentWindow) + self._dialog.set_default_response(gtk.RESPONSE_OK) + response = self._dialog.run() + if response != gtk.RESPONSE_OK: + raise RuntimeError("Login Cancelled") + + username = self._usernameEntry.get_text() + password = self._passwordEntry.get_text() + finally: + self._dialog.hide() + + itr = self._serviceCombo.get_active_iter() + serviceId = int(self._serviceList.get_value(itr, 0)) + self._serviceList.clear() + return serviceId, username, password + + def _on_loginbutton_clicked(self, *args): + self._dialog.response(gtk.RESPONSE_OK) + + def _on_loginclose_clicked(self, *args): + self._dialog.response(gtk.RESPONSE_CANCEL) + + +def safecall(f, errorDisplay=None, default=None, exception=Exception): + ''' + Returns modified f. When the modified f is called and throws an + exception, the default value is returned + ''' + def _safecall(*args, **argv): + try: + return f(*args,**argv) + except exception, e: + if errorDisplay is not None: + errorDisplay.push_exception(e) + return default + return _safecall + + class ErrorDisplay(object): def __init__(self, widgetTree): @@ -99,31 +415,25 @@ class ErrorDisplay(object): self.push_message(message) def push_message(self, message): - if 0 < len(self.__messages): - self.__messages.append(message) - else: + self.__messages.append(message) + if 1 == len(self.__messages): self.__show_message(message) - def push_exception_with_lock(self, exception = None): + def push_exception_with_lock(self): with gtk_lock(): - self.push_exception(exception) + self.push_exception() - def push_exception(self, exception = None): - if exception is None: - userMessage = str(sys.exc_value) - warningMessage = str(traceback.format_exc()) - else: - userMessage = str(exception) - warningMessage = str(exception) + def push_exception(self): + userMessage = str(sys.exc_info()[1]) self.push_message(userMessage) - warnings.warn(warningMessage, stacklevel=3) + _moduleLogger.exception(userMessage) def pop_message(self): - if 0 < len(self.__messages): - self.__show_message(self.__messages[0]) - del self.__messages[0] - else: + del self.__messages[0] + if 0 == len(self.__messages): self.__hide_message() + else: + self.__errorDescription.set_text(self.__messages[0]) def _on_close(self, *args): self.pop_message() @@ -155,11 +465,8 @@ class DummyErrorDisplay(object): self.__show_message(message) def push_exception(self, exception = None): - if exception is None: - warningMessage = traceback.format_exc() - else: - warningMessage = exception - warnings.warn(warningMessage, stacklevel=3) + userMessage = str(sys.exc_value) + _moduleLogger.exception(userMessage) def pop_message(self): if 0 < len(self.__messages): @@ -167,7 +474,7 @@ class DummyErrorDisplay(object): del self.__messages[0] def __show_message(self, message): - warnings.warn(message, stacklevel=2) + _moduleLogger.debug(message) class MessageBox(gtk.MessageDialog): @@ -240,8 +547,8 @@ class PopupCalendar(object): try: self._calendar.select_month(self._displayDate.month, self._displayDate.year) self._calendar.select_day(self._displayDate.day) - except StandardError, e: - warnings.warn(e.message) + except Exception, e: + _moduleLogger.exception(e) if __name__ == "__main__": diff --git a/src/hildonize.py b/src/hildonize.py new file mode 100755 index 0000000..998a6c5 --- /dev/null +++ b/src/hildonize.py @@ -0,0 +1,731 @@ +#!/usr/bin/env python + +""" +Open Issues + @bug Buttons are too small +""" + + +import gobject +import gtk +import dbus + + +class _NullHildonModule(object): + pass + + +try: + import hildon as _hildon + hildon = _hildon # Dumb but gets around pyflakiness +except (ImportError, OSError): + hildon = _NullHildonModule + + +IS_HILDON_SUPPORTED = hildon is not _NullHildonModule + + +class _NullHildonProgram(object): + + def add_window(self, window): + pass + + +def _hildon_get_app_class(): + return hildon.Program + + +def _null_get_app_class(): + return _NullHildonProgram + + +try: + hildon.Program + get_app_class = _hildon_get_app_class +except AttributeError: + get_app_class = _null_get_app_class + + +def _hildon_set_application_title(window, title): + pass + + +def _null_set_application_title(window, title): + window.set_title(title) + + +if IS_HILDON_SUPPORTED: + set_application_title = _hildon_set_application_title +else: + set_application_title = _null_set_application_title + + +def _fremantle_hildonize_window(app, window): + oldWindow = window + newWindow = hildon.StackableWindow() + oldWindow.get_child().reparent(newWindow) + app.add_window(newWindow) + return newWindow + + +def _hildon_hildonize_window(app, window): + oldWindow = window + newWindow = hildon.Window() + oldWindow.get_child().reparent(newWindow) + app.add_window(newWindow) + return newWindow + + +def _null_hildonize_window(app, window): + return window + + +try: + hildon.StackableWindow + hildonize_window = _fremantle_hildonize_window +except AttributeError: + try: + hildon.Window + hildonize_window = _hildon_hildonize_window + except AttributeError: + hildonize_window = _null_hildonize_window + + +def _fremantle_hildonize_menu(window, gtkMenu, buttons): + appMenu = hildon.AppMenu() + for button in buttons: + appMenu.append(button) + window.set_app_menu(appMenu) + gtkMenu.get_parent().remove(gtkMenu) + return appMenu + + +def _hildon_hildonize_menu(window, gtkMenu, ignoredButtons): + hildonMenu = gtk.Menu() + for child in gtkMenu.get_children(): + child.reparent(hildonMenu) + window.set_menu(hildonMenu) + gtkMenu.destroy() + return hildonMenu + + +def _null_hildonize_menu(window, gtkMenu, ignoredButtons): + return gtkMenu + + +try: + hildon.AppMenu + GTK_MENU_USED = False + IS_FREMANTLE_SUPPORTED = True + hildonize_menu = _fremantle_hildonize_menu +except AttributeError: + GTK_MENU_USED = True + IS_FREMANTLE_SUPPORTED = False + if IS_HILDON_SUPPORTED: + hildonize_menu = _hildon_hildonize_menu + else: + hildonize_menu = _null_hildonize_menu + + +def _hildon_set_button_auto_selectable(button): + button.set_theme_size(hildon.HILDON_SIZE_AUTO_HEIGHT) + + +def _null_set_button_auto_selectable(button): + pass + + +try: + hildon.HILDON_SIZE_AUTO_HEIGHT + gtk.Button.set_theme_size + set_button_auto_selectable = _hildon_set_button_auto_selectable +except AttributeError: + set_button_auto_selectable = _null_set_button_auto_selectable + + +def _hildon_set_button_finger_selectable(button): + button.set_theme_size(hildon.HILDON_SIZE_FINGER_HEIGHT) + + +def _null_set_button_finger_selectable(button): + pass + + +try: + hildon.HILDON_SIZE_FINGER_HEIGHT + gtk.Button.set_theme_size + set_button_finger_selectable = _hildon_set_button_finger_selectable +except AttributeError: + set_button_finger_selectable = _null_set_button_finger_selectable + + +def _hildon_set_button_thumb_selectable(button): + button.set_theme_size(hildon.HILDON_SIZE_THUMB_HEIGHT) + + +def _null_set_button_thumb_selectable(button): + pass + + +try: + hildon.HILDON_SIZE_THUMB_HEIGHT + gtk.Button.set_theme_size + set_button_thumb_selectable = _hildon_set_button_thumb_selectable +except AttributeError: + set_button_thumb_selectable = _null_set_button_thumb_selectable + + +def _hildon_set_cell_thumb_selectable(renderer): + renderer.set_property("scale", 1.5) + + +def _null_set_cell_thumb_selectable(renderer): + pass + + +if IS_HILDON_SUPPORTED: + set_cell_thumb_selectable = _hildon_set_cell_thumb_selectable +else: + set_cell_thumb_selectable = _null_set_cell_thumb_selectable + + +def _fremantle_show_information_banner(parent, message): + hildon.hildon_banner_show_information(parent, "", message) + + +def _hildon_show_information_banner(parent, message): + hildon.hildon_banner_show_information(parent, None, message) + + +def _null_show_information_banner(parent, message): + pass + + +if IS_FREMANTLE_SUPPORTED: + show_information_banner = _fremantle_show_information_banner +else: + try: + hildon.hildon_banner_show_information + show_information_banner = _hildon_show_information_banner + except AttributeError: + show_information_banner = _null_show_information_banner + + +def _fremantle_show_busy_banner_start(parent, message): + hildon.hildon_gtk_window_set_progress_indicator(parent, True) + return parent + + +def _fremantle_show_busy_banner_end(banner): + hildon.hildon_gtk_window_set_progress_indicator(banner, False) + + +def _hildon_show_busy_banner_start(parent, message): + return hildon.hildon_banner_show_animation(parent, None, message) + + +def _hildon_show_busy_banner_end(banner): + banner.destroy() + + +def _null_show_busy_banner_start(parent, message): + return None + + +def _null_show_busy_banner_end(banner): + assert banner is None + + +try: + hildon.hildon_gtk_window_set_progress_indicator + show_busy_banner_start = _fremantle_show_busy_banner_start + show_busy_banner_end = _fremantle_show_busy_banner_end +except AttributeError: + try: + hildon.hildon_banner_show_animation + show_busy_banner_start = _hildon_show_busy_banner_start + show_busy_banner_end = _hildon_show_busy_banner_end + except AttributeError: + show_busy_banner_start = _null_show_busy_banner_start + show_busy_banner_end = _null_show_busy_banner_end + + +def _hildon_hildonize_text_entry(textEntry): + textEntry.set_property('hildon-input-mode', 7) + + +def _null_hildonize_text_entry(textEntry): + pass + + +if IS_HILDON_SUPPORTED: + hildonize_text_entry = _hildon_hildonize_text_entry +else: + hildonize_text_entry = _null_hildonize_text_entry + + +def _hildon_mark_window_rotatable(window): + # gtk documentation is unclear whether this does a "=" or a "|=" + window.set_flags(hildon.HILDON_PORTRAIT_MODE_SUPPORT) + + +def _null_mark_window_rotatable(window): + pass + + +try: + hildon.HILDON_PORTRAIT_MODE_SUPPORT + mark_window_rotatable = _hildon_mark_window_rotatable +except AttributeError: + mark_window_rotatable = _null_mark_window_rotatable + + +def _hildon_window_to_portrait(window): + # gtk documentation is unclear whether this does a "=" or a "|=" + window.set_flags(hildon.HILDON_PORTRAIT_MODE_SUPPORT) + + +def _hildon_window_to_landscape(window): + # gtk documentation is unclear whether this does a "=" or a "&= ~" + window.unset_flags(hildon.HILDON_PORTRAIT_MODE_REQUEST) + + +def _null_window_to_portrait(window): + pass + + +def _null_window_to_landscape(window): + pass + + +try: + hildon.HILDON_PORTRAIT_MODE_SUPPORT + hildon.HILDON_PORTRAIT_MODE_REQUEST + + window_to_portrait = _hildon_window_to_portrait + window_to_landscape = _hildon_window_to_landscape +except AttributeError: + window_to_portrait = _null_window_to_portrait + window_to_landscape = _null_window_to_landscape + + +def get_device_orientation(): + bus = dbus.SystemBus() + try: + rawMceRequest = bus.get_object("com.nokia.mce", "/com/nokia/mce/request") + mceRequest = dbus.Interface(rawMceRequest, dbus_interface="com.nokia.mce.request") + orientation, standState, faceState, xAxis, yAxis, zAxis = mceRequest.get_device_orientation() + except dbus.exception.DBusException: + # catching for documentation purposes that when a system doesn't + # support this, this is what to expect + raise + + if orientation == "": + return gtk.ORIENTATION_HORIZONTAL + elif orientation == "": + return gtk.ORIENTATION_VERTICAL + else: + raise RuntimeError("Unknown orientation: %s" % orientation) + + +def _hildon_hildonize_password_entry(textEntry): + textEntry.set_property('hildon-input-mode', 7 | (1 << 29)) + + +def _null_hildonize_password_entry(textEntry): + pass + + +if IS_HILDON_SUPPORTED: + hildonize_password_entry = _hildon_hildonize_password_entry +else: + hildonize_password_entry = _null_hildonize_password_entry + + +def _hildon_hildonize_combo_entry(comboEntry): + comboEntry.set_property('hildon-input-mode', 1 << 4) + + +def _null_hildonize_combo_entry(textEntry): + pass + + +if IS_HILDON_SUPPORTED: + hildonize_combo_entry = _hildon_hildonize_combo_entry +else: + hildonize_combo_entry = _null_hildonize_combo_entry + + +def _fremantle_hildonize_scrollwindow(scrolledWindow): + pannableWindow = hildon.PannableArea() + + child = scrolledWindow.get_child() + scrolledWindow.remove(child) + pannableWindow.add(child) + + parent = scrolledWindow.get_parent() + parent.remove(scrolledWindow) + parent.add(pannableWindow) + + return pannableWindow + + +def _hildon_hildonize_scrollwindow(scrolledWindow): + hildon.hildon_helper_set_thumb_scrollbar(scrolledWindow, True) + return scrolledWindow + + +def _null_hildonize_scrollwindow(scrolledWindow): + return scrolledWindow + + +try: + hildon.PannableArea + hildonize_scrollwindow = _fremantle_hildonize_scrollwindow + hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow +except AttributeError: + try: + hildon.hildon_helper_set_thumb_scrollbar + hildonize_scrollwindow = _hildon_hildonize_scrollwindow + hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow + except AttributeError: + hildonize_scrollwindow = _null_hildonize_scrollwindow + hildonize_scrollwindow_with_viewport = _null_hildonize_scrollwindow + + +def _hildon_request_number(parent, title, range, default): + spinner = hildon.NumberEditor(*range) + spinner.set_value(default) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(spinner) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + return spinner.get_value() + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +def _null_request_number(parent, title, range, default): + adjustment = gtk.Adjustment(default, range[0], range[1], 1, 5, 0) + spinner = gtk.SpinButton(adjustment, 0, 0) + spinner.set_wrap(False) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(spinner) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + return spinner.get_value_as_int() + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +try: + hildon.NumberEditor # TODO deprecated in fremantle + request_number = _hildon_request_number +except AttributeError: + request_number = _null_request_number + + +def _hildon_touch_selector(parent, title, items, defaultIndex): + model = gtk.ListStore(gobject.TYPE_STRING) + for item in items: + model.append((item, )) + + selector = hildon.TouchSelector() + selector.append_text_column(model, True) + selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE) + selector.set_active(0, defaultIndex) + + dialog = hildon.PickerDialog(parent) + dialog.set_selector(selector) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + return selector.get_active(0) + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +def _on_null_touch_selector_activated(treeView, path, column, dialog, pathData): + dialog.response(gtk.RESPONSE_OK) + pathData[0] = path + + +def _null_touch_selector(parent, title, items, defaultIndex = -1): + parentSize = parent.get_size() + + model = gtk.ListStore(gobject.TYPE_STRING) + for item in items: + model.append((item, )) + + cell = gtk.CellRendererText() + set_cell_thumb_selectable(cell) + column = gtk.TreeViewColumn(title) + column.pack_start(cell, expand=True) + column.add_attribute(cell, "text", 0) + + treeView = gtk.TreeView() + treeView.set_model(model) + treeView.append_column(column) + selection = treeView.get_selection() + selection.set_mode(gtk.SELECTION_SINGLE) + if 0 < defaultIndex: + selection.select_path((defaultIndex, )) + + scrolledWin = gtk.ScrolledWindow() + scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolledWin.add(treeView) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(scrolledWin) + dialog.resize(parentSize[0], max(parentSize[1]-100, 100)) + + scrolledWin = hildonize_scrollwindow(scrolledWin) + pathData = [None] + treeView.connect("row-activated", _on_null_touch_selector_activated, dialog, pathData) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + if pathData[0] is None: + raise RuntimeError("No selection made") + return pathData[0][0] + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +try: + hildon.PickerDialog + hildon.TouchSelector + touch_selector = _hildon_touch_selector +except AttributeError: + touch_selector = _null_touch_selector + + +def _hildon_touch_selector_entry(parent, title, items, defaultItem): + # Got a segfault when using append_text_column with TouchSelectorEntry, so using this way + try: + selector = hildon.TouchSelectorEntry(text=True) + except TypeError: + selector = hildon.hildon_touch_selector_entry_new_text() + defaultIndex = -1 + for i, item in enumerate(items): + selector.append_text(item) + if item == defaultItem: + defaultIndex = i + + dialog = hildon.PickerDialog(parent) + dialog.set_selector(selector) + + if 0 < defaultIndex: + selector.set_active(0, defaultIndex) + else: + selector.get_entry().set_text(defaultItem) + + try: + dialog.show_all() + response = dialog.run() + finally: + dialog.hide() + + if response == gtk.RESPONSE_OK: + return selector.get_entry().get_text() + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + + +def _on_null_touch_selector_entry_entry_changed(entry, result, selection, defaultIndex): + custom = entry.get_text().strip() + if custom: + result[0] = custom + selection.unselect_all() + else: + result[0] = None + selection.select_path((defaultIndex, )) + + +def _on_null_touch_selector_entry_entry_activated(customEntry, dialog, result): + dialog.response(gtk.RESPONSE_OK) + result[0] = customEntry.get_text() + + +def _on_null_touch_selector_entry_tree_activated(treeView, path, column, dialog, result): + dialog.response(gtk.RESPONSE_OK) + model = treeView.get_model() + itr = model.get_iter(path) + if itr is not None: + result[0] = model.get_value(itr, 0) + + +def _null_touch_selector_entry(parent, title, items, defaultItem): + parentSize = parent.get_size() + + model = gtk.ListStore(gobject.TYPE_STRING) + defaultIndex = -1 + for i, item in enumerate(items): + model.append((item, )) + if item == defaultItem: + defaultIndex = i + + cell = gtk.CellRendererText() + set_cell_thumb_selectable(cell) + column = gtk.TreeViewColumn(title) + column.pack_start(cell, expand=True) + column.add_attribute(cell, "text", 0) + + treeView = gtk.TreeView() + treeView.set_model(model) + treeView.append_column(column) + selection = treeView.get_selection() + selection.set_mode(gtk.SELECTION_SINGLE) + + scrolledWin = gtk.ScrolledWindow() + scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolledWin.add(treeView) + + customEntry = gtk.Entry() + + layout = gtk.VBox() + layout.pack_start(customEntry, expand=False) + layout.pack_start(scrolledWin) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(layout) + dialog.resize(parentSize[0], max(parentSize[1]-100, 100)) + + scrolledWin = hildonize_scrollwindow(scrolledWin) + + result = [None] + if 0 < defaultIndex: + selection.select_path((defaultIndex, )) + result[0] = defaultItem + else: + customEntry.set_text(defaultItem) + + customEntry.connect("activate", _on_null_touch_selector_entry_entry_activated, dialog, result) + customEntry.connect("changed", _on_null_touch_selector_entry_entry_changed, result, selection, defaultIndex) + treeView.connect("row-activated", _on_null_touch_selector_entry_tree_activated, dialog, result) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + _, itr = selection.get_selected() + if itr is not None: + return model.get_value(itr, 0) + else: + enteredText = customEntry.get_text().strip() + if enteredText: + return enteredText + elif result[0] is not None: + return result[0] + else: + raise RuntimeError("No selection made") + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +try: + hildon.PickerDialog + hildon.TouchSelectorEntry + touch_selector_entry = _hildon_touch_selector_entry +except AttributeError: + touch_selector_entry = _null_touch_selector_entry + + +if __name__ == "__main__": + app = get_app_class()() + + label = gtk.Label("Hello World from a Label!") + + win = gtk.Window() + win.add(label) + win = hildonize_window(app, win) + if False: + print touch_selector(win, "Test", ["A", "B", "C", "D"], 2) + if True: + print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "C") + print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "Blah") + if False: + import pprint + name, value = "", "" + goodLocals = [ + (name, value) for (name, value) in locals().iteritems() + if not name.startswith("_") + ] + pprint.pprint(goodLocals) + if False: + import time + show_information_banner(win, "Hello World") + time.sleep(5) + if False: + import time + banner = show_busy_banner_start(win, "Hello World") + time.sleep(5) + show_busy_banner_end(banner) diff --git a/src/libhistory.py b/src/libhistory.py index 2bf0a8b..c897cd9 100644 --- a/src/libhistory.py +++ b/src/libhistory.py @@ -10,6 +10,8 @@ published by the Free Software Foundation. """ +import logging + import gtk @@ -19,6 +21,9 @@ except NameError: _ = lambda x: x +_moduleLogger = logging.getLogger("history") + + class Dialog(gtk.Dialog): def __init__(self, daten = None): diff --git a/src/libkopfzeile.py b/src/libkopfzeile.py index 6b0a7ba..017d22b 100644 --- a/src/libkopfzeile.py +++ b/src/libkopfzeile.py @@ -22,6 +22,9 @@ except NameError: _ = lambda x: x +_moduleLogger = logging.getLogger("kopfzeile") + + class Kopfzeile(gtk.HBox): __gsignals__ = { @@ -33,7 +36,7 @@ class Kopfzeile(gtk.HBox): self._db = db gtk.HBox.__init__(self, homogeneous = False, spacing = 3) - logging.info("libkopfzeile, init") + _moduleLogger.info("libkopfzeile, init") categoryHBox = gtk.HBox() self.pack_start(categoryHBox, expand = False, fill = True, padding = 0) @@ -57,7 +60,7 @@ class Kopfzeile(gtk.HBox): self._searchEntry.connect("changed", self.search_entry_changed, None) def category_combo_changed(self, widget = None, data = None): - logging.debug("comboCategoryChanged") + _moduleLogger.debug("comboCategoryChanged") if self._lastCategory != self.categoryCombo.get_active(): sql = "UPDATE categories SET liste = ? WHERE id = 1" self._db.speichereSQL(sql, (self.categoryCombo.get_active(), )) @@ -65,7 +68,7 @@ class Kopfzeile(gtk.HBox): self.emit("reload_notes") def search_entry_changed(self, widget = None, data = None): - logging.debug("search_entry_changed") + _moduleLogger.debug("search_entry_changed") self.emit("reload_notes") def get_category(self): diff --git a/src/libnotizen.py b/src/libnotizen.py index 9f720e8..b18e599 100644 --- a/src/libnotizen.py +++ b/src/libnotizen.py @@ -29,6 +29,9 @@ except NameError: _ = lambda x: x +_moduleLogger = logging.getLogger("notizen") + + class Notizen(gtk.HBox): def __init__(self, db, topBox): @@ -40,7 +43,7 @@ class Notizen(gtk.HBox): self._categoryName = "" gtk.HBox.__init__(self, homogeneous = False, spacing = 0) - logging.info("libnotizen, init") + _moduleLogger.info("libnotizen, init") self._noteslist = simple_list.SimpleList() self._noteslist.set_eventfunction_cursor_changed(self._update_noteslist) @@ -119,7 +122,7 @@ class Notizen(gtk.HBox): self._historyBox.hide() def load_notes(self, data = None): - logging.info("load_notes params: pos:"+str(self._pos)+" noteid:"+str(self.noteId)) + _moduleLogger.info("load_notes params: pos:"+str(self._pos)+" noteid:"+str(self.noteId)) self._noteslist.clear_items() self._noteslist.append_item(_("New Note..."), "new") @@ -138,7 +141,7 @@ class Notizen(gtk.HBox): self._noteBodyView.get_buffer().set_text("") def save_note(self, widget = None, data = None, data2 = None): - logging.info("save_note params: pos:"+str(self._pos)+" noteid:"+str(self.noteId)) + _moduleLogger.info("save_note params: pos:"+str(self._pos)+" noteid:"+str(self.noteId)) #print "params:", data, data2 buf = self._noteBodyView.get_buffer().get_text(self._noteBodyView.get_buffer().get_start_iter(), self._noteBodyView.get_buffer().get_end_iter()) if buf is None or len(buf) == 0: @@ -147,7 +150,7 @@ class Notizen(gtk.HBox): if buf == self._noteBody: return - logging.info("Saving note: "+buf) + _moduleLogger.info("Saving note: "+buf) if self._pos == -1 or self.noteId == -1: self._pos = -1 self.noteId = str(uuid.uuid4()) @@ -261,7 +264,7 @@ class Notizen(gtk.HBox): data = dialog.get_selected_row() if data is not None: self._db.speichereSQL(data[2], data[3].split(" <> "), rowid = self.noteId) - logging.info("loading History") + _moduleLogger.info("loading History") self._update_noteslist() dialog.destroy() diff --git a/src/libquicknote.py b/src/libquicknote.py index bf2870f..d44925f 100644 --- a/src/libquicknote.py +++ b/src/libquicknote.py @@ -1,4 +1,4 @@ -#/usr/bin/env python2.5 +#!/usr/bin/env python # -*- coding: utf-8 -*- """ @@ -49,6 +49,9 @@ except NameError: _ = lambda x: x +_moduleLogger = logging.getLogger("quick") + + class QuicknoteProgram(hildon.Program): _user_data = os.path.join(os.path.expanduser("~"), ".%s" % constants.__app_name__) @@ -61,17 +64,7 @@ class QuicknoteProgram(hildon.Program): dblog = os.path.join(self._user_data, "quicknote.log") - # define a Handler which writes INFO messages or higher to the sys.stderr - console = logging.StreamHandler() - console.setLevel(logging.DEBUG) - # set a format which is simpler for console use - formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s') - # tell the handler to use this format - console.setFormatter(formatter) - # add the handler to the root logger - logging.getLogger('').addHandler(console) - - logging.info('Starting quicknote') + _moduleLogger.info('Starting quicknote') if osso is not None: self._osso_c = osso.Context(constants.__app_name__, constants.__version__, False) @@ -310,7 +303,7 @@ class QuicknoteProgram(hildon.Program): res = sqldiag.run() sqldiag.hide() if res == sqldiag.EXPORT_RESPONSE: - logging.info("exporting sql") + _moduleLogger.info("exporting sql") dlg = hildon.FileChooserDialog(parent=self._window, action=gtk.FILE_CHOOSER_ACTION_SAVE) @@ -419,3 +412,9 @@ class QuicknoteProgram(hildon.Program): dialog.set_authors(["Christoph Wurstle ", "Ed Page (Blame him for the most recent bugs)"]) dialog.run() dialog.destroy() + + +if __name__ == "__main__": + logging.basicConfig(level=logging.DEBUG) + app = QuicknoteProgram() + app.main() diff --git a/src/libspeichern.py b/src/libspeichern.py index 6e0d9e4..44d1525 100644 --- a/src/libspeichern.py +++ b/src/libspeichern.py @@ -24,6 +24,9 @@ except NameError: _ = lambda x: x +_moduleLogger = logging.getLogger("speichern") + + class Speichern(): def __init__(self): @@ -34,7 +37,7 @@ class Speichern(): def speichereDirekt(self, schluessel, daten): self.d[schluessel] = daten - logging.info("speichereDirekt "+str(schluessel)+" "+str(daten)+" lesen: "+str(self.d[schluessel])) + _moduleLogger.info("speichereDirekt "+str(schluessel)+" "+str(daten)+" lesen: "+str(self.d[schluessel])) def ladeDirekt(self, schluessel, default = ""): if (self.d.has_key(schluessel) == True): @@ -69,9 +72,9 @@ class Speichern(): s = str(sys.exc_info()) if s.find(" already exists") == -1: if (programSQLError == True): - logging.error("speichereSQL-Exception "+str(sys.exc_info())+" "+str(sql)+" "+str(tupel)) + _moduleLogger.error("speichereSQL-Exception "+str(sys.exc_info())+" "+str(sql)+" "+str(tupel)) else: - logging.error("speichereSQL-Exception in Logging!!!! :"+str(sys.exc_info())+" "+str(sql)+" "+str(tupel)) + _moduleLogger.error("speichereSQL-Exception in Logging!!!! :"+str(sys.exc_info())+" "+str(sql)+" "+str(tupel)) return False def commitSQL(self): @@ -86,7 +89,7 @@ class Speichern(): self.cur.execute(sql, tupel) return self.cur.fetchall() except StandardError: - logging.error("ladeSQL-Exception "+str(sys.exc_info())+" "+str(sql)+" "+str(tupel)) + _moduleLogger.error("ladeSQL-Exception "+str(sys.exc_info())+" "+str(sql)+" "+str(tupel)) return () def ladeHistory(self, sql_condition, param_condition): @@ -199,7 +202,7 @@ class Speichern(): self.conn.close() except StandardError: pass - logging.info("Alle Data saved") + _moduleLogger.info("Alle Data saved") def __del__(self): self.close() diff --git a/src/libsqldialog.py b/src/libsqldialog.py index 682ca6f..a6cc2d6 100755 --- a/src/libsqldialog.py +++ b/src/libsqldialog.py @@ -22,6 +22,9 @@ except NameError: _ = lambda x: x +_moduleLogger = logging.getLogger("sqldialog") + + class SqlDialog(gtk.Dialog): EXPORT_RESPONSE = 444 @@ -29,7 +32,7 @@ class SqlDialog(gtk.Dialog): def __init__(self, db): self.db = db - logging.info("sqldialog, init") + _moduleLogger.info("sqldialog, init") gtk.Dialog.__init__(self, _("SQL History (the past two days):"), None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT) diff --git a/src/libsync.py b/src/libsync.py index 48bcc86..f8acdac 100755 --- a/src/libsync.py +++ b/src/libsync.py @@ -38,12 +38,15 @@ except NameError: _ = lambda x: x +_moduleLogger = logging.getLogger("sync") + + class ProgressDialog(gtk.Dialog): def __init__(self, title = _("Sync process"), parent = None): gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, ()) - logging.info("ProgressDialog, init") + _moduleLogger.info("ProgressDialog, init") label = gtk.Label(_("Sync process running...please wait")) self.vbox.pack_start(label, True, True, 0) @@ -67,7 +70,7 @@ class Sync(gtk.VBox): def __init__(self, db, parentwindow, port): gtk.VBox.__init__(self, homogeneous = False, spacing = 0) - logging.info("Sync, init") + _moduleLogger.info("Sync, init") self.db = db self.progress = None self.server = None @@ -168,13 +171,13 @@ class Sync(gtk.VBox): syncpartner, pcdatum = rows[0] else: pcdatum = -1 - logging.info("LastSyncDatum: "+str(pcdatum)+" Jetzt "+str(int(time.time()))) + _moduleLogger.info("LastSyncDatum: "+str(pcdatum)+" Jetzt "+str(int(time.time()))) return pcdatum def check4commit(self, newSQL, lastdate): - logging.info("check4commit 1") + _moduleLogger.info("check4commit 1") if self.concernedRows is None: - logging.info("check4commit Updatung concernedRows") + _moduleLogger.info("check4commit Updatung concernedRows") sql = "SELECT pcdatum, rowid FROM logtable WHERE pcdatum>? ORDER BY pcdatum DESC" self.concernedRows = self.db.ladeSQL(sql, (lastdate, )) @@ -185,7 +188,7 @@ class Sync(gtk.VBox): for x in self.concernedRows: if x[1] == rowid: if pcdatum < x[0]: - logging.info("newer sync entry, ignoring old one") + _moduleLogger.info("newer sync entry, ignoring old one") return False else: return True @@ -198,7 +201,7 @@ class Sync(gtk.VBox): self.concernedRows = None pausenzaehler = 0 - logging.info("writeSQLTupel got "+str(len(newSQLs))+" sql tupels") + _moduleLogger.info("writeSQLTupel got "+str(len(newSQLs))+" sql tupels") for newSQL in newSQLs: if newSQL[3] != "": param = newSQL[3].split(" <> ") @@ -214,7 +217,7 @@ class Sync(gtk.VBox): if (commitSQL == True): self.db.speichereSQL(newSQL[2], param, commit = False, pcdatum = newSQL[1], rowid = newSQL[5]) else: - logging.error("writeSQLTupel: Error") + _moduleLogger.error("writeSQLTupel: Error") pausenzaehler += 1 if (pausenzaehler % 10) == 0: @@ -222,9 +225,9 @@ class Sync(gtk.VBox): while gtk.events_pending(): gtk.main_iteration() - logging.info("Alle SQLs an sqlite geschickt, commiting now") + _moduleLogger.info("Alle SQLs an sqlite geschickt, commiting now") self.db.commitSQL() - logging.info("Alle SQLs commited") + _moduleLogger.info("Alle SQLs commited") def doSync(self, sync_uuid, pcdatum, newSQLs, pcdatumjetzt): self.changeSyncStatus(True, "sync process running") @@ -236,13 +239,13 @@ class Sync(gtk.VBox): if 30 < diff: return -1 - logging.info("doSync read sqls") + _moduleLogger.info("doSync read sqls") sql = "SELECT * FROM logtable WHERE pcdatum>?" rows = self.db.ladeSQL(sql, (pcdatum, )) - logging.info("doSync read sqls") + _moduleLogger.info("doSync read sqls") self.writeSQLTupel(newSQLs, pcdatum) - logging.info("doSync wrote "+str(len(newSQLs))+" sqls") - logging.info("doSync sending "+str(len(rows))+" sqls") + _moduleLogger.info("doSync wrote "+str(len(newSQLs))+" sqls") + _moduleLogger.info("doSync sending "+str(len(rows))+" sqls") return rows def getRemoteSyncUUID(self): @@ -253,7 +256,7 @@ class Sync(gtk.VBox): self.db.speichereDirekt("syncServerIP", self.comboIP.get_child().get_text()) if widget.get_active(): - logging.info("Starting Server") + _moduleLogger.info("Starting Server") try: ip = self.comboIP.get_child().get_text() @@ -274,7 +277,7 @@ class Sync(gtk.VBox): except StandardError: s = str(sys.exc_info()) - logging.error("libsync: could not start server. Error: "+s) + _moduleLogger.error("libsync: could not start server. Error: "+s) mbox = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Could not start SyncServer. Check IP, port settings.")) #gtk.DIALOG_MODAL mbox.set_modal(False) response = mbox.run() @@ -282,7 +285,7 @@ class Sync(gtk.VBox): mbox.destroy() widget.set_active(False) else: - logging.info("Stopping Server") + _moduleLogger.info("Stopping Server") try: del self.rpcserver except StandardError: @@ -309,7 +312,7 @@ class Sync(gtk.VBox): return (self.sync_uuid, pcdatum) def syncButton(self, widget, data = None): - logging.info("Syncing") + _moduleLogger.info("Syncing") self.changeSyncStatus(True, _("sync process running")) while (gtk.events_pending()): @@ -324,11 +327,11 @@ class Sync(gtk.VBox): sql = "SELECT * FROM logtable WHERE pcdatum>?" rows = self.db.ladeSQL(sql, (lastDate, )) - logging.info("loaded concerned rows") + _moduleLogger.info("loaded concerned rows") newSQLs = self.server.doSync(self.sync_uuid, lastDate, rows, time.time()) - logging.info("did do sync, processing sqls now") + _moduleLogger.info("did do sync, processing sqls now") if newSQLs != -1: self.writeSQLTupel(newSQLs, lastDate) @@ -342,14 +345,14 @@ class Sync(gtk.VBox): mbox.hide() mbox.destroy() else: - logging.warning("Zeitdiff zu groß/oder anderer db-Fehler") + _moduleLogger.warning("Zeitdiff zu groß/oder anderer db-Fehler") self.changeSyncStatus(False, _("no sync process (at the moment)")) mbox = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("The clocks are not synchronized between stations")) response = mbox.run() mbox.hide() mbox.destroy() except StandardError: - logging.warning("Sync connect failed") + _moduleLogger.warning("Sync connect failed") self.changeSyncStatus(False, _("no sync process (at the moment)")) mbox = gtk.MessageDialog( None, diff --git a/src/quicknote.py b/src/quicknote.py index 0848b53..9410944 100755 --- a/src/quicknote.py +++ b/src/quicknote.py @@ -8,17 +8,30 @@ it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. """ - +import os import sys -sys.path.append('/usr/lib/quicknote') - -import locale +import logging import gettext + +_moduleLogger = logging.getLogger("quicknote") gettext.install('quicknote', unicode = 1) +sys.path.append('/usr/lib/quicknote') + +import constants import libquicknote if __name__ == "__main__": + try: + os.makedirs(constants._data_path_) + except OSError, e: + if e.errno != 17: + raise + + userLogPath = "%s/quicknote.log" % constants._data_path_ + logging.basicConfig(level=logging.DEBUG, filename=userLogPath) + _moduleLogger.info("quicknote %s-%s" % (constants.__version__, constants.__build__)) + app = libquicknote.QuicknoteProgram() app.main() diff --git a/support/builddeb.py b/support/builddeb.py index 3a2f793..6234f03 100755 --- a/support/builddeb.py +++ b/support/builddeb.py @@ -12,12 +12,15 @@ import constants __appname__ = constants.__app_name__ -__description__ = "Simple note taking application in a similar vein as PalmOS Memos" +__description__ = """Simple note taking application in a similar vein as PalmOS Memos +. +Homepage: http://quicknote.garage.maemo.org/ +""" __author__ = "Christoph Wurstle" __email__ = "n800@axique.net" __version__ = constants.__version__ -__build__ = 0 -__changelog__ = ''' +__build__ = constants.__build__ +__changelog__ = """ 0.7.8 * Spell checking * Fixing the application title @@ -48,14 +51,14 @@ __changelog__ = ''' 0.7.0 * Initial Release. -''' +""" -__postinstall__ = '''#!/bin/sh -e +__postinstall__ = """#!/bin/sh -e gtk-update-icon-cache -f /usr/share/icons/hicolor -exit 0 -''' +rm -f ~/.quicknote/quicknote.log +""" def find_files(path, root): @@ -86,23 +89,48 @@ def build_package(distribution): except: pass + py2deb.Py2deb.SECTIONS = py2deb.SECTIONS_BY_POLICY[distribution] p = py2deb.Py2deb(__appname__) + p.prettyName = constants.__pretty_app_name__ p.description = __description__ + p.bugTracker = "https://bugs.maemo.org/enter_bug.cgi?product=ejpi" + p.upgradeDescription = __changelog__.split("\n\n", 1)[0] p.author = __author__ p.mail = __email__ p.license = "lgpl" - p.depends = { - "diablo": "python2.5, python2.5-gtk2", - "mer": "python2.6, python-gtk2", + p.depends = ", ".join([ + "python2.6 | python2.5", + "python-gtk2 | python2.5-gtk2", + "python-xml | python2.5-xml", + ]) + maemoSpecificDepends = ", python-osso | python2.5-osso, python-hildon | python2.5-hildon" + p.depends += { + "debian": ", python-glade2", + "chinook": maemoSpecificDepends, + "diablo": maemoSpecificDepends, + "fremantle": maemoSpecificDepends + ", python-glade2", + "mer": maemoSpecificDepends + ", python-glade2", + }[distribution] + p.section = { + "debian": "accessories", + "chinook": "accessories", + "diablo": "user/office", + "fremantle": "user/office", + "mer": "user/office", }[distribution] - p.section = "user/other" p.arch = "all" p.urgency = "low" - p.distribution = "chinook diablo fremantle mer" + p.distribution = "chinook diablo fremantle mer debian" p.repository = "extras" p.changelog = __changelog__ p.postinstall = __postinstall__ - p.icon = "26x26-quicknote.png" + p.icon = { + "debian": "26x26-quicknote.png", + "chinook": "26x26-quicknote.png", + "diablo": "26x26-quicknote.png", + "fremantle": "64x64-quicknote.png", # Fremantle natively uses 48x48 + "mer": "64x64-quicknote.png", + }[distribution] p["/usr/bin"] = [ "quicknote.py" ] for relPath, files in unflatten_files(find_files(".", "locale")).iteritems(): fullPath = "/usr/share/locale" @@ -121,7 +149,7 @@ def build_package(distribution): for (oldName, newName) in files ) p["/usr/share/applications/hildon"] = ["quicknote.desktop"] - p["/usr/share/dbus-1/services"] = ["quicknote.service"] + #p["/usr/share/dbus-1/services"] = ["quicknote.service"] p["/usr/share/icons/hicolor/26x26/hildon"] = ["26x26-quicknote.png|quicknote.png"] p["/usr/share/icons/hicolor/40x40/hildon"] = ["40x40-quicknote.png|quicknote.png"] p["/usr/share/icons/hicolor/48x48/hildon"] = ["48x48-quicknote.png|quicknote.png"] @@ -129,8 +157,12 @@ def build_package(distribution): print p print p.generate( - __version__, __build__, changelog=__changelog__, - tar=True, dsc=True, changes=True, build=False, src=True + version="%s-%s" % (__version__, __build__), + changelog=__changelog__, + build=False, + tar=True, + changes=True, + dsc=True, ) print "Building for %s finished" % distribution diff --git a/support/py2deb.py b/support/py2deb.py new file mode 100644 index 0000000..6018f91 --- /dev/null +++ b/support/py2deb.py @@ -0,0 +1,989 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +## +## Copyright (C) 2009 manatlan manatlan[at]gmail(dot)com +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +""" +Known limitations : +- don't sign package (-us -uc) +- no distinctions between author and maintainer(packager) + +depends on : +- dpkg-dev (dpkg-buildpackage) +- alien +- python +- fakeroot + +changelog + - ??? ?/??/20?? (By epage) + - PEP8 + - added recommends + - fixed bug where it couldn't handle the contents of the pre/post scripts being specified + - Added customization based on the targeted policy for sections (Maemo support) + - Added maemo specific tarball, dsc, changes file generation support (including icon support) + - Added armel architecture + - Reduced the size of params being passed around by reducing the calls to locals() + - Added respository, distribution, priority + - Made setting control file a bit more flexible + - 0.5 05/09/2009 + - pre/post install/remove scripts enabled + - deb package install py2deb in dist-packages for py2.6 + - 0.4 14/10/2008 + - use os.environ USERNAME or USER (debian way) + - install on py 2.(4,5,6) (*FIX* do better here) + +""" + +import os +import hashlib +import sys +import shutil +import time +import string +import StringIO +import stat +import commands +import base64 +import tarfile +from glob import glob +from datetime import datetime +import socket # gethostname() +from subprocess import Popen, PIPE + +#~ __version__ = "0.4" +__version__ = "0.5" +__author__ = "manatlan" +__mail__ = "manatlan@gmail.com" + + +PERMS_URW_GRW_OR = stat.S_IRUSR | stat.S_IWUSR | \ + stat.S_IRGRP | stat.S_IWGRP | \ + stat.S_IROTH + +UID_ROOT = 0 +GID_ROOT = 0 + + +def run(cmds): + p = Popen(cmds, shell=False, stdout=PIPE, stderr=PIPE) + time.sleep(0.01) # to avoid "IOError: [Errno 4] Interrupted system call" + out = string.join(p.stdout.readlines()).strip() + outerr = string.join(p.stderr.readlines()).strip() + return out + + +def deb2rpm(file): + txt=run(['alien', '-r', file]) + return txt.split(" generated")[0] + + +def py2src(TEMP, name): + l=glob("%(TEMP)s/%(name)s*.tar.gz" % locals()) + if len(l) != 1: + raise Py2debException("don't find source package tar.gz") + + tar = os.path.basename(l[0]) + shutil.move(l[0], tar) + + return tar + + +def md5sum(filename): + f = open(filename, "r") + try: + return hashlib.md5(f.read()).hexdigest() + finally: + f.close() + + +class Py2changes(object): + + def __init__(self, ChangedBy, description, changes, files, category, repository, **kwargs): + self.options = kwargs # TODO: Is order important? + self.description = description + self.changes=changes + self.files=files + self.category=category + self.repository=repository + self.ChangedBy=ChangedBy + + def getContent(self): + content = ["%s: %s" % (k, v) + for k,v in self.options.iteritems()] + + if self.description: + description=self.description.replace("\n","\n ") + content.append('Description: ') + content.append(' %s' % description) + if self.changes: + changes=self.changes.replace("\n","\n ") + content.append('Changes: ') + content.append(' %s' % changes) + if self.ChangedBy: + content.append("Changed-By: %s" % self.ChangedBy) + + content.append('Files:') + + for onefile in self.files: + md5 = md5sum(onefile) + size = os.stat(onefile).st_size.__str__() + content.append(' ' + md5 + ' ' + size + ' ' + self.category +' '+self.repository+' '+os.path.basename(onefile)) + + return "\n".join(content) + "\n\n" + + +def py2changes(params): + changescontent = Py2changes( + "%(author)s <%(mail)s>" % params, + "%(description)s" % params, + "%(changelog)s" % params, + ( + "%(TEMP)s/%(name)s_%(version)s.tar.gz" % params, + "%(TEMP)s/%(name)s_%(version)s.dsc" % params, + ), + "%(section)s" % params, + "%(repository)s" % params, + Format='1.7', + Date=time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()), + Source="%(name)s" % params, + Architecture="%(arch)s" % params, + Version="%(version)s" % params, + Distribution="%(distribution)s" % params, + Urgency="%(urgency)s" % params, + Maintainer="%(author)s <%(mail)s>" % params + ) + f = open("%(TEMP)s/%(name)s_%(version)s.changes" % params,"wb") + f.write(changescontent.getContent()) + f.close() + + fileHandle = open('/tmp/py2deb2.tmp', 'w') + fileHandle.write('#!/bin/sh\n') + fileHandle.write("cd " +os.getcwd()+ "\n") + fileHandle.write("gpg --local-user %(mail)s --clearsign %(TEMP)s/%(name)s_%(version)s.changes\n" % params) + fileHandle.write("mv %(TEMP)s/%(name)s_%(version)s.changes.asc %(TEMP)s/%(name)s_%(version)s.changes\n" % params) + fileHandle.write('\nexit') + fileHandle.close() + commands.getoutput("chmod 777 /tmp/py2deb2.tmp") + commands.getoutput("/tmp/py2deb2.tmp") + + ret = [] + + l=glob("%(TEMP)s/%(name)s*.tar.gz" % params) + if len(l)!=1: + raise Py2debException("don't find source package tar.gz") + tar = os.path.basename(l[0]) + shutil.move(l[0],tar) + ret.append(tar) + + l=glob("%(TEMP)s/%(name)s*.dsc" % params) + if len(l)!=1: + raise Py2debException("don't find source package dsc") + tar = os.path.basename(l[0]) + shutil.move(l[0],tar) + ret.append(tar) + + l=glob("%(TEMP)s/%(name)s*.changes" % params) + if len(l)!=1: + raise Py2debException("don't find source package changes") + tar = os.path.basename(l[0]) + shutil.move(l[0],tar) + ret.append(tar) + + return ret + + +class Py2dsc(object): + + def __init__(self, StandardsVersion, BuildDepends, files, **kwargs): + self.options = kwargs # TODO: Is order important? + self.StandardsVersion = StandardsVersion + self.BuildDepends=BuildDepends + self.files=files + + @property + def content(self): + content = ["%s: %s" % (k, v) + for k,v in self.options.iteritems()] + + if self.BuildDepends: + content.append("Build-Depends: %s" % self.BuildDepends) + if self.StandardsVersion: + content.append("Standards-Version: %s" % self.StandardsVersion) + + content.append('Files:') + + for onefile in self.files: + print onefile + md5 = md5sum(onefile) + size = os.stat(onefile).st_size.__str__() + content.append(' '+md5 + ' ' + size +' '+os.path.basename(onefile)) + + return "\n".join(content)+"\n\n" + + +def py2dsc(TEMP, name, version, depends, author, mail, arch): + dsccontent = Py2dsc( + "%(version)s" % locals(), + "%(depends)s" % locals(), + ("%(TEMP)s/%(name)s_%(version)s.tar.gz" % locals(),), + Format='1.0', + Source="%(name)s" % locals(), + Version="%(version)s" % locals(), + Maintainer="%(author)s <%(mail)s>" % locals(), + Architecture="%(arch)s" % locals(), + ) + + filename = "%(TEMP)s/%(name)s_%(version)s.dsc" % locals() + + f = open(filename, "wb") + try: + f.write(dsccontent.content) + finally: + f.close() + + fileHandle = open('/tmp/py2deb.tmp', 'w') + try: + fileHandle.write('#!/bin/sh\n') + fileHandle.write("cd " + os.getcwd() + "\n") + fileHandle.write("gpg --local-user %(mail)s --clearsign %(TEMP)s/%(name)s_%(version)s.dsc\n" % locals()) + fileHandle.write("mv %(TEMP)s/%(name)s_%(version)s.dsc.asc %(filename)s\n" % locals()) + fileHandle.write('\nexit') + fileHandle.close() + finally: + f.close() + + commands.getoutput("chmod 777 /tmp/py2deb.tmp") + commands.getoutput("/tmp/py2deb.tmp") + + return filename + + +class Py2tar(object): + + def __init__(self, dataDirectoryPath): + self._dataDirectoryPath = dataDirectoryPath + + def packed(self): + return self._getSourcesFiles() + + def _getSourcesFiles(self): + directoryPath = self._dataDirectoryPath + + outputFileObj = StringIO.StringIO() # TODO: Do more transparently? + + tarOutput = tarfile.TarFile.open('sources', + mode = "w:gz", + fileobj = outputFileObj) + + # Note: We can't use this because we need to fiddle permissions: + # tarOutput.add(directoryPath, arcname = "") + + for root, dirs, files in os.walk(directoryPath): + archiveRoot = root[len(directoryPath):] + + tarinfo = tarOutput.gettarinfo(root, archiveRoot) + # TODO: Make configurable? + tarinfo.uid = UID_ROOT + tarinfo.gid = GID_ROOT + tarinfo.uname = "" + tarinfo.gname = "" + tarOutput.addfile(tarinfo) + + for f in files: + tarinfo = tarOutput.gettarinfo(os.path.join(root, f), + os.path.join(archiveRoot, f)) + tarinfo.uid = UID_ROOT + tarinfo.gid = GID_ROOT + tarinfo.uname = "" + tarinfo.gname = "" + tarOutput.addfile(tarinfo, file(os.path.join(root, f))) + + tarOutput.close() + + data_tar_gz = outputFileObj.getvalue() + + return data_tar_gz + + +def py2tar(DEST, TEMP, name, version): + tarcontent = Py2tar("%(DEST)s" % locals()) + filename = "%(TEMP)s/%(name)s_%(version)s.tar.gz" % locals() + f = open(filename, "wb") + try: + f.write(tarcontent.packed()) + finally: + f.close() + return filename + + +class Py2debException(Exception): + pass + + +SECTIONS_BY_POLICY = { + # http://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections + "debian": "admin, base, comm, contrib, devel, doc, editors, electronics, embedded, games, gnome, graphics, hamradio, interpreters, kde, libs, libdevel, mail, math, misc, net, news, non-free, oldlibs, otherosfs, perl, python, science, shells, sound, tex, text, utils, web, x11", + # http://maemo.org/forrest-images/pdf/maemo-policy.pdf + "chinook": "accessories, communication, games, multimedia, office, other, programming, support, themes, tools", + # http://wiki.maemo.org/Task:Package_categories + "diablo": "user/desktop, user/development, user/education, user/games, user/graphics, user/multimedia, user/navigation, user/network, user/office, user/science, user/system, user/utilities", + # http://wiki.maemo.org/Task:Fremantle_application_categories + "mer": "user/desktop, user/development, user/education, user/games, user/graphics, user/multimedia, user/navigation, user/network, user/office, user/science, user/system, user/utilities", + # http://wiki.maemo.org/Task:Fremantle_application_categories + "fremantle": "user/desktop, user/development, user/education, user/games, user/graphics, user/multimedia, user/navigation, user/network, user/office, user/science, user/system, user/utilities", +} + + +LICENSE_AGREEMENT = { + "gpl": """ + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +On Debian systems, the complete text of the GNU General +Public License can be found in `/usr/share/common-licenses/GPL'. +""", + "lgpl":""" + This package is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +On Debian systems, the complete text of the GNU Lesser General +Public License can be found in `/usr/share/common-licenses/LGPL'. +""", + "bsd": """ + Redistribution and use in source and binary forms, with or without + modification, are permitted under the terms of the BSD License. + + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + +On Debian systems, the complete text of the BSD License can be +found in `/usr/share/common-licenses/BSD'. +""", + "artistic": """ + This program is free software; you can redistribute it and/or modify it + under the terms of the "Artistic License" which comes with Debian. + + THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES + OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +On Debian systems, the complete text of the Artistic License +can be found in `/usr/share/common-licenses/Artistic'. +""" +} + + +class Py2deb(object): + """ + heavily based on technic described here : + http://wiki.showmedo.com/index.php?title=LinuxJensMakingDeb + """ + ## STATICS + clear = False # clear build folder after py2debianization + + SECTIONS = SECTIONS_BY_POLICY["debian"] + + #http://www.debian.org/doc/debian-policy/footnotes.html#f69 + ARCHS = "all i386 ia64 alpha amd64 armeb arm hppa m32r m68k mips mipsel powerpc ppc64 s390 s390x sh3 sh3eb sh4 sh4eb sparc darwin-i386 darwin-ia64 darwin-alpha darwin-amd64 darwin-armeb darwin-arm darwin-hppa darwin-m32r darwin-m68k darwin-mips darwin-mipsel darwin-powerpc darwin-ppc64 darwin-s390 darwin-s390x darwin-sh3 darwin-sh3eb darwin-sh4 darwin-sh4eb darwin-sparc freebsd-i386 freebsd-ia64 freebsd-alpha freebsd-amd64 freebsd-armeb freebsd-arm freebsd-hppa freebsd-m32r freebsd-m68k freebsd-mips freebsd-mipsel freebsd-powerpc freebsd-ppc64 freebsd-s390 freebsd-s390x freebsd-sh3 freebsd-sh3eb freebsd-sh4 freebsd-sh4eb freebsd-sparc kfreebsd-i386 kfreebsd-ia64 kfreebsd-alpha kfreebsd-amd64 kfreebsd-armeb kfreebsd-arm kfreebsd-hppa kfreebsd-m32r kfreebsd-m68k kfreebsd-mips kfreebsd-mipsel kfreebsd-powerpc kfreebsd-ppc64 kfreebsd-s390 kfreebsd-s390x kfreebsd-sh3 kfreebsd-sh3eb kfreebsd-sh4 kfreebsd-sh4eb kfreebsd-sparc knetbsd-i386 knetbsd-ia64 knetbsd-alpha knetbsd-amd64 knetbsd-armeb knetbsd-arm knetbsd-hppa knetbsd-m32r knetbsd-m68k knetbsd-mips knetbsd-mipsel knetbsd-powerpc knetbsd-ppc64 knetbsd-s390 knetbsd-s390x knetbsd-sh3 knetbsd-sh3eb knetbsd-sh4 knetbsd-sh4eb knetbsd-sparc netbsd-i386 netbsd-ia64 netbsd-alpha netbsd-amd64 netbsd-armeb netbsd-arm netbsd-hppa netbsd-m32r netbsd-m68k netbsd-mips netbsd-mipsel netbsd-powerpc netbsd-ppc64 netbsd-s390 netbsd-s390x netbsd-sh3 netbsd-sh3eb netbsd-sh4 netbsd-sh4eb netbsd-sparc openbsd-i386 openbsd-ia64 openbsd-alpha openbsd-amd64 openbsd-armeb openbsd-arm openbsd-hppa openbsd-m32r openbsd-m68k openbsd-mips openbsd-mipsel openbsd-powerpc openbsd-ppc64 openbsd-s390 openbsd-s390x openbsd-sh3 openbsd-sh3eb openbsd-sh4 openbsd-sh4eb openbsd-sparc hurd-i386 hurd-ia64 hurd-alpha hurd-amd64 hurd-armeb hurd-arm hurd-hppa hurd-m32r hurd-m68k hurd-mips hurd-mipsel hurd-powerpc hurd-ppc64 hurd-s390 hurd-s390x hurd-sh3 hurd-sh3eb hurd-sh4 hurd-sh4eb hurd-sparc armel".split(" ") + + # license terms taken from dh_make + LICENSES = list(LICENSE_AGREEMENT.iterkeys()) + + def __setitem__(self, path, files): + + if not type(files)==list: + raise Py2debException("value of key path '%s' is not a list"%path) + if not files: + raise Py2debException("value of key path '%s' should'nt be empty"%path) + if not path.startswith("/"): + raise Py2debException("key path '%s' malformed (don't start with '/')"%path) + if path.endswith("/"): + raise Py2debException("key path '%s' malformed (shouldn't ends with '/')"%path) + + nfiles=[] + for file in files: + + if ".." in file: + raise Py2debException("file '%s' contains '..', please avoid that!"%file) + + + if "|" in file: + if file.count("|")!=1: + raise Py2debException("file '%s' is incorrect (more than one pipe)"%file) + + file, nfile = file.split("|") + else: + nfile=file # same localisation + + if os.path.isdir(file): + raise Py2debException("file '%s' is a folder, and py2deb refuse folders !"%file) + + if not os.path.isfile(file): + raise Py2debException("file '%s' doesn't exist"%file) + + if file.startswith("/"): # if an absolute file is defined + if file==nfile: # and not renamed (pipe trick) + nfile=os.path.basename(file) # it's simply copied to 'path' + + nfiles.append((file, nfile)) + + nfiles.sort(lambda a, b: cmp(a[1], b[1])) #sort according new name (nfile) + + self.__files[path]=nfiles + + def __delitem__(self, k): + del self.__files[k] + + def __init__(self, + name, + description="no description", + license="gpl", + depends="", + section="utils", + arch="all", + + url="", + author = None, + mail = None, + + preinstall = None, + postinstall = None, + preremove = None, + postremove = None + ): + + if author is None: + author = ("USERNAME" in os.environ) and os.environ["USERNAME"] or None + if author is None: + author = ("USER" in os.environ) and os.environ["USER"] or "unknown" + + if mail is None: + mail = author+"@"+socket.gethostname() + + self.name = name + self.prettyName = "" + self.description = description + self.upgradeDescription = "" + self.bugTracker = "" + self.license = license + self.depends = depends + self.recommends = "" + self.section = section + self.arch = arch + self.url = url + self.author = author + self.mail = mail + self.icon = "" + self.distribution = "" + self.respository = "" + self.urgency = "low" + + self.preinstall = preinstall + self.postinstall = postinstall + self.preremove = preremove + self.postremove = postremove + + self.__files={} + + def __repr__(self): + name = self.name + license = self.license + description = self.description + depends = self.depends + recommends = self.recommends + section = self.section + arch = self.arch + url = self.url + author = self.author + mail = self.mail + + preinstall = self.preinstall + postinstall = self.postinstall + preremove = self.preremove + postremove = self.postremove + + paths=self.__files.keys() + paths.sort() + files=[] + for path in paths: + for file, nfile in self.__files[path]: + #~ rfile=os.path.normpath(os.path.join(path, nfile)) + rfile=os.path.join(path, nfile) + if nfile==file: + files.append(rfile) + else: + files.append(rfile + " (%s)"%file) + + files.sort() + files = "\n".join(files) + + + lscripts = [ preinstall and "preinst", + postinstall and "postinst", + preremove and "prerm", + postremove and "postrm", + ] + scripts = lscripts and ", ".join([i for i in lscripts if i]) or "None" + return """ +---------------------------------------------------------------------- +NAME : %(name)s +---------------------------------------------------------------------- +LICENSE : %(license)s +URL : %(url)s +AUTHOR : %(author)s +MAIL : %(mail)s +---------------------------------------------------------------------- +DEPENDS : %(depends)s +RECOMMENDS : %(recommends)s +ARCH : %(arch)s +SECTION : %(section)s +---------------------------------------------------------------------- +DESCRIPTION : +%(description)s +---------------------------------------------------------------------- +SCRIPTS : %(scripts)s +---------------------------------------------------------------------- +FILES : +%(files)s +""" % locals() + + def generate(self, version, changelog="", rpm=False, src=False, build=True, tar=False, changes=False, dsc=False): + """ generate a deb of version 'version', with or without 'changelog', with or without a rpm + (in the current folder) + return a list of generated files + """ + if not sum([len(i) for i in self.__files.values()])>0: + raise Py2debException("no files are defined") + + if not changelog: + changelog="* no changelog" + + name = self.name + description = self.description + license = self.license + depends = self.depends + recommends = self.recommends + section = self.section + arch = self.arch + url = self.url + distribution = self.distribution + repository = self.repository + urgency = self.urgency + author = self.author + mail = self.mail + files = self.__files + preinstall = self.preinstall + postinstall = self.postinstall + preremove = self.preremove + postremove = self.postremove + + if section not in Py2deb.SECTIONS: + raise Py2debException("section '%s' is unknown (%s)" % (section, str(Py2deb.SECTIONS))) + + if arch not in Py2deb.ARCHS: + raise Py2debException("arch '%s' is unknown (%s)"% (arch, str(Py2deb.ARCHS))) + + if license not in Py2deb.LICENSES: + raise Py2debException("License '%s' is unknown (%s)" % (license, str(Py2deb.LICENSES))) + + # create dates (buildDate, buildDateYear) + d=datetime.now() + buildDate=d.strftime("%a, %d %b %Y %H:%M:%S +0000") + buildDateYear=str(d.year) + + #clean description (add a space before each next lines) + description=description.replace("\r", "").strip() + description = "\n ".join(description.split("\n")) + + #clean changelog (add 2 spaces before each next lines) + changelog=changelog.replace("\r", "").strip() + changelog = "\n ".join(changelog.split("\n")) + + TEMP = ".py2deb_build_folder" + DEST = os.path.join(TEMP, name) + DEBIAN = os.path.join(DEST, "debian") + + packageContents = locals() + + # let's start the process + try: + shutil.rmtree(TEMP) + except: + pass + + os.makedirs(DEBIAN) + try: + rules=[] + dirs=[] + for path in files: + for ofile, nfile in files[path]: + if os.path.isfile(ofile): + # it's a file + + if ofile.startswith("/"): # if absolute path + # we need to change dest + dest=os.path.join(DEST, nfile) + else: + dest=os.path.join(DEST, ofile) + + # copy file to be packaged + destDir = os.path.dirname(dest) + if not os.path.isdir(destDir): + os.makedirs(destDir) + + shutil.copy2(ofile, dest) + + ndir = os.path.join(path, os.path.dirname(nfile)) + nname = os.path.basename(nfile) + + # make a line RULES to be sure the destination folder is created + # and one for copying the file + fpath = "/".join(["$(CURDIR)", "debian", name+ndir]) + rules.append('mkdir -p "%s"' % fpath) + rules.append('cp -a "%s" "%s"' % (ofile, os.path.join(fpath, nname))) + + # append a dir + dirs.append(ndir) + + else: + raise Py2debException("unknown file '' "%ofile) # shouldn't be raised (because controlled before) + + # make rules right + rules= "\n\t".join(rules) + "\n" + packageContents["rules"] = rules + + # make dirs right + dirs= [i[1:] for i in set(dirs)] + dirs.sort() + + #========================================================================== + # CREATE debian/dirs + #========================================================================== + open(os.path.join(DEBIAN, "dirs"), "w").write("\n".join(dirs)) + + #========================================================================== + # CREATE debian/changelog + #========================================================================== + clog="""%(name)s (%(version)s) stable; urgency=low + + %(changelog)s + + -- %(author)s <%(mail)s> %(buildDate)s +""" % packageContents + + open(os.path.join(DEBIAN, "changelog"), "w").write(clog) + + #========================================================================== + #Create pre/post install/remove + #========================================================================== + def mkscript(name, dest): + if name and name.strip()!="": + if os.path.isfile(name): # it's a file + content = file(name).read() + else: # it's a script + content = name + open(os.path.join(DEBIAN, dest), "w").write(content) + + mkscript(preinstall, "preinst") + mkscript(postinstall, "postinst") + mkscript(preremove, "prerm") + mkscript(postremove, "postrm") + + + #========================================================================== + # CREATE debian/compat + #========================================================================== + open(os.path.join(DEBIAN, "compat"), "w").write("5\n") + + #========================================================================== + # CREATE debian/control + #========================================================================== + generalParagraphFields = [ + "Source: %(name)s", + "Maintainer: %(author)s <%(mail)s>", + "Section: %(section)s", + "Priority: extra", + "Build-Depends: debhelper (>= 5)", + "Standards-Version: 3.7.2", + ] + + specificParagraphFields = [ + "Package: %(name)s", + "Architecture: %(arch)s", + "Depends: %(depends)s", + "Recommends: %(recommends)s", + "Description: %(description)s", + ] + + if self.prettyName: + prettyName = "XSBC-Maemo-Display-Name: %s" % self.prettyName.strip() + specificParagraphFields.append("\n ".join(prettyName.split("\n"))) + + if self.bugTracker: + bugTracker = "XSBC-Bugtracker: %s" % self.bugTracker.strip() + specificParagraphFields.append("\n ".join(bugTracker.split("\n"))) + + if self.upgradeDescription: + upgradeDescription = "XSBC-Maemo-Upgrade-Description: %s" % self.upgradeDescription.strip() + specificParagraphFields.append("\n ".join(upgradeDescription.split("\n"))) + + if self.icon: + f = open(self.icon, "rb") + try: + rawIcon = f.read() + finally: + f.close() + uueIcon = base64.b64encode(rawIcon) + uueIconLines = [] + for i, c in enumerate(uueIcon): + if i % 60 == 0: + uueIconLines.append("") + uueIconLines[-1] += c + uueIconLines[0:0] = ("XSBC-Maemo-Icon-26:", ) + specificParagraphFields.append("\n ".join(uueIconLines)) + + generalParagraph = "\n".join(generalParagraphFields) + specificParagraph = "\n".join(specificParagraphFields) + controlContent = "\n\n".join((generalParagraph, specificParagraph)) % packageContents + open(os.path.join(DEBIAN, "control"), "w").write(controlContent) + + #========================================================================== + # CREATE debian/copyright + #========================================================================== + packageContents["txtLicense"] = LICENSE_AGREEMENT[license] + packageContents["pv"] =__version__ + txt="""This package was py2debianized(%(pv)s) by %(author)s <%(mail)s> on +%(buildDate)s. + +It was downloaded from %(url)s + +Upstream Author: %(author)s <%(mail)s> + +Copyright: %(buildDateYear)s by %(author)s + +License: + +%(txtLicense)s + +The Debian packaging is (C) %(buildDateYear)s, %(author)s <%(mail)s> and +is licensed under the GPL, see above. + + +# Please also look if there are files or directories which have a +# different copyright/license attached and list them here. +""" % packageContents + open(os.path.join(DEBIAN, "copyright"), "w").write(txt) + + #========================================================================== + # CREATE debian/rules + #========================================================================== + txt="""#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + + + + +CFLAGS = -Wall -g + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 +else + CFLAGS += -O2 +endif + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + + touch configure-stamp + + +build: build-stamp + +build-stamp: configure-stamp + dh_testdir + touch build-stamp + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # ====================================================== + #$(MAKE) DESTDIR="$(CURDIR)/debian/%(name)s" install + mkdir -p "$(CURDIR)/debian/%(name)s" + + %(rules)s + # ====================================================== + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs debian/changelog + dh_installdocs + dh_installexamples +# dh_install +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python +# dh_installinit +# dh_installcron +# dh_installinfo + dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl +# dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure +""" % packageContents + open(os.path.join(DEBIAN, "rules"), "w").write(txt) + os.chmod(os.path.join(DEBIAN, "rules"), 0755) + + ########################################################################### + ########################################################################### + ########################################################################### + + generatedFiles = [] + + if build: + #http://www.debian.org/doc/manuals/maint-guide/ch-build.fr.html + ret = os.system('cd "%(DEST)s"; dpkg-buildpackage -tc -rfakeroot -us -uc' % packageContents) + if ret != 0: + raise Py2debException("buildpackage failed (see output)") + + l=glob("%(TEMP)s/%(name)s*.deb" % packageContents) + if len(l) != 1: + raise Py2debException("didn't find builded deb") + + tdeb = l[0] + deb = os.path.basename(tdeb) + shutil.move(tdeb, deb) + + generatedFiles = [deb, ] + + if rpm: + rpmFilename = deb2rpm(deb) + generatedFiles.append(rpmFilename) + + if src: + tarFilename = py2src(TEMP, name) + generatedFiles.append(tarFilename) + + if tar: + tarFilename = py2tar(DEST, TEMP, name, version) + generatedFiles.append(tarFilename) + + if dsc: + dscFilename = py2dsc(TEMP, name, version, depends, author, mail, arch) + generatedFiles.append(dscFilename) + + if changes: + changesFilenames = py2changes(packageContents) + generatedFiles.extend(changesFilenames) + + return generatedFiles + + #~ except Exception,m: + #~ raise Py2debException("build error :"+str(m)) + + finally: + if Py2deb.clear: + shutil.rmtree(TEMP) + + +if __name__ == "__main__": + try: + os.chdir(os.path.dirname(sys.argv[0])) + except: + pass + + p=Py2deb("python-py2deb") + p.description="Generate simple deb(/rpm/tgz) from python (2.4, 2.5 and 2.6)" + p.url = "http://www.manatlan.com/page/py2deb" + p.author=__author__ + p.mail=__mail__ + p.depends = "dpkg-dev, fakeroot, alien, python" + p.section="python" + p["/usr/lib/python2.6/dist-packages"] = ["py2deb.py", ] + p["/usr/lib/python2.5/site-packages"] = ["py2deb.py", ] + p["/usr/lib/python2.4/site-packages"] = ["py2deb.py", ] + #~ p.postinstall = "s.py" + #~ p.preinstall = "s.py" + #~ p.postremove = "s.py" + #~ p.preremove = "s.py" + print p + print p.generate(__version__, changelog = __doc__, src=True) diff --git a/support/todo.py b/support/todo.py new file mode 100755 index 0000000..90cbd04 --- /dev/null +++ b/support/todo.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +from __future__ import with_statement +import itertools + + +verbose = False + + +def tag_parser(file, tag): + """ + >>> nothing = [] + >>> for todo in tag_parser(nothing, "@todo"): + ... print todo + ... + >>> one = ["@todo Help!"] + >>> for todo in tag_parser(one, "@todo"): + ... print todo + ... + 1: @todo Help! + >>> mixed = ["one", "@todo two", "three"] + >>> for todo in tag_parser(mixed, "@todo"): + ... print todo + ... + 2: @todo two + >>> embedded = ["one @todo two", "three"] + >>> for todo in tag_parser(embedded, "@todo"): + ... print todo + ... + 1: @todo two + >>> continuation = ["one", "@todo two", " three"] + >>> for todo in tag_parser(continuation, "@todo"): + ... print todo + ... + 2: @todo two three + >>> series = ["one", "@todo two", "@todo three"] + >>> for todo in tag_parser(series, "@todo"): + ... print todo + ... + 2: @todo two + 3: @todo three + """ + currentTodo = [] + prefix = None + for lineNumber, line in enumerate(file): + column = line.find(tag) + if column != -1: + if currentTodo: + yield "\n".join (currentTodo) + prefix = line[0:column] + currentTodo = ["%d: %s" % (lineNumber+1, line[column:].strip())] + elif prefix is not None and len(prefix)+1 < len(line) and line.startswith(prefix) and line[len(prefix)].isspace(): + currentTodo.append (line[len(prefix):].rstrip()) + elif currentTodo: + yield "\n".join (currentTodo) + currentTodo = [] + prefix = None + if currentTodo: + yield "\n".join (currentTodo) + + +def tag_finder(filename, tag): + todoList = [] + + with open(filename) as file: + body = "\n".join (tag_parser(file, tag)) + passed = not body + if passed: + output = "No %s's for %s" % (tag, filename) if verbose else "" + else: + header = "%s's for %s:\n" % (tag, filename) if verbose else "" + output = header + body + output += "\n" if verbose else "" + + return (passed, output) + + +if __name__ == "__main__": + import sys + import os + import optparse + + opar = optparse.OptionParser() + opar.add_option("-v", "--verbose", dest="verbose", help="Toggle verbosity", action="store_true", default=False) + options, args = opar.parse_args(sys.argv[1:]) + verbose = options.verbose + + bugsAsError = True + todosAsError = False + + completeOutput = [] + allPassed = True + for filename in args: + bugPassed, bugOutput = tag_finder(filename, "@bug") + todoPassed, todoOutput = tag_finder(filename, "@todo") + output = "\n".join ([bugOutput, todoOutput]) + if (not bugPassed and bugsAsError) or (not todoPassed and todosAsError): + allPassed = False + output = output.strip() + if output: + completeOutput.append(filename+":\n"+output+"\n\n") + print "\n".join(completeOutput) + + sys.exit(0 if allPassed else 1); -- 1.7.9.5