Logo Search packages:      
Sourcecode: webboard version File versions  Download package

webboard.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# WebBoard allows you to publish text on a public pastebin on the net
# This applications is based on the command line tool paste of 
# Dennis Kaarsemaker
#
#   (c) 2005 - Dennis Kaarsemaker <dennis@kaarsemaker.net>
#   (c) 2006 - Sebastian Heinlein <sebastian.heinlein@web.de>
#   (c) 2009 - Olivier Le Thanh Duong <olivier@lethanh.be>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


import sys
import os
import thread
import time
import datetime
import subprocess
import urllib
import xmlrpclib

import pygtk
pygtk.require('2.0')
import gtk
import gtk.glade
import pango
import gtksourceview
from re import compile
import gettext
import gobject
from gettext import gettext as _
gettext.bindtextdomain('webboard')
gettext.textdomain('webboard')
gtk.glade.bindtextdomain('webboard')
gtk.glade.textdomain('webboard')

import wbconfig
from wbconfig import WebBoardConfig

class WebBoard:
    def __init__(self, config, history, clip=False, file=None):
        """
        Initialize the main window of webboard
        """
        icons = gtk.icon_theme_get_default()
        self.logo_pixbuf=icons.load_icon("gtk-paste", 32, 0)
        gtk.window_set_default_icon_list(self.logo_pixbuf)

        self.format = "text"

        
        self.glade = gtk.glade.XML(wbconfig.find_glade_file("webboard.glade"))
        self.glade.signal_autoconnect(self)

        # setup the gtksourceview
        self.lm = gtksourceview.SourceLanguagesManager()
        buffer = gtksourceview.SourceBuffer()
        buffer.set_data('languages-manager', self.lm)
        self.textview = gtksourceview.SourceView(buffer)
        gconf_mono_font = config.gconf.get_string("/desktop/gnome/interface/monospace_font_name")
        if gconf_mono_font :
            font = pango.FontDescription(gconf_mono_font)
            self.textview.modify_font(font)
        scroller = self.glade.get_widget("scrolledwindow_code")
        scroller.add(self.textview)

        self.language = "Plain"

        # The key used here is the format name as know by paste.debian.net
        # Fixme Not all format supported by paste.debian.net are listed
        self.langs = {
            "Plain"         : (_("Plain text"), "None"),
            "sh"            : ("Bash", "text/x-sh"),
            "python"        : ("Python", "text/x-python"),
            "c"             : ("C", "text/x-c"),
            "cpp"           : ("C++", "text/x-cpp"),
            "html4"         : ("HTML (4.0.1)", "text/html"),
            "java"          : ("Java", "text/x-java"),
            "javascript"    : ("Javascript", "text/x-javascript"),
            "perl"          : ("Perl", "text/x-perl"),
            "php"           : ("PHP", "text/x-php"),
            "sql"           : ("SQL", "text/x-sql"),
            "ada"           : ("Ada", "text/x-ada"),
            "apache"        : (_("Apache log file"), "None"),
            "asm"           : (_("ASM (NASM based)"), "None"),
            "aspvbs"        : ("Active Server Page", "None"),
            "dcl"           : ("CAD DCL", "None"),
            "lisp"          : ("CAD Lisp", "None"),
            "cs"            : ("C#", "text/x-csharp"),
            "css"           : ("CSS", "text/css"),
            "lisp"          : ("Lisp", "None"),
            "lua"           : ("Lua", "None"),
            "nsis"          : (_("NullSoft Installer"), "None"),
            "objc"          : (_("Objective C"), "text/x-c"),
            "oracle8"       : ("Oracle 8", "None"),
            "pascal"        : ("Pascal", "text/x-pascal"),
            "smarty"        : ("Smarty", "None"),
            "vb"            : ("Visual Basic", "text/x-vb"),
            "foxpro"        : ("Visual Fox Pro", "None"),
            "xml"           : ("XML", "text/xml")
        }

        lang_store = gtk.ListStore(gobject.TYPE_STRING,
                                   gobject.TYPE_STRING,
                                   gobject.TYPE_STRING)
        lang_store.set_sort_column_id(1, gtk.SORT_ASCENDING)
        for format in self.langs.keys():
            lang_store.append([format, self.langs[format][0],
                              self.langs[format][1]])

        self.combobox_syntax = self.glade.get_widget("combobox_syntax")
        cell = gtk.CellRendererText()
        self.combobox_syntax.pack_start(cell, True)
        self.combobox_syntax.add_attribute(cell, 'text', 1)

        self.combobox_syntax.set_model(lang_store)
        # FIXME : Do this in a cleaner way (won't work when translated)
        self.combobox_syntax.set_active(21)
        self.combobox_syntax.connect("changed", self.on_combobox_syntax_changed)
        
        # setup drag'n'drop
        self.textview.drag_dest_set(gtk.DEST_DEFAULT_ALL, \
                                    [('text/uri-list',0 , 0)], \
                                    gtk.gdk.ACTION_COPY)
        self.textview.connect("drag_motion", \
                              self.on_textview_drag_motion)
        self.textview.connect("drag_data_received", \
                              self.on_textview_drag_data_received)
        self.window_main = self.glade.get_widget("window_main")
        self.toolbutton_open = self.glade.get_widget("toolbutton_open")
        self.toolbutton_copy = self.glade.get_widget("toolbutton_copy")
        self.toolbutton_send = self.glade.get_widget("toolbutton_send")
        self.combobox_pastebin = self.glade.get_widget("combobox_pastebin")
        self.statusbar = self.glade.get_widget("statusbar")

        self.context = self.statusbar.get_context_id("context_webboard")

        self.clipboard = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD)

        self.config = config
        self.config.add_notifier(self.on_config_changed)
        self.history = history

        self.tooltips = gtk.Tooltips()

        self.on_config_changed()

        self.buffer = self.textview.get_buffer()
        text = self.clipboard.wait_for_text()
        if (clip is False) or (text is None):
            self.buffer.set_text(self.welcome)
        else:
            self.buffer.set_text(text)
        self.buffer.select_range(self.buffer.get_start_iter(),
                                 self.buffer.get_end_iter())

        if file != None:
            self.open_file(file)

        #self.toolbutton_send.grab_default()
        self.textview.show()
        self.window_main.show()

        gtk.main()
        return

    def on_combobox_syntax_changed(self, combobox):
        """
        Set the selected synatx highlightenging for
        the source view
        """
        lang_store = combobox.get_model()
        iter = combobox.get_active_iter()
        mime = lang_store.get_value(iter, 2)
        self.language = lang_store.get_value(iter, 0)
        buffer = self.textview.get_buffer()
        if mime == "None":
            buffer.set_highlight(False)
        else:
            try:
                language = self.lm.get_language_from_mime_type(mime)
                buffer.set_language(language)
                buffer.set_highlight(True)
            except:
                buffer.set_highlight(False)

    def _get_file_path_from_dnd_dropped_uri(self, uri):
        """
        Guess the correct file path of the dropped file
        """
        path = urllib.url2pathname(uri) # escape special chars
        path = path.strip('\r\n\x00') # remove \r\n and NULL
        # get the path to file
        if path.startswith('file:\\\\\\'): # windows
            path = path[8:] # 8 is len('file:///')
        elif path.startswith('file://'): # nautilus, rox
            path = path[7:] # 7 is len('file://')
        elif path.startswith('file:'): # xffm
            path = path[5:] # 5 is len('file:')
        return path
    
    def on_textview_drag_motion(self, widget, content, x, y, time):
        """
        Deselect all text in the text view, so that the drag'n'drop
        doesn't replace the selection
        """
        self.buffer.select_range(self.buffer.get_start_iter(),
                                 self.buffer.get_start_iter())

    def open_file(self, path):
        """
        Read the content of the specified file and write it to
        the text view
        """
        #FIXME: Should make use of async gnome vfs
        if os.path.exists(path):
            self.textview.get_buffer().set_text(file(path).read())

    def on_textview_drag_data_received(self, widget, context, x, y,
                                       selection, target_type, timestamp):
        uri = selection.data.strip()
        uri_splitted = uri.split()
        for uri in uri_splitted:
            path = self._get_file_path_from_dnd_dropped_uri(uri)
            self.window_main.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
            while gtk.events_pending(): gtk.main_iteration()
            try:
                self.open_file(path)
            except IOError, e:
                pass
            self.window_main.window.set_cursor(None)

    def on_history_changed(self):
        self.history.update()

    def on_config_changed(self):
        # TRANSLATORS: tooltip of the button "publish"
        #              %s is the URL of the pastebin server
        self.tooltips.set_tip(self.toolbutton_send,
                              _("Publish the text on %s and copy the link "\
                                "to the relating website into the clipboard") \
                                % self.config.pastebin)
        # TRANSLATORS: default welcome text
        #              %s is the URL of the pastebin server
        self.welcome = _("Enter, copy or drag and drop source code and text "
                         "notes for publishing on %s") % self.config.pastebin
        # TRANSLATORS: The Window title - %s is the URL of the server
        self.window_main.set_title(_("WebBoard - %s") % self.config.pastebin)


    def on_window_main_destroy(self, widget):
        self.clipboard.store()
        gtk.main_quit()
        return

    def on_history_activate(self, widget, data):
        self.clipboard.set_text(data, len=-1)

    def on_button_preferences_clicked(self, widget=None):
        """
        Show the preferences window
        """
        self.config.preferences()

    def on_button_send_clicked(self, widget=None):
        """
        Lock the interface and run the send function
        in the background
        """
        self.statusbar.push(self.context,_("Publishing..."))
        self.window_main.set_sensitive(False)
        self.textview.set_sensitive(False)
        self.textview.set_editable(False)
        self.window_main.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
        self.post = self.buffer.get_text(self.buffer.get_start_iter(),
                                             self.buffer.get_end_iter(), False)
        if self.post is "":
            self.statusbar.push(self.context,_("No text for publishing"))
        else:
            lock = thread.allocate_lock()
            lock.acquire()
            thread.start_new_thread(self.send, (self.post, lock))
            while lock.locked():
                time.sleep(0.05)
                while gtk.events_pending():
                    gtk.main_iteration()

            if self.ret is False:
                self.statusbar.push(self.context,_("Could not publish the text"\
                                                   " at %s") % self.config.pastebin)
                self.toolbutton_copy.set_sensitive(False)
                self.toolbutton_open.set_sensitive(False)
            else:
                self.url = "%s/%s" % (self.config.pastebin, self.ret)
                if not (self.url.startswith("http://") or
                        self.url.startswith("https://")):
                    self.url = "http://" + self.url
                # Write URL to the clipboard for easy paste
                self.statusbar.push(self.context,
                        _("Published at %s") % self.url)
                # set a nice title for the link
                text = self.post[:].lstrip(" \n\t").rstrip(" \n\t")
                text.replace("\n", "")
                if len(text) > 26:
                    text = "%s..." % text[0:20]
                # add to history
                now = datetime.datetime.now()
                stamp = time.mktime(now.timetuple())
                self.history.add(stamp , text, self.url)
                # copy link to the clipboard
                self.clipboard.set_text(self.url, len=-1)
                self.toolbutton_copy.set_sensitive(True)
                self.toolbutton_open.set_sensitive(True)

        self.window_main.window.set_cursor(None)
        self.window_main.set_sensitive(True)
        self.textview.set_editable(True)
        self.textview.set_sensitive(True)

    def on_menu_quit_activate(self, widget):
        self.window_main.destroy()

    def on_menu_info_activate(self, widget):
        wbconfig.about_info(self)

    def on_button_copy_clicked(self, widget):
        """
        Copy the current URL to the clipboard
        """
        self.clipboard.set_text(self.url, len=-1)

    def on_button_clear_clicked(self, widget):
        """
        Clear the textview
        """
        self.buffer.set_text("")

    def on_button_open_clicked(self, widget=None):
        """
        Open the URL of the latest pastebin publication in a browser window
        """
        if os.path.exists('/usr/bin/gnome-open'):
            command = ['gnome-open', self.url]
        else:
            command = ['x-www-browser', self.url]
        p = subprocess.Popen(command, close_fds=True, stdin=subprocess.PIPE,\
                             stdout=subprocess.PIPE)

    def send(self, post, lock, *args):
        """
        Send the post to the pastebin
        """
        self.ret = False
        pastebin = self.config.pastebin

        if not (pastebin[:7] == 'http://'):
            pastebin = 'http://' + pastebin
        
        rpcpath = '/server.pl' #FIXME Should be configurable
        server = xmlrpclib.Server(pastebin + rpcpath)
        
        # paste.debian.net limit name to 10 chars
        user = self.config.user[:10]
        print user

        # TODO : Guess format or allow user to choose it
        format = self.language

        try:
            #Handle error better
            rep = server.paste.addPaste(post, user, '', format)
            if rep['rc'] != 0 :
                print 'An error occured : %s ' % (rep['statusmessage'])
                self.ret = False
            else:
                #Some how display this message to the user
                print rep['statusmessage']
                print 'Code pasted at %s/%i' % (pastebin, rep['id'])
                self.ret =  str(rep['id'])
        except xmlrpclib.ProtocolError, err:
            print "A protocol error occurred"
            print "URL: %s" % err.url
            print "HTTP/HTTPS headers: %s" % err.headers
            print "Error code: %d" % err.errcode
            print "Error message: %s" % err.errmsg
            self.ret = False
        except Exception, err:
            print type(err)   
            print err
            self.ret = False
        finally:
            lock.release()


    def urlencode(self, data):
        """Encode all non-alphanumeric characters as hexadecimal codes"""
        out = ""
        for char in data:
            if char.isalnum() or char in ['-','_']:
                out += char
            else:
                char = hex(ord(char))[2:]
                if len(char) == 1:
                    char = "0" + char
                out += "%" + char
        return out

def main():
    config = WebBoardConfig()
    wb = WebBoard(config, clip=True)
    config.add_notifier(wb.on_config_changed)
    del wb
    del config

if __name__ == '__main__':
    main()


Generated by  Doxygen 1.6.0   Back to index