[Inteproxy-commits] r65 - in trunk: . inteproxy

scm-commit@wald.intevation.org scm-commit at wald.intevation.org
Thu Apr 26 21:04:26 CEST 2007


Author: bh
Date: 2007-04-26 21:04:26 +0200 (Thu, 26 Apr 2007)
New Revision: 65

Added:
   trunk/inteproxy/InteProxy-icon.png
   trunk/inteproxy/app.py
   trunk/inteproxy/gtkapp.py
Modified:
   trunk/ChangeLog
   trunk/inteproxy/feesdialog.py
   trunk/inteproxy/getpassword.py
   trunk/inteproxy/httpserver.py
   trunk/inteproxy/main.py
   trunk/inteproxy/proxycore.py
Log:
* inteproxy/app.py: New.  Base application class.

* inteproxy/gtkapp.py: New. GTK based application with mainwindow
and status icon

* inteproxy/main.py (the_application): New.  Global variable to
hold the application object
(run_server): Instantiate the gtk InteProxyApplication and run it.

* inteproxy/getpassword.py (get_password_with_cache): Ask for the
password via the application.

* inteproxy/feesdialog.py (handle_fees_and_access_constraints):
Run the dialog via the application

* inteproxy/httpserver.py (ServerThread): New.  Class to run the
proxy server in a separate thread.

* inteproxy/InteProxy-icon.png: New.  Status icon.

* inteproxy/proxycore.py: Tweak import statements because of newly
introduced circular imports.


Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog	2007-04-24 15:33:23 UTC (rev 64)
+++ trunk/ChangeLog	2007-04-26 19:04:26 UTC (rev 65)
@@ -1,3 +1,28 @@
+2007-04-26  Bernhard Herzog  <bh at intevation.de>
+
+	* inteproxy/app.py: New.  Base application class.
+
+	* inteproxy/gtkapp.py: New. GTK based application with mainwindow
+	and status icon
+
+	* inteproxy/main.py (the_application): New.  Global variable to
+	hold the application object
+	(run_server): Instantiate the gtk InteProxyApplication and run it.
+
+	* inteproxy/getpassword.py (get_password_with_cache): Ask for the
+	password via the application.
+
+	* inteproxy/feesdialog.py (handle_fees_and_access_constraints):
+	Run the dialog via the application
+
+	* inteproxy/httpserver.py (ServerThread): New.  Class to run the
+	proxy server in a separate thread.
+
+	* inteproxy/InteProxy-icon.png: New.  Status icon.
+
+	* inteproxy/proxycore.py: Tweak import statements because of newly
+	introduced circular imports.
+
 2007-04-24  Bernhard Herzog  <bh at intevation.de>
 
 	* InteProxy.py, inteproxy/main.py: Move practically everything

Added: trunk/inteproxy/InteProxy-icon.png
===================================================================
(Binary files differ)


Property changes on: trunk/inteproxy/InteProxy-icon.png
___________________________________________________________________
Name: svn:mime-type
   + image/png

Added: trunk/inteproxy/app.py
===================================================================
--- trunk/inteproxy/app.py	2007-04-24 15:33:23 UTC (rev 64)
+++ trunk/inteproxy/app.py	2007-04-26 19:04:26 UTC (rev 65)
@@ -0,0 +1,87 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Base application class for the InteProxy."""
+
+from inteproxy.proxycore import MasterWorkerServer, InteProxyHTTPRequestHandler
+from inteproxy.httpserver import ServerThread
+
+import inteproxy.getpassword
+from inteproxy.feesdialog import run_fees_dialog
+
+
+class InteProxyApplication(object):
+
+    """Base Application object
+
+    An InteProxyApplication instance servers as the basis of the user
+    interface of InteProxy.  This base class implements some basic
+    functionality needed by all InteProxy application objects.  Derived
+    classes provide a concrete user interface with e.g. a real GUI or a
+    mock interface for tests.
+
+    Each InteProxyApplication has a server object that implements the
+    actual proxy.  It can be any server object compatible with
+    inteproxy.httpserver.ServerThread, but it will usually be an
+    inteproxy.proxycore.MasterWorkerServer instance.
+    InteProxyApplication can can run this server in a separate thread.
+    """
+
+    def __init__(self, server):
+        """Inititialize the InteProxyApplication with the given server object"""
+        self.server = server
+        self.thread = None
+
+    def start_inte_proxy_thread(self, daemon):
+        """Start a new thread running the proxy
+
+        If the parameter daemon is true, the new thread will be daemon
+        thread (see Python's threading module for details on deamon
+        threads).
+        """
+        self.thread = ServerThread(self.server)
+        self.thread.start(daemon=daemon)
+
+    def stop_inte_proxy_thread(self):
+        """Stops a server thread previously started with start_inte_proxy_thread
+        """
+        self.thread.stop()
+
+    def run(self):
+        """Runs the InteProxy application.
+
+        The default implementation simply calls the server's
+        serve_forever method.  Derived classes may do something more
+        sophisticated such as running the server in a separate thread
+        and a GUI in the main thread.
+        """
+        self.server.serve_forever()
+
+    def get_password(self, title):
+        """Callback to ask for a username and password.
+
+        The parameter title should be a string with a title for the
+        password dialog.  The title should contain all information the
+        user may need to determine which username and password they have
+        to enter.  This method returns the entered username/password
+        pair as a tuple.
+
+        This method should be called by request handlers of the server
+        when they need to query the user for usernames and passwords.
+        The default implementation simply passes all parameters through
+        to inteproxy.getpassword.getpassword.
+        """
+        return inteproxy.getpassword.getpassword(title)
+
+    def run_fees_dialog(self, title, fees, access_constraints):
+        """Callback to run the fees dialog.
+
+        The parameters have the same meaning as in the function
+        inteproxy.feesdialog.run_fees_dialog.  The default
+        implementation simply calls that function.
+        """
+        run_fees_dialog(title, fees, access_constraints)


Property changes on: trunk/inteproxy/app.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Modified: trunk/inteproxy/feesdialog.py
===================================================================
--- trunk/inteproxy/feesdialog.py	2007-04-24 15:33:23 UTC (rev 64)
+++ trunk/inteproxy/feesdialog.py	2007-04-26 19:04:26 UTC (rev 65)
@@ -18,6 +18,9 @@
     gtk = None
 
 
+import inteproxy.main
+
+
 def run_fees_dialog(title, fees, access_constraints):
     """Shows fees and access_constraints information in a dialog
 
@@ -130,7 +133,8 @@
 
     if is_get_capabilities_url(remote_url):
         title, fees, access_constraints = parse_capabilities(response_read())
-        run_fees_dialog(title, fees, access_constraints)
+        inteproxy.main.the_application.run_fees_dialog(title, fees,
+                                                       access_constraints)
 
         dialog_shown_for[remote_url] = True
 

Modified: trunk/inteproxy/getpassword.py
===================================================================
--- trunk/inteproxy/getpassword.py	2007-04-24 15:33:23 UTC (rev 64)
+++ trunk/inteproxy/getpassword.py	2007-04-26 19:04:26 UTC (rev 65)
@@ -10,6 +10,8 @@
 import threading
 import traceback
 
+import inteproxy.main
+
 # Determine the window system we're using.  This determines how the user
 # is asked for the username/password.
 try:
@@ -107,7 +109,7 @@
             user, password = pw_cache[path]
         else:
             title = "Username for %s: " % path
-            user, password = getpassword(title)
+            user, password = inteproxy.main.the_application.get_password(title)
             pw_cache[path] = user, password
 
         return user, password

Added: trunk/inteproxy/gtkapp.py
===================================================================
--- trunk/inteproxy/gtkapp.py	2007-04-24 15:33:23 UTC (rev 64)
+++ trunk/inteproxy/gtkapp.py	2007-04-26 19:04:26 UTC (rev 65)
@@ -0,0 +1,223 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""GTK Based InteProxyApplication"""
+
+import sys
+import os
+
+import gtk
+
+import inteproxy.app
+import inteproxy.main
+
+
+# UI definition with the menus used in the InteProxy GUI
+ui_definition = """<ui>
+  <!-- Menubar for the main window -->
+  <menubar name='Menubar'>
+    <menu action='FileMenu'>
+      <menuitem action='Close'/>
+      <menuitem action='Quit'/>
+    </menu>
+    <menu action='HelpMenu'>
+      <menuitem action='About'/>
+    </menu>
+  </menubar>
+
+  <!-- Menu for the Status Icon -->
+  <menubar name='StatusIconMenuBar'>
+    <menu action='StatusIconMenu'>
+      <menuitem action='ShowWindow'/>
+      <menuitem action='About'/>
+      <separator/>
+      <menuitem action='Quit'/>
+    </menu>
+  </menubar>
+
+</ui>"""
+
+
+
+class StatusIcon(object):
+
+    """Icon for the system tray.
+
+    The StatusIcon is a small icon in the system tray.  The user can
+    interact with that icon by clicking on it to show the main window or
+    to popup a menu with the most important commands.
+    """
+
+    def __init__(self, application):
+        """Initializes the StatusIcon with the application to which it belongs
+        """
+        self.application = application
+
+        iconfile = os.path.join(os.path.dirname(__file__), "InteProxy-icon.png")
+        self.status_icon = gtk.status_icon_new_from_file(iconfile)
+        self.status_icon.set_tooltip("InteProxy")
+        self.status_icon.connect("activate", self.show_window)
+        self.status_icon.connect("popup-menu", self.popup_menu)
+
+    def popup_menu(self, status_icon, button, activate_time):
+        """Pops up the StatusIconMenu.
+
+        This method is a signal handler for the status icon's popup-menu
+        signal.
+        """
+        ui = self.application.ui
+        menu = ui.get_widget('/StatusIconMenuBar/StatusIconMenu').get_submenu()
+        menu.popup(None, None, None, button, activate_time)
+
+    def show_window(self, *args):
+        """Shows the main window"""
+        self.application.show_main_window()
+
+    def quit(self, *args):
+        """Quits the inteproxy application"""
+        self.application.quit()
+
+
+
+
+class InteProxyMainWindow(gtk.Window):
+
+    """The mainwindow of InteProxy"""
+
+    def __init__(self, application):
+        """Initializes the main window with the application to which it belongs
+        """
+        gtk.Window.__init__(self)
+        self.application = application
+
+        self.add_accel_group(self.application.ui.get_accel_group())
+
+        self.set_position(gtk.WIN_POS_CENTER)
+        self.set_title('InteProxy')
+        self.connect('delete-event', self.delete_event)
+        self.set_size_request(400, 200)
+        vbox = gtk.VBox()
+        self.add(vbox)
+
+        vbox.pack_start(self.application.ui.get_widget('/Menubar'),
+                        expand=False)
+        vbox.pack_start(gtk.Label("InteProxy Status"))
+
+        vbox.show_all()
+
+    def delete_event(self, *args):
+        """Handler for the delete-even that hides the window"""
+        self.application.hide_main_window()
+        return True
+
+
+
+class InteProxyApplication(inteproxy.app.InteProxyApplication):
+
+    """GUI version of InteProxyApplication
+
+    This InteProxyApplication shows a main window and a status icon if
+    the GTK version used implements it (requires GTK >= 2.10).  If a
+    status icon is shown the main window is hidden initially.
+    """
+
+    def __init__(self, server):
+        """
+        Initializes the InteProxyApplication with the server and creates the GUI
+        """
+        super(InteProxyApplication, self).__init__(server)
+
+        self.create_ui()
+        if hasattr(gtk, "StatusIcon"):
+            self.status_icon = StatusIcon(self)
+        else:
+            self.status_icon = None
+        self.main_window = InteProxyMainWindow(self)
+
+        if not self.status_icon:
+            self.main_window.show()
+
+    def create_ui(self):
+        """Internal: Parses the ui_definition"""
+        ag = gtk.ActionGroup('WindowActions')
+        actions = [
+            ('FileMenu', None, '_File'),
+            ('Close',    gtk.STOCK_CLOSE, '_Close', '<control>W',
+             'Close the InteProxy window', self.hide_main_window),
+            ('Quit',     gtk.STOCK_QUIT, '_Quit', '<control>Q',
+             'Quit application', self.quit),
+            ('HelpMenu', None, '_Help'),
+            ('About',    None, '_About', None, 'About application',
+             self.run_about_dialog),
+            ('StatusIconMenu', None, '_StatusIconMenu'),
+            ('ShowWindow', None, '_Show Window', '<control>S',
+             'Show the InteProxy window', self.show_main_window),
+            ]
+        ag.add_actions(actions)
+        self.ui = gtk.UIManager()
+        self.ui.insert_action_group(ag, 0)
+        self.ui.add_ui_from_string(ui_definition)
+
+    def run(self):
+        """Runs the InteProxyApplication until the user quits.
+
+        The method starts a thread for the inteproxy server and then
+        enters the gtk main loop.
+        """
+        self.start_inte_proxy_thread(daemon=True)
+        gtk.gdk.threads_init()
+        gtk.main()
+
+    def quit(self, *args):
+        """Quits the inte proxy"""
+        sys.exit()
+
+    def show_main_window(self, *args):
+        """Shows the main window"""
+        self.main_window.show()
+
+    def hide_main_window(self, *args):
+        """Hides the main window if a status icon is shown other wise quits"""
+        if self.status_icon:
+            self.main_window.hide()
+        else:
+            self.quit()
+
+    def run_about_dialog(self, *args):
+        """Run the about dialog"""
+        dialog = gtk.MessageDialog(self.main_window,
+                                   (gtk.DIALOG_MODAL |
+                                    gtk.DIALOG_DESTROY_WITH_PARENT),
+                                   gtk.MESSAGE_INFO, gtk.BUTTONS_OK,
+                                   ("InteProxy version %s"
+                                    % inteproxy.main.inteproxy_version))
+        dialog.run()
+        dialog.destroy()
+
+    def get_password(self, *args):
+        """Asks the user for username and password
+
+        Extends the inherited method to be thread safe in GTK, so that
+        it can be called from the server's worker threads.
+        """
+        gtk.gdk.threads_enter()
+        try:
+            return super(InteProxyApplication, self).get_password(*args)
+        finally:
+            gtk.gdk.threads_leave()
+
+    def run_fees_dialog(self, *args):
+        """Runs the fees dialog.
+
+        Extends the inherited method to be thread safe in GTK, so that
+        it can be called from the server's worker threads.
+        """
+        gtk.gdk.threads_enter()
+        try:
+            super(InteProxyApplication, self).run_fees_dialog(*args)
+        finally:
+            gtk.gdk.threads_leave()


Property changes on: trunk/inteproxy/gtkapp.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Modified: trunk/inteproxy/httpserver.py
===================================================================
--- trunk/inteproxy/httpserver.py	2007-04-24 15:33:23 UTC (rev 64)
+++ trunk/inteproxy/httpserver.py	2007-04-26 19:04:26 UTC (rev 65)
@@ -40,3 +40,48 @@
         """
         BaseHTTPServer.HTTPServer.server_close(self)
         self.do_shutdown = True
+
+
+class ServerThread(object):
+
+    """Class to run a HTTPServer instance in a thread"""
+
+    def __init__(self, server):
+        """Initializes the server thread with an instance of HTTPServer"""
+        self.server = server
+        self.server_port = self.server.getsockname()[1]
+        self.server_thread = None
+
+    def start(self, daemon=False):
+        """Starts the server thread"""
+        self.server_thread = threading.Thread(target=self._serve_forever)
+        self.server_thread.setDaemon(daemon)
+        self.server_thread.start()
+
+    def _serve_forever(self):
+        """Helper method to run the server's serve_forever method in a thread"""
+        self.server.serve_forever()
+
+    def stop(self):
+        """Stops the server thread"""
+        if self.server_thread is None:
+            return
+
+        self.server.server_close()
+
+        # The server thread might be blocked while listening on the
+        # socket.  Unblock it by connecting to the port.
+        try:
+            s = socket.socket()
+            s.connect(("localhost", self.server_port))
+            s.close()
+        except socket.error, exc:
+            # The server may have shut down already when we try to
+            # connect, so we ignore connection failures.
+            if exc[0] == errno.ECONNREFUSED:
+                pass
+            else:
+                raise
+
+        self.server_thread.join()
+        self.server_thread = None

Modified: trunk/inteproxy/main.py
===================================================================
--- trunk/inteproxy/main.py	2007-04-24 15:33:23 UTC (rev 64)
+++ trunk/inteproxy/main.py	2007-04-26 19:04:26 UTC (rev 65)
@@ -14,11 +14,11 @@
 import urlparse
 import urllib2
 import inteproxy.proxyconnection as proxyconnection
-from inteproxy.transcoder import transcoder_map
-from inteproxy.getpassword import getpassword
+import inteproxy.transcoder
 from inteproxy.proxycore import (InteProxyHTTPRequestHandler,
                                  MasterWorkerServer,
                                  log_date_time_string)
+from inteproxy.gtkapp import InteProxyApplication
 
 inteproxy_version = "0.1.2"
 
@@ -32,7 +32,7 @@
             # no password known yet.  Ask the user
             authuri = "https://" + "".join(self.reduce_uri(authuri))
             title = "Username for %r realm %r: " % (authuri, realm)
-            user, password = getpassword(title)
+            user, password = the_application.get_password(title)
         return user, password
 
 def setup_urllib2(debuglevel):
@@ -56,6 +56,11 @@
     urllib2.install_opener(opener)
 
 
+# The main application object.  It's a global variable so that worker
+# threads can easily get access to it.
+the_application = None
+
+
 def run_server(HandlerClass = InteProxyHTTPRequestHandler,
                ServerClass = MasterWorkerServer):
     """Run the InteProxy server"""
@@ -80,7 +85,7 @@
     HandlerClass.allow_shutdown = opts.allow_shutdown
     HandlerClass.debug_level = opts.debug_level
 
-    transcoder_map.read_config(opts.config_file)
+    inteproxy.transcoder.transcoder_map.read_config(opts.config_file)
 
     setup_urllib2(opts.debug_level)
 
@@ -94,10 +99,13 @@
 
     httpd = ServerClass(server_address, HandlerClass, opts.workers)
 
+    global the_application
+    the_application = InteProxyApplication(httpd)
+
     print "Serving HTTP on port", opts.port, "..."
     sys.stderr.write("[%s] serving HTTP on port %d\n"
                      % (log_date_time_string(), opts.port))
-    httpd.serve_forever()
+    the_application.run()
     logmessage = "[%s] server stopped" % (log_date_time_string(),)
     print logmessage
     sys.stderr.write(logmessage + "\n")

Modified: trunk/inteproxy/proxycore.py
===================================================================
--- trunk/inteproxy/proxycore.py	2007-04-24 15:33:23 UTC (rev 64)
+++ trunk/inteproxy/proxycore.py	2007-04-26 19:04:26 UTC (rev 65)
@@ -15,7 +15,7 @@
 import BaseHTTPServer
 import socket
 
-from inteproxy.transcoder import transcoder_map
+import inteproxy.transcoder
 from inteproxy.threadpool import ThreadPool
 from inteproxy.feesdialog import handle_fees_and_access_constraints
 from inteproxy.httpserver import HTTPServer
@@ -92,6 +92,7 @@
         #
         # Create a http request for the real location
         #
+        transcoder_map = inteproxy.transcoder.transcoder_map
         transcoder = transcoder_map.get_transcoder(method, self.path)
         remote_url = transcoder.get_url()
         self.log_debug("Converted url: %r", remote_url)



More information about the Inteproxy-commits mailing list