class EanSearch(BarcodeDecoder): def __init__(self): BarcodeDecoder.__init__(self) self.logger = Logger('EanSearch') @staticmethod def url(barcode): return 'https://www.ean-search.org/perl/ean-search.pl?q=' + quote( barcode.encode('utf-8')) def item(self, barcode): url = EanSearch.url(barcode) try: blob = BeautifulSoup(get(url).text, "html.parser") if 'Excessive use' in blob.text: self.logger.warn("Overuse") return None name = blob.find('a', {'href': "/perl/ean-info.pl?ean=" + barcode}) if name: return Item(name=name.text, url=url) else: return None except Exception as e: self.logger.warn("Exception while searching for " + barcode + "\n" + str(e)) return None
def __init__(self, shop, shop_list, web_hook_url=None, web_server_ip=None, web_server_port=8080, asynchron=True): self.logger = Logger('ShopSync') self.shop = shop self.wu_list = shop_list self.meta = MetaShop(self, self.wu_list) self.shop_list_rev = 0 self.shop_task_revs = {} self.shop_items = {} self.choice = Choice("choices-" + shop.__class__.__name__ + ".db") if web_hook_url: self.web_hook_url = web_hook_url self.web_server_ip = web_server_ip self.web_server_port = web_server_port function = self.start_hook else: function = self.listen if asynchron: start_new_thread(function, ()) else: function()
def __init__(self, email, password, cookie_file="ayn_cookies"): self.logger = Logger('AllYouNeed') self.base_url = 'https://www.allyouneedfresh.de' self.basket_url = 'https://www.allyouneedfresh.de/warenkorbuebersicht' self.take_url = 'https://www.allyouneedfresh.de/responsive/pages/checkout1.jsf' self.take_page_view_state = None self.driver = webdriver.PhantomJS(executable_path='/usr/local/bin/phantomjs') # self.driver = webdriver.Chrome('./chromedriver') self.driver.set_window_size(1280, 1024) Shop.__init__(self, email, password, cookie_file)
def __init__(self, email, password, captcha_service, cookie_file="kl_cookies"): self.logger = Logger('Kaufland') self.captcha_service = captcha_service self.base_url = 'https://shop.kaufland.de' self.login_url = "https://shop.kaufland.de/login" self.account_url = "https://shop.kaufland.de/my-account" self.take_url = 'https://shop.kaufland.de/cart/modify' self.basket_url = 'https://shop.kaufland.de/cart' self.driver = webdriver.PhantomJS(executable_path='/usr/local/bin/phantomjs') # self.driver = webdriver.Chrome('./chromedriver') self.driver.set_window_size(1280, 1024) Shop.__init__(self, email, password, cookie_file)
def __init__(self, device): self.logger = Logger("Barcode scan") devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()] for d in devices: if device in d.name: self.logger.info("Found device " + d.name) self.device = d break if self.device is None: raise Exception("No barcode device found") self.q = queue.Queue() start_new_thread(self.scan, ())
def __new__(cls, *args, **kwds): #Singleton interface for logger thisSingleton = cls.__dict__.get("__thisSingleton__") if thisSingleton is not None: return thisSingleton cls.__thisSingleton__ = thisSingleton = LOG.__new__(cls) return thisSingleton
def __init__(self, barcode_descriptor, config, shopList, asynchron=True): self.logger = Logger('BarcodeSync') self.barcode_descriptor = barcode_descriptor self.barcode_reader = BarcodeReader(config['barcode_device_name']) self.shop_list = shopList self.file_name = "barcode.db" self.matches = self.load() if 'notification_homeassistant_bearer_token' in config: self.notification_url = config['notification_homeassistant_url'] self.bearer_token = config[ 'notification_homeassistant_bearer_token'] self.notify("Booted") if asynchron: start_new_thread(self.listen, ()) else: self.listen()
class Geizhals(BarcodeDecoder): def __init__(self): BarcodeDecoder.__init__(self) self.logger = Logger('Geizhalz') @staticmethod def url(barcode): return 'https://geizhals.de/?fs=' + quote(barcode.encode('utf-8')) def item(self, barcode): url = Geizhals.url(barcode) try: blob = BeautifulSoup(get(url).text, "html.parser") blob = blob.find('div', {'id': 'gh_artbox'}) if not blob: return None return Item(name=self.parse_name(blob), price=self.parse_price(blob), url=url) except Exception as e: self.logger.warn("Exception while searching for " + barcode + "\n" + str(e)) return None def parse_name(self, blob): header = blob.find('h1', {'class': 'arthdr'}) if header: return header.find('span', {'itemprop': 'name'}).text else: return blob.find('b', {'itemprop': 'name'}).__next__ def parse_price(self, blob): header = blob.find('h1', {'class': 'arthdr'}) if header: price = header.find('span', {'class': 'gh_price'}) price = price.text.replace('€', '').strip() p = price.split(',') return int(p[0]) * 100 + int(p[1]) else: return None
class BarcodeReader: def __init__(self, device): self.logger = Logger("Barcode scan") devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()] for d in devices: if device in d.name: self.logger.info("Found device " + d.name) self.device = d break if self.device is None: raise Exception("No barcode device found") self.q = queue.Queue() start_new_thread(self.scan, ()) def scan(self): while True: try: self.logger.info('Waiting for scanner data') barcode = self.read() self.logger.info("Scanned barcode '{0}'".format(barcode)) self.q.put(barcode) except Exception: traceback.print_exc() def read(self): current_barcode = "" for event in self.device.read_loop(): if event.type == evdev.ecodes.EV_KEY and event.value == 1: keycode = categorize(event).keycode if keycode == 'KEY_ENTER': return current_barcode else: current_barcode += keycode[4:]
class BarcodeSync: def __init__(self, barcode_descriptor, config, shopList, asynchron=True): self.logger = Logger('BarcodeSync') self.barcode_descriptor = barcode_descriptor self.barcode_reader = BarcodeReader(config['barcode_device_name']) self.shop_list = shopList self.file_name = "barcode.db" self.matches = self.load() if 'notification_homeassistant_bearer_token' in config: self.notification_url = config['notification_homeassistant_url'] self.bearer_token = config[ 'notification_homeassistant_bearer_token'] self.notify("Booted") if asynchron: start_new_thread(self.listen, ()) else: self.listen() def save(self): with open(self.file_name, 'wb') as f: pickle.dump(self.matches, f) def load(self): try: with open(self.file_name, 'rb') as f: return pickle.load(f) except IOError: return {} def remember_choice(self, barcode, item): self.matches[barcode] = item self.save() def match(self, barcode): if barcode in self.matches: self.logger.info("Use old match for: " + barcode) return self.matches[barcode] else: return None def listen(self): while True: try: barcode = self.barcode_reader.q.get() self.logger.info("Work on: " + barcode) item = self.match(barcode) if not item: item = self.barcode_descriptor.item(barcode) if item: self.remember_choice(barcode, item) if item: self.logger.info("Detected: " + item.name) self.add_barcode(item) else: self.logger.info("Could not id: " + barcode) except Exception: traceback.print_exc() def add_barcode(self, item): self.notify(item.name) for task, existing in self.shop_list.list_items(): self.logger.info("Existing item: " + existing.selected_shop_item().name) if existing.synced() and item.name.lower( ) in existing.selected_shop_item().name.lower(): existing.inc_amount() self.shop_list.update_item(task, existing) self.logger.info("Updated existing item: " + existing.selected_shop_item().name) return self.shop_list.create_item(item) def notify(self, message): if not self.bearer_token: self.logger.debug("no notification credentials provided") return headers = { "Content-Type": "application/json", "Authorization": "Bearer " + self.bearer_token, } data = { "title": "Barcode scan", "message": message, "data": { "group": "barcode_scan", "push": { "interruption-level": "passive", "sound": "none" } } } response = requests.request("POST", self.notification_url, headers=headers, data=json.dumps(data)) self.logger.info("Notification " + message + "sent. Response: " + str(response.status_code)) self.logger.info("Create new item")
def __init__(self): SubItem.__init__(self) self.logger = Logger('CheckCartAction') self.notes = None
class CheckCartAction(SubItem): def __init__(self): SubItem.__init__(self) self.logger = Logger('CheckCartAction') self.notes = None # noinspection SpellCheckingInspection def title(self): return "Einkaufswagen und Liste vergleichen" def selected(self): return False def action(self, meta_shop): cart_items = meta_shop.shop_sync.shop.cart() shop_items = [] msg0 = "" msg1 = "" for task, item in meta_shop.wu_list.list_items(): shop_item = item.selected_shop_item() if shop_item: shop_items.append(shop_item) if shop_item in cart_items: for c in cart_items: if c.name == shop_item.name: if shop_item.amount != c.amount: self.logger.warn( "Task item and shop item amounts differ: " + shop_item.name.encode('utf-8')) msg0 += shop_item.name.encode( 'utf-8') + ": " + str( shop_item.amount) + " vs. " + str( c.amount) + "\n" break else: self.logger.warn("Task item without shop item: " + shop_item.name.encode('utf-8')) msg1 += " - " + shop_item.name.encode('utf-8') + "\n" msg2 = "" for cart_item in cart_items: if cart_item not in shop_items: self.logger.warn("Cart item without task: " + cart_item.name.encode('utf-8')) msg2 += " + " + cart_item.name.encode('utf-8') + "\n" msg = "" if msg0: msg += "Mengenunterschiede: Liste vs. Einkaufswagen\n" + msg0 if msg1: msg += "\nNicht im Einkaufswagen gefunden:\n" + msg1 if msg2: msg += "\nNicht auf der Liste gefunden:\n" + msg2 self.notes = msg def note(self): return self.notes @classmethod def parse(cls, string): pass
def __init__(self): SubItem.__init__(self) self.logger = Logger('ClearAction')
def __init__(self): BarcodeDecoder.__init__(self) self.logger = Logger('EanSearch')
class Kaufland(Shop): search_url_prefix = 'https://shop.kaufland.de/search?pageSize=48&sort=relevance&text=' def __init__(self, email, password, captcha_service, cookie_file="kl_cookies"): self.logger = Logger('Kaufland') self.captcha_service = captcha_service self.base_url = 'https://shop.kaufland.de' self.login_url = "https://shop.kaufland.de/login" self.account_url = "https://shop.kaufland.de/my-account" self.take_url = 'https://shop.kaufland.de/cart/modify' self.basket_url = 'https://shop.kaufland.de/cart' self.driver = webdriver.PhantomJS(executable_path='/usr/local/bin/phantomjs') # self.driver = webdriver.Chrome('./chromedriver') self.driver.set_window_size(1280, 1024) Shop.__init__(self, email, password, cookie_file) @staticmethod def search_url(name): return Kaufland.search_url_prefix + quote(name.encode('utf-8')) def login(self): self.logger.info("Logging in...") self.session = Session() self.driver.get(self.account_url) time.sleep(2) x = self.driver.find_element_by_id('kLoginForm') x.find_element_by_id('j_username').send_keys(self.email) x.find_element_by_id('j_password').send_keys(self.password) x.find_element_by_tag_name('button').click() time.sleep(3) self.new_session_with_cookies(self.driver.get_cookies()) self.save_session() def is_logged_in(self, html=None): if not html: html = self.session.get(self.account_url).text return html.find('Abmelden') > 0 def save_session(self): with open(self.cookie_file, 'w') as f: pickle.dump(self.session.cookies, f) def load_session(self): try: with open(self.cookie_file) as f: cookies = pickle.load(f) self.session = Session() self.session.cookies = cookies return self.is_logged_in() except IOError: return False def get(self, url): html = self.session.get(url).text if self.is_logged_in(html): self.save_session() return html self.login() html = self.session.get(url).text if self.is_logged_in(html): self.save_session() return html self.logger.error("Can not log in") exit(1) def cart(self): blob = BeautifulSoup(self.get(self.basket_url), "html.parser") r = blob.select('section.product-list') if len(r) == 0: return [] r = r[0] ids = [] for i in r.findAll('article'): a = i.find('a') link = urllib.parse.urljoin(self.base_url, a['href']) title = i.find('p', {'class': 'product-list__title'}).text.strip() amount = i.find('div', {'class': 'product-list__amount'}) article_id = amount['data-dynamicblock'] amount = int(amount.find('input', {'name': 'quantity'}).get('value')) price = i.find('div', {'data-dynamiccontent': 'prices'}) red = price.find('span', {'class': 'product-list__reduced-price'}) if red: price = red price = price.text.replace('€', '').strip() price = int(float(price) * 100) title = unicodedata.normalize('NFKC', title) item = ShopItem(article_id, amount, title, price, link) ids.append(item) return ids def search(self, term, sub_term=None): html = self.get(Kaufland.search_url(term)) ids = self.parse_search(html) split_terms = [x for x in re.split('-| |\n', term) if len(x) > 1] if 0 < len(ids) < 48: return self.order_by_matches(split_terms, ids) if sub_term and len(ids) == 0: return self.search(term + " " + sub_term) if len(split_terms) > 1: ids = [] for criteria in split_terms: if len(criteria) > 1: ids += self.search(criteria) return self.order_by_matches(split_terms, ids, max=20, perfect=0.6, cut_off=0.25) def parse_search(self, html): blob = BeautifulSoup(html, "html.parser") ids = [] r = blob.select('div.productmatrix') if len(r) > 0: r = r[0] for i in r.findAll('article'): a = i.find('a') article_id = i['data-dynamicblock'].split('_')[0] link = urllib.parse.urljoin(self.base_url, a['href']) title = a.find('p', {'class': 'product-tile__infos--title'}).text.strip() price = a.find('div', {'class': 'product-tile__price--regular'}) if not price: price = a.find('div', {'class': 'product-tile__price--reduced'}) price = price.text.replace('€', '').strip() price = int(float(price) * 100) title = unicodedata.normalize('NFKC', title) item = ShopItem(article_id, 1, title, price, link) ids.append(item) return ids def order_by_matches(self, terms, ids, max=None, perfect=None, cut_off=None): if len(ids) == 0: return [] normal_fit = {} perfect_fit = {} normal_ids = [] perfect_ids = [] for item in ids: if item in normal_ids or item in perfect_ids: continue match = len([x for x in terms if x.lower() in item.name.lower()]) if not cut_off or match > len(terms) * cut_off: normal_ids.append(item) normal_fit[item] = match if perfect and match > len(terms) * perfect: perfect_ids.append(item) perfect_fit[item] = match if len(perfect_fit) > 0: normal_ids = perfect_ids normal_fit = perfect_fit ordered = sorted(normal_ids, key=normal_fit.__getitem__, reverse=True) if max: ordered = ordered[:max] return ordered def take(self, item): html = self.get(Kaufland.search_url(item.name)) blob = BeautifulSoup(html, "html.parser") token = blob.find('input', {'name': 'CSRFToken'}).get('value') self.session.post(self.take_url, data=[ ('qty', item.amount), ('productCodePost', item.article_id), ('pageTemplate', 'producttile'), ('CSRFToken', token), ]) self.save_session() def shelf_life(self, item_link): pass
def __init__(self, config): ShopList.__init__(self) self.logger = Logger('Wunderlist') self.client = wunderpy2.WunderApi().get_client(config['wunderlist_token'], config['wunderlist_client_id']) self.list_id = config['wunderlist_list_id']
class AllYouNeed(Shop): search_url_prefix = 'https://www.allyouneedfresh.de/suchen?term=' def __init__(self, email, password, cookie_file="ayn_cookies"): self.logger = Logger('AllYouNeed') self.base_url = 'https://www.allyouneedfresh.de' self.basket_url = 'https://www.allyouneedfresh.de/warenkorbuebersicht' self.take_url = 'https://www.allyouneedfresh.de/responsive/pages/checkout1.jsf' self.take_page_view_state = None self.driver = webdriver.PhantomJS(executable_path='/usr/local/bin/phantomjs') # self.driver = webdriver.Chrome('./chromedriver') self.driver.set_window_size(1280, 1024) Shop.__init__(self, email, password, cookie_file) @staticmethod def search_url(name): return AllYouNeed.search_url_prefix + quote(name.encode('utf-8')) def j_faces_view_state(self, url): res = self.session.get(url) blob = BeautifulSoup(res.text, "html.parser") return blob.find('input', {'name': 'javax.faces.ViewState'}).get('value') def login(self): self.logger.info("Logging in...") self.session = Session() self.driver.get(self.base_url) time.sleep(1) self.driver.find_element_by_link_text("Anmelden").click() time.sleep(1) self.driver.find_element_by_id('zipCodeForm:loginEmail').send_keys(self.email) self.driver.find_element_by_id('zipCodeForm:loginPassword').send_keys(self.password) self.driver.find_element_by_id('zipCodeForm:loginButton').click() time.sleep(1) self.new_session_with_cookies(self.driver.get_cookies()) self.take_page_view_state = self.j_faces_view_state(self.basket_url) self.save_session() def is_logged_in(self, html=None): if not html: html = self.session.get(self.base_url).text x = html.find('Anmelden/Registrieren') return x < 0 def save_session(self): with open(self.cookie_file, 'w') as f: pickle.dump(self.driver.get_cookies(), f) def load_session(self): try: with open(self.cookie_file) as f: cookies = pickle.load(f) self.new_session_with_cookies(cookies) for cookie in cookies: for k in ('name', 'value', 'domain', 'path', 'expiry'): if k not in list(cookie.keys()): if k == 'expiry': cookie[k] = 1475825481 self.driver.add_cookie({k: cookie[k] for k in ('name', 'value', 'domain', 'path', 'expiry') if k in cookie}) return self.is_logged_in() except IOError: return False def cart(self): html = self.session.get(self.basket_url).text x = html.find('Sie haben leider') if x > 0: return [] blob = BeautifulSoup(html, "html.parser") r = blob.find('div', {'class': "panel-body table"}) amount_col = r.findAll('span', {'class': 'td col-sm-1 nopadding text-nowrap text-center'}) name_col = r.findAll('span', {'class': 'td col-sm-7 nopadding'}) price_col = [] for p in r.findAll('span', class_='td col-sm-3 text-center nopadding'): pr = p.find('span', class_='styledPrice') if pr: price_col.append(pr) article_col = [] for d in r.findAll('div', class_="row"): for clazz in d.get('class'): # article_col[0] is the table header if 'artId' in clazz: article_col.append(int(clazz[5:])) break res = [] for i in range(len(amount_col)): article_id = article_col[i] amount = int(amount_col[i].text.replace('x', '').strip()) item = name_col[i] link = urllib.parse.urljoin(self.base_url, item.find('a')['href']) price = extract_price(price_col[i]) item.span.clear() name = item.text.strip() res.append(ShopItem(article_id, amount, name, price, link)) return res def search(self, term, sub_term=None): html = self.session.get(AllYouNeed.search_url(term)).text blob = BeautifulSoup(html, "html.parser") r = blob.select('div.product-box.item') ids = [] perfect = [] for i in r: link = i.find('a', class_='article-link')['href'] link = urllib.parse.urljoin(self.base_url, link) price = extract_price(i.find('span', class_='product-price')) desc = i.find('div', class_='product-description') details = desc.find('span', class_='product-story').find('span') if details: details.extract() span = desc.find('span', class_='text-nowrap') if span: span.replace_with('') desc = desc.text.strip().replace('\n', ', ') for c in i['class']: if 'artId' in c: article_id = int(c[5:]) item = ShopItem(article_id, 1, desc, price, link) ids.append(item) match = True for criteria in term.split(): if criteria.lower() not in desc.lower(): match = False break if match: perfect.append(item) break return perfect if perfect else ids def take(self, item): self.driver.get(AllYouNeed.search_url(item.name)) time.sleep(2) ccc = self.driver.find_element_by_css_selector('.artId' + str(item.article_id)) ccc = ccc.find_element_by_class_name('product-cart') inps = ccc.find_elements_by_tag_name('input') if len(inps) == 0 or not inps[0].is_displayed(): self.logger.info("taking..") ccc = ccc.find_element_by_tag_name('a') ccc.click() time.sleep(3) ccc = self.driver.find_element_by_css_selector('.artId' + str(item.article_id)) ccc = ccc.find_element_by_class_name('product-cart') inp = ccc.find_element_by_tag_name('input') else: inp = inps[0] inp.click() # inp.send_keys(Keys.CONTROL, 'a') inp.send_keys(str(item.amount)) inp.send_keys(Keys.ENTER) time.sleep(1) def shelf_life(self, item_link): html = self.session.get(item_link).text blob = BeautifulSoup(html, "html.parser") for desc in blob.findAll('div', class_='product-card'): for more in desc.findAll('div', class_='product-story'): for td in more.findAll('div', class_='td'): date = td.find("strong").text.strip() return datetime.strptime(date, '%d.%m.%Y') return datetime.max
'--rmsd_threshold', dest="rmsd_threshold", action="store", default=0.3, type=float, help= "If set, the RMSD threshold for considering a superimposition as correct will take this value. If not, it will be 0.3 by default. The output of the program is very sensitive to this value, we advise to be careful when modifying it." ) parser.add_argument( '-cl', '--clashes_threshold', dest="clashes", action="store", default=30, type=int, help= "If set, the threshold of the number of clashes will take this value. If not, it will be 30 by default. The output of the program is very sensitive to this value, we advise to be careful when modifying it." ) parser.add_argument( '-opt', '--optimisation', dest="optimisation", action="store_true", default=False, help= "If set, it runs an optimisation on your output structure and saves it in a separate PDB file. It is only available for structures with less than 99.999 residues" ) l = Logger()
class CodeCheck(BarcodeDecoder): def __init__(self): BarcodeDecoder.__init__(self) self.logger = Logger('Codecheck') @staticmethod def url(barcode): base_url = 'http://www.codecheck.info/product.search' return '{0}?q={1}&OK=Suchen'.format(base_url, quote(barcode.encode('utf-8'))) def item(self, barcode): url = CodeCheck.url(barcode) try: blob = BeautifulSoup(get(url).text, "html.parser") if not blob.find("meta", property="og:title"): return None ratings, numeric = self.parse_ratings(blob) return Item(name=self.parse_name(blob), sub_name=self.parse_sub_name(blob), price=self.parse_price(blob), url=url, ingredients=self.parse_ingredients(blob), ratings=ratings, num_rating=numeric) except Exception as e: self.logger.warn("Exception while searching for " + barcode + "\n" + str(e)) return None def parse_name(self, blob): return blob.find("meta", property="og:title")['content'] def parse_sub_name(self, blob): return blob.find('h3', {'class': 'page-title-subline'}).text def parse_ingredients(self, blob): return blob.find('span', {'class': 'rated-ingredients'}).text def parse_price(self, blob): r = blob.find('div', {'class': "area", 'id': "price-container"}) r = r.find('div', {'class': "lighter-right"}) if not r: return None r.span.clear() price = r.text.replace('EUR', '').strip() p = price.split(',') return int(p[0]) * 100 + int(p[1]) def parse_ratings(self, blob): r = blob.find('div', {'class': "area spacing ingredient-rating"}) if not r: return [], 0 ratings = r.findAll('div', {'class': 'c c-2 rating-color'}) rate = r.findAll('div', {'class': 'c c-3 rating-color'}) numbers = r.findAll('div', {'class': 'rating-group-header'}) rate_list = [] agg = 0 num = 0 for i in range(len(ratings)): val = self.map_rating_class(numbers[i].get('class', [])[1]) if val > 0: agg += val num += 1 rate_list.append(ratings[i].text + ": " + rate[i].text) return rate_list, agg / num if num else 0 def map_rating_class(self, clazz): num = int(clazz.replace('pr-rating-', '')) / 100 if num in [1,8]: return 0 else: return num
def __init__(self): BarcodeDecoder.__init__(self) self.logger = Logger('Geizhalz')
def __init__(self): BarcodeDecoder.__init__(self) self.logger = Logger('Codecheck')
class ShopSync: def __init__(self, shop, shop_list, web_hook_url=None, web_server_ip=None, web_server_port=8080, asynchron=True): self.logger = Logger('ShopSync') self.shop = shop self.wu_list = shop_list self.meta = MetaShop(self, self.wu_list) self.shop_list_rev = 0 self.shop_task_revs = {} self.shop_items = {} self.choice = Choice("choices-" + shop.__class__.__name__ + ".db") if web_hook_url: self.web_hook_url = web_hook_url self.web_server_ip = web_server_ip self.web_server_port = web_server_port function = self.start_hook else: function = self.listen if asynchron: start_new_thread(function, ()) else: function() def start_hook(self): self.sync_shop_list() self.sync_shop_list() self.wu_list.create_web_hook(self.web_hook_url, self.web_server_port) run_simple(self.web_server_ip, self.web_server_port, self.hook) @Request.application def hook(self, _): self.timed_sync() return Response() def timed_sync(self): try: self.sync_shop_list() except: Timer(10 * 60.0, self.timed_sync).start() # 10 Minutes return Response() def listen(self): wait = [10, 30, 60, 120, 240, 480] wait_index = 0 while True: try: change = self.sync_shop_list() if change: wait_index = 0 else: sleep(wait[wait_index]) wait_index = min(wait_index + 1, len(wait) - 1) except Exception: traceback.print_exc() def sync_shop_list(self): if self.shop_list_rev == self.wu_list.list_revision(): return False new, changed, deleted_ids, meta_changed = self.detect_changed_tasks() for iid in deleted_ids: self.remove_item_by_id(iid) for task in new: self.new_item(task) for task in changed: self.update_item(task) if meta_changed: self.meta.sync() self.update_meta() return len(new) + len(changed) + len(deleted_ids) > 0 or meta_changed def update_meta(self): shop_items = [ item.selected_shop_item() for item in list(self.shop_items.values()) if item.synced() ] price = 0 for s in shop_items: price += s.amount * s.price self.meta.set_price(price) def detect_changed_tasks(self): self.shop_list_rev = self.wu_list.list_revision() new_tasks = self.wu_list.list_tasks() meta_changed = self.meta.detect_changes(new_tasks) changed = [] new = [] for new_task in new_tasks: if self.wu_list.is_meta_item(new_task): continue iid = new_task['id'] revision = new_task['revision'] if iid in self.shop_task_revs: if self.shop_task_revs[iid] != revision: self.shop_task_revs[iid] = revision changed.append(new_task) else: self.shop_task_revs[iid] = revision new.append(new_task) deleted_ids = [] for iid in self.shop_task_revs: found = False for new_task in new_tasks: if iid == new_task['id']: found = True break if not found: deleted_ids.append(iid) for iid in deleted_ids: self.shop_task_revs.pop(iid) return new, changed, deleted_ids, meta_changed def remove_item_by_id(self, iid): item = self.shop_items.pop(iid) self.logger.info("delete - " + item.name.encode('utf-8')) if item.synced(): self.shop.delete(item.selected_shop_item()) def new_item(self, task): self.logger.info("new - " + task['title'].encode('utf-8')) iid = task['id'] item = self.wu_list.item_from_task(task) shop_items = self.shop.search(item.name, item.sub_name) item.set_shop_items(shop_items) if item.selected_item: self.choice.remember_choice(item) else: self.choice.match(item) if item.selected_item: self.shop.take(item.selected_shop_item()) self.shop_items[iid] = item self.wu_list.update_item(task, item, rebuild_notes=True, rebuild_subs=True) def update_item(self, task): self.logger.info("Update - " + task['title'].encode('utf-8')) iid = task['id'] item = self.wu_list.item_from_task(task) existing = self.shop_items[iid] if item != existing: self.remove_item_by_id(iid) self.new_item(task) else: update = False if item.synced() and not existing.synced(): existing.select_shop_item(item.selected_shop_item()) self.shop.take(existing.selected_shop_item()) update = True if not item.synced() and existing.synced(): self.shop.delete(existing.selected_shop_item()) existing.select_shop_item(None) update = True if existing.amount != item.amount: existing.amount = item.amount if existing.synced(): self.shop.take(existing.selected_shop_item()) update = True if update: self.choice.remember_choice(existing) self.shop_task_revs[iid] = self.wu_list.update_item( task, existing)