diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..be9958c --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = *tests*,python/* \ No newline at end of file diff --git a/.gitignore b/.gitignore index ef5fa94..911a918 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.pyc .cache .coverage +htmlcov/* .pytest_cache .mypy_cache/ __pycache__ @@ -24,7 +25,7 @@ webdriver/* .idea/* venv/* tests/data/mbplugin/* -BalanceHistory.sqlite +*.sqlite balance.html plugin/debug.log mbplugin*.zip diff --git a/build.bat b/build.bat index f671656..d28f2a0 100644 --- a/build.bat +++ b/build.bat @@ -2,10 +2,23 @@ %~d0 @REM clean: @REM git clean -fXd +set "ptime= " +where ptime +if %errorlevel%==0 set ptime=ptime if NOT "%1"=="" goto %1 ECHO RUN build clean/test/build/fixup +goto :EOF +@REM @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ test +:coverage +%~d0 +cd "%~dp0" +call python\python -m coverage run -m pytest tests %2 %3 %4 %5 %6 %7 %8 %9 +echo coverage html +call python\python -m coverage html +@rem call start htmlcov\index.html +echo %errorlevel% goto :EOF @REM @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ test :test diff --git a/changelist.md b/changelist.md index e26aa8b..e06d318 100644 --- a/changelist.md +++ b/changelist.md @@ -429,3 +429,7 @@ FIX: Tele2 исправлена работа через Chrome (напомню FIX: Beeline подправил расчет интернета FIX: Sipnet в процессе получения ругается на неправильный сертификат, пришлось отключить проверку сертификатов. FIX: Похоже поменялась страница входа в beeline_uz поправил, но проверить не могу, т.к. не имею телефонов + +## mbplugin v1.00.38 (24.10.22) refactoring и консольные команды +REFACTORING: Переработано большое количество кода, с целью большей управляемости, но без изменения функционала, pytest+coverage +ADD: Диагностические сообщения и коды возврата для консольной команды mbp get-balance и check-plugin diff --git a/docker/requirements_pytest.txt b/docker/requirements_pytest.txt index a644c34..bd94f68 100644 --- a/docker/requirements_pytest.txt +++ b/docker/requirements_pytest.txt @@ -1,5 +1,6 @@ atomicwrites==1.4.0 attrs==21.4.0 +coverage==6.5.0 iniconfig==1.1.1 packaging==21.3 pluggy==1.0.0 diff --git a/plugin/beeline_uz.py b/plugin/beeline_uz.py index 557b7e9..d162a04 100644 --- a/plugin/beeline_uz.py +++ b/plugin/beeline_uz.py @@ -26,7 +26,7 @@ def data_collector(self): {'name': 'TarifPlan', 'url_tag': ['/dashboard$'], 'jsformula': "data.subUsers[0].pricePlan.ru"}, {'name': 'UserName', 'url_tag': ['/dashboard$'], 'jsformula': "data.subUsers[0].fio"}, {'name': 'Internet', 'url_tag': ['/dashboard$'], 'jsformula': "parseFloat(data.usages.filter(el => el.belongsTo=='GPRS_PACK_2')[0].leftover/1024/1024).toFixed(2)"}, - {'name': 'SMS', 'url_tag': ['/dashboard$'], 'jsformula': "parseFloat(data.usages.filter(el => el.belongsTo=='SMS_ACTIVE')[0].leftover).toFixed(0)"}, + {'name': 'SMS', 'url_tag': ['/dashboard$'], 'jsformula': "parseFloat(data.usages.filter(el => el.belongsTo.startsWith('SMS'))[0].leftover).toFixed(0)"}, {'name': 'Min', 'url_tag': ['/dashboard$'], 'jsformula': "parseFloat(data.usages.filter(el => el.belongsTo=='CBM_ON_MINUTE_BALANCE')[0].leftover/60).toFixed(0)"}, {'name': 'LicSchet', 'url_tag': ['/dashboard$'], 'jsformula': "data.subUsers[0].subAccount"}, {'name': 'BlockStatus', 'url_tag': ['/dashboard$'], 'jsformula': "data.state"}, diff --git a/plugin/dbengine.py b/plugin/dbengine.py index 4344186..78b08c2 100644 --- a/plugin/dbengine.py +++ b/plugin/dbengine.py @@ -135,12 +135,17 @@ class Dbengine(): - def __init__(self, dbname=None, updatescheme=True, fast=False): + 'Класс для работы с базой, внутри одного экземпляра используется только один курсор, для нескольких курсоров используйте несколько экземпляров' + + def __init__(self, dbname=None, updatescheme=True, fast=False, row_factory=None, make_headers=True): 'fast - быстрее, но менее безопасно' if dbname is None: dbname = store.abspath_join(settings.mbplugin_ini_path, 'BalanceHistory.sqlite') self.dbname = dbname + logging.debug(f'Db open {self.dbname}') self.conn = sqlite3.connect(self.dbname) # detect_types=sqlite3.PARSE_DECLTYPES + if row_factory is not None: + self.conn.row_factory = row_factory self.cur = self.conn.cursor() cache_size = int(store.options('sqlite_cache_size')) if cache_size != 0: @@ -150,17 +155,33 @@ def __init__(self, dbname=None, updatescheme=True, fast=False): self.conn.commit() if updatescheme: self.check_and_add_addition() - rows = self.cur.execute('SELECT * FROM phones limit 1;') - self.phoneheader = list(zip(*rows.description))[0] + if make_headers: + self.cur.execute('SELECT * FROM phones limit 1;') + self.phoneheader = list(zip(*self.cur.description))[0] + + def __enter__(self): + logging.debug(f'Db __enter__ {self.dbname}') + return self + + def __exit__(self, type, value, traceback): + logging.info(f'Db __exit__ close {self.dbname}') + self.conn.close() def cur_execute(self, query, *args, **kwargs): - 'Обертка для cur.execute c логированием и таймингом' + 'Обертка для cur.execute(...).fetchall() c логированием и таймингом сразу возврящает list с результатом' t_start = time.process_time() - res = self.cur.execute(query, *args, **kwargs) + res = self.cur.execute(query, *args, **kwargs).fetchall() logging.debug(f'{query} {args} {kwargs}') logging.debug(f'Execution time {time.process_time()-t_start:.6f}') return res + def cur_execute_00(self, query, *args, **kwargs): + 'cur.execute(...).fetchall()[0][0] ' + res = self.cur_execute(query, *args, **kwargs) + if len(res) != 0: + return res[0][0] + return None + def write_result(self, plugin, login, result, commit=True): 'Записывает результат в базу' # Делаем копию, чтобы не трогать оригинал @@ -182,12 +203,13 @@ def write_result(self, plugin, login, result, commit=True): line['Operator'] = plugin line['PhoneNumber'] = login # PhoneNumber=PhoneNum line['QueryDateTime'] = datetime.datetime.now().replace(microsecond=0) # no microsecond - self.cur.execute(f"select cast(julianday('now')-julianday(max(QueryDateTime)) as integer) from phones where phonenumber='{login}' and operator='{plugin}' and abs(balance-({result['Balance']}))>0.02") - line['NoChangeDays'] = self.cur.fetchall()[0][0] # Дней без изм. + # Дней без изм. + query = f"select cast(julianday('now')-julianday(max(QueryDateTime)) as integer) from phones where phonenumber='{login}' and operator='{plugin}' and abs(balance-({result['Balance']}))>0.02" + line['NoChangeDays'] = self.cur_execute(query)[0][0] if line['NoChangeDays'] is None: # Если баланс не менялся ни разу - то ориентируемся на самый первый запрос баланса - self.cur.execute(f"select cast(julianday('now')-julianday(min(QueryDateTime)) as integer) from phones where phonenumber='{login}' and operator='{plugin}' ") - line['NoChangeDays'] = self.cur.fetchall()[0][0] + query = f"select cast(julianday('now')-julianday(min(QueryDateTime)) as integer) from phones where phonenumber='{login}' and operator='{plugin}' " + line['NoChangeDays'] = self.cur_execute(query)[0][0] # Если записей по этому номеру совсем нет (первый запрос), тогда просто ставим 0 line['NoChangeDays'] = line['NoChangeDays'] if line['NoChangeDays'] is not None else 0 try: @@ -195,16 +217,13 @@ def write_result(self, plugin, login, result, commit=True): average_days = int(options_ini['Additional']['AverageDays']) except Exception: average_days = int(store.options('average_days')) - self.cur.execute(f"select {line['Balance']}-balance from phones where phonenumber='{login}' and operator='{plugin}' and QueryDateTime>date('now','-{average_days} day') and strftime('%Y%m%d', QueryDateTime)<>strftime('%Y%m%d', date('now')) order by QueryDateTime desc limit 1") - qres = self.cur.fetchall() + qres = self.cur_execute(f"select {line['Balance']}-balance from phones where phonenumber='{login}' and operator='{plugin}' and QueryDateTime>date('now','-{average_days} day') and strftime('%Y%m%d', QueryDateTime)<>strftime('%Y%m%d', date('now')) order by QueryDateTime desc limit 1") if qres != []: line['BalDelta'] = round(qres[0][0], 2) # Delta (день) - self.cur.execute(f"select {line['Balance']}-balance from phones where phonenumber='{login}' and operator='{plugin}' order by QueryDateTime desc limit 1") - qres = self.cur.fetchall() + qres = self.cur_execute(f"select {line['Balance']}-balance from phones where phonenumber='{login}' and operator='{plugin}' order by QueryDateTime desc limit 1") if qres != []: line['BalDeltaQuery'] = round(qres[0][0], 2) # Delta (запрос) - self.cur.execute(f"select avg(b) from (select min(BalDelta) b from phones where phonenumber='{login}' and operator='{plugin}' and QueryDateTime>date('now','-{average_days} day') group by strftime('%Y%m%d', QueryDateTime))") - qres = self.cur.fetchall() + qres = self.cur_execute(f"select avg(b) from (select min(BalDelta) b from phones where phonenumber='{login}' and operator='{plugin}' and QueryDateTime>date('now','-{average_days} day') group by strftime('%Y%m%d', QueryDateTime))") if qres != [] and qres[0][0] is not None: line['RealAverage'] = round(qres[0][0], 2) # $/День(Р) if line.get('RealAverage', 0.0) < 0: @@ -218,9 +237,8 @@ def write_result(self, plugin, login, result, commit=True): def report(self): ''' Генерирует отчет по последнему состоянию телефонов''' reportsql = f'''select * from phones where NN in (select NN from (SELECT NN,max(QueryDateTime) FROM Phones GROUP BY PhoneNumber,Operator)) order by PhoneNumber,Operator''' - cur = self.cur_execute(reportsql) - dbheaders = list(zip(*cur.description))[0] - dbdata = cur.fetchall() + dbdata = self.cur_execute(reportsql) + dbheaders = list(zip(*self.cur.description))[0] phones = store.ini('phones.ini').phones() dbdata.sort(key=lambda line: (phones.get(line[0:2], {}).get('NN', 999))) # округляем float до 2х знаков @@ -244,9 +262,8 @@ def history(self, phone_number, operator, days=7, lastonly=1, pkey=None): if days == 0: return [] historysql = f'''select * from phones where phonenumber=? and operator=? and QueryDateTime>date('now','-'|| ? ||' day') order by QueryDateTime desc''' - cur = self.cur_execute(historysql, [phone_number, operator, days]) - dbheaders = list(zip(*cur.description))[0] - dbdata = cur.fetchall() + dbdata = self.cur_execute(historysql, [phone_number, operator, days]) + dbheaders = list(zip(*self.cur.description))[0] dbdata_sets = [set(el) for el in zip(*dbdata)] # составляем список уникальных значений по каждой колонке dbdata_sets = [{i for i in el if str(i).strip() not in ['', 'None', '0.0', '0']} for el in dbdata_sets] # подправляем косяки if len(dbdata_sets) == 0: # Истории нет - возвращаем пустой @@ -268,8 +285,7 @@ def check_and_add_addition(self): 'Создаем таблицы, добавляем новые поля, и нужные индексы если их нет' [self.cur.execute(query) for query in DB_SCHEMA] for k, v in addition_phone_fields.items(): - self.cur.execute("SELECT COUNT(*) AS CNTREC FROM pragma_table_info('phones') WHERE name=?", [k]) - if self.cur.fetchall()[0][0] == 0: + if self.cur_execute("SELECT COUNT(*) AS CNTREC FROM pragma_table_info('phones') WHERE name=?", [k])[0][0] == 0: self.cur.execute(f"ALTER TABLE phones ADD COLUMN {k} {v}") for idx in addition_indexes: self.cur.execute(f"CREATE INDEX IF NOT EXISTS {idx}") @@ -278,27 +294,29 @@ def check_and_add_addition(self): def copy_data(self, path: str): 'Копирует данные по запросам из другой БД sqlite, может быть полезно когда нужно объединить данные с нескольких источников' try: - src_conn = sqlite3.connect(path) - src_conn.row_factory = sqlite3.Row - src_cur = src_conn.cursor() - table = 'Phones' - current_data = set(self.cur.execute('select distinct PhoneNumber,Operator,QueryDateTime from phones').fetchall()) - print(len(current_data)) - print("Copying Phones %s => %s" % (path, self.dbname)) - sc = src_cur.execute('SELECT * FROM %s' % table) - ins = None - dc = self.cur - cnt_write, cnt_skip = 0, 0 - for row in sc.fetchall(): - if not ins: - cols = tuple([k for k in row.keys() if k != 'id']) - ins = 'INSERT OR REPLACE INTO Phones %s VALUES (%s)' % (cols, ','.join(['?'] * len(cols))) - if (row['PhoneNumber'], row['Operator'], row['QueryDateTime']) in current_data: - cnt_skip += 1 - else: - c = [row[c] for c in cols] - res = dc.execute(ins, c) - cnt_write += res.rowcount + logging.debug(f'Open db {path}') + if not os.path.exists(path): + raise FileNotFoundError + with sqlite3.connect(path) as src_conn: + src_conn.row_factory = sqlite3.Row + src_cur = src_conn.cursor() + table = 'Phones' + current_data = set(self.cur_execute('select distinct PhoneNumber,Operator,QueryDateTime from phones')) + print(len(current_data)) + print("Copying Phones %s => %s" % (path, self.dbname)) + src_cur.execute('SELECT * FROM %s' % table) + ins = None + cnt_write, cnt_skip = 0, 0 + for row in src_cur.fetchall(): + if not ins: + cols = tuple([k for k in row.keys() if k != 'id']) + ins = 'INSERT OR REPLACE INTO Phones %s VALUES (%s)' % (cols, ','.join(['?'] * len(cols))) + if (row['PhoneNumber'], row['Operator'], row['QueryDateTime']) in current_data: + cnt_skip += 1 + else: + c = [row[c] for c in cols] + res = self.cur.execute(ins, c) + cnt_write += res.rowcount print(f'Update {cnt_write} row, skip {cnt_skip} row') self.conn.commit() except Exception: @@ -315,13 +333,19 @@ def __init__(self, dbname=None): DRV = '{Microsoft Access Driver (*.mdb)}' self.conn = pyodbc.connect(f'DRIVER={DRV};DBQ={dbname}') self.cur = self.conn.cursor() - rows = self.cur.execute('SELECT top 1 * FROM phones') - self.phoneheader = list(zip(*rows.description))[0] + self.cur.execute('SELECT top 1 * FROM phones') + self.phoneheader = list(zip(*self.cur.description))[0] phones_ini = store.ini('phones.ini').read() # phones - словарь key=MBphonenumber values=[phonenumber,region] self.phones = {v['Number']: (re.sub(r' #\d+', '', v['Number']), v['Region']) for k, v in phones_ini.items() if k.isnumeric() and 'Monitor' in v} + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.conn.close() + def to_sqlite(self, line): '''конвертирует строчку для sqlite: Убираем последовательный номер NN @@ -350,85 +374,83 @@ def update_sqlite_from_mdb_core(dbname=None, deep=None) -> bool: if deep is None: deep = int(store.options('updatefrommdbdeep')) # читаем sqlite БД - db = Dbengine(fast=True) - mdb = Mdbengine(dbname) # BalanceHistory.mdb - # Дата согласно указанному deep от которой сверяем данные - dd = datetime.datetime.now() - datetime.timedelta(days=deep) - logging.debug(f'Read from sqlite QueryDateTime>{dd}') - db.cur.execute("SELECT * FROM phones where QueryDateTime>?", [dd]) - sqldata = db.cur.fetchall() - dsqlite = {datetime.datetime.strptime(i[db.phoneheader.index('QueryDateTime')].split('.')[0], '%Y-%m-%d %H:%M:%S').timestamp(): i for i in sqldata} - # теперь все то же самое из базы MDB - logging.debug(f'Read from mdb QueryDateTime>{dd}') - mdb.cur.execute("SELECT * FROM phones where QueryDateTime>?", [dd]) - mdbdata = mdb.cur.fetchall() - dmdb = {i[mdb.phoneheader.index('QueryDateTime')].timestamp(): i for i in mdbdata} - logging.debug('calculate') - # Строим общий список timestamp всех данных - allt = sorted(set(list(dsqlite) + list(dmdb))) - # обрабатываем и составляем пары данных которые затем будем подправлять - pairs = [] # mdb timestamp, sqlite timestamp - while allt: - # берем одну строчку из общего списка - c = allt.pop(0) - # Если для этого timestamp есть строчка добавляем из - pair = [c if c in dmdb else None, c if c in dsqlite else None] - if allt == [] or allt[0] in dmdb and pair[0] is not None or allt[0] in dsqlite and pair[1] is not None: - # Это следующая строка или была последняя - pairs.append(pair) - elif allt[0] in dmdb and pair[0] is None and allt[0] - c < 10: - # следующий timestamp это пара MDB к записи sqlite ? - pair[0] = allt.pop(0) - pairs.append(pair) - elif allt[0] in dsqlite and pair[1] is None and allt[0] - c < 10: - # следующий timestamp это пара sqlite к записи mdb ? - pair[1] = allt.pop(0) - pairs.append(pair) - logging.debug('Before:') - logging.debug(f'Difference time:{len([1 for a,b in pairs if a!=b and a is not None and b is not None])}') - logging.debug(f'Only mdb:{len([1 for a,b in pairs if b is None])}') - logging.debug(f'Only sqlite:{len([1 for a,b in pairs if a is None])}') - update_param = [] - insert_param, insert_header = [], [] - for num, [mdb_ts, sqlite_ts] in enumerate(pairs): - if mdb_ts != sqlite_ts and mdb_ts is not None and sqlite_ts is not None: - # исправляем время в sqlite чтобы совпадало с mdb - update_param.append([datetime.datetime.fromtimestamp(mdb_ts), datetime.datetime.fromtimestamp(sqlite_ts)]) - pairs[num][1] = mdb_ts - elif mdb_ts is not None and sqlite_ts is None: - # Копируем несуществующие записи в sqlite из mdb - header, line = mdb.to_sqlite(dmdb[mdb_ts]) - insert_param.append(line) - insert_header = header - pairs[num][1] = mdb_ts - - # есть что вставить ? - logging.debug(f'Insert {len(insert_param)}') - if insert_param: - db.cur.executemany(f'insert into phones ({",".join(insert_header)}) VALUES ({",".join(list("?"*len(insert_header)))})', insert_param) - db.conn.commit() + with Dbengine(fast=True) as db, Mdbengine(dbname) as mdb: # BalanceHistory.mdb + # Дата согласно указанному deep от которой сверяем данные + dd = datetime.datetime.now() - datetime.timedelta(days=deep) + logging.debug(f'Read from sqlite QueryDateTime>{dd}') + sqldata = db.cur_execute("SELECT * FROM phones where QueryDateTime>?", [dd]) + dsqlite = {datetime.datetime.strptime(i[db.phoneheader.index('QueryDateTime')].split('.')[0], '%Y-%m-%d %H:%M:%S').timestamp(): i for i in sqldata} + # теперь все то же самое из базы MDB + logging.debug(f'Read from mdb QueryDateTime>{dd}') + mdb.cur.execute("SELECT * FROM phones where QueryDateTime>?", [dd]) + mdbdata = mdb.cur.fetchall() + dmdb = {i[mdb.phoneheader.index('QueryDateTime')].timestamp(): i for i in mdbdata} + logging.debug('calculate') + # Строим общий список timestamp всех данных + allt = sorted(set(list(dsqlite) + list(dmdb))) + # обрабатываем и составляем пары данных которые затем будем подправлять + pairs = [] # mdb timestamp, sqlite timestamp + while allt: + # берем одну строчку из общего списка + c = allt.pop(0) + # Если для этого timestamp есть строчка добавляем из + pair = [c if c in dmdb else None, c if c in dsqlite else None] + if allt == [] or allt[0] in dmdb and pair[0] is not None or allt[0] in dsqlite and pair[1] is not None: + # Это следующая строка или была последняя + pairs.append(pair) + elif allt[0] in dmdb and pair[0] is None and allt[0] - c < 10: + # следующий timestamp это пара MDB к записи sqlite ? + pair[0] = allt.pop(0) + pairs.append(pair) + elif allt[0] in dsqlite and pair[1] is None and allt[0] - c < 10: + # следующий timestamp это пара sqlite к записи mdb ? + pair[1] = allt.pop(0) + pairs.append(pair) + logging.debug('Before:') + logging.debug(f'Difference time:{len([1 for a,b in pairs if a!=b and a is not None and b is not None])}') + logging.debug(f'Only mdb:{len([1 for a,b in pairs if b is None])}') + logging.debug(f'Only sqlite:{len([1 for a,b in pairs if a is None])}') + update_param = [] + insert_param, insert_header = [], [] + for num, [mdb_ts, sqlite_ts] in enumerate(pairs): + if mdb_ts != sqlite_ts and mdb_ts is not None and sqlite_ts is not None: + # исправляем время в sqlite чтобы совпадало с mdb + update_param.append([datetime.datetime.fromtimestamp(mdb_ts), datetime.datetime.fromtimestamp(sqlite_ts)]) + pairs[num][1] = mdb_ts + elif mdb_ts is not None and sqlite_ts is None: + # Копируем несуществующие записи в sqlite из mdb + header, line = mdb.to_sqlite(dmdb[mdb_ts]) + insert_param.append(line) + insert_header = header + pairs[num][1] = mdb_ts - # есть что проапдейтить ? - logging.debug(f'Update {len(update_param)}') - if update_param: - db.cur.executemany('update phones set QueryDateTime=? where QueryDateTime=?', update_param) - db.conn.commit() + # есть что вставить ? + logging.debug(f'Insert {len(insert_param)}') + if insert_param: + db.cur.executemany(f'insert into phones ({",".join(insert_header)}) VALUES ({",".join(list("?"*len(insert_header)))})', insert_param) + db.conn.commit() + + # есть что проапдейтить ? + logging.debug(f'Update {len(update_param)}') + if update_param: + db.cur.executemany('update phones set QueryDateTime=? where QueryDateTime=?', update_param) + db.conn.commit() - # дополнительные фиксы (у меня в mdb мусор оказался, чтобы не трогать mdb чистим здесь) - for sql in addition_queries: - db.cur.execute(sql) + # дополнительные фиксы (у меня в mdb мусор оказался, чтобы не трогать mdb чистим здесь) + for sql in addition_queries: + db.cur.execute(sql) + db.conn.commit() + # прописываем колонку mbnumber + update_mbnumber = [[MBphonenumber, phonenumber, region] for MBphonenumber, (phonenumber, region) in mdb.phones.items()] + db.cur.executemany(f'update phones set MBPhonenumber=? where MBPhonenumber is null and Phonenumber=? and operator=?', update_mbnumber) + logging.debug(f'Update empty MBPhonenumber {db.cur.rowcount}:') db.conn.commit() - # прописываем колонку mbnumber - update_mbnumber = [[MBphonenumber, phonenumber, region] for MBphonenumber, (phonenumber, region) in mdb.phones.items()] - db.cur.executemany(f'update phones set MBPhonenumber=? where MBPhonenumber is null and Phonenumber=? and operator=?', update_mbnumber) - logging.debug(f'Update empty MBPhonenumber {db.cur.rowcount}:') - db.conn.commit() - - logging.debug(f'After:') - logging.debug(f'Difference time:{len([1 for a,b in pairs if a!=b and a is not None and b is not None])}') - logging.debug(f'Only mdb:{len([1 for a,b in pairs if b is None])}') - logging.debug(f'Only sqlite:{len([1 for a,b in pairs if a is None])}') - logging.debug(f'Update complete') + + logging.debug(f'After:') + logging.debug(f'Difference time:{len([1 for a,b in pairs if a!=b and a is not None and b is not None])}') + logging.debug(f'Only mdb:{len([1 for a,b in pairs if b is None])}') + logging.debug(f'Only sqlite:{len([1 for a,b in pairs if a is None])}') + logging.debug(f'Update complete') return True @@ -445,9 +467,9 @@ def write_result_to_db(plugin, login, result): 'пишем в базу если в ini установлен sqlitestore=1' try: if store.options('sqlitestore') == '1': - db = Dbengine() - logging.info(f'Пишем в базу {db.dbname}') - db.write_result(plugin, login, result) + with Dbengine() as db: + logging.info(f'Пишем в базу {db.dbname}') + db.write_result(plugin, login, result) except AttributeError: logging.info(f'Отсутствуют параметры {store.exception_text()} дополнительные действия не производятся') except Exception: @@ -465,30 +487,28 @@ def flags(cmd, key=None, value=None): try: if store.options('sqlitestore') == '1': logging.debug(f'Flag:{cmd}') - db = Dbengine() - if cmd.lower() == 'set': - db.cur.execute('REPLACE INTO flags(key,value) VALUES(?,?)', [key, value]) - db.conn.commit() - if cmd.lower() == 'setunic': - db.cur.execute('delete from flags where value=?', [value]) - db.conn.commit() - db.cur.execute('REPLACE INTO flags(key,value) VALUES(?,?)', [key, value]) - db.conn.commit() - elif cmd.lower() == 'get': - db.cur.execute('select value from flags where key=?', [key]) - qres = db.cur.fetchall() - if len(qres) > 0: - return qres[0][0] - elif cmd.lower() == 'getall': - db.cur.execute('select * from flags') - qres = db.cur.fetchall() - return {k: v for k, v in qres} - elif cmd.lower() == 'deleteall': - db.cur.execute('delete from flags') - db.conn.commit() - elif cmd.lower() == 'delete': - db.cur.execute('delete from flags where key=?', [key]) - db.conn.commit() + with Dbengine() as db: + if cmd.lower() == 'set': + db.cur.execute('REPLACE INTO flags(key,value) VALUES(?,?)', [key, value]) + db.conn.commit() + if cmd.lower() == 'setunic': + db.cur.execute('delete from flags where value=?', [value]) + db.conn.commit() + db.cur.execute('REPLACE INTO flags(key,value) VALUES(?,?)', [key, value]) + db.conn.commit() + elif cmd.lower() == 'get': + qres = db.cur_execute('select value from flags where key=?', [key]) + if len(qres) > 0: + return qres[0][0] + elif cmd.lower() == 'getall': + qres = db.cur_execute('select * from flags') + return {k: v for k, v in qres} + elif cmd.lower() == 'deleteall': + db.cur.execute('delete from flags') + db.conn.commit() + elif cmd.lower() == 'delete': + db.cur.execute('delete from flags where key=?', [key]) + db.conn.commit() else: if cmd.lower() == 'getall': return {} @@ -502,8 +522,7 @@ def responses() -> typing.Dict[str, str]: if store.options('sqlitestore') == '1': logging.debug(f'Responses from sqlite') db = Dbengine() - db.cur.execute('select key,value from responses') - qres = db.cur.fetchall() + qres = db.cur_execute('select key,value from responses') return {k: v for k, v in qres} else: return {} diff --git a/plugin/httpserver_mobile.py b/plugin/httpserver_mobile.py index 6a3ad97..5a368ef 100644 --- a/plugin/httpserver_mobile.py +++ b/plugin/httpserver_mobile.py @@ -40,7 +40,7 @@ После включения, запустите mbplugin\\setup_and_check.bat ''' -# TODO в командах для traymeny используется os.system(f'start ... это будет работать только в windows, но пока пофигу, т.к. сам pystrayработает только в windows +# TODO в командах для traymeny используется os.system(f'start ... это будет работать только в windows, но пока пофигу, т.к. сам pystray работает только в windows TRAY_MENU = ( {'text': "Main page", 'cmd': lambda: os.system(f'start http://localhost:{store.options("port", section="HttpServer")}/main'), 'show': True}, {'text': "View report", 'cmd': lambda: os.system(f'start http://localhost:{store.options("port", section="HttpServer")}/report'), 'show': True}, @@ -58,7 +58,7 @@ ) -def getbalance_standalone_one_pass(filter: list = [], only_failed: bool = False) -> None: +def getbalance_standalone_one_pass(filter: list = [], only_failed: bool = False): ''' Получаем балансы самостоятельно без mobilebalance ОДИН ПРОХОД Если filter пустой то по всем номерам из phones.ini Если не пустой - то логин/алиас/оператор или его часть @@ -68,6 +68,7 @@ def getbalance_standalone_one_pass(filter: list = [], only_failed: bool = False) для Standalone версии в файле phones_add.ini only_failed=True - делать запросы только по тем номерам, по которым прошлый запрос был неудачный ''' + result: typing.Dict = {} phones = store.ini('phones.ini').phones() queue_balance = [] # Очередь телефонов на получение баланса for val in phones.values(): @@ -84,7 +85,8 @@ def getbalance_standalone_one_pass(filter: list = [], only_failed: bool = False) dbengine.flags('set', f'{keypair}', 'queue') # выставляем флаг о постановке в очередь store.feedback.text(f'Queued {len(queue_balance)} numbers') for val in queue_balance: - # TODO пока дергаем метод от вебсервера там уже все есть, потом может вынесем отдельно + # TODO пока дергаем метод от веб сервера там уже все есть, потом может вынесем отдельно + keypair = f"{val['Region']}_{val['Number']}" try: # проверяем на сигнал Q_CMD_CANCEL, все остальное - кладем обратно if Q_CMD_CANCEL in cmdqueue.queue: @@ -92,14 +94,17 @@ def getbalance_standalone_one_pass(filter: list = [], only_failed: bool = False) [cmdqueue.put(el) for el in qu if el != Q_CMD_CANCEL] # type: ignore logging.info(f'Receive cancel signal to query') store.feedback.text(f"Receive cancel signal") - return + return result store.feedback.text(f"Receive {val['Alias']}:{val['Region']}_{val['Number']}") - getbalance_plugin('get', {'plugin': [val['Region']], 'login': [val['Number']], 'password': [val['Password2']], 'date': ['date']}) + r1 = getbalance_plugin('get', {'plugin': [val['Region']], 'login': [val['Number']], 'password': [val['Password2']], 'date': ['date']}) + result[keypair] = 'Balance' in repr(r1) except Exception: + result[keypair] = False logging.error(f"Unsuccessful check {val['Region']} {val['Number']} {store.exception_text()}") + return result -def getbalance_standalone(filter: list = [], only_failed: bool = False, retry: int = -1, params=None) -> None: +def getbalance_standalone(filter: list = [], only_failed: bool = False, retry: int = -1, params=None): ''' Получаем балансы делая несколько проходов по неудачным retry=N количество повторов по неудачным попыткам, после запроса по всем (повторы только при only_failed=False) params добавлен чтобы унифицировать вызовы @@ -108,12 +113,14 @@ def getbalance_standalone(filter: list = [], only_failed: bool = False, retry: i logging.info(f'getbalance_standalone: filter={filter}') if retry == -1: retry = int(store.options('retry_failed', flush=True)) + result = {} if only_failed: - getbalance_standalone_one_pass(filter=filter, only_failed=True) + result.update(getbalance_standalone_one_pass(filter=filter, only_failed=True)) else: - getbalance_standalone_one_pass(filter=filter, only_failed=False) + result.update(getbalance_standalone_one_pass(filter=filter, only_failed=False)) for i in range(retry): - getbalance_standalone_one_pass(filter=filter, only_failed=True) + result.update(getbalance_standalone_one_pass(filter=filter, only_failed=True)) + return result def get_full_info_one_number(keypair: str, check: bool = False) -> str: @@ -246,9 +253,9 @@ def view_log(param): def prepare_loglist_personal(): 'Делает список пар по которым есть скриншоты' ss = glob.glob(store.abspath_join(store.options('loggingfolder'), '*.png')) - allmatch = [re.search(r'(.*)_\d+\.png', os.path.split(fn)[-1]) for fn in ss] - allgroups = sorted(set([m.groups()[0] for m in allmatch if m])) - return allgroups + all_match = [re.search(r'(.*)_\d+\.png', os.path.split(fn)[-1]) for fn in ss] + all_groups = sorted(set([m.groups()[0] for m in all_match if m])) + return all_groups def prepare_log_personal(prefix): 'Готовит html лог со скриншотами начинающимися на prefix' @@ -293,7 +300,7 @@ def pp_field(pkey, he, el, hover, unwanted=False): store.options('logginglevel', flush=True) # Запускаем, чтобы сбросить кэш и перечитать ini template_page = settings.table_template['page'] template_history = settings.table_template['history'] - temlate_style = settings.table_template['style'] + template_style = settings.table_template['style'] html_script = settings.table_template['script'] db = dbengine.Dbengine() flags = dbengine.flags('getall') # берем все флаги словарем @@ -353,8 +360,8 @@ def pp_field(pkey, he, el, hover, unwanted=False): if flags.get(f"{line['Operator']}_{line['PhoneNumber']}", '').startswith('queue'): classflag = 'n_us' html_table.append(f'
{"Balance": 124.45, "SMS": 43, "Min": 222}
'), + ({'Balance': 124.45, 'SMS': 43, 'Min': 222}, '{"Balance": 124.45, "SMS": 43, "Min": 222}
'), ({'Balance': 124.45, 'SMS': '43', 'Min': '222'}, '{"Balance": 124.45, "SMS": 43, "Min": 222}
'), ]) def param_test_result_to_html(request):