diff --git a/.bzrignore b/.bzrignore
index 9ba0b609f..59bafa945 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -3,5 +3,6 @@ build
.project
.pydevproject
.settings
+.ropeproject
tags
*.pyc
diff --git a/lutris/config.py b/lutris/config.py
index bb3c84c6f..db7c4fe87 100644
--- a/lutris/config.py
+++ b/lutris/config.py
@@ -15,14 +15,17 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
+"""Handle the basic configuration of Lutris."""
import os
import yaml
import logging
from os.path import join
+import lutris.pga as pga
import lutris.constants as constants
from lutris.settings import PGA_PATH
+from lutris.constants import CONFIG_EXTENSION, GAME_CONFIG_PATH
def check_config():
@@ -43,7 +46,6 @@ def check_config():
os.mkdir(config_path)
if not os.path.isfile(PGA_PATH):
- import lutris.pga as pga
pga.create()
@@ -69,13 +71,14 @@ class LutrisConfig():
#By default config type is system, it can also be runner and game
#this means that when you call lutris_config_instance["key"] is will
#pick up the right configuration depending of config_type
- self.config_type = "system"
if runner:
self.runner = runner
self.config_type = "runner"
elif game:
self.game = game
self.config_type = "game"
+ else:
+ self.config_type = "system"
#Read system configuration
if os.path.exists(constants.system_config_full_path):
@@ -87,31 +90,32 @@ class LutrisConfig():
file(constants.system_config_full_path, "w+")
if self.game:
- game_config_full_path = os.path.join(constants.GAME_CONFIG_PATH,
- self.game + constants.CONFIG_EXTENSION)
+ game_config_full_path = join(GAME_CONFIG_PATH,
+ self.game + CONFIG_EXTENSION)
if os.path.exists(game_config_full_path):
try:
content = file(game_config_full_path, 'r').read()
self.game_config = yaml.load(content)
self.runner = self.game_config["runner"]
except yaml.scanner.ScannerError:
- logging.debug("error parsing config file %s" % game_config_full_path)
+ logging.debug("error parsing config file %s",
+ game_config_full_path)
except yaml.parser.ParserError:
- logging.debug("error parsing config file %s" % game_config_full_path)
+ logging.debug("error parsing config file %s",
+ game_config_full_path)
except KeyError:
logging.debug("Runner key is mandatory !")
if self.runner:
- runner_config_full_path = os.path.join(constants.runner_config_path,
- self.runner + constants.CONFIG_EXTENSION)
+ runner_config_full_path = join(constants.runner_config_path,
+ self.runner + CONFIG_EXTENSION)
if os.path.exists(runner_config_full_path):
- self.runner_config = yaml.load(file(runner_config_full_path, 'r').read())
+ yaml_content = file(runner_config_full_path, 'r').read()
+ self.runner_config = yaml.load(yaml_content)
self.update_global_config()
def __getitem__(self, key):
- """Allow to access config data directly by keys.
-
- """
+ """Allow to access config data directly by keys."""
if self.config_type == "game":
value = self.game_config[key]
elif self.config_type == "runner":
@@ -130,6 +134,7 @@ class LutrisConfig():
self.update_global_config()
def update_global_config(self):
+ """Update the global config dict."""
for key in self.system_config.keys():
if key in self.config:
self.config[key].update(self.system_config[key])
@@ -150,34 +155,35 @@ class LutrisConfig():
self.config[key] = self.game_config[key]
def get_real_name(self):
+ """Return the real name of a game."""
+
return self.config["realname"]
def remove(self, game_name):
- logging.debug("removing %s" % game_name)
- os.remove(join(constants.GAME_CONFIG_PATH,
- game_name + constants.CONFIG_EXTENSION))
+ """Delete the configuration file from disk."""
+
+ logging.debug("removing %s", game_name)
+ os.remove(join(GAME_CONFIG_PATH,
+ game_name + CONFIG_EXTENSION))
def is_valid(self):
- """Check the config data and return True if config is ok.
+ """Check the config data and return True if config is ok."""
- """
- try:
- self.runner_name = self.game_config["runner"]
- except KeyError:
+ if "runner" in self.game_config:
+ return True
+ else:
print "Error in %s config file : No runner" % self.game
return False
- return True
def save(self, runner_type=None):
"""Save configuration file
The way to save config files can be set by the type argument
or with self.config_type
-
"""
self.update_global_config()
- logging.debug("Saving config (type %s)" % runner_type)
+ logging.debug("Saving config (type %s)", runner_type)
logging.debug(self.config)
if runner_type is None:
runner_type = self.config_type
@@ -187,15 +193,16 @@ class LutrisConfig():
file(constants.system_config_full_path, "w").write(yaml_config)
elif runner_type == "runner":
runner_config_path = join(constants.runner_config_path,
- self.runner + constants.CONFIG_EXTENSION)
+ self.runner + CONFIG_EXTENSION)
file(runner_config_path, "w").write(yaml_config)
elif runner_type == "game":
if not self.game:
self.game = self.config["runner"] \
+ "-" + self.config["realname"].replace(" ", "_")
- self.game_config_path = join(constants.GAME_CONFIG_PATH,
- self.game.replace('/', '_') + constants.CONFIG_EXTENSION)
- config_file = file(self.game_config_path, "w")
+ game_config_path = join(GAME_CONFIG_PATH,
+ self.game.replace('/', '_') + \
+ CONFIG_EXTENSION)
+ config_file = file(game_config_path, "w")
config_file.write(yaml_config)
return self.game
else:
@@ -203,7 +210,7 @@ class LutrisConfig():
print "i don't know how to save this yet"
def get_path(self, default=None):
- """Get the path to install games for a given runner
+ """Get the path to install games for a given runner.
Return False if it can't find an installation path
"""
diff --git a/lutris/gui/widgets.py b/lutris/gui/widgets.py
index 0ef6d10ce..0d1090a8b 100644
--- a/lutris/gui/widgets.py
+++ b/lutris/gui/widgets.py
@@ -18,12 +18,14 @@
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
###############################################################################
+"""Misc widgets used in the GUI."""
import os
import gtk
import gobject
import pango
import Image
+
from lutris.downloader import Downloader
from lutris.constants import COVER_PATH, DATA_PATH
import lutris.constants
@@ -47,37 +49,46 @@ class GameTreeView(gtk.TreeView):
model = gtk.ListStore(str, gtk.gdk.Pixbuf, str)
model.set_sort_column_id(0, gtk.SORT_ASCENDING)
self.set_model(model)
- tp = gtk.CellRendererPixbuf()
- column = gtk.TreeViewColumn("Runner", tp, pixbuf=self.COL_ICON)
+ image_cell = gtk.CellRendererPixbuf()
+ column = gtk.TreeViewColumn("Runner", image_cell, pixbuf=self.COL_ICON)
self.append_column(column)
- tr = gtk.CellRendererText()
- tr.set_property("ellipsize", pango.ELLIPSIZE_END)
- column = gtk.TreeViewColumn("Game", tr, markup=self.COL_TEXT)
+ text_cell = gtk.CellRendererText()
+ text_cell.set_property("ellipsize", pango.ELLIPSIZE_END)
+ column = gtk.TreeViewColumn("Game", text_cell, markup=self.COL_TEXT)
self.append_column(column)
- for game in sorted(games):
- self.add_row(game)
+ if games:
+ for game in sorted(games):
+ self.add_row(game)
def add_row(self, game):
+ """Add a game in the treeview."""
+
model = self.get_model()
- s = "%s \n%s" % (game['name'], game['runner'])
+ label = "%s \n%s" % \
+ (game['name'], game['runner'])
icon_path = os.path.join(lutris.constants.DATA_PATH,
'media/runner_icons',
game['runner'] + '.png')
pix = gtk.gdk.pixbuf_new_from_file_at_size(icon_path,
ICON_SIZE, ICON_SIZE)
- row = model.append([game['id'], pix, s, ])
+ row = model.append([game['id'], pix, label, ])
return row
def remove_row(self, model_iter):
+ """Remove a game from the treeview."""
+
model = self.get_model()
model.remove(model_iter)
def sort_rows(self):
+ """Sort the game list."""
+
model = self.get_model()
gtk.TreeModelSort(model)
class GameCover(gtk.Image):
+ """Widget displaing the selected game's cover"""
def __init__(self, parent=None):
super(GameCover, self).__init__()
self.parent_window = parent
@@ -85,14 +96,15 @@ class GameCover(gtk.Image):
self.connect('drag_data_received', self.on_cover_drop)
def set_game_cover(self, name):
- coverFile = os.path.join(COVER_PATH, name + ".jpg")
- if os.path.exists(coverFile):
+ """Change the cover currently displayed."""
+ cover_file = os.path.join(COVER_PATH, name + ".jpg")
+ if os.path.exists(cover_file):
#Resize the image
- cover_pixbuf = gtk.gdk.pixbuf_new_from_file(coverFile)
+ cover_pixbuf = gtk.gdk.pixbuf_new_from_file(cover_file)
dest_w = 250.0
- h = cover_pixbuf.get_height()
- w = cover_pixbuf.get_width()
- dest_h = h * (dest_w / w)
+ height = cover_pixbuf.get_height()
+ width = cover_pixbuf.get_width()
+ dest_h = height * (dest_w / width)
self.set_from_pixbuf(cover_pixbuf.scale_simple(
int(dest_w),
int(dest_h),
@@ -103,9 +115,11 @@ class GameCover(gtk.Image):
self.set_from_file(os.path.join(DATA_PATH, "media/background.png"))
def desactivate_drop(self):
+ """Deactivate DnD for the widget."""
self.drag_dest_unset()
def activate_drop(self):
+ """Activate DnD for the widget."""
targets = [('text/plain', 0, 0),
('text/uri-list', 0, 0),
('text/html', 0, 0),
@@ -115,6 +129,7 @@ class GameCover(gtk.Image):
gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)
def on_cover_drop(self, widget, context, x, y, selection, target, ts):
+ """Take action based on a drop on the widget."""
# TODO : Change mouse cursor if no game is selected
# of course, it must not be handled here
file_path = selection.data.strip()
@@ -138,6 +153,7 @@ class GameCover(gtk.Image):
class DownloadProgressBox(gtk.HBox):
+ """Progress bar used to monitor a file download."""
__gsignals__ = {'complete': (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,)),
@@ -147,6 +163,7 @@ class DownloadProgressBox(gtk.HBox):
def __init__(self, params, cancelable=True):
gtk.HBox.__init__(self, False, 2)
+ self.downloader = None
self.progressbar = gtk.ProgressBar()
self.progressbar.show()
self.pack_start(self.progressbar, True)
@@ -161,13 +178,16 @@ class DownloadProgressBox(gtk.HBox):
self.dest = params['dest']
def start(self):
+ """Start downloading a file."""
print "starting to download %s" % self.url
self.downloader = Downloader(self.url, self.dest)
- self.timer_id = gobject.timeout_add(100, self.progress)
+ timer_id = gobject.timeout_add(100, self.progress)
self.cancel_button.set_sensitive(True)
self.downloader.start()
+ return timer_id
def progress(self):
+ """Show download progress."""
progress = min(self.downloader.progress, 1)
self.progressbar.set_fraction(progress)
percent = progress * 100
@@ -179,10 +199,12 @@ class DownloadProgressBox(gtk.HBox):
return False
return True
- def __stop_download(self, widget):
+ def __stop_download(self):
+ """Stop the current download."""
self.downloader.kill = True
self.cancel_button.set_sensitive(False)
def cancel(self):
+ """Cancel the current download."""
print "cancelling download"
self.downloader.kill()
diff --git a/lutris/pga.py b/lutris/pga.py
index b45b28ef3..84e0911ac 100644
--- a/lutris/pga.py
+++ b/lutris/pga.py
@@ -15,8 +15,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
+"""Personnal Game Archive module. Handle local database of user's games."""
-
+import unicodedata
import sqlite3
import re
@@ -24,48 +25,53 @@ from lutris.settings import PGA_PATH
def slugify(value):
- """
+ """Remove special characters from a string and slugify it.
+
Normalizes string, converts to lowercase, removes non-alpha characters,
and converts spaces to hyphens.
"""
- import unicodedata
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
return re.sub('[-\s]+', '-', value)
def connect():
+ """Connect to the local PGA database."""
return sqlite3.connect(PGA_PATH)
def create():
- c = connect()
- q = 'create table games (name text, slug text, machine text, runner text)'
- c.execute(q)
- c.commit()
- c.close()
+ """Create the local PGA database."""
+ con = connect()
+ query = 'create table games ' + \
+ '(name text, slug text, machine text, runner text)'
+ con.execute(query)
+ con.commit()
+ con.close()
def get_games(name_filter=None):
- c = connect()
- cur = c.cursor()
+ """Get the list of every game in database."""
+ con = connect()
+ cur = con.cursor()
if filter is not None:
- q = "select * from where name LIKE = ?"
- rows = cur.execute(q, (name_filter, ))
+ query = "select * from where name LIKE = ?"
+ rows = cur.execute(query, (name_filter, ))
else:
- q = "select * from games"
- rows = cur.execute(q)
+ query = "select * from games"
+ rows = cur.execute(query)
results = rows.fetchall()
cur.close()
- c.close()
+ con.close()
return results
def add_game(name, machine, runner):
+ """Adds a game to the PGA database."""
slug = slugify(name)
- c = connect()
- c.execute("""insert into games(name, slug, machine, runner) values
+ con = connect()
+ con.execute("""insert into games(name, slug, machine, runner) values
(?, ?, ?, ?)""", (name, slug, machine, runner))
- c.commit()
- c.close()
+ con.commit()
+ con.close()
diff --git a/lutris/util/log.py b/lutris/util/log.py
index fd5b25f79..8e0a6e36f 100644
--- a/lutris/util/log.py
+++ b/lutris/util/log.py
@@ -1,19 +1,22 @@
-from os.path import join, isdir, realpath
-from os import makedirs
+"""Utility module for creating an application wide logger."""
import logging
import logging.handlers
import xdg.BaseDirectory
-cache_dir = realpath(join(xdg.BaseDirectory.xdg_cache_home, "lutris"))
-if not isdir(cache_dir):
- makedirs(cache_dir)
+from os import makedirs
+from os.path import join, isdir, realpath
-LOG_FILENAME = join(cache_dir, "lutris.log")
+CACHE_DIR = realpath(join(xdg.BaseDirectory.xdg_cache_home, "lutris"))
+if not isdir(CACHE_DIR):
+ makedirs(CACHE_DIR)
+
+LOG_FILENAME = join(CACHE_DIR, "lutris.log")
loghandler = logging.handlers.RotatingFileHandler(LOG_FILENAME,
- maxBytes=20971520, backupCount=5)
+ maxBytes=20971520,
+ backupCount=5)
logger = logging.getLogger('Lutris')
-logformatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+logformatter = logging.Formatter(log_format)
loghandler.setFormatter(logformatter)
logger.setLevel(logging.INFO)
logger.addHandler(loghandler)
-