From 85b338cca53373d70ecf57260677d6211bfec7d4 Mon Sep 17 00:00:00 2001 From: mdmujtabaraza <45493966+mdmujtabaraza@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:38:40 +0530 Subject: [PATCH] Fixed Android permission denied error, improved helpers.py + db.py --- buildozer.spec | 2 +- main.py | 15 +-- src/__pycache__/app.cpython-38.pyc | Bin 20809 -> 21309 bytes src/app.kv | 46 +++++--- src/app.py | 110 +++++++++++------- src/db.py | 104 ++++++++++++----- src/dict_scraper/spiders/cambridge.py | 50 +------- .../__pycache__/json_to_apkg.cpython-38.pyc | Bin 4383 -> 4404 bytes src/lib/helpers.py | 89 +++++++++++++- src/lib/json_to_apkg.py | 2 +- 10 files changed, 268 insertions(+), 150 deletions(-) diff --git a/buildozer.spec b/buildozer.spec index 872469c..dcc45ef 100644 --- a/buildozer.spec +++ b/buildozer.spec @@ -52,7 +52,7 @@ requirements = python3,android,pyjnius,beautifulsoup4,brotlipy,cached-property,c #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, sensorLandscape, portrait or all) -orientation = all +orientation = portrait # (list) List of service to declare #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY diff --git a/main.py b/main.py index 0e87b3b..9611d5a 100644 --- a/main.py +++ b/main.py @@ -54,20 +54,13 @@ import ssl from src.app import MyApp -from src.db import connection from src.lib.helpers import get_root_path -if __name__ == '__main__': - if 'ANDROID_STORAGE' in os.environ: - try: - from android import loadingscreen - - loadingscreen.hide_loading_screen() - except Exception as e: - print("Loading screen is not removed", e) +def main(): ssl._create_default_https_context = ssl._create_unverified_context MyApp().run() + MyApp().db_connection.close() # Delete Files on exit. root_path = get_root_path() @@ -76,4 +69,6 @@ os.remove(f) os.remove(root_path + 'output.apkg') - connection.close() + +if __name__ == '__main__': + main() diff --git a/src/__pycache__/app.cpython-38.pyc b/src/__pycache__/app.cpython-38.pyc index b18b5172e305a977fe1fde95c292c5480f377a4c..459508808123d2137777b2c57a0808dca4f243d2 100644 GIT binary patch delta 9846 zcma(%32+?8al3o}gS*535x`M=#3RAu2Yw*<6Cgq2Ap!6kSvnqW7Qg|!cfjm|AkT-K zWYCgqi?*zBl@*Dy0js1$@)w)7W2@x2oH&o{s@PUViL!Q5eqzN>BENAQ`b+oh0SD5g zWU{WOXQt=t>F(+7#Y-QOKmV*8I$c)gm*C&~wwxU9ee%7b74q}%-SFvxEK!O2w0gaP zGzi?UHR>zK3V{c-CcT+73p}W`=ur|Cc$v0Rzl+?ZuOh4T)nv733u$ZgwPdZp!`j{Y zI#C+qbMWP{#HTJ?=&qaG)5(O0e&HtCzmW>Klow&+{QR)JS)+w?Zlrf(

UX|%Cy8D_! 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 delta 9416 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 IconLeftWidget: - icon: root.icon \ No newline at end of file + icon: root.icon + +: + size_hint_y: None + height: word_input.height + + MDTextField: + id: word_input + multiline: False + hint_text: root.hint_text + text: root.text + helper_text: root.helper_text + helper_text_mode: "on_focus" + icon_left: get_text("tag_icon") + + MDIconButton: + icon: get_text('paste_icon') + pos_hint: {"center_y": .5} + pos: word_input.width - self.width + dp(8), 0 + theme_text_color: "Hint" + on_release: word_input.text = Clipboard.paste() diff --git a/src/app.py b/src/app.py index 677f6c9..34124f1 100644 --- a/src/app.py +++ b/src/app.py @@ -23,14 +23,14 @@ from bs4 import BeautifulSoup from kivy.animation import Animation -from kivy import require, platform +from kivy import require from kivy.metrics import dp from kivy.properties import StringProperty from kivymd.app import MDApp from kivymd.toast import toast from kivymd.uix.list import OneLineListItem, TwoLineListItem, OneLineIconListItem from kivymd.uix.dialog import MDDialog -from kivymd.uix.boxlayout import MDBoxLayout +from kivymd.uix.relativelayout import MDRelativeLayout from kivymd.uix.button import MDFlatButton, MDRaisedButton, MDRectangleFlatButton, MDIconButton, MDFloatingActionButton from kivymd.uix.menu import MDDropdownMenu from kivy.lang.builder import Builder @@ -51,8 +51,9 @@ from kivy.uix.button import Button from kivy.utils import get_color_from_hex +from src.db import create_connection, create_table, create_tag, select_all_tags, select_tags_which_contains from src.dict_scraper.spiders import cambridge -from src.lib.helpers import get_root_path +from src.lib.helpers import get_root_path, is_platform, check_android_permissions, request_android_permissions from src.lib.json_to_apkg import JsonToApkg from src.lib.strings import get_text @@ -60,7 +61,7 @@ # os.environ["KIVY_NO_CONSOLELOG"] = "1" # require('2.1.0') -CONTAINER = {'current_url': '', 'requests': [], 'tags': [], 'tags_input_text': '', +CONTAINER = {'current_url': '', 'requests': [], 'tags': [], 'm_checkboxes': [], 'm_checkboxes_selected': 0, 'm_checkboxes_total': 0} DICTIONARIES = { get_text("cambridge"): "dictionary.cambridge.org/dictionary/english/", @@ -70,7 +71,7 @@ get_text("vocabulary_com"): "vocabulary.com/dictionary/", } HEADERS = { - 'User-Agent': generate_user_agent(device_type='smartphone' if 'ANDROID_STORAGE' in os.environ else 'desktop'), + 'User-Agent': generate_user_agent(device_type='smartphone' if is_platform('android') else 'desktop'), 'Referer': 'https://www.google.com' } @@ -148,6 +149,12 @@ def clear_request(word_url=None): # Window.size = (500, 400) +class ClickableTextFieldRound(MDRelativeLayout): + text = StringProperty() + hint_text = StringProperty() + helper_text = StringProperty() + + class MeaningsPanelContent(MDGridLayout): def __init__(self, *args, **kwargs): super().__init__() @@ -355,7 +362,7 @@ def open_dictionary_dropdown(self): self.dictionary_menu = MDDropdownMenu( caller=self.ids.dict_dropdown, items=menu_items, - position='bottom', + position='center', width_mult=4, max_height=dp(248), ) @@ -460,7 +467,6 @@ def checkbox_click(self, instance, value, tld): self.tld = tld def generate_flashcards(self, btn): - from src.db import connection, cursor selected_checkboxes = [] for checkbox in CONTAINER['m_checkboxes']: if type(checkbox) is list: @@ -483,17 +489,12 @@ def generate_flashcards(self, btn): # print(CONTAINER['tags']) for tag in CONTAINER['tags']: try: - cursor.execute(""" - INSERT OR REPLACE INTO tags (tag) VALUES (?) - """, (tag,)) + create_tag(MDApp.get_running_app().db_connection, (tag,)) except Exception as e: print(e) print(traceback.format_exc()) # print("inserted") - # print('committing') - connection.commit() - # print('committed') - jta.generate_apkg(notes) + apkg_filename = jta.generate_apkg(notes) MDApp.get_running_app().soft_restart() self.dialog_popup( @@ -527,10 +528,8 @@ def change_checkbox_state(self, checkbox): checkbox.active = True def show_meanings(self): - word_url = self.ids.word_input.text.split('#')[0].split('?')[0] + word_url = self.ids.url_field.text.split('#')[0].split('?')[0] CONTAINER['tags'] = self.ids.tags_input.text.split() - if not CONTAINER['tags']: - CONTAINER['tags'] = [''] dict_name = None if not validators.url(word_url): self.toast(get_text("url_not_found")) @@ -557,6 +556,11 @@ def show_meanings(self): dict_name = name break if dict_name: + if not check_android_permissions(): + self.dialog.dismiss() + MDApp.get_running_app().soft_restart() + return False + MDApp.get_running_app().create_tables() # d = runner.crawl( # CambridgeSpider, # url=word_url, @@ -717,6 +721,7 @@ class MyApp(MDApp): def __init__(self, **kwargs): super().__init__(**kwargs) self.screen = Builder.load_file("src/app.kv") + self.db_connection = None def build(self): Window.bind(on_keyboard=self._on_keyboard_handler) @@ -731,25 +736,9 @@ def build(self): # return MyLayout() def on_start(self): + print('Starting...') print("Size:", Window.size) - if platform == 'android': - try: - from android.storage import app_storage_path - settings_path = app_storage_path() - print("settings_path", settings_path) - - from android.storage import primary_external_storage_path - primary_ext_storage = primary_external_storage_path() - print("primary_ext_storage", primary_ext_storage) - - from android.storage import secondary_external_storage_path - secondary_ext_storage = secondary_external_storage_path() - print("secondary_ext_storage", secondary_ext_storage) - except Exception as e: - print("Error printing paths", e) - - from android.permissions import request_permissions, Permission - request_permissions([Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE]) + request_android_permissions() def restart(self): self.root.clear_widgets() @@ -774,12 +763,14 @@ def change_screen(self): self.root.current = 'menu_screen' def soft_restart(self): + print('Restarting..') global CONTAINER CONTAINER['current_url'] = '' CONTAINER['tags'] = [] CONTAINER['m_checkboxes'] = [] CONTAINER['m_checkboxes_selected'] = 0 CONTAINER['m_checkboxes_total'] = 0 + request_android_permissions() meanings_screen = self.root.get_screen("meanings_screen") meanings_screen.ids.toolbar.title = get_text("app_title") @@ -789,10 +780,37 @@ def soft_restart(self): # meanings_screen.ids.toolbar.right_action_items = \ # [[get_text("select_all_icon"), lambda x: self.get_running_app().meanings_screen_instance.select_all()]] meanings_screen.ids.meanings_selection_list.clear_widgets() - # self.root.transition.direction = 'right' - # self.root.transition.duration = 0.5 # 0.5 second - # self.root.current = 'menu_screen' - self.change_screen() + self.root.transition.direction = 'right' + self.root.transition.duration = 0.5 # 0.5 second + self.root.current = 'menu_screen' + # self.change_screen() + + def create_tables(self): + if self.db_connection is not None: + return True + db_path = f'{get_root_path()}data.db' + self.db_connection = create_connection(db_path) + # https://stackoverflow.com/a/44951682 + sql_create_tags_table = """ + CREATE TABLE IF NOT EXISTS tags( + tag TEXT NOT NULL COLLATE NOCASE, + PRIMARY KEY(tag) + ) + """ + create_table(self.db_connection, sql_create_tags_table) + + # https://www.designcise.com/web/tutorial/how-to-do-case-insensitive-comparisons-in-sqlite + # Without a COLLATE INDEX our queries will do a full table scan + # EXPLAIN QUERY PLAN SELECT * FROM tags WHERE tag = 'some-tag; + # output: SCAN TABLE tags + # When COLLATE NOCASE index is present, the query does not scan all rows. + # EXPLAIN QUERY PLAN SELECT * FROM tags WHERE tag = 'some-tag'; + # output: SEARCH TABLE tags USING INDEX idx_nocase_tags (tag=?) + sql_create_tags_index = """ + CREATE INDEX IF NOT EXISTS idx_nocase_tags ON tags (tag COLLATE NOCASE) + """ + create_table(self.db_connection, sql_create_tags_index) + return True # def callback(self, button): # Snackbar(text="Hello World").open() @@ -838,7 +856,10 @@ def _on_keyboard_handler(self, instance, key, *args): # CONTAINER['tags_input_text'] += chr(key) def open_tags_dropdown(self, key=None): - from src.db import cursor + if not check_android_permissions(): + self.soft_restart() + return False + self.create_tables() menu_screen = self.root.get_screen("menu_screen") menu_screen_instance = self.get_running_app().menu_screen_instance menu_items = [] @@ -849,10 +870,10 @@ def open_tags_dropdown(self, key=None): typed_tag = '' # print("typed_tag:", typed_tag) if not typed_tag: - cursor.execute(f"SELECT tag FROM tags ORDER BY tag DESC LIMIT 5") + rows = select_all_tags(self.db_connection) else: - cursor.execute(f"SELECT tag FROM tags WHERE tag LIKE '%{typed_tag}%' ORDER BY tag DESC LIMIT 5") - for row in cursor.fetchall(): + rows = select_tags_which_contains(self.db_connection, typed_tag) + for row in rows: some_dict = { "viewclass": "IconListItem", "icon": get_text("tag_icon"), @@ -864,7 +885,8 @@ def open_tags_dropdown(self, key=None): menu_screen_instance.tags_menu = MDDropdownMenu( caller=menu_screen.ids.tags_input, items=menu_items, - position='bottom', + position='auto', + ver_growth='up' if is_platform('android') else 'down', width_mult=4, ) menu_screen_instance.tags_menu.open() diff --git a/src/db.py b/src/db.py index af51c9a..fd29696 100644 --- a/src/db.py +++ b/src/db.py @@ -1,28 +1,80 @@ import sqlite3 -from src.lib.helpers import get_root_path - -connection = sqlite3.connect(f'{get_root_path()}data.db') -cursor = connection.cursor() - -# https://stackoverflow.com/a/44951682 -cursor.execute(""" -CREATE TABLE IF NOT EXISTS tags( -tag TEXT NOT NULL COLLATE NOCASE, -PRIMARY KEY(tag) -) -""") -# dt datetime default current_timestamp - -# Without a COLLATE INDEX our queries will do a full table scan -# EXPLAIN QUERY PLAN SELECT * FROM tags WHERE tag = 'some-tag; -# output: SCAN TABLE tags - -# When COLLATE NOCASE index is present, the query does not scan all rows. -# EXPLAIN QUERY PLAN SELECT * FROM tags WHERE tag = 'some-tag'; -# output: SEARCH TABLE tags USING INDEX idx_nocase_tags (tag=?) - -# Refer: https://www.designcise.com/web/tutorial/how-to-do-case-insensitive-comparisons-in-sqlite -cursor.execute(""" -CREATE INDEX IF NOT EXISTS idx_nocase_tags ON tags (tag COLLATE NOCASE) -""") + +def create_connection(db_path): + """ create a database connection to the SQLite database + :param db_path: Path to db + :return: Connection object or None + """ + conn = None + try: + conn = sqlite3.connect(db_path) + return conn + except sqlite3.Error as e: + print(e) + + return conn + + +def create_table(conn, create_table_sql): + """ create a table from the create_table_sql statement + :param conn: Connection object + :param create_table_sql: a CREATE TABLE statement + :return: + """ + try: + c = conn.cursor() + c.execute(create_table_sql) + except sqlite3.Error as e: + print(e) + + +def create_tag(conn, tag): + """ + Create a new tag + :param conn: + :param tag: + :return: + """ + + sql = ''' INSERT OR REPLACE INTO tags (tag) VALUES (?) ''' + cur = conn.cursor() + cur.execute(sql, tag) + conn.commit() + + return cur.lastrowid + + +def select_all_tags(conn): + """ + Query all rows in the tags table + :param conn: the Connection object + :return: + """ + cur = conn.cursor() + cur.execute("SELECT tag FROM tags ORDER BY tag DESC LIMIT 5") + + rows = cur.fetchall() + + # for row in rows: + # print(row) + + return rows + + +def select_tags_which_contains(conn, tag): + """ + Query tags by tag + :param conn: the Connection object + :param tag: + :return: + """ + cur = conn.cursor() + cur.execute(f"SELECT tag FROM tags WHERE tag LIKE '%{tag}%' ORDER BY tag DESC LIMIT 5") + + rows = cur.fetchall() + + # for row in rows: + # print(row) + + return rows diff --git a/src/dict_scraper/spiders/cambridge.py b/src/dict_scraper/spiders/cambridge.py index 3b82d07..fea1021 100644 --- a/src/dict_scraper/spiders/cambridge.py +++ b/src/dict_scraper/spiders/cambridge.py @@ -1,63 +1,15 @@ import os import re -import requests from gtts import gTTS -from bs4.element import ResultSet, Tag -from src.lib.helpers import get_root_path +from src.lib.helpers import extract_text, get_root_path, get_tree, get_valid_filename allowed_domains = ['dictionary.cambridge.org'] start_urls = ['https://dictionary.cambridge.org/'] -class SuspiciousOperation(Exception): - """The user did something suspicious""" - - -def get_valid_filename(name): - """ - Return the given string converted to a string that can be used for a clean - filename. Remove leading and trailing spaces; convert other spaces to - underscores; and remove anything that is not an alphanumeric, dash, - underscore, or dot. - >>> get_valid_filename("john's portrait in 2004.jpg") - 'johns_portrait_in_2004.jpg' - """ - s = str(name).strip().replace(" ", "-") - s = re.sub(r"(?u)[^-\w.]", "", s) - if s in {"", ".", ".."}: - raise SuspiciousOperation("Could not derive file name from '%s'" % name) - return s - - -def get_tree(branch, seen, *args, **kwargs): - out = [] - for d in branch.find_all("div", class_="cid"): - if d not in seen: - seen.add(d) - out.append(d["id"]) - t = get_tree(d, seen) - if t: - out.append(t) - return out - - -def extract_text(data, join_char=''): - strings = [] - if type(data) is ResultSet: - if data: - for element in data: - for string in element.strings: - strings.append(repr(string)[1:-1]) - elif type(data) is Tag: - if data: - for string in data.strings: - strings.append(repr(string)[1:-1]) - return join_char.join(strings) - - class MeaningsSpider: def __init__(self, soup, *args, **kwargs): self.soup = soup diff --git a/src/lib/__pycache__/json_to_apkg.cpython-38.pyc b/src/lib/__pycache__/json_to_apkg.cpython-38.pyc index 3e67f96f3b30b0625fef86f65abd71eaa579c1bd..ba21e807f9d22eb33b377221abca8247b445d418 100644 GIT binary patch delta 207 zcmbQQv_*+Gl$V!_0SFF@1|;p;$h(Nyk{QTz0ODdTAd$)t#hAhn#gxLB!qmbL#hl8L z!aRpDg(Zcxg(ZqLg)N00D9V%m1T=YWF&CE<6&ZunJ55d#&}8JBJV8L3QG4OQKU6Fh~JXYZgLO*PDZxLZUUO@MMfZ{j+3heG-Wx& oi)4X3O`cm!`Nc&7APJEBZZV~l0CgS|&}Y5HTwGE#`Ky3E0N?5_zW@LL diff --git a/src/lib/helpers.py b/src/lib/helpers.py index 76ae602..62c28c7 100644 --- a/src/lib/helpers.py +++ b/src/lib/helpers.py @@ -1,11 +1,18 @@ import os +import re + +from bs4.element import ResultSet, Tag +from kivy import platform + + +def is_platform(os_name) -> bool: + return os_name == platform def get_root_path() -> str: - if 'ANDROID_STORAGE' in os.environ: + if is_platform('android'): # if 'ANDROID_STORAGE' in os.environ: from android.storage import app_storage_path # path = f'{app_storage_path()}/' - package_name = app_storage_path().split('/')[-2] path = f'/storage/emulated/0/Android/data/{package_name}/files/' else: # platform == 'win' @@ -13,3 +20,81 @@ def get_root_path() -> str: if not os.path.exists(path + 'media/'): os.makedirs(path + 'media/') return path + + +class SuspiciousOperation(Exception): + """The user did something suspicious""" + + +def get_valid_filename(name): + """ + Return the given string converted to a string that can be used for a clean + filename. Remove leading and trailing spaces; convert other spaces to + underscores; and remove anything that is not an alphanumeric, dash, + underscore, or dot. + >>> get_valid_filename("john's portrait in 2004.jpg") + 'johns_portrait_in_2004.jpg' + """ + s = str(name).strip().replace(" ", "-") + s = re.sub(r"(?u)[^-\w.]", "", s) + if s in {"", ".", ".."}: + raise SuspiciousOperation("Could not derive file name from '%s'" % name) + return s + + +def get_tree(branch, seen, *args, **kwargs): + out = [] + for d in branch.find_all("div", class_="cid"): + if d not in seen: + seen.add(d) + out.append(d["id"]) + t = get_tree(d, seen) + if t: + out.append(t) + return out + + +def extract_text(data, join_char=''): + strings = [] + if type(data) is ResultSet: + if data: + for element in data: + for string in element.strings: + strings.append(repr(string)[1:-1]) + elif type(data) is Tag: + if data: + for string in data.strings: + strings.append(repr(string)[1:-1]) + return join_char.join(strings) + + +def check_android_permissions() -> bool: + if is_platform('android'): + from android.permissions import check_permission + return check_permission('android.permission.WRITE_EXTERNAL_STORAGE') + else: + return True + + +def request_android_permissions(): + if is_platform('android'): + from android.permissions import request_permissions, Permission + request_permissions([Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE]) + + +def print_android_storage_path(): + if is_platform('android'): + try: + from android.storage import app_storage_path + settings_path = app_storage_path() + print("settings_path", settings_path) + + from android.storage import primary_external_storage_path + primary_ext_storage = primary_external_storage_path() + print("primary_ext_storage", primary_ext_storage) + + from android.storage import secondary_external_storage_path + secondary_ext_storage = secondary_external_storage_path() + print("secondary_ext_storage", secondary_ext_storage) + except Exception as e: + print("Error printing paths", e) diff --git a/src/lib/json_to_apkg.py b/src/lib/json_to_apkg.py index 3d398ac..1ff8afe 100644 --- a/src/lib/json_to_apkg.py +++ b/src/lib/json_to_apkg.py @@ -88,7 +88,7 @@ class JsonToApkg: def __init__(self): pass - def generate_apkg(self, notes): + def generate_apkg(self, notes) -> str: # print('Before my_deck') my_deck = genanki.Deck( 1646145285163, # todo: change id and name