Package list sugar-read-activity / debian/123-1 readdb.py
debian/123-1

Tree @debian/123-1 (Download .tar.gz)

readdb.py @debian/123-1raw · history · blame

# 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')

    # 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):
        # in this case, sqlite3 takes care of things
        conn = sqlite3.connect(dbpath)
        conn.execute("""
        CREATE TABLE "bookmarks"(
              md5 text,
              page integer,
              content text,
              timestamp real,
              user text,
              color text,
              local integer
            );""")
        conn.commit()
        conn.close()

        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: str(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 update_bookmarks(self, bookmarks_list):
        need_reync = False
        for bookmark_data in bookmarks_list:
            # compare with all the bookmarks
            found = False
            for bookmark in self._bookmarks:
                if bookmark.compare_equal_to_dict(bookmark_data):
                    found = True
                    break
            if not found:
                if bookmark_data['nick'] == self._user and \
                        bookmark_data['color'] == self._color:
                    local = 1
                else:
                    local = 0
                t = (bookmark_data['md5'], bookmark_data['page_no'],
                     bookmark_data['content'], bookmark_data['timestamp'],
                     bookmark_data['nick'], bookmark_data['color'], local)
                self._conn.execute('insert into bookmarks values ' +
                                   '(?, ?, ?, ?, ?, ?, ?)', t)
                need_reync = True
                title = json.loads(bookmark_data['content'])['title']
                self.emit('added_bookmark', bookmark_data['page_no'] + 1,
                          title)

        if need_reync:
            self._resync_bookmark_cache()

    def add_bookmark(self, page, content, local=1):
        logging.debug('add_bookmark page %d', page)
        # local = 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 get_all_highlights(self):
        return self._highlights

    def update_highlights(self, highlights_dict):
        for page in list(highlights_dict.keys()):
            # json store the keys as strings
            # but the page is used as a int in all the code
            highlights_in_page = highlights_dict[page]
            page = int(page)
            highlights_stored = self.get_highlights(page)
            for highlight_tuple in highlights_in_page:
                if highlight_tuple not in highlights_stored:
                    self.add_highlight(page, highlight_tuple)

    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)