# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com>
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
import string
import re
import os
ALLOWED_KEYS = string.ascii_letters + string.digits + "`'"
class Bookmarks(object):
"""Bookmarks is a container which associates keys with bookmarks.
A key is a string with: len(key) == 1 and key in ALLOWED_KEYS.
A bookmark is an object with: bookmark == bookmarktype(str(instance))
Which is true for str or FileSystemObject. This condition is required
so bookmark-objects can be saved to and loaded from a file.
Optionally, a bookmark.go() method is used for entering a bookmark.
"""
last_mtime = None
autosave = True
load_pattern = re.compile(r"^[\d\w']:.")
def __init__(self, bookmarkfile, bookmarktype=str, autosave=False):
"""<bookmarkfile> specifies the path to the file where
bookmarks are saved in.
"""
self.autosave = autosave
self.dct = {}
self.path = bookmarkfile
self.bookmarktype = bookmarktype
def load(self):
"""Load the bookmarks from path/bookmarks"""
try:
new_dict = self._load_dict()
except OSError:
return
self._set_dict(new_dict, original=new_dict)
def delete(self, key):
"""Delete the bookmark with the given key"""
if key == '`':
key = "'"
if key in self.dct:
del self.dct[key]
if self.autosave: self.save()
def enter(self, key):
"""Enter the bookmark with the given key.
Requires the bookmark instance to have a go() method.
"""
try:
return self[key].go()
except (IndexError, KeyError, AttributeError):
return False
def update_if_outdated(self):
if self.last_mtime != self._get_mtime():
self.update()
def remember(self, value):
"""Bookmarks <value> to the key '"""
self["'"] = value
if self.autosave: self.save()
def __iter__(self):
return iter(self.dct.items())
def __getitem__(self, key):
"""Get the bookmark associated with the key"""
if key == '`':
key = "'"
if key in self.dct:
return self.dct[key]
else:
raise KeyError("Nonexistant Bookmark!")
def __setitem__(self, key, value):
"""Bookmark <value> to the key <key>.
key is expected to be a 1-character string and element of ALLOWED_KEYS.
value is expected to be a filesystemobject.
"""
if key == '`':
key = "'"
if key in ALLOWED_KEYS:
self.dct[key] = value
if self.autosave: self.save()
def __contains__(self, key):
"""Test whether a bookmark-key is defined"""
return key in self.dct
def update(self):
"""Update the bookmarks from the bookmark file.
Useful if two instances are running which define different bookmarks.
"""
try:
real_dict = self._load_dict()
real_dict_copy = real_dict.copy()
except OSError:
return
for key in set(self.dct.keys()) | set(real_dict.keys()):
# set some variables
if key in self.dct:
current = self.dct[key]
else:
current = None
if key in self.original_dict:
original = self.original_dict[key]
else:
original = None
if key in real_dict:
real = real_dict[key]
else:
real = None
# determine if there have been changes
if current == original and current != real:
continue # another ranger instance has changed the bookmark
if key not in self.dct:
del real_dict[key] # the user has deleted it
else:
real_dict[key] = current # the user has changed it
self._set_dict(real_dict, original=real_dict_copy)
def save(self):
"""Save the bookmarks to the bookmarkfile.
This is done automatically after every modification if autosave is True."""
import os
self.update()
if os.access(self.path, os.W_OK):
f = open(self.path, 'w')
for key, value in self.dct.items():
if type(key) == str\
and key in ALLOWED_KEYS:
f.write("{0}:{1}\n".format(str(key), str(value)))
f.close()
self._update_mtime()
def _load_dict(self):
import os
dct = {}
if not os.path.exists(self.path):
try:
f = open(self.path, 'w')
except:
raise OSError('Cannot read the given path')
f.close()
if os.access(self.path, os.R_OK):
f = open(self.path, 'r')
for line in f:
if self.load_pattern.match(line):
key, value = line[0], line[2:-1]
if key in ALLOWED_KEYS:
dct[key] = self.bookmarktype(value)
f.close()
return dct
else:
raise OSError('Cannot read the given path')
def _set_dict(self, dct, original):
if original is None:
original = {}
self.dct.clear()
self.dct.update(dct)
self.original_dict = original
self._update_mtime()
def _get_mtime(self):
import os
try:
return os.stat(self.path).st_mtime
except OSError:
return None
def _update_mtime(self):
self.last_mtime = self._get_mtime()