Commit 78e5d293 authored by Jack Leigh's avatar Jack Leigh

Try for a GUI/non-GUI split like upstream

parent ee4e0e39
"""
A temporary holding zone for troublesome things.
"""
import datetime
import functools
import os
from gi.repository import GObject, Gtk, Soup
from gtimelog.timelog import TaskList
from gtimelog.tzoffset import TZOffset
# Global HTTP stuff
class Authenticator(object):
# try to use GNOME Keyring if available
try:
import gnomekeyring
except ImportError:
gnomekeyring = None
def __init__(self):
object.__init__(self)
self.pending = []
self.lookup_in_progress = False
def find_in_keyring(self, uri, callback):
"""Attempts to load a username and password from the keyring, if the
keyring is available"""
if self.gnomekeyring is None:
callback(None, None)
return
username = None
password = None
try:
# FIXME - would be nice to make all keyring calls async, to dodge
# the possibility of blocking the UI. The code is all set up for
# that, but there's no easy way to use the keyring asynchronously
# from Python (as of Gnome 3.2)...
l = self.gnomekeyring.find_network_password_sync(
None, # user
uri.get_host(), # domain
uri.get_host(), # server
None, # object
uri.get_scheme(), # protocol
None, # authtype
uri.get_port()) # port
except self.gnomekeyring.NoMatchError:
# We didn't find any passwords, just continue
pass
except self.gnomekeyring.NoKeyringDaemonError:
pass
except IOError:
gnomekeyring = None
except gnomekeyring.IOError: # thanks, gnomekeyring python binding maker
gnomekeyring = None
else:
l = l[-1] # take the last key (Why?)
username = l['user']
password = l['password']
callback(username, password)
def save_to_keyring(self, uri, username, password):
try:
self.gnomekeyring.set_network_password_sync(
None, # keyring
username, # user
uri.get_host(), # domain
uri.get_host(), # server
None, # object
uri.get_scheme(), # protocol
None, # authtype
uri.get_port(), # port
password) # password
except self.gnomekeyring.NoKeyringDaemonError:
pass
def ask_the_user(self, auth, uri, callback):
"""Pops up a username/password dialog for uri"""
d = Gtk.Dialog()
d.set_title('Authentication Required')
d.set_resizable(False)
grid = Gtk.Grid()
grid.set_border_width(5)
grid.set_row_spacing(5)
grid.set_column_spacing(5)
l = Gtk.Label('Authentication is required for the domain "%s".' % auth.get_realm())
l.set_line_wrap(True)
grid.attach(l, 0, 0, 2, 1)
username_label = Gtk.Label("Username:")
grid.attach_next_to(username_label, l, Gtk.PositionType.BOTTOM, 1, 1)
password_label = Gtk.Label("Password:")
grid.attach_next_to(password_label, username_label, Gtk.PositionType.BOTTOM, 1, 1)
userentry = Gtk.Entry()
userentry.set_hexpand(True)
passentry = Gtk.Entry()
passentry = Gtk.Entry()
passentry.set_visibility(False)
userentry.set_activates_default(True)
passentry.set_activates_default(True)
grid.attach_next_to(userentry, username_label, Gtk.PositionType.RIGHT, 1, 1)
grid.attach_next_to(passentry, password_label, Gtk.PositionType.RIGHT, 1, 1)
if self.gnomekeyring:
savepasstoggle = Gtk.CheckButton("Save Password in Keyring")
savepasstoggle.set_active(True)
grid.attach_next_to(savepasstoggle, passentry,
Gtk.PositionType.BOTTOM, 1, 1)
d.vbox.pack_start(grid, True, True, 0)
d.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
ok_button = d.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
d.set_default(ok_button)
def update_ok_sensitivity(*args):
ok_button.set_sensitive(userentry.get_text() and passentry.get_text())
userentry.connect('notify::text', update_ok_sensitivity)
passentry.connect('notify::text', update_ok_sensitivity)
update_ok_sensitivity()
def on_response(dialog, r):
save_to_keyring = self.gnomekeyring and savepasstoggle.get_active()
if r == Gtk.ResponseType.OK:
username = userentry.get_text()
password = passentry.get_text()
if username and password and save_to_keyring:
self.save_to_keyring(uri, username, password)
else:
username = None
password = None
d.destroy()
callback(username, password)
d.connect('response', on_response)
d.show_all()
def find_password(self, auth, uri, retrying, callback):
def keyring_callback(username, password):
# If not found, ask the user for it
if username is None or retrying:
GObject.idle_add(lambda: self.ask_the_user(auth, uri, callback))
else:
callback(username, password)
self.find_in_keyring(uri, keyring_callback)
def http_auth_cb(self, session, message, auth, retrying, *args):
session.pause_message(message)
self.pending.insert(0, (session, message, auth, retrying))
self.maybe_pop_queue()
def maybe_pop_queue(self):
# I don't think we need any locking, because GIL.
if self.lookup_in_progress:
return
try:
(session, message, auth, retrying) = self.pending.pop()
except IndexError:
pass
else:
self.lookup_in_progress = True
uri = message.get_uri()
self.find_password(
auth,
uri,
retrying,
callback=functools.partial(
self.http_auth_finish, session, message, auth)
)
def http_auth_finish(self, session, message, auth, username, password):
if username and password:
auth.authenticate(username, password)
session.unpause_message(message)
self.lookup_in_progress = False
self.maybe_pop_queue()
soup_session = Soup.SessionAsync()
authenticator = Authenticator()
soup_session.connect('authenticate', authenticator.http_auth_cb)
class RemoteTaskList(TaskList):
"""Task list stored on a remote server.
Keeps a cached copy of the list in a local file, so you can use it offline.
"""
def __init__(self, settings, cache_filename):
self.url = settings.task_list_url
TaskList.__init__(self, cache_filename)
self.settings = settings
#Even better would be to use the Expires: header on the list itself I suppose...
self.max_age = settings.task_list_expiry
mtime = self.get_mtime()
if mtime:
self.last_time = datetime.datetime.fromtimestamp(mtime, TZOffset())
else:
self.last_time = datetime.datetime.now(TZOffset()) - self.max_age * 2
def check_reload(self):
"""Check whether the task list needs to be reloaded.
Download the task list if this is the first time, and a cached copy is
not found.
Returns True if the file was reloaded.
"""
if datetime.datetime.now(TZOffset()) - self.last_time > self.max_age:
self.last_time = datetime.datetime.now(TZOffset())
#Always redownload if past the expiry date.
self.download()
return True
return TaskList.check_reload(self)
def download_finished_cb(self, session, message, *args):
if message.status_code == 200:
try:
out = open(self.filename, 'w')
out.write(message.response_body.data)
except IOError, e:
print e
if self.error_callback:
self.error_callback()
finally:
out.close()
self.load_file()
else:
if self.error_callback:
self.error_callback()
def download(self):
"""Download the task list from the server."""
if self.loading_callback:
self.loading_callback()
if not os.path.exists(self.settings.server_cert):
self.error_callback("Certificate file not found")
return
message = Soup.Message.new('GET', self.url)
soup_session.queue_message(message, self.download_finished_cb, None)
def load_file(self):
"""Load the file in the UI"""
self.load()
if self.loaded_callback:
self.loaded_callback()
def reload(self):
"""Reload the task list."""
self.download()
This diff is collapsed.
"""
Settings for GTimeLog
"""
import ConfigParser
import datetime
import os
from gtimelog.timelog import parse_time, parse_timedelta
from gtimelog.tzoffset import TZOffset
class Settings(object):
"""Configurable settings for GTimeLog."""
# Insane defaults
email = 'activity-list@example.com'
name = 'Anonymous'
editor = 'xdg-open'
mailer = 'x-terminal-emulator -e mutt -H %s'
spreadsheet = 'xdg-open %s'
enable_gtk_completion = True # False enables gvim-style completion
show_time_label = True
hours = 8
virtual_midnight = datetime.time(2, 0, tzinfo=TZOffset())
task_list_url = ''
task_list_expiry = '24 hours'
show_office_hours = True
report_to_url = ""
remind_idle = '10 minutes'
server_cert = ''
# Should we create '-automatic arrival-' marks in the log?
autoarrival = True
def _config(self):
config = ConfigParser.RawConfigParser()
config.add_section('gtimelog')
config.set('gtimelog', 'list-email', self.email)
config.set('gtimelog', 'name', self.name)
config.set('gtimelog', 'editor', self.editor)
config.set('gtimelog', 'mailer', self.mailer)
config.set('gtimelog', 'spreadsheet', self.spreadsheet)
config.set('gtimelog', 'gtk-completion',
str(self.enable_gtk_completion))
config.set('gtimelog', 'show-time-label',
str(self.show_time_label))
config.set('gtimelog', 'hours', str(self.hours))
config.set('gtimelog', 'virtual_midnight',
self.virtual_midnight.strftime('%H:%M'))
config.set('gtimelog', 'task_list_url', self.task_list_url)
config.set('gtimelog', 'task_list_expiry', self.task_list_expiry)
config.set('gtimelog', 'show_office_hours',
str(self.show_office_hours))
config.set('gtimelog', 'report_to_url', self.report_to_url)
config.set('gtimelog', 'remind_idle', self.remind_idle)
config.set('gtimelog', 'server_cert', self.server_cert)
config.set('gtimelog', 'autoarrival', str(self.autoarrival))
return config
def load(self, filename):
config = self._config()
config.read([filename])
self.email = config.get('gtimelog', 'list-email')
self.name = config.get('gtimelog', 'name')
self.editor = config.get('gtimelog', 'editor')
self.mailer = config.get('gtimelog', 'mailer')
self.spreadsheet = config.get('gtimelog', 'spreadsheet')
self.enable_gtk_completion = config.getboolean('gtimelog',
'gtk-completion')
self.show_time_label = config.getboolean('gtimelog',
'show-time-label')
self.hours = config.getfloat('gtimelog', 'hours')
self.virtual_midnight = parse_time(config.get('gtimelog',
'virtual_midnight'))
self.task_list_url = config.get('gtimelog', 'task_list_url')
self.task_list_expiry = parse_timedelta(config.get('gtimelog', 'task_list_expiry'))
self.show_office_hours = config.getboolean('gtimelog',
'show_office_hours')
self.report_to_url = config.get('gtimelog', 'report_to_url')
self.remind_idle = parse_timedelta(config.get('gtimelog', 'remind_idle'))
self.server_cert = os.path.expanduser(config.get('gtimelog', 'server_cert'))
self.autoarrival = config.getboolean('gtimelog', 'autoarrival')
#Anything shorter than 2 minutes will tick every minute
#if self.remind_idle > datetime.timedelta (0, 120):
# self.remind_idle = datetime.timedelta (0, 120)
def save(self, filename):
config = self._config()
f = file(filename, 'w')
try:
config.write(f)
finally:
f.close()
......@@ -10,7 +10,7 @@ import unittest
def doctest_as_hours():
"""Tests for as_hours
>>> from gtimelog.main import as_hours
>>> from gtimelog.timelog import as_hours
>>> from datetime import timedelta
>>> as_hours(timedelta(0))
0.0
......@@ -26,7 +26,7 @@ def doctest_as_hours():
def doctest_format_duration():
"""Tests for format_duration.
>>> from gtimelog.main import format_duration
>>> from gtimelog.timelog import format_duration
>>> from datetime import timedelta
>>> format_duration(timedelta(0))
'0 h 0 min'
......@@ -40,7 +40,7 @@ def doctest_format_duration():
def doctest_format_short():
"""Tests for format_duration_short.
>>> from gtimelog.main import format_duration_short
>>> from gtimelog.timelog import format_duration_short
>>> from datetime import timedelta
>>> format_duration_short(timedelta(0))
'0:00'
......@@ -58,7 +58,7 @@ def doctest_format_short():
def doctest_format_duration_long():
"""Tests for format_duration_long.
>>> from gtimelog.main import format_duration_long
>>> from gtimelog.timelog import format_duration_long
>>> from datetime import timedelta
>>> format_duration_long(timedelta(0))
'0 min'
......@@ -78,7 +78,7 @@ def doctest_format_duration_long():
def doctest_parse_datetime():
"""Tests for parse_datetime
>>> from gtimelog.main import parse_datetime
>>> from gtimelog.timelog import parse_datetime
>>> parse_datetime('2005-02-03 02:13')
datetime.datetime(2005, 2, 3, 2, 13, tzinfo=0)
>>> parse_datetime('2005-02-03 02:13 +0800')
......@@ -95,7 +95,7 @@ def doctest_parse_datetime():
def doctest_parse_time():
"""Tests for parse_time
>>> from gtimelog.main import parse_time
>>> from gtimelog.timelog import parse_time
>>> parse_time('02:13')
datetime.time(2, 13, tzinfo=0)
>>> parse_time('xyzzy')
......@@ -108,7 +108,7 @@ def doctest_parse_time():
def doctest_parse_timedelta():
"""Tests for parse_timedelta
>>> from gtimelog.main import parse_timedelta
>>> from gtimelog.timelog import parse_timedelta
>>> parse_timedelta('10s 14h 3d')
datetime.timedelta(3, 50410)
>>> #parse_timedelta('14 days 240 MINUTES')
......@@ -130,7 +130,7 @@ def doctest_virtual_day():
"""Tests for virtual_day
>>> from datetime import datetime, time
>>> from gtimelog.main import virtual_day
>>> from gtimelog.timelog import virtual_day
Virtual midnight
......@@ -155,7 +155,7 @@ def doctest_different_days():
"""Tests for different_days
>>> from datetime import datetime, time
>>> from gtimelog.main import different_days
>>> from gtimelog.timelog import different_days
Virtual midnight
......@@ -175,7 +175,7 @@ def doctest_different_days():
def doctest_first_of_month():
"""Tests for first_of_month
>>> from gtimelog.main import first_of_month
>>> from gtimelog.timelog import first_of_month
>>> from datetime import date, timedelta
>>> first_of_month(date(2007, 1, 1))
......@@ -210,7 +210,7 @@ def doctest_first_of_month():
def doctest_next_month():
"""Tests for next_month
>>> from gtimelog.main import next_month
>>> from gtimelog.timelog import next_month
>>> from datetime import date, timedelta
>>> next_month(date(2007, 1, 1))
......@@ -246,7 +246,7 @@ def doctest_next_month():
def doctest_uniq():
"""Tests for uniq
>>> from gtimelog.main import uniq
>>> from gtimelog.timelog import uniq
>>> uniq(['a', 'b', 'b', 'c', 'd', 'b', 'd'])
['a', 'b', 'c', 'd', 'b', 'd']
>>> uniq(['a'])
......@@ -263,7 +263,7 @@ def doctest_TimeWindow_monthly_report():
>>> from datetime import datetime, time
>>> from tempfile import NamedTemporaryFile
>>> from gtimelog.main import TimeWindow, TZOffset
>>> from gtimelog.timelog import TimeWindow, TZOffset
>>> vm = time(2, 0, tzinfo=TZOffset())
>>> min = datetime(2007, 9, 1, tzinfo=TZOffset())
......@@ -310,7 +310,7 @@ def doctest_TimeWindow_monthly_report():
def doctest_TimeWindow_to_csv_daily():
r"""Tests for TimeWindow.to_csv_daily
>>> from gtimelog.main import TZOffset
>>> from gtimelog.timelog import TZOffset
>>> from datetime import datetime, time
>>> min = datetime(2008, 6, 1, tzinfo=TZOffset())
>>> max = datetime(2008, 7, 1, tzinfo=TZOffset())
......@@ -326,7 +326,7 @@ def doctest_TimeWindow_to_csv_daily():
... 2008-06-05 13:15: something
... ''')
>>> from gtimelog.main import TimeWindow
>>> from gtimelog.timelog import TimeWindow
>>> window = TimeWindow(sampledata, min, max, vm)
>>> import sys
......
This diff is collapsed.
import datetime
import time
class TZOffset (datetime.tzinfo):
ZERO = datetime.timedelta(0)
def __init__(self, offset=None):
# offset is an integer in 'hhmm' form. That is, UTC +5.5 = 530
if offset is not None:
offset = int(offset)
else:
# time.timezone is in seconds back to UTC
if time.daylight and time.localtime().tm_isdst:
offset = -time.altzone / 36
else:
offset = -time.timezone / 36
# (offset % 100) needs to be adjusted to be in minutes
# now (e.g. UTC +5.5 => offset = 550, when it should
# be 530) - yes, treating hhmm as an integer is a pain
m = ((offset % 100) * 60) / 100
offset -= (offset % 100) - m
self._offset = offset
h = offset / 100
m = offset % 100
self._offsetdelta = datetime.timedelta(hours=h, minutes=m)
def utcoffset(self, dt):
return self._offsetdelta
def dst(self, dt):
return self.ZERO
def tzname(self, dt):
return str(self._offset)
def __repr__(self):
return self.tzname(False)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment