Ejemplo n.º 1
0
 def __init__(self):
     L.debug("Service is running")
     # Initialize internal queue
     self.thread = threading.Thread(name='execution', target=self.async_run)
     self.internal_queue = Queue()
     self.thread.start()
     # Initialize OSC
     osc.init()
     self.oscid = osc.listen(ipAddr='127.0.0.1', port=3333)
     osc.bind(self.oscid, self.handle_message, '/android_park')
     self.server = ServerInterface()
     self.pending = {}
     self.last_time = None
     self.notified = True
     self.config = None
     # set to indicate that a month has been processed by the pattern
     self.pattern_processed=set()
     # Initialize timezones
     self.met = pytz.timezone('Europe/Madrid')
     self.t1 = datetime.time(hour=0, minute=0, second=0, tzinfo=self.met)  # 00:00
     self.t2 = datetime.time(hour=15, minute=0, second=0, tzinfo=self.met)  # 15:00
     self.t3 = datetime.time(hour=17, minute=30, second=0, tzinfo=self.met)  # 17:30
     self.t4 = datetime.time(hour=23, minute=59, second=59, tzinfo=self.met)  # 23:59
Ejemplo n.º 2
0
class ServerThread:
    def __init__(self):
        L.debug("Service is running")
        # Initialize internal queue
        self.thread = threading.Thread(name='execution', target=self.async_run)
        self.internal_queue = Queue()
        self.thread.start()
        # Initialize OSC
        osc.init()
        self.oscid = osc.listen(ipAddr='127.0.0.1', port=3333)
        osc.bind(self.oscid, self.handle_message, '/android_park')
        self.server = ServerInterface()
        self.pending = {}
        self.last_time = None
        self.notified = True
        self.config = None
        # set to indicate that a month has been processed by the pattern
        self.pattern_processed=set()
        # Initialize timezones
        self.met = pytz.timezone('Europe/Madrid')
        self.t1 = datetime.time(hour=0, minute=0, second=0, tzinfo=self.met)  # 00:00
        self.t2 = datetime.time(hour=15, minute=0, second=0, tzinfo=self.met)  # 15:00
        self.t3 = datetime.time(hour=17, minute=30, second=0, tzinfo=self.met)  # 17:30
        self.t4 = datetime.time(hour=23, minute=59, second=59, tzinfo=self.met)  # 23:59

    def async_run(self):
        while True:
            try:
                event = self.internal_queue.get(block=True, timeout=1)
                event()
            except Empty:
                pass
            except Exception:
                L.error("Exception:\n" + traceback.format_exc())

    def run(self):
        while True:
            try:
                osc.readQueue(self.oscid)
                if not self.notified:
                    self.update_notification("Intento: " + self.last_time.strftime('%Y-%m-%d %H:%M'))
                    self.notified = True
                if self.check_pattern():
                    self.add_pattern()
                # Pending operations
                if self.check_interval():
                    # Update last_time here to avoid repeated requests
                    self.last_time = datetime.datetime.now(tz=self.met)
                    self.internal_queue.put(lambda: self.modify(-1))
            except:
                L.error("Exception:\n" + traceback.format_exc())
            sleep(.1)

    def handle_message(self, message, *args):
        pickle_msg = message[2]
        msg = pickle.loads(pickle_msg)
        L.debug("Received message %s" % msg['request'])

        self.config = msg['config']
        self.server.config(self.config)

        if msg['request'] == 'ping':
            # Reply immediatelly to pings.
            self.ping(msg['id'])
        if msg['request'] == 'query':
            self.internal_queue.put(lambda: self.query(msg['id']))
        if msg['request'] == 'modify':
            self.pending = msg['operations']
            self.internal_queue.put(lambda: self.modify(msg['id']))

        L.debug("End Received message %s" % msg['request'])

    def ping(self, _id):
        self.send_ping_result(_id)

    def query(self, _id):
        L.info("Calling query id: %s " % _id)
        try:
            result = self.server.query()
            self.update_pending(result)
            self.send_query_result(_id, result, self.pending)
        except ServerException as e:
            self.send_query_result(_id, None, self.pending, e.status)

    def modify(self, _id):
        L.info("Calling modify id:%s " % _id)
        try:
            result = self.server.modify(self.pending, lambda (s): self.send_modify_partial_result(_id, s))
            self.last_time = datetime.datetime.now(tz=self.met)
            self.notified = False
            self.update_pending(result)
            self.send_modify_result(_id, "OK")
        except ServerException as e:
            self.send_modify_result(_id, e.status)

    def send_modify_result(self, _id, status=None):
        L.debug("Send modify result %d" % _id)
        pickle_msg = pickle.dumps({
            'response': 'modify',
            'id': _id,
            'status': status})
        osc.sendMsg('/android_park', [pickle_msg, ], port=3334)

    def send_modify_partial_result(self, _id, status):
        L.debug("Send modify partial result %d" % _id)
        pickle_msg = pickle.dumps({
            'response': 'modify',
            'is_partial': True,
            'id': _id,
            'status': status})
        osc.sendMsg('/android_park', [pickle_msg, ], port=3334)

    def send_query_result(self, _id, result, pending, status=None):
        L.debug("Send query result %d" % _id)
        pickle_msg = pickle.dumps({
            'response': 'query',
            'id': _id,
            'result': result,
            'pending': pending,
            'status': status})
        osc.sendMsg('/android_park', [pickle_msg, ], port=3334)

    def send_ping_result(self, _id):
        L.debug("Send ping result %d" % _id)
        pickle_msg = pickle.dumps({
            'response': 'ping',
            'id': _id,
            'config': self.config})
        osc.sendMsg('/android_park', [pickle_msg, ], port=3334)

    def check_pattern(self):
        random.seed()
        pattern = ""
        if self.config is not None:
            pattern = self.config.get("general", "pattern")

        now = datetime.datetime.now(tz=self.met)
        next_month = (now.month + 1)
        if next_month == 13:
            next_month = 1

        random_minute = random.randrange(0, 60)
        if pattern != "" and \
           now.day == 1 and \
                ((now.hour == 11 and now.minute >= random_minute) or (now.hour > 11)) and \
           next_month not in self.pattern_processed:
            # Add the pattern the first day of the month, at the 11:XXam, and
            # only if there's nothing pending for next month.
            self.mark_next_month_as_processed(next_month)
            return True
        else:
            return False

    def mark_next_month_as_processed(self,next_month):
        self.pattern_processed.add(next_month)
        if next_month==1:
            self.pattern_processed.discard(12)
        else:
            self.pattern_processed.discard(next_month-1)


    def check_interval(self):
        """
        Check if it is time for a new attempt
        :return: True if it is time for a new attempt
        """
        now = datetime.datetime.now(tz=self.met)
        tomorrow = now.date() + datetime.timedelta(days=1)
        f1 = 0
        f2 = 0
        if self.config is not None:
            f1 = int(self.config.get("timers", "frequency")) * 60
            f2 = int(self.config.get("timers", "frequency2")) * 60
        if len(self.pending) > 0 and \
                ((self.t1 <= now.time() < self.t2) or (self.t3 <= now.time() < self.t4)):
            # Period 00:00 - 14:59: attempt every 30 min
            if self.last_time is None or (0 < f1 < (now - self.last_time).total_seconds()):
                L.info("modify on regular interval")
                return True
        if (self.t2 <= now.time() < self.t3) and tomorrow in self.pending:
            # Period 15:00 - 17:30: attempt 1 min (but only if there's something pending for tomorrow
            if self.last_time is None or (0 < f2 < (now - self.last_time).total_seconds()):
                L.info("modify on repesca interval")
                return True
        if len(self.pending) > 0 and \
           now.hour == 14 and now.minute > 50 and \
           now.second > random.randrange(0, 60) and \
           ((self.last_time is None) or ((now - self.last_time).total_seconds() > (10 * 60))):
            # One final at 14:50 + random seconds
            # and only if we haven't requested  more than 10 minutes ago.
            L.info("modify on Last call")
            return True
        return False

    def add_pattern(self):
        pattern = ""
        if self.config is not None:
            pattern = self.config.get("general", "pattern")
        dates_from_pattern = [t[1] for t in self.parse_spec(pattern) if t[0]]
        L.debug("Using pattern %s, adding %s" % (pattern, str(dates_from_pattern)))
        self.pending.update({t: DayStatus.TO_REQUEST for t in dates_from_pattern})
        if len(dates_from_pattern) > 0:
            self.last_time = None  # Force a modify.

    def parse_spec(self, text):
        # This is going to be a comma separated list of tokens:
        # - token: <weekday>|<date>
        # - todo: todo=L,M,X,J,V
        # - weekday: <raw>
        # - raw: L,M,X,J,V

        specs = text.split(',')
        result = []
        for s in specs:
            tk = self.parse_token(s)
            if tk is not None:
                result.extend(tk)
        return result

    def parse_token(self, s):
        # Parse individual tokens.
        s = s.strip()
        if len(s) == 0:
            return None
        if s == "todo":
            return self.parse_spec("L,M,X,J,V")
        if s in ("L", "M", "X", "J", "V"):
            today = datetime.datetime.today()
            if today.month == 12:
                next_month = datetime.date(today.year + 1, 1, 1)
            else:
                next_month = datetime.date(today.year, today.month + 1, 1)
            out = []
            for i in range(1, 32):
                try:
                    d = datetime.date(next_month.year, next_month.month, i)
                    if (s[0] == "L" and d.weekday() == 0) or \
                            (s[0] == "M" and d.weekday() == 1) or \
                            (s[0] == "X" and d.weekday() == 2) or \
                            (s[0] == "J" and d.weekday() == 3) or \
                            (s[0] == "V" and d.weekday() == 4):
                        out.append((True, d))
                except:
                    pass
            return out
        if s[0] == '!':
            tkns = self.parse_token(s[1:])
            return [(not tk[0], tk[1]) for tk in tkns]

    def check_next_month(self,result):
        now = datetime.datetime.now(tz=self.met)
        next_month = (now.month + 1)
        if next_month == 13:
            next_month = 1
        days_of_next_month = [ day for day in result if day.month == next_month and result[day].status in (DayStatus.RESERVED,DayStatus.REQUESTED)]
        if len(days_of_next_month) > 0:
            self.mark_next_month_as_processed(next_month)


    def update_pending(self, result):
        """
        Update the pending operations with the result retrieved from the web.
        :param result:
        :return: None
        """
        self.check_next_month(result)
        to_delete = []

        L.info("pending (before) " + str(self.pending))

        for i in sorted(self.pending):
            today = datetime.datetime.now(tz=self.met)
            if(i.month==1 and today.month==12):
                mes = 1
            else:
                mes = i.month - today.month
            if mes not in (0, 1):
                L.error(str(i) + " is neither current nor next month")
                to_delete.append(i)
                continue
            if self.pending[i] == DayStatus.TO_FREE:
                # Request was to Free... target should be Free.
                if i not in result or result[i].status in (DayStatus.AVAILABLE, DayStatus.NOT_AVAILABLE):
                    to_delete.append(i)

            if self.pending[i] == DayStatus.TO_REQUEST:
                # Request was to Request.... target is Assigned.
                if i not in result or result[i].status in (DayStatus.RESERVED, DayStatus.NOT_AVAILABLE):
                    to_delete.append(i)

            if self.pending[i] == DayStatus.TO_UNREQUEST:
                # Request was to Unrequest.
                if i not in result or result[i].status in (DayStatus.AVAILABLE, DayStatus.NOT_AVAILABLE):
                    to_delete.append(i)

        for i in to_delete:
            del self.pending[i]

        for i in [i for i in result if result[i].status == DayStatus.REQUESTED]:
            self.pending[i] = DayStatus.TO_REQUEST

        L.info("pending (after) " + str(self.pending))

    def update_notification(self, text):
        L.info("Notification: " + text)
        if platform == 'android':
            try:
                service = PythonService.mService
                builder = NotificationBuilder(service)
                builder.setSmallIcon(service.getApplicationInfo().icon)
                builder.setContentTitle("AndroidPark(ing)")
                builder.setContentText(text)
                context_intent = Intent(service, Class.forName('org.renpy.android.PythonActivity'))
                pending_intent = PendingIntent.getActivity(service, 0, context_intent,
                                                           PendingIntent.FLAG_UPDATE_CURRENT)
                builder.setContentIntent(pending_intent)
                manager = service.getSystemService(Context.NOTIFICATION_SERVICE)
                manager.notify(1, builder.build())
            except:
                L.error("Exception:\n" + traceback.format_exc())