# Copyright 2009 One Laptop Per Child
# Author: Sayamindu Dasgupta <sayamindu@laptop.org>
#
# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import logging
import os
import shutil
import sqlite3
import time
import base64
import json
from gi.repository import GObject
from sugar3 import profile
from readbookmark import Bookmark
_logger = logging.getLogger('read-activity')
def _init_db():
dbdir = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'], 'data')
dbpath = os.path.join(dbdir, 'read_v1.db')
olddbpath = os.path.join(dbdir, 'read.db')
srcpath = os.path.join(os.environ['SUGAR_BUNDLE_PATH'], 'read_v1.db')
#Situation 0: Db is existent
if os.path.exists(dbpath):
return dbpath
#Situation 1: DB is non-existent at all
if not os.path.exists(dbpath) and not os.path.exists(olddbpath):
try:
os.makedirs(dbdir)
except:
pass
shutil.copy(srcpath, dbpath)
return dbpath
#Situation 2: DB is outdated
if not os.path.exists(dbpath) and os.path.exists(olddbpath):
shutil.copy(olddbpath, dbpath)
conn = sqlite3.connect(dbpath)
conn.execute(
"CREATE TABLE temp_bookmarks AS SELECT md5, page, " +
"title 'content', timestamp, user, color, local FROM bookmarks")
conn.execute("ALTER TABLE bookmarks RENAME TO bookmarks_old")
conn.execute("ALTER TABLE temp_bookmarks RENAME TO bookmarks")
conn.execute("DROP TABLE bookmarks_old")
conn.commit()
conn.close()
return dbpath
# Should not reach this point
return None
def _init_db_highlights(conn):
conn.execute('CREATE TABLE IF NOT EXISTS HIGHLIGHTS ' +
'(md5 TEXT, page INTEGER, ' +
'init_pos INTEGER, end_pos INTEGER)')
conn.commit()
def _init_db_previews(conn):
conn.execute('CREATE TABLE IF NOT EXISTS PREVIEWS ' +
'(md5 TEXT, page INTEGER, ' +
'preview)')
conn.commit()
class BookmarkManager(GObject.GObject):
__gsignals__ = {
'added_bookmark': (GObject.SignalFlags.RUN_FIRST,
None, ([int, str])),
'removed_bookmark': (GObject.SignalFlags.RUN_FIRST,
None, ([int])), }
def __init__(self, filehash):
GObject.GObject.__init__(self)
self._filehash = filehash
dbpath = _init_db()
assert dbpath is not None
self._conn = sqlite3.connect(dbpath)
_init_db_highlights(self._conn)
_init_db_previews(self._conn)
self._conn.text_factory = lambda x: unicode(x, "utf-8", "ignore")
self._bookmarks = []
self._populate_bookmarks()
self._highlights = {0: []}
self._populate_highlights()
self._user = profile.get_nick_name()
self._color = profile.get_color().to_string()
def add_bookmark(self, page, content, local=1):
logging.debug('add_bookmark page %d', page)
# locale = 0 means that this is a bookmark originally
# created by the person who originally shared the file
timestamp = time.time()
t = (self._filehash, page, content, timestamp, self._user,
self._color, local)
self._conn.execute('insert into bookmarks values ' +
'(?, ?, ?, ?, ?, ?, ?)', t)
self._conn.commit()
self._resync_bookmark_cache()
title = json.loads(content)['title']
self.emit('added_bookmark', page + 1, title)
def del_bookmark(self, page):
# We delete only the locally made bookmark
logging.debug('del_bookmark page %d', page)
t = (self._filehash, page, self._user)
self._conn.execute('delete from bookmarks ' +
'where md5=? and page=? and user=?', t)
self._conn.commit()
self._del_bookmark_preview(page)
self._resync_bookmark_cache()
self.emit('removed_bookmark', page + 1)
def add_bookmark_preview(self, page, preview):
logging.debug('add_bookmark_preview page %d', page)
t = (self._filehash, page, base64.b64encode(preview))
self._conn.execute('insert into previews values ' +
'(?, ?, ?)', t)
self._conn.commit()
def _del_bookmark_preview(self, page):
logging.debug('del_bookmark_preview page %d', page)
t = (self._filehash, page)
self._conn.execute('delete from previews ' +
'where md5=? and page=?', t)
self._conn.commit()
def get_bookmark_preview(self, page):
logging.debug('get_bookmark page %d', page)
rows = self._conn.execute('select preview from previews ' +
'where md5=? and page=?',
(self._filehash, page))
for row in rows:
return base64.b64decode(row[0])
return None
def _populate_bookmarks(self):
# TODO: Figure out if caching the entire set of bookmarks
# is a good idea or not
rows = self._conn.execute('select * from bookmarks ' +
'where md5=? order by page',
(self._filehash, ))
for row in rows:
self._bookmarks.append(Bookmark(row))
logging.debug('loading %d bookmarks', len(self._bookmarks))
def get_bookmarks(self):
return self._bookmarks
def get_bookmarks_for_page(self, page):
bookmarks = []
for bookmark in self._bookmarks:
if bookmark.belongstopage(page):
bookmarks.append(bookmark)
return bookmarks
def _resync_bookmark_cache(self):
# To be called when a new bookmark has been added/removed
self._bookmarks = []
self._populate_bookmarks()
def get_prev_bookmark_for_page(self, page, wrap=True):
if not len(self._bookmarks):
return None
if page <= self._bookmarks[0].page_no and wrap:
return self._bookmarks[-1]
else:
for i in range(page - 1, -1, -1):
for bookmark in self._bookmarks:
if bookmark.belongstopage(i):
return bookmark
return None
def get_next_bookmark_for_page(self, page, wrap=True):
if not len(self._bookmarks):
return None
if page >= self._bookmarks[-1].page_no and wrap:
return self._bookmarks[0]
else:
for i in range(page + 1, self._bookmarks[-1].page_no + 1):
for bookmark in self._bookmarks:
if bookmark.belongstopage(i):
return bookmark
return None
def get_highlights(self, page):
try:
return self._highlights[page]
except KeyError:
self._highlights[page] = []
return self._highlights[page]
def add_highlight(self, page, highlight_tuple):
logging.error('Adding hg page %d %s' % (page, highlight_tuple))
self.get_highlights(page).append(highlight_tuple)
t = (self._filehash, page, highlight_tuple[0], highlight_tuple[1])
self._conn.execute('insert into highlights values ' +
'(?, ?, ?, ?)', t)
self._conn.commit()
def del_highlight(self, page, highlight_tuple):
self._highlights[page].remove(highlight_tuple)
t = (self._filehash, page, highlight_tuple[0],
highlight_tuple[1])
self._conn.execute(
'delete from highlights ' +
'where md5=? and page=? and init_pos=? and end_pos=?', t)
self._conn.commit()
def _populate_highlights(self):
rows = self._conn.execute('select * from highlights ' +
'where md5=? order by page',
(self._filehash, ))
for row in rows:
# md5 = row[0]
page = row[1]
init_pos = row[2]
end_pos = row[3]
highlight_tuple = [init_pos, end_pos]
self.get_highlights(page).append(highlight_tuple)