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 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 start_modify_request(self): if not self.service_running: self.error.text = "El Servicio en segundo plano no está corriendo" self.error.open() return L.debug("Send start") days = self.get_days_with_change() operations = {} for d in days: if d.day.status != DayStatus.RESERVED and d.day.status != DayStatus.REQUESTED: # request operations[d.day.date] = DayStatus.TO_REQUEST elif d.day.status == DayStatus.RESERVED: # free operations[d.day.date] = DayStatus.TO_FREE elif d.day.status == DayStatus.REQUESTED: # unrequest operations[d.day.date] = DayStatus.TO_UNREQUEST free_count = len([x for x in operations if operations[x] == DayStatus.TO_FREE]) unrequest_count = len([x for x in operations if operations[x] == DayStatus.TO_UNREQUEST]) request_count = len([x for x in operations if operations[x] == DayStatus.TO_REQUEST]) # Now add to the operations those days that are already requested. requested_days = self.get_days_requested() for d in requested_days: operations[d.day.date] = DayStatus.TO_REQUEST total_request_count = len([x for x in operations if operations[x] == DayStatus.TO_REQUEST]) self.confirm.text = "Se liberarán {0:d} plazas, se quitará la solicitud para {1:d} plazas y se solicitarán {2:d} nuevas plazas. {3:d} plazas están solicitadas" \ .format(free_count, unrequest_count, request_count, total_request_count - request_count) self.confirm.on_accept = lambda: self.modify_request(operations) self.confirm.open()
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 stop_service(self): if self.service is None: return L.info("Trying to stop service") if platform == 'android': from android import AndroidService self.service.stop() self.service = None
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 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 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 start_service(self): if self.service is not None: return L.info("Trying to start service") if platform == 'android': from android import AndroidService service = AndroidService('AndroidPark(ing)', 'en ejecución...') service.start('service started') self.service = service
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 modify(self, operations, partial): status = self.query() last_text = "" today = datetime.datetime.now(tz=self.met) for i in sorted(operations): 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") partial(str(i) + " no es parte del mes actual o siguiente") continue if operations[i] == DayStatus.TO_REQUEST: if i not in status or status[i].status in ( DayStatus.RESERVED, DayStatus.NOT_AVAILABLE, DayStatus.REQUESTED, DayStatus.BUSY, ): partial(str(i) + " está ya solicitado") continue if operations[i] in (DayStatus.TO_FREE, DayStatus.TO_UNREQUEST): # Operation is to free/unrequest if i not in status or status[i].status in (DayStatus.AVAILABLE, DayStatus.NOT_AVAILABLE): partial(str(i) + " está ya liberado") continue try: L.debug("Requesting " + str(i) + ": " + self.map_operation(operations[i])) r = self.session.post( "http://" + self.host + "/perfil.php", data={"dia": i.day, "mes": mes, "libre": operations[i]} ) if r.status_code != requests.codes.ok: L.error("Failed request on " + self.host + ", response:" + r) partial(str(i) + "`: Failed request on server response:" + r) continue # Parse the web page to obtain the result message last_text = self.get_response_text(r) result = self.parse_result(last_text) if result == "": result = "La modificación de " + str(i) + " no fue aceptada" partial(result) L.info(str(i) + ": " + result) except (KeyboardInterrupt, requests.ConnectionError): L.debug("Petición fallida en " + self.host + ", con:\n" + traceback.format_exc()) if last_text != "": return self.parse_months(last_text) else: # We didn't send any operation, use the status retrieved # when we entered the method. return status
def add_fake_request(self,msg): if msg['response'] == 'ping': request=Ping() elif msg['response'] == 'query': request=Refresh() elif msg['response'] == 'modify': request=Modify() else: L.error("Respuesta desconocida %s" % msg['response']) request.id=msg['id'] self.pending[request.id] = request
def refresh_request(self): if not self.is_config_ready(): return if not self.service_running: self.error.text = "El Servicio en segundo plano no está corriendo" self.error.open() return L.debug("Query start") self.last_update = datetime.datetime.now() self.querying.open() self.status_bar.text = "Consultando al servidor del parking..." self.app.do_refresh()
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 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())
def handle_message(self, message, *args): pickle_msg = message[2] msg = pickle.loads(pickle_msg) if 'response' in msg: if msg['id'] == -1: self.add_fake_request(msg) keep = 'is_partial' in msg and msg['is_partial'] req = self.get_id(msg['id'], keep) if req is not None: if keep: req.callback_partial(msg) else: req.callback(msg) else: L.debug("Ignoring unknown response %d" % msg['id']) else: L.debug("Got a request %s for %d" % (msg['response'], msg['id']))
def update_info(self, state, pending, status=None): self.status_bar.text = "" if not state: L.error("state is None") self.querying.dismiss() if status is not None: self.error.text = status else: self.error.text = "Error desconocido" self.error.open() return self.unrequest_month(self.current_month) self.unrequest_month(self.next_month) self.current_month.update(state, pending) self.next_month.update(state, pending) L.debug("Update ends") self.querying.dismiss()
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 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 login(self): L.debug("Starting login") # # Start a session so that we can reuse cookies # self.session = requests.Session() # self.session.proxies= { # "http": "http://es.proxy.lucent.com:8000", # "https": "http://es.proxy.lucent.com:8000", # } # # Prepare the login request # try: r = self.session.post( "http://" + self.host + "/index.php", data={"usuario": self.username, "contrasena": self.password, "aceptar": "ACEPTAR"}, ) if r.status_code != requests.codes.ok: status = "Fallo al hacer login " + self.host + ", respuesta:" + r raise ServerException(status) except (KeyboardInterrupt, requests.ConnectionError) as e: status = str(e) raise ServerException(status) # # Parse the web page we have read # L.debug("Parsing query result") state = self.parse_months(self.get_response_text(r)) if state is None: raise ServerException( "Problema consultando con el servidor, es probable que el usuario/contraseña sean incorrectos" ) return state
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 query(self): L.debug("Starting query") if self.session is None: # # No session, start a login session # the result is fine as a query result # state = self.login() return state # # Reuse session # try: r = self.session.get("http://" + self.host + "/perfil.php") if r.status_code != requests.codes.ok: status = "Error consultando al " + self.host + ", respuesta:" + r raise ServerException(status) # # Parse the web page we have read # L.debug("Parsing query result") state = self.parse_months(self.get_response_text(r)) if state is None: L.debug("parsing failed, login might have failed") self.session = None return self.query() except (KeyboardInterrupt, requests.ConnectionError, ServerException): L.error(traceback.format_exc()) # try to login again self.session = None return self.query() return state
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 ping_callback(self, value, config): if self.service_running != value: L.debug("Service state change: " + str(value)) self.service_running = value
def modify_partial_callback(self, status): L.info(status) self.status_bar.text = status.decode('utf-8', 'replace').encode('utf-8')
def parse_months(self, text): """ Parse the web page to obtain the list of the days and their status :param text: the text from the http response :return: a dictionary with the status of the days """ result = [] out = {} soup = bs4.BeautifulSoup(text, "html5lib") # get the cells... this is better reference I could get toptables = soup.select("body > table ") if len(toptables) != 6: L.error("Parsing: did not found 6 tables") return None tables = [toptables[4].tbody.tr.td.table, toptables[5].tbody.contents[1].td.table] # Now, for each table. for t in tables: month = [] # for each week (row) for w in t.select("tr"): # for each day (cell) for d in w.select("td"): # only pay attention to colored cells if "bgcolor" in d.attrs: # Try to parse the cell cell_list = d.select("div") if len(cell_list) == 0: # No divs... not interested in this continue elif len(cell_list) < 2: # only one div day = cell_list[0] slot = None else: # two divs. (day, slot) = cell_list # some days appear with two nested divs. if day.string is not None: mday = int(day.string.strip()) else: mday = int([x for x in day.stripped_strings][0]) rslot = None # parse the status based on the color if d.attrs["bgcolor"] == "#999999": status = DayStatus.NOT_AVAILABLE elif d.attrs["bgcolor"] == "#FF9900": status = DayStatus.RESERVED if slot is not None: rslot = [x for x in slot.stripped_strings][0] elif d.attrs["bgcolor"] == "#339900": status = DayStatus.AVAILABLE elif d.attrs["bgcolor"] == "#000000": status = DayStatus.REQUESTED else: status = DayStatus.BUSY month.append(DayStatus(mday, status, rslot)) result.append(month) if len(result) != 2: L.error("Parsing: did not found 2 months") return None # Now rebuild the date today = datetime.datetime.now(tz=self.met) today = datetime.date(today.year, today.month, 1) for d in [x.fix(today) for x in result[0]]: out[d.date] = d for d in [x.fix(today + datetime.timedelta(days=31)) for x in result[1]]: out[d.date] = d busy = len([x for x in out if out[x].status == DayStatus.BUSY]) requested = len([x for x in out if out[x].status == DayStatus.REQUESTED]) reserved = len([x for x in out if out[x].status == DayStatus.RESERVED]) available = len([x for x in out if out[x].status == DayStatus.AVAILABLE]) L.info("Parsed: %d busy,%d requested, %d reserved, %d available" % (busy, requested, reserved, available)) return out
def callback_partial(self, msg): if self.calendar is not None: L.debug("Got a partial response %s for %d" % (msg['response'], msg['id'])) self.calendar.modify_partial_callback(msg['status'])
def callback(self, msg): if self.calendar is not None: L.debug("Got a response %s for %d" % (msg['response'], msg['id'])) self.calendar.modify_callback()
def callback(self, msg): if self.calendar is not None: L.debug("Got a response %s for %d" % (msg['response'], msg['id'])) self.calendar.refresh_callback(msg['result'], msg['pending'], msg['status'])
def refresh_callback(self, state, pending, status=None): if status is not None: L.debug("Update status: " + str(status) + " pending operations: " + str(pending)) Clock.schedule_once(lambda (dt): self.update_info(state, pending, status))