#!/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()