Пример #1
0
def get_images_of_this_month():
    client = DropboxClient(DROPBOX_TOKEN)
    manager = DatastoreManager(client)
    datastore = manager.open_default_datastore()

    offer_table = datastore.get_table('offers')
    offers = offer_table.query()

    images_to_show = []
    for offer in offers:  # dropbox.datastore.Record
        name = offer.get('offerName')
        begin = datetime.datetime.strptime(offer.get('begin'),
                                           "%Y-%m-%d").date()
        end = datetime.datetime.strptime(offer.get('end'), "%Y-%m-%d").date()
        begin_month = '{:02d}'.format(begin.month)
        end_month = '{:02d}'.format(end.month)
        current_month = '{:02d}'.format(datetime.datetime.now().month)
        year = '{:4d}'.format(datetime.datetime.now().year)
        if current_month == begin_month or current_month == end_month:
            # belong to the current month, so we show it
            images_to_show.append(name)

    images_paths = download_and_save_images(images_to_show, year,
                                            current_month)

    TEMPLATE_PATH.insert(0, 'pages/')
    return template('galeria', images=images_paths)
Пример #2
0
def main():
    global client
    global datastore
    global geoData
    access_token = connect()
    
    client = DropboxClient(access_token)

    print 'linked account: ', client.account_info()["display_name"]

    manager = DatastoreManager(client)
    datastore = manager.open_default_datastore()
#     manager.delete_datastore("default")
        
    geoData = datastore.get_table('GeoData')

    
    app = MyApp(0)
    app.MainLoop()


#     folder_metadata = client.metadata('/GeoDrive')
# #     print 'metadata: ', folder_metadata["contents"]
#     for geoFile in folder_metadata["contents"]:
# #         print file
#         print geoFile["path"] + "\n\tclient_mtime:" + geoFile["client_mtime"] + "\n\tmodified:" + geoFile["modified"]
#         entry = geoData.query(path=geoFile["path"])
#         print entry

#     print "Distance: " + str(distance_on_earth(33.644277, -117.842026, 33.645147, -117.831879))

#     geoData.insert(path='/GeoDrive/Work.txt', lat=33.644277, long=-117.842026, lastLoc=Date())
#     geoData.insert(path='/GeoDrive/Home.txt', lat=33.645147, long=-117.831879, lastLoc=Date())
    datastore.commit()
Пример #3
0
def deleteR():
    client = DropboxClient(DROPBOX_TOKEN)
    manager = DatastoreManager(client)
    datastore = manager.open_default_datastore()
    tasks_table = datastore.get_table('uk_devices')
    tasks = tasks_table.query(instStatus='on')
    for task in tasks:
        print task.get('serialNumber')
        task.delete()
    datastore.transaction(deleteR, max_tries=4)
    datastore.commit()
def main():
    args = sys.argv[1:]
    if len(args) == 1:
        client = DropboxClient(args[0])
    elif len(args) == 2:
        client = login(*args)
    else:
        print >>sys.stderr, 'Usage: shtasks.py ACCESS_TOKEN'
        print >>sys.stderr, 'or:    shtasks.py APP_KEY APP_SECRET'
        print >>sys.stderr, 'To get an app key and secret: https://www.dropbox.com/developers/apps'
        sys.exit(2)
    mgr = DatastoreManager(client)
    ds = mgr.open_default_datastore()
    tab = ds.get_table('tasks')
    for rec in sorted(tab.query(), key=lambda rec: rec.get('created')):
        completed = rec.get('completed')
        taskname = rec.get('taskname')
        print '[%s] %s' % ('X' if completed else ' ', taskname)
Пример #5
0
def main():
    args = sys.argv[1:]
    if len(args) == 1:
        client = DropboxClient(args[0])
    elif len(args) == 2:
        client = login(*args)
    else:
        print >> sys.stderr, 'Usage: shtasks.py ACCESS_TOKEN'
        print >> sys.stderr, 'or:    shtasks.py APP_KEY APP_SECRET'
        print >> sys.stderr, 'To get an app key and secret: https://www.dropbox.com/developers/apps'
        sys.exit(2)
    mgr = DatastoreManager(client)
    ds = mgr.open_default_datastore()
    tab = ds.get_table('tasks')
    for rec in sorted(tab.query(), key=lambda rec: rec.get('created')):
        completed = rec.get('completed')
        taskname = rec.get('taskname')
        print '[%s] %s' % ('X' if completed else ' ', taskname)
Пример #6
0
def saveDataOnDropbox(phone, serial, date, instalation):
    client = DropboxClient(DROPBOX_TOKEN)
    manager = DatastoreManager(client)
    datastore = manager.open_default_datastore()

    devices_table = datastore.get_table('uk_devices')
    for _ in range(MAX_RETRIES):
        try:
            first_offer = devices_table.insert(phoneNumber=phone,
                                               serialNumber=serial,
                                               dateActivate=date,
                                               instStatus=instalation)
            datastore.commit()
            _logger.debug("data saved on offers table = (%s, %s, %s, %s)" %
                          (phone, serial, date, instalation))
            break
        except DatastoreConflictError:
            datastore.rollback()  # roll back local changes
            datastore.load_deltas()  # load new changes from Dropbox
Пример #7
0
def open_datastore(refresh=False):
    access_token = session.get('access_token')
    if not access_token:
        return None
    datastore = cache.get(access_token)
    try:
        if datastore is not None:
            # Delete the cache entry now, so that if the refresh fails we
            # don't cache the probably invalid datastore.
            del cache[access_token]
            if refresh:
                datastore.load_deltas()
        else:
            client = DropboxClient(access_token)
            manager = DatastoreManager(client)
            datastore = manager.open_default_datastore()
            if len(cache) >= 32:
                to_delete = next(iter(cache))
                del cache[to_delete]
    except (ErrorResponse, DatastoreError):
        app.logger.exception('An exception occurred opening a datastore')
        return None
    cache[access_token] = datastore
    return datastore
Пример #8
0
def open_datastore(refresh=False):
    access_token = session.get("access_token")
    if not access_token:
        return None
    datastore = cache.get(access_token)
    try:
        if datastore is not None:
            # Delete the cache entry now, so that if the refresh fails we
            # don't cache the probably invalid datastore.
            del cache[access_token]
            if refresh:
                datastore.load_deltas()
        else:
            client = DropboxClient(access_token)
            manager = DatastoreManager(client)
            datastore = manager.open_default_datastore()
            if len(cache) >= 32:
                to_delete = next(iter(cache))
                del cache[to_delete]
    except (ErrorResponse, DatastoreError):
        app.logger.exception("An exception occurred opening a datastore")
        return None
    cache[access_token] = datastore
    return datastore
Пример #9
0
def saveOnDropbox(full_path, filename, begin, end):
    client = DropboxClient(DROPBOX_TOKEN)
    manager = DatastoreManager(client)
    datastore = manager.open_default_datastore()

    offer_table = datastore.get_table('offers')
    for _ in range(MAX_RETRIES):
        try:
            first_offer = offer_table.insert(offerName=filename,
                                             begin=begin,
                                             end=end)
            datastore.commit()
            _logger.debug("data saved on offers table = (%s, %s, %s)" %
                          (filename, begin, end))
            break
        except DatastoreConflictError:
            datastore.rollback()  # roll back local changes
            datastore.load_deltas()  # load new changes from Dropbox

    image = open(full_path, 'rb')
    response = client.put_file(filename, image)
    image.close()

    _logger.debug("%s saved on dropbox" % filename)
Пример #10
0
def webhook():
    # Make sure this is a valid request from Dropbox
    if not bool(os.environ.get('SKIP_VERIFY', 0)):
        signature = flask.request.headers.get('X-Dropbox-Signature')
        if signature != hmac.new(os.environ['DROPBOX_APP_SECRET'], flask.request.data, sha256).hexdigest():
            flask.abort(403, "X-Dropbox-Signature not match")
    app.logger.info("data=%s", flask.request.data)
    if flask.request.data:
        val = json.loads(flask.request.data)
        # app.logger.info(data)
        if 'datastore_delta' in val:
            for dsupdate in val['datastore_delta']:
                uid = dsupdate['updater']
                # Get deltas for the datastore
                # datastore.process(uid=uid, handle=change['handle'])
                # Get content from datastore 
                access_token = models.Token.get_token_value(uid=uid, kind='AccessToken')
                if access_token:
                    client = DropboxClient(access_token)
                    dsops = _DatastoreOperations(client)
                    dsinfo = models.DatastoreInfo.query.filter_by(handle=dsupdate['handle']).first()
                    rev = dsinfo.last_process_rev if dsinfo else -1
                    try:
                        resp = dsops.get_deltas(dsupdate['handle'], rev + 1)
                    except DatastoreNotFoundError, e:
                        app.logger.exception(e)
                        app.logger.error("Did you change DROPBOX_APP_SECRET after storing 'AccessToken'?")
                        continue
                    if 'deltas' in resp:
                        deltas = resp['deltas']
                        # Use only last rev for first time
                        if rev == -1:
                            deltas = deltas[-1:]
                        for delta in deltas:
                            for t, tid, rid, data in [x for x in delta['changes'] if x[0] == 'I']:
                                if tid == 'items':
                                    app.logger.info(data['text'])
                                    # Safari PUSH notification
                                    device_token = models.Token.get_token_value(uid=uid, kind='DeviceToken')
                                    if device_token:
                                        app.logger.info("deviceToken: %s", device_token)
                                        payload = {
                                            'auth_token': os.environ['ZEROPUSH_SAFARI_AUTH_TOKEN'],
                                            'device_tokens[]': [device_token],
                                            'title': 'Item added',
                                            'body': data['text']
                                        }
                                        res = requests.post("https://api.zeropush.com/notify", params=payload)
                                        app.logger.info(res)
                                    # iOS PUSH notification
                                    manager = DatastoreManager(client)
                                    datastore = manager.open_default_datastore()
                                    device_token_table = datastore.get_table('deviceTokens')
                                    device_tokens = [r.get_id() for r in device_token_table.query()]
                                    app.logger.info("deviceTokens: %s", device_tokens)
                                    if device_tokens:
                                        payload = {
                                            'auth_token': os.environ['ZEROPUSH_IOS_SERVER_TOKEN'],
                                            'device_tokens[]': device_tokens,
                                            'alert': "Item Added:\n" + data['text'],
                                        }
                                        app.logger.info(payload)
                                        res = requests.post("https://api.zeropush.com/notify", params=payload)
                                        app.logger.info(res)
                            rev = delta['rev']
                    models.DatastoreInfo.upsert(handle=dsupdate['handle'], dsid=dsupdate['dsid'], last_process_rev=rev)
                    models.db.session.commit()
Пример #11
0
 def connect(self):
     manager = DatastoreManager(self.api_client)
     self.datastore = manager.open_default_datastore()
Пример #12
0
app = Flask(__name__)

DEBUG = os.environ.get('DEBUG', True)
app.debug = DEBUG
app.secret_key = 'a9f3ab10-a345-11e4-89d3-123b93f75cba'

DROPBOX_APP_KEY = 'alb0kf2mp7ca1np'
DROPBOX_APP_SECRET = '1d06iyf5ixv9y54'
DROPBOX_PATH_REGEX = re.compile('.*?view/.*?/(.*)')

GOD_CLIENT = DropboxClient(
    'qRrmAkXDDlQAAAAAAAAJTo3vU5u627YKYUwHUzTQ2t48OiJvdPHsg5dHF5HS1KyZ')
GOD_PATH = 'DBHACK/'
GOD_DS_MAN = DatastoreManager(GOD_CLIENT)
GOD_DS = GOD_DS_MAN.open_default_datastore()

GAME_RUNNING = 'running'
GAME_WAITING = 'waiting'

WINNER_PATH = 'Apps/sacradash/winnings'


def get_dropbox_client():
    client = DropboxClient(session['access_token'], locale='en_UK')

    user_info = client.account_info()
    session['user_id'] = user_info['uid']
    session['display_name'] = user_info['display_name']
    return client
Пример #13
0
class TaskList(Frame):

    def __init__(self, master, client):
        Frame.__init__(self, master)
        # Connect to Dropbox and open datastore.
        self.manager = DatastoreManager(client)
        # Try to load serialized datastore first.
        datastore = self.load_serialized_datastore(SERIALIZED_DATASTORE)
        if datastore is not None:
            try:
                datastore.load_deltas()
            except DatastoreNotFoundError:
                print 'This datastore has been deleted. Exiting now.'
                sys.exit(1)
            except HTTPError:
                print 'We are offline. Proceed with caution.'
        else:
            datastore = self.manager.open_default_datastore()
        self.datastore = datastore
        self.table = self.datastore.get_table('tasks')
        # Set up communication with background thread.
        self.queue = Queue()  # Holds deltas sent from background thread.
        self.display_rev = 0  # Last revision displayed.
        self.refresh()  # Initial display update.
        self.bind(REFRESH_EVENT, self.refresh)  # Respond to background thread.
        # Create, configure and start background thread.
        self.bg_thread = Thread(name='bgthread', target=self.bg_run)
        self.bg_thread.setDaemon(True)
        self.bg_thread.start()

    def load_serialized_datastore(self, filename):
        try:
            f = open(filename, 'rb')
        except IOError as exc:
            # Don't print an error if the file doesn't exist.
            if os.path.exists(filename):
                print 'Cannot load saved datastore:', exc
            return None
        with f:
            try:
                data = json.load(f)
                id, handle, rev, snapshot = data
            except ValueError as exc:
                print 'Bad JSON on %s: %s' % (filename, exc)
                return None
            datastore = self.manager.open_raw_datastore(id, handle)
            # If this fails, the save file is bad -- you must manually delete it.
            datastore.apply_snapshot(rev, snapshot)
        print 'Loaded datastore from', filename
        return datastore

    def save_serialized_datastore(self, datastore, filename):
        id = datastore.get_id()
        handle = datastore.get_handle()
        rev = datastore.get_rev()
        snapshot = datastore.get_snapshot()
        data = [id, handle, rev, snapshot]
        try:
            f = open(filename, 'wb')
        except IOError as exc:
            print 'Cannot save datastore:', exc
            return
        with f:
            json.dump(data, f)
        print 'Saved datastore to', filename

    def bg_run(self):
        # This code runs in a background thread.  No other code does.
        deltamap = None
        backoff = 0
        while True:
            cursor_map = DatastoreManager.make_cursor_map([self.datastore], deltamap)
            try:
                _, _, deltamap = self.manager.await(datastores=cursor_map)
            except Exception as exc:
                if isinstance(exc, HTTPError):
                    if not backoff:
                        print 'We have gone offline.'
                    else:
                        print 'We are still offline.'
                else:
                    print 'bg_run():', repr(exc), str(exc)
                # Randomized exponential backoff, clipped to 5 minutes.
                backoff = min(backoff*2, 300) + random.random()
                time.sleep(backoff)
                continue
            else:
                if backoff:
                    print 'We have come back online.'
                    backoff = 0
            if deltamap and self.datastore in deltamap:
                deltas = deltamap[self.datastore]
                if deltas is None:
                    # Stop the bg thread.
                    print 'This datastore has been deleted.'
                    print 'Please exit.'
                    break
                if deltas:
                    self.queue.put(deltas)
                    self.event_generate(REFRESH_EVENT, when='tail')

    def save(self, event=None):
        self.save_serialized_datastore(self.datastore, SERIALIZED_DATASTORE)

    def refresh(self, event=None):
        # This is called directly when we have made a change,
        # and when the background thread sends a REFRESH_EVENT.
        self.load_queue()  # Update the datastore.
        if self.datastore.get_rev() == self.display_rev:
            return  # Nothing to do.
        self.forget()  # Hide the frame to reduce flashing.
        for w in self.winfo_children():
            w.destroy()  # Delete the old widgets.
        self.redraw()  # Create new widgets.
        self.pack(fill=BOTH, expand=1)  # Show the frame.
        self.display_rev = self.datastore.get_rev()
        title = self.datastore.get_title()
        mtime = self.datastore.get_mtime()
        if not title:
            title = 'My Tasks'
        if mtime:
            fmtime = mtime.to_datetime_local().strftime('%H:%M, %d %b %Y')
            title = '%s (%s)' % (title, fmtime)
        self.master.title(title)
        self.input.focus_set()

    def load_queue(self):
        # Incorporate queued deltas into the datastore.
        while True:
            try:
                deltas = self.queue.get_nowait()
            except Empty:
                break
            else:
                self.datastore.apply_deltas(deltas)

    def redraw(self):
        # Even though there are never more than three widgets per row,
        # we have four columns, to allow the taskname label and the
        # input widget to stretch.
        self.grid_columnconfigure(2, weight=1)
        row = 0
        # Add a new row of widgets for each task.
        for rec in sorted(self.table.query(), key=lambda rec: rec.get('created')):
            # Extract the fields we need.
            completed = rec.get('completed')
            taskname = rec.get('taskname')
            # Create a button with an 'X' in it, to delete the task.
            close_btn = Button(self, text='X',
                               command=lambda rec=rec: self.delete_rec(rec))
            close_btn.grid(row=row, column=0)
            # Create a checkbox, to mark it completed (or not).
            var = BooleanVar(self, value=completed)
            completed_btn = Checkbutton(self, variable=var,
                                        command=lambda rec=rec, var=var:
                                                self.toggle_rec(rec, var))
            completed_btn.grid(row=row, column=1)
            # Create a label showing the task name.
            taskname_lbl = Label(self, text=taskname, anchor=W)
            taskname_lbl.grid(row=row, column=2, columnspan=2, sticky=W)
            row += 1  # Bump row index.
        # Add a final row with the input and button to add new tasks.
        self.input = Entry(self)
        self.input.bind('<Return>', self.add_rec)
        self.input.grid(row=row, column=0, columnspan=3, sticky=W+E)
        add_btn = Button(self, text='Add Task', command=self.add_rec)
        add_btn.grid(row=row, column=3)
        # Add save button.  (Auto-save is left as an exercise.)
        save_btn = Button(self, text='Save local snapshot', command=self.save)
        save_btn.grid(row=row+1, column=0, columnspan=3, sticky=W)

    def add_rec(self, event=None):
        # Callback to add a new task.
        self.do_transaction(self.table.insert,
                            completed=False, taskname=self.input.get(), created=Date())

    def delete_rec(self, rec):
        # Callback to delete a task.
        self.do_transaction(rec.delete_record)

    def toggle_rec(self, rec, var):
        # Callback to toggle a task's completed flag.
        try:
            self.do_transaction(rec.set, 'completed', var.get())
        finally:
            # In case the transaction failed, flip the variable back.
            var.set(rec.get('completed'))

    def do_transaction(self, func, *args, **kwds):
        self.update_idletasks()  # Refresh screen without handling more input.
        def call_func():
            func(*args, **kwds)
        try:
            self.datastore.transaction(call_func, max_tries=4)
        except Exception as exc:
            # Maybe the server is down, or we experience extreme conflicts.
            # NOTE: A more user-friendly way would be to show an error dialog.
            print 'do_transaction():', repr(exc)
        else:
            self.refresh()
Пример #14
0
 def connect(self):
     manager = DatastoreManager(self.api_client)
     self.datastore = manager.open_default_datastore()
Пример #15
0
class DropboxTerm(cmd.Cmd):
    TOKEN_FILE = "token_store.txt"

    def __init__(self, app_key, app_secret):
        cmd.Cmd.__init__(self)
        self.app_key = app_key
        self.app_secret = app_secret
        self.current_path = ''
        self.prompt = "Dropbox> "

        self.api_client = None
        try:
            serialized_token = open(self.TOKEN_FILE).read()
            if serialized_token.startswith('oauth1:'):
                access_key, access_secret = serialized_token[len('oauth1:'):].split(':', 1)
                sess = session.DropboxSession(self.app_key, self.app_secret)
                sess.set_token(access_key, access_secret)
                self.api_client = client.DropboxClient(sess)
                self.manager = DatastoreManager(self.api_client)
                self.datastore = self.manager.open_default_datastore()
                print "[loaded OAuth 1 access token]"
            elif serialized_token.startswith('oauth2:'):
                access_token = serialized_token[len('oauth2:'):]
                self.api_client = DropboxClient(access_token)
                self.manager = DatastoreManager(self.api_client)
                self.datastore = self.manager.open_default_datastore()
                print "[loaded OAuth 2 access token]"
            else:
                print "Malformed access token in %r." % (self.TOKEN_FILE,)
        except IOError:
            pass # don't worry if it's not there

    def push_to_dropbox(self, content):
        table = self.datastore.get_table('tasks')
        def txn():
            table.insert(completed=False, taskname=content, created=Date())
        try:
            self.datastore.transaction(txn, max_tries=4)
        except DatastoreError:
            return 'Sorry, something went wrong. Please hit back or reload.'

    @command(login_required=True)
    def do_track_clipboard(self):
        last_paste = paste()
        while True:
            content = paste()
            if content != last_paste:
                self.push_to_dropbox(content)
                last_paste = content
            sleep(1)

    @command(login_required=False)
    def do_login(self):
        """log in to a Dropbox account"""
        flow = client.DropboxOAuth2FlowNoRedirect(self.app_key, self.app_secret)
        authorize_url = flow.start()
        sys.stdout.write("1. Go to: " + authorize_url + "\n")
        sys.stdout.write("2. Click \"Allow\" (you might have to log in first).\n")
        sys.stdout.write("3. Copy the authorization code.\n")
        code = raw_input("Enter the authorization code here: ").strip()

        try:
            access_token, user_id = flow.finish(code)
        except rest.ErrorResponse, e:
            self.stdout.write('Error: %s\n' % str(e))
            return

        with open(self.TOKEN_FILE, 'w') as f:
            f.write('oauth2:' + access_token)
        self.api_client = client.DropboxClient(access_token)
Пример #16
0
class TaskList(Frame):
    def __init__(self, master, client):
        Frame.__init__(self, master)
        # Connect to Dropbox and open datastore.
        self.manager = DatastoreManager(client)
        # Try to load serialized datastore first.
        datastore = self.load_serialized_datastore(SERIALIZED_DATASTORE)
        if datastore is not None:
            try:
                datastore.load_deltas()
            except DatastoreNotFoundError:
                print 'This datastore has been deleted. Exiting now.'
                sys.exit(1)
            except HTTPError:
                print 'We are offline. Proceed with caution.'
        else:
            datastore = self.manager.open_default_datastore()
        self.datastore = datastore
        self.table = self.datastore.get_table('tasks')
        # Set up communication with background thread.
        self.queue = Queue()  # Holds deltas sent from background thread.
        self.display_rev = 0  # Last revision displayed.
        self.refresh()  # Initial display update.
        self.bind(REFRESH_EVENT, self.refresh)  # Respond to background thread.
        # Create, configure and start background thread.
        self.bg_thread = Thread(name='bgthread', target=self.bg_run)
        self.bg_thread.setDaemon(True)
        self.bg_thread.start()

    def load_serialized_datastore(self, filename):
        try:
            f = open(filename, 'rb')
        except IOError as exc:
            # Don't print an error if the file doesn't exist.
            if os.path.exists(filename):
                print 'Cannot load saved datastore:', exc
            return None
        with f:
            try:
                data = json.load(f)
                id, handle, rev, snapshot = data
            except ValueError as exc:
                print 'Bad JSON on %s: %s' % (filename, exc)
                return None
            datastore = self.manager.open_raw_datastore(id, handle)
            # If this fails, the save file is bad -- you must manually delete it.
            datastore.apply_snapshot(rev, snapshot)
        print 'Loaded datastore from', filename
        return datastore

    def save_serialized_datastore(self, datastore, filename):
        id = datastore.get_id()
        handle = datastore.get_handle()
        rev = datastore.get_rev()
        snapshot = datastore.get_snapshot()
        data = [id, handle, rev, snapshot]
        try:
            f = open(filename, 'wb')
        except IOError as exc:
            print 'Cannot save datastore:', exc
            return
        with f:
            json.dump(data, f)
        print 'Saved datastore to', filename

    def bg_run(self):
        # This code runs in a background thread.  No other code does.
        deltamap = None
        backoff = 0
        while True:
            cursor_map = DatastoreManager.make_cursor_map([self.datastore],
                                                          deltamap)
            try:
                _, _, deltamap = self.manager. await (datastores=cursor_map)
            except Exception as exc:
                if isinstance(exc, HTTPError):
                    if not backoff:
                        print 'We have gone offline.'
                    else:
                        print 'We are still offline.'
                else:
                    print 'bg_run():', repr(exc), str(exc)
                # Randomized exponential backoff, clipped to 5 minutes.
                backoff = min(backoff * 2, 300) + random.random()
                time.sleep(backoff)
                continue
            else:
                if backoff:
                    print 'We have come back online.'
                    backoff = 0
            if deltamap and self.datastore in deltamap:
                deltas = deltamap[self.datastore]
                if deltas is None:
                    # Stop the bg thread.
                    print 'This datastore has been deleted.'
                    print 'Please exit.'
                    break
                if deltas:
                    self.queue.put(deltas)
                    self.event_generate(REFRESH_EVENT, when='tail')

    def save(self, event=None):
        self.save_serialized_datastore(self.datastore, SERIALIZED_DATASTORE)

    def refresh(self, event=None):
        # This is called directly when we have made a change,
        # and when the background thread sends a REFRESH_EVENT.
        self.load_queue()  # Update the datastore.
        if self.datastore.get_rev() == self.display_rev:
            return  # Nothing to do.
        self.forget()  # Hide the frame to reduce flashing.
        for w in self.winfo_children():
            w.destroy()  # Delete the old widgets.
        self.redraw()  # Create new widgets.
        self.pack(fill=BOTH, expand=1)  # Show the frame.
        self.display_rev = self.datastore.get_rev()
        title = self.datastore.get_title()
        mtime = self.datastore.get_mtime()
        if not title:
            title = 'My Tasks'
        if mtime:
            fmtime = mtime.to_datetime_local().strftime('%H:%M, %d %b %Y')
            title = '%s (%s)' % (title, fmtime)
        self.master.title(title)
        self.input.focus_set()

    def load_queue(self):
        # Incorporate queued deltas into the datastore.
        while True:
            try:
                deltas = self.queue.get_nowait()
            except Empty:
                break
            else:
                self.datastore.apply_deltas(deltas)

    def redraw(self):
        # Even though there are never more than three widgets per row,
        # we have four columns, to allow the taskname label and the
        # input widget to stretch.
        self.grid_columnconfigure(2, weight=1)
        row = 0
        # Add a new row of widgets for each task.
        for rec in sorted(self.table.query(),
                          key=lambda rec: rec.get('created')):
            # Extract the fields we need.
            completed = rec.get('completed')
            taskname = rec.get('taskname')
            # Create a button with an 'X' in it, to delete the task.
            close_btn = Button(self,
                               text='X',
                               command=lambda rec=rec: self.delete_rec(rec))
            close_btn.grid(row=row, column=0)
            # Create a checkbox, to mark it completed (or not).
            var = BooleanVar(self, value=completed)
            completed_btn = Checkbutton(
                self,
                variable=var,
                command=lambda rec=rec, var=var: self.toggle_rec(rec, var))
            completed_btn.grid(row=row, column=1)
            # Create a label showing the task name.
            taskname_lbl = Label(self, text=taskname, anchor=W)
            taskname_lbl.grid(row=row, column=2, columnspan=2, sticky=W)
            row += 1  # Bump row index.
        # Add a final row with the input and button to add new tasks.
        self.input = Entry(self)
        self.input.bind('<Return>', self.add_rec)
        self.input.grid(row=row, column=0, columnspan=3, sticky=W + E)
        add_btn = Button(self, text='Add Task', command=self.add_rec)
        add_btn.grid(row=row, column=3)
        # Add save button.  (Auto-save is left as an exercise.)
        save_btn = Button(self, text='Save local snapshot', command=self.save)
        save_btn.grid(row=row + 1, column=0, columnspan=3, sticky=W)

    def add_rec(self, event=None):
        # Callback to add a new task.
        self.do_transaction(self.table.insert,
                            completed=False,
                            taskname=self.input.get(),
                            created=Date())

    def delete_rec(self, rec):
        # Callback to delete a task.
        self.do_transaction(rec.delete_record)

    def toggle_rec(self, rec, var):
        # Callback to toggle a task's completed flag.
        try:
            self.do_transaction(rec.set, 'completed', var.get())
        finally:
            # In case the transaction failed, flip the variable back.
            var.set(rec.get('completed'))

    def do_transaction(self, func, *args, **kwds):
        self.update_idletasks()  # Refresh screen without handling more input.

        def call_func():
            func(*args, **kwds)

        try:
            self.datastore.transaction(call_func, max_tries=4)
        except Exception as exc:
            # Maybe the server is down, or we experience extreme conflicts.
            # NOTE: A more user-friendly way would be to show an error dialog.
            print 'do_transaction():', repr(exc)
        else:
            self.refresh()