summaryrefslogtreecommitdiff
path: root/portato
diff options
context:
space:
mode:
Diffstat (limited to 'portato')
-rw-r--r--portato/db/__init__.py94
-rw-r--r--portato/db/database.py2
-rw-r--r--portato/db/exceptions.py24
-rw-r--r--portato/db/sql.py4
-rw-r--r--portato/gui/dialogs.py18
-rw-r--r--portato/gui/exception_handling.py4
-rw-r--r--portato/gui/templates/AboutWindow.ui7
-rw-r--r--portato/gui/templates/MailInfoWindow.ui38
-rw-r--r--portato/gui/windows/about.py8
-rw-r--r--portato/gui/windows/basic.py3
-rw-r--r--portato/gui/windows/mailinfo.py137
-rw-r--r--portato/gui/windows/main.py19
-rw-r--r--portato/session.py12
13 files changed, 264 insertions, 106 deletions
diff --git a/portato/db/__init__.py b/portato/db/__init__.py
index a4b4bde..41bd017 100644
--- a/portato/db/__init__.py
+++ b/portato/db/__init__.py
@@ -12,66 +12,66 @@
from __future__ import absolute_import
+from . import database as db
+from .exceptions import UnknownDatabaseTypeError, DatabaseInstantiationError
from ..session import Session, SectionDict
from ..helper import debug, warning, error
-class UnknownDatabaseTypeError (Exception):
- pass
-
-_SESSION = None
-_TYPE = None
-_DEFAULT = "dict"
-_DATABASE = None
-
types = {
"sql": (_("SQLite"), _("Uses an SQLite-database to store package information.\nMay take longer to generate at the first time, but has advantages if portato is re-started with an unchanged portage tree. Additionally it allows to use fast SQL expressions for fetching the data.")),
"dict": (_("Hashmap"), _("Uses an in-memory hashmap to store package information.\nHas been used since at least version 0.3.3, but all information has to be regenerated on each startup.")),
"eixsql" : (_("eix + SQLite"), _("Similar to SQLite, but now uses the eix database to get the package information.\nThis should be much faster on startup, but requires that your eix database is always up-to-date.\nAdditionally, this is the only database allowing searching in descriptions."))
}
-def Database(type = None):
- global _SESSION, _TYPE, _DATABASE
+class Database(db.Database):
+ DEFAULT = "dict"
- if type is None:
- if _DATABASE is None:
- warning("No database type specified! Falling back to default.")
- return Database(_DEFAULT)
- else:
- return _DATABASE
+ def __new__ (cls, type = None):
+ if not '_the_instance' in cls.__dict__:
+ dbcls = cls._generate(type)
+ cls._the_instance = dbcls(cls._get_session())
+ elif type is not None:
+ raise DatabaseInstantiationError("Database instantiation called with 'type' argument multiple times.")
+ return cls._the_instance
- if _SESSION is None:
- _SESSION = Session("db.session", name = "DB", oldfiles = ["db.cfg"])
- _SESSION.load()
+ @classmethod
+ def _generate(cls, type):
- _TYPE = type
+ if type is None:
+ warning("No database type specified! Falling back to default.")
+ type = cls.DEFAULT
+
+ cls.DB_TYPE = type
- if type == "sql":
- debug("Using SQLDatabase")
- try:
- from .sql import SQLDatabase
- except ImportError:
- warning(_("Cannot load %s."), "SQLDatabase")
- _DATABASE = Database("dict")
- else:
- _DATABASE = SQLDatabase(SectionDict(_SESSION, type))
+ if type == "sql":
+ debug("Using SQLDatabase")
+ try:
+ from .sql import SQLDatabase
+ except ImportError:
+ warning(_("Cannot load %s."), "SQLDatabase")
+ return cls._generate("dict")
+ else:
+ return SQLDatabase
- elif type == "dict":
- debug("Using HashDatabase")
- from .hash import HashDatabase
- _DATABASE = HashDatabase(SectionDict(_SESSION, type))
-
- elif type == "eixsql":
- debug("Using EixSQLDatabase")
- try:
- from .eix_sql import EixSQLDatabase
- except ImportError:
- warning(_("Cannot load %s."), "EixSQLDatabase.")
- _DATABASE = Database("sql")
- else:
- _DATABASE = EixSQLDatabase(SectionDict(_SESSION, type))
+ elif type == "dict":
+ debug("Using HashDatabase")
+ from .hash import HashDatabase
+ return HashDatabase
+
+ elif type == "eixsql":
+ debug("Using EixSQLDatabase")
+ try:
+ from .eix_sql import EixSQLDatabase
+ except ImportError:
+ warning(_("Cannot load %s."), "EixSQLDatabase.")
+ return cls._generate("sql")
+ else:
+ return EixSQLDatabase
- else:
- error(_("Unknown database type: %s"), type)
- raise UnknownDatabaseTypeError, type
+ else:
+ error(_("Unknown database type: %s"), type)
+ raise UnknownDatabaseTypeError, type
- return _DATABASE
+ @classmethod
+ def _get_session(cls):
+ return SectionDict(Session("db.session", name = "DB", oldfiles = ["db.cfg"]), cls.DB_TYPE)
diff --git a/portato/db/database.py b/portato/db/database.py
index 6e92d79..c679d06 100644
--- a/portato/db/database.py
+++ b/portato/db/database.py
@@ -16,6 +16,8 @@ from threading import RLock
from functools import wraps
from ..helper import warning
+from .exceptions import UnsupportedSearchTypeError
+
class UnsupportedSearchTypeError(Exception):
pass
diff --git a/portato/db/exceptions.py b/portato/db/exceptions.py
new file mode 100644
index 0000000..8a6e424
--- /dev/null
+++ b/portato/db/exceptions.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+#
+# File: portato/db/exceptions.py
+# This file is part of the Portato-Project, a graphical portage-frontend.
+#
+# Copyright (C) 2006-2010 René 'Necoro' Neumann
+# This is free software. You may redistribute copies of it under the terms of
+# the GNU General Public License version 2.
+# There is NO WARRANTY, to the extent permitted by law.
+#
+# Written by René 'Necoro' Neumann <necoro@necoro.net>
+
+class DatabaseError (Exception):
+ pass
+
+class UnknownDatabaseTypeError (DatabaseError):
+ pass
+
+class DatabaseInstantiationError (DatabaseError):
+ pass
+
+class UnsupportedSearchTypeError(DatabaseError):
+ pass
+
diff --git a/portato/db/sql.py b/portato/db/sql.py
index a9c27ab..f5dc257 100644
--- a/portato/db/sql.py
+++ b/portato/db/sql.py
@@ -47,6 +47,7 @@ class SQLDatabase (Database):
updateFormat = False
if "format" not in session or session["format"] != self.FORMAT:
+ debug("Need to update database format from '%s' to '%s'", session["format"], self.FORMAT)
session["format"] = self.FORMAT
updateFormat = True
@@ -61,6 +62,7 @@ class SQLDatabase (Database):
pkg_conn = sql.connect(pkgdb)
pkg_conn.row_factory = sql.Row
if pkgdb_existed and updateFormat:
+ debug("Dropping old table")
pkg_conn.execute("DROP TABLE packages")
pkg_conn.execute("""
@@ -118,7 +120,7 @@ class SQLDatabase (Database):
os.remove(dbpath)
db_existed = False
- self.session["pickle"] = True # no need for a certain value
+ self.session["pickle"] = "1" # no need for a certain value
if db_existed:
debug("portdirs.db already existant")
diff --git a/portato/gui/dialogs.py b/portato/gui/dialogs.py
index 6044a5b..2cc2d6b 100644
--- a/portato/gui/dialogs.py
+++ b/portato/gui/dialogs.py
@@ -20,6 +20,13 @@ def mail_failure_dialog(e):
dialog.destroy()
return ret
+def no_email_dialog(p):
+ dialog = gtk.MessageDialog(p, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK_CANCEL, _("No email address given"))
+ dialog.format_secondary_text(_("You haven't specified an email address. Without it, it will not be possible for the developers to contact you for questions and thus it might be harder to fix the bug.\n\nDo you want to proceed nevertheless?"))
+ ret = dialog.run()
+ dialog.destroy()
+ return ret
+
def queue_not_empty_dialog():
dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_NONE, _("Do you really want to quit?"))
dialog.format_secondary_text(_("There are some packages in the emerge queue and/or an emerge process is running."))
@@ -76,6 +83,17 @@ def changed_flags_dialog (what = "flags"):
return ret, check.get_active()
+def update_world_warning_dialog ():
+ check = gtk.CheckButton(_("Do not show this dialog again."))
+ hintMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, _("'Update World' may be giving errors"))
+ hintMB.format_secondary_text(_("Due to the fast changing portage, 'update world' might not work correctly or even throw errors.\nThis will be fixed (hopefully) in the next release."))
+ hintMB.vbox.add(check)
+ hintMB.vbox.show_all()
+ ret = hintMB.run()
+ hintMB.destroy()
+
+ return ret, check.get_active()
+
def remove_deps_dialog ():
infoMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("You cannot remove dependencies. :)"))
ret = infoMB.run()
diff --git a/portato/gui/exception_handling.py b/portato/gui/exception_handling.py
index dbafa7e..d9b133c 100644
--- a/portato/gui/exception_handling.py
+++ b/portato/gui/exception_handling.py
@@ -99,7 +99,7 @@ def convert (version):
def get_version_infos():
from ..constants import VERSION, REVISION
from ..backend import system
- from ..db import _TYPE as db_type
+ from ..db import Database
if REVISION:
VERSION = "%s (git: %s)" % (VERSION, REVISION)
@@ -109,7 +109,7 @@ def get_version_infos():
"System: %s" % " ".join(get_runsystem()),
"Python version: %s" % sys.version,
"Used backend: %s" % system.get_version(),
- "Used database type: %s" % db_type,
+ "Used database type: %s" % Database.DB_TYPE,
"pygtk: %s (using GTK+: %s)" % (convert(gtk.pygtk_version), convert(gtk.gtk_version)),
"pygobject: %s (using GLib: %s)" % (convert(gobject.pygobject_version), convert(gobject.glib_version))))
diff --git a/portato/gui/templates/AboutWindow.ui b/portato/gui/templates/AboutWindow.ui
index 5590e2b..a3f81cf 100644
--- a/portato/gui/templates/AboutWindow.ui
+++ b/portato/gui/templates/AboutWindow.ui
@@ -25,13 +25,6 @@ Thanks goto:
- The Porthole team, which often inspired me and gave me hints :)
- franzf, who often tested and gave comments
- the Sabayon-Distro for making Portato the default Portage-GUI</property>
- <property name="translator_credits">Catalan - Roger Calv&#xF3;
-German - Ren&#xE9; 'Necoro' Neumann
-Italian - Ponsi
-Polish - Tomasz Osi&#x144;ski
-Portugese (Brazilian) - Alberto Federman Neto
-Spanish - Daniel Halens
-Turkish - G&#xFC;rkan 'seqizz' G&#xFC;r</property>
<property name="artists">p4r4d0x (inspired by wolfden)</property>
<signal name="response" handler="close"/>
<child internal-child="vbox">
diff --git a/portato/gui/templates/MailInfoWindow.ui b/portato/gui/templates/MailInfoWindow.ui
index 4551cdb..e3cdd1f 100644
--- a/portato/gui/templates/MailInfoWindow.ui
+++ b/portato/gui/templates/MailInfoWindow.ui
@@ -134,25 +134,39 @@ what did you do to hit the bug?</property>
</packing>
</child>
<child>
- <object class="GtkCheckButton" id="logCheck">
- <property name="label" translatable="yes">Attach _Logfile</property>
+ <object class="GtkFrame" id="frame1">
<property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="tooltip_text" translatable="yes">Attaches the logfile to the mail. This log only contains debug information.</property>
- <property name="use_underline">True</property>
- <property name="active">True</property>
- <property name="draw_indicator">True</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkTreeView" id="fileList">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="headers_clickable">False</property>
+ <property name="enable_search">False</property>
+ <signal name="row_activated" handler="cb_file_clicked"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">These files will be sent:</property>
+ </object>
+ </child>
</object>
<packing>
+ <property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
- <property name="x_options">GTK_FILL</property>
</packing>
</child>
- <child>
- <placeholder/>
- </child>
</object>
<packing>
<property name="padding">5</property>
diff --git a/portato/gui/windows/about.py b/portato/gui/windows/about.py
index a15fd24..0d2ce1a 100644
--- a/portato/gui/windows/about.py
+++ b/portato/gui/windows/about.py
@@ -10,12 +10,13 @@
#
# Written by René 'Necoro' Neumann <necoro@necoro.net>
-from __future__ import absolute_import
+from __future__ import absolute_import, with_statement
+import os
import gtk
from .basic import AbstractDialog
-from ...constants import VERSION, REVISION
+from ...constants import VERSION, REVISION, DATA_DIR
class AboutWindow (AbstractDialog):
"""A window showing the "about"-informations."""
@@ -27,6 +28,9 @@ class AboutWindow (AbstractDialog):
self.window.set_version(VERSION)
self.window.set_logo(None)
+ with open(os.path.join(DATA_DIR, "TRANSLATORS")) as f:
+ self.window.set_translator_credits("".join(f.readlines()))
+
if REVISION:
gitlabel = self.tree.get_widget("gitLabel")
gitlabel.set_label(REVISION)
diff --git a/portato/gui/windows/basic.py b/portato/gui/windows/basic.py
index 92c35f4..01d31e5 100644
--- a/portato/gui/windows/basic.py
+++ b/portato/gui/windows/basic.py
@@ -151,6 +151,9 @@ class AbstractDialog (Window):
self.window.set_transient_for(parent)
self.parent = parent
+ # type hint
+ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+
# catch the ESC-key
self.window.connect("key-press-event", self.cb_key_pressed)
diff --git a/portato/gui/windows/mailinfo.py b/portato/gui/windows/mailinfo.py
index 0ee232a..4367482 100644
--- a/portato/gui/windows/mailinfo.py
+++ b/portato/gui/windows/mailinfo.py
@@ -14,34 +14,98 @@ from __future__ import absolute_import, with_statement
import smtplib, socket
import time
+import gtk, pango, gobject
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
+from os.path import basename
from .basic import AbstractDialog
from ..utils import GtkThread
-from ..dialogs import mail_failure_dialog
+from .. import dialogs
from ...helper import debug, info
-from ...constants import VERSION
+from ...constants import VERSION, CONFIG_LOCATION
from ...log import LOGFILE
+from ... import session
+
+def mail_failure_dialog(*a):
+ dialogs.mail_failure_dialog(*a)
+ return False
+
+class ShowDialog (gtk.Dialog):
+
+ def __init__(self, parent, f):
+ gtk.Dialog.__init__(self, f, parent, buttons = (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
+
+ textview = gtk.TextView()
+ textview.set_editable(False)
+ textview.modify_font(pango.FontDescription("Monospace"))
+ textview.set_size_request(gtk.gdk.screen_width()/2, gtk.gdk.screen_height()/3)
+
+ sw = gtk.ScrolledWindow();
+ sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ sw.add(textview)
+
+ self.vbox.add(sw)
+
+ textbuffer = textview.get_buffer()
+
+ with open(f) as text:
+ textbuffer.set_text(text.read())
+
+ self.vbox.show_all()
class MailInfoWindow (AbstractDialog):
- TO = "bugs@portato.necoro.net"
+ TO = "bugs@necoro.eu"
def __init__ (self, parent, tb):
AbstractDialog.__init__(self, parent)
+
+ self.files = [LOGFILE, CONFIG_LOCATION] + [s._file for s in session.sessionlist]
+ self.fileList = self.tree.get_widget("fileList")
+ self.build_file_list()
+ self.mailEntry = self.tree.get_widget("mailEntry")
self.tb = tb
self.window.show_all()
+ def build_file_list(self):
+ store = gtk.ListStore(bool, str)
+
+ for f in self.files:
+ store.append((True, f))
+
+ self.fileList.set_model(store)
+ cell = gtk.CellRendererText()
+ tCell = gtk.CellRendererToggle()
+ tCell.set_property("activatable", True)
+ tCell.connect("toggled", self.cb_file_toggled)
+
+ self.fileList.append_column(gtk.TreeViewColumn(None, tCell, active = 0))
+ self.fileList.append_column(gtk.TreeViewColumn(None, cell, text = 1))
+
+ def cb_file_toggled(self, cell, path):
+ store = self.fileList.get_model()
+ store[path][0] = not store[path][0]
+ return True
+
+ def cb_file_clicked(self, view, path, *args):
+ store = view.get_model()
+ f = store[path][1]
+
+ dialog = ShowDialog(self.window, f)
+ dialog.run()
+ dialog.destroy()
+
def set_data (self):
self.message = MIMEMultipart()
self.message["Subject"] = "[Bug Report] Bug in Portato %s" % VERSION
self.message["To"] = self.TO
+ self.message["X-Portato-Version"] = VERSION
# TO and FROM
name = self.tree.get_widget("nameEntry").get_text()
- self.addr = self.tree.get_widget("mailEntry").get_text()
+ self.addr = self.mailEntry.get_text()
if not self.addr:
self.addr = self.TO
@@ -63,40 +127,61 @@ class MailInfoWindow (AbstractDialog):
txtmsg = MIMEText(text, "plain", "utf-8")
self.message.attach(txtmsg)
- # log
- if self.tree.get_widget("logCheck").get_active():
- with open(LOGFILE, "r") as f:
- log = MIMEText(f.read(), "plain", "utf-8")
- log.add_header('Content-Disposition', 'attachment', filename='portato.log')
+ # logs
+ for (active, f) in self.fileList.get_model():
+ if active:
+ debug("Attaching '%s'", f)
+ with open(f, "r") as text:
+ log = MIMEText(text.read(), "plain", "utf-8")
+ log.add_header('Content-Disposition', 'attachment', filename=basename(f))
- self.message.attach(log)
+ self.message.attach(log)
def send (self):
try:
- debug("Connecting to server")
- server = smtplib.SMTP("mail.necoro.eu")
- debug("Sending mail")
- try:
+ for i in range(5): # try 5 times at max
+ if i > 0:
+ info(_("Retrying after waiting %d seconds."), 300)
+ time.sleep(300)
try:
+ debug("Connecting to server")
+ server = smtplib.SMTP("mail.necoro.eu")
+ debug("Sending mail")
+
+ if smtplib._have_ssl: server.starttls()
+ else: debug("TLS not supported in Python. Continuing without it.")
+
server.sendmail(self.addr, self.TO, self.message.as_string())
except smtplib.SMTPRecipientsRefused, e:
- info(_("An error occurred while sending. I think we were greylisted. The error: %s") % e)
- info(_("Retrying after waiting 60 seconds."))
- time.sleep(60)
- server.sendmail(self.addr, self.TO, self.message.as_string())
- debug("Sent")
- finally:
- server.quit()
+ if e.recipients[self.TO][0] < 500:
+ info(_("An error occurred while sending. I think we were greylisted. The error: %s") % e)
+ else: raise
+ else:
+ debug("Sent")
+ break
+ finally:
+ try:
+ server.quit()
+ except smtplib.SMTPServerDisconnected:
+ pass # ignore this
except socket.error, e:
- mail_failure_dialog("%s (Code: %s)" % (e.args[1], e.args[0]))
+ gobject.idle_add(mail_failure_dialog, "%s (Code: %s)" % (e.args[1], e.args[0]))
+ except smtplib.SMTPResponseException, e:
+ gobject.idle_add(mail_failure_dialog, "%s (Code: %s)" % (e.smtp_error, e.smtp_code))
+ except smtplib.SMTPException, e:
+ gobject.idle_add(mail_failure_dialog, e.args)
def cb_cancel_clicked (self, *args):
-
self.close()
return True
def cb_send_clicked (self, *args):
- self.set_data()
- GtkThread(target = self.send, name = "Mail Send Thread").start()
- self.close()
+ if self.mailEntry.get_text() or dialogs.no_email_dialog(self.window) == gtk.RESPONSE_OK:
+ self.set_data()
+ GtkThread(target = self.send, name = "Mail Send Thread").start()
+ self.close()
+ else:
+ self.window.present()
+ self.mailEntry.grab_focus()
+
return True
diff --git a/portato/gui/windows/main.py b/portato/gui/windows/main.py
index cd4cf24..2f5b48e 100644
--- a/portato/gui/windows/main.py
+++ b/portato/gui/windows/main.py
@@ -1434,8 +1434,8 @@ class MainWindow (Window):
"""Execute the current queue."""
if len(flags.newUseFlags) > 0:
- if not self.session.get_boolean("useflags", "dialogs"):
- self.session.set("useflags", dialogs.changed_flags_dialog(_("use flags"))[1], "dialogs")
+ if not self.session.get_bool("useflags", "dialogs"):
+ self.session.set("useflags", str(dialogs.changed_flags_dialog(_("use flags"))[1]), "dialogs")
try:
flags.write_use_flags()
except IOError, e:
@@ -1446,8 +1446,8 @@ class MainWindow (Window):
debug("new masked: %s",flags.new_masked)
debug("new unmasked: %s", flags.new_unmasked)
debug("new testing: %s", flags.newTesting)
- if not self.session.get_boolean("keywords", "dialogs"):
- self.session.set("keywords", dialogs.changed_flags_dialog(_("masking keywords"))[1], "dialogs")
+ if not self.session.get_bool("keywords", "dialogs"):
+ self.session.set("keywords", str(dialogs.changed_flags_dialog(_("masking keywords"))[1]), "dialogs")
try:
flags.write_masked()
flags.write_testing()
@@ -1511,8 +1511,15 @@ class MainWindow (Window):
gobject.idle_add(cb_idle_append, updating)
finally:
self.window.window.set_cursor(None)
-
- GtkThread(name="Update-Thread", target=__update).start()
+
+ # for some reason, I have to create the thread before displaying the dialog
+ # else the GUI hangs
+ t = GtkThread(name="Update-Thread", target=__update)
+
+ if not self.session.get_bool("update_world_warning", "dialogs"):
+ self.session.set("update_world_warning", str(dialogs.update_world_warning_dialog()[1]), "dialogs")
+
+ t.start()
return True
diff --git a/portato/session.py b/portato/session.py
index be13275..ebf49af 100644
--- a/portato/session.py
+++ b/portato/session.py
@@ -28,9 +28,6 @@ class Session (object):
A small class allowing to save certain states of a program.
This class works in a quite abstract manner, as it works with handlers, which
define what options to use out of the config file and how to apply them to the program.
-
- Note: This class currently does not work with boolean config options. If you
- want to define boolean values, use 0 and 1. This is future proof.
"""
# the current session format version
@@ -159,6 +156,15 @@ class Session (object):
return self._cfg.get(section, key)
except NoSuchThing:
return None
+
+ def get_bool (self, key, section = None):
+ if section is None: section = self._name
+ section = section.upper()
+
+ try:
+ return self._cfg.getboolean(section, key)
+ except NoSuchThing, ValueError:
+ return None
def remove (self, key, section = None):
if section is None: section = self._name