168513a70bcd92384395513322f1b801e7bf9c729Steve Block# Copyright (C) 2010 Google Inc. All rights reserved.
268513a70bcd92384395513322f1b801e7bf9c729Steve Block#
368513a70bcd92384395513322f1b801e7bf9c729Steve Block# Redistribution and use in source and binary forms, with or without
468513a70bcd92384395513322f1b801e7bf9c729Steve Block# modification, are permitted provided that the following conditions are
568513a70bcd92384395513322f1b801e7bf9c729Steve Block# met:
668513a70bcd92384395513322f1b801e7bf9c729Steve Block#
768513a70bcd92384395513322f1b801e7bf9c729Steve Block#     * Redistributions of source code must retain the above copyright
868513a70bcd92384395513322f1b801e7bf9c729Steve Block# notice, this list of conditions and the following disclaimer.
968513a70bcd92384395513322f1b801e7bf9c729Steve Block#     * Redistributions in binary form must reproduce the above
1068513a70bcd92384395513322f1b801e7bf9c729Steve Block# copyright notice, this list of conditions and the following disclaimer
1168513a70bcd92384395513322f1b801e7bf9c729Steve Block# in the documentation and/or other materials provided with the
1268513a70bcd92384395513322f1b801e7bf9c729Steve Block# distribution.
1368513a70bcd92384395513322f1b801e7bf9c729Steve Block#     * Neither the name of Google Inc. nor the names of its
1468513a70bcd92384395513322f1b801e7bf9c729Steve Block# contributors may be used to endorse or promote products derived from
1568513a70bcd92384395513322f1b801e7bf9c729Steve Block# this software without specific prior written permission.
1668513a70bcd92384395513322f1b801e7bf9c729Steve Block#
1768513a70bcd92384395513322f1b801e7bf9c729Steve Block# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1868513a70bcd92384395513322f1b801e7bf9c729Steve Block# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1968513a70bcd92384395513322f1b801e7bf9c729Steve Block# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2068513a70bcd92384395513322f1b801e7bf9c729Steve Block# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2168513a70bcd92384395513322f1b801e7bf9c729Steve Block# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2268513a70bcd92384395513322f1b801e7bf9c729Steve Block# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2368513a70bcd92384395513322f1b801e7bf9c729Steve Block# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2468513a70bcd92384395513322f1b801e7bf9c729Steve Block# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2568513a70bcd92384395513322f1b801e7bf9c729Steve Block# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2668513a70bcd92384395513322f1b801e7bf9c729Steve Block# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2768513a70bcd92384395513322f1b801e7bf9c729Steve Block# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2868513a70bcd92384395513322f1b801e7bf9c729Steve Block
2968513a70bcd92384395513322f1b801e7bf9c729Steve Blockfrom google.appengine.ext import db
3068513a70bcd92384395513322f1b801e7bf9c729Steve Block
31bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsenfrom datetime import timedelta, datetime
3268513a70bcd92384395513322f1b801e7bf9c729Steve Blockimport time
3368513a70bcd92384395513322f1b801e7bf9c729Steve Block
34a94275402997c11dd2e778633dacf4b7e630a35dBen Murdochfrom model.queuepropertymixin import QueuePropertyMixin
3568513a70bcd92384395513322f1b801e7bf9c729Steve Block
36a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
37a94275402997c11dd2e778633dacf4b7e630a35dBen Murdochclass ActiveWorkItems(db.Model, QueuePropertyMixin):
3868513a70bcd92384395513322f1b801e7bf9c729Steve Block    queue_name = db.StringProperty()
3968513a70bcd92384395513322f1b801e7bf9c729Steve Block    item_ids = db.ListProperty(int)
4068513a70bcd92384395513322f1b801e7bf9c729Steve Block    item_dates = db.ListProperty(float)
4168513a70bcd92384395513322f1b801e7bf9c729Steve Block    date = db.DateTimeProperty(auto_now_add=True)
4268513a70bcd92384395513322f1b801e7bf9c729Steve Block
43bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen    # The id/date pairs should probably just be their own class.
44bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen    def _item_time_pairs(self):
45bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        return zip(self.item_ids, self.item_dates)
46bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen
47bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen    def _set_item_time_pairs(self, pairs):
48bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        if pairs:
49bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen            # The * operator raises on an empty list.
50bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen            # db.Model does not tuples, we have to make lists.
51bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen            self.item_ids, self.item_dates = map(list, zip(*pairs))
52bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        else:
53bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen            self.item_ids = []
54bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen            self.item_dates = []
55bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen
56bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen    def _append_item_time_pair(self, pair):
57bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        self.item_ids.append(pair[0])
58bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        self.item_dates.append(pair[1])
59bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen
60a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    def _remove_item(self, item_id):
61bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        nonexpired_pairs = [pair for pair in self._item_time_pairs() if pair[0] != item_id]
62bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        self._set_item_time_pairs(nonexpired_pairs)
63bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen
642daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch    @classmethod
652daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch    def key_for_queue(cls, queue_name):
662daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        return "active-work-items-%s" % (queue_name)
672daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch
682daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch    @classmethod
692daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch    def lookup_by_queue(cls, queue_name):
702daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        return cls.get_or_insert(key_name=cls.key_for_queue(queue_name), queue_name=queue_name)
712daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch
72a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    @staticmethod
73a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    def _expire_item(key, item_id):
74a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        active_work_items = db.get(key)
75a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        active_work_items._remove_item(item_id)
76a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        active_work_items.put()
77a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
78a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    def expire_item(self, item_id):
79a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        return db.run_in_transaction(self._expire_item, self.key(), item_id)
80a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
8168513a70bcd92384395513322f1b801e7bf9c729Steve Block    def deactivate_expired(self, now):
8268513a70bcd92384395513322f1b801e7bf9c729Steve Block        one_hour_ago = time.mktime((now - timedelta(minutes=60)).timetuple())
83bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        nonexpired_pairs = [pair for pair in self._item_time_pairs() if pair[1] > one_hour_ago]
84bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        self._set_item_time_pairs(nonexpired_pairs)
8568513a70bcd92384395513322f1b801e7bf9c729Steve Block
8668513a70bcd92384395513322f1b801e7bf9c729Steve Block    def next_item(self, work_item_ids, now):
8768513a70bcd92384395513322f1b801e7bf9c729Steve Block        for item_id in work_item_ids:
8868513a70bcd92384395513322f1b801e7bf9c729Steve Block            if item_id not in self.item_ids:
89bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen                self._append_item_time_pair([item_id, time.mktime(now.timetuple())])
9068513a70bcd92384395513322f1b801e7bf9c729Steve Block                return item_id
9168513a70bcd92384395513322f1b801e7bf9c729Steve Block        return None
92bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen
93bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen    def time_for_item(self, item_id):
94bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        for active_item_id, time in self._item_time_pairs():
95bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen            if active_item_id == item_id:
96bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen                return datetime.fromtimestamp(time)
97bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        return None
98