FlowLauncher-OSRS-Wiki-Search/lib/flox/__init__.py

337 lines
11 KiB
Python
Raw Normal View History

2024-07-04 23:17:56 +02:00
import sys
import traceback
import os
import json
import time
import webbrowser
import urllib.parse
from datetime import date
import logging
import logging.handlers
from pathlib import Path
from typing import Union
from functools import wraps, cached_property
from tempfile import gettempdir
from .launcher import Launcher
from .browser import Browser
from .settings import Settings
PLUGIN_MANIFEST = 'plugin.json'
FLOW_LAUNCHER_DIR_NAME = "FlowLauncher"
SCOOP_FLOW_LAUNCHER_DIR_NAME = "flow-launcher"
WOX_DIR_NAME = "Wox"
FLOW_API = 'Flow.Launcher'
WOX_API = 'Wox'
APP_DIR = None
USER_DIR = None
LOCALAPPDATA = Path(os.getenv('LOCALAPPDATA'))
APPDATA = Path(os.getenv('APPDATA'))
FILE_PATH = os.path.dirname(os.path.abspath(__file__))
CURRENT_WORKING_DIR = Path().cwd()
LAUNCHER_NOT_FOUND_MSG = f"Unable to locate Launcher directory\nCurrent working directory: {CURRENT_WORKING_DIR}"
launcher_dir = None
path = CURRENT_WORKING_DIR
if SCOOP_FLOW_LAUNCHER_DIR_NAME.lower() in str(path).lower():
launcher_name = SCOOP_FLOW_LAUNCHER_DIR_NAME
API = FLOW_API
elif FLOW_LAUNCHER_DIR_NAME.lower() in str(path).lower():
launcher_name = FLOW_LAUNCHER_DIR_NAME
API = FLOW_API
elif WOX_DIR_NAME.lower() in str(path).lower():
launcher_name = WOX_DIR_NAME
API = WOX_API
else:
raise FileNotFoundError(LAUNCHER_NOT_FOUND_MSG)
while True:
if len(path.parts) == 1:
raise FileNotFoundError(LAUNCHER_NOT_FOUND_MSG)
if path.joinpath('Settings').exists():
USER_DIR = path
if USER_DIR.name == 'UserData':
APP_DIR = USER_DIR.parent
elif str(CURRENT_WORKING_DIR).startswith(str(APPDATA)):
APP_DIR = LOCALAPPDATA.joinpath(launcher_name)
else:
raise FileNotFoundError(LAUNCHER_NOT_FOUND_MSG)
break
path = path.parent
APP_ICONS = APP_DIR.joinpath("Images")
ICON_APP = APP_DIR.joinpath('app.png')
ICON_APP_ERROR = APP_DIR.joinpath(APP_ICONS, 'app_error.png')
ICON_BROWSER = APP_DIR.joinpath(APP_ICONS, 'browser.png')
ICON_CALCULATOR = APP_DIR.joinpath(APP_ICONS, 'calculator.png')
ICON_CANCEL = APP_DIR.joinpath(APP_ICONS, 'cancel.png')
ICON_CLOSE = APP_DIR.joinpath(APP_ICONS, 'close.png')
ICON_CMD = APP_DIR.joinpath(APP_ICONS, 'cmd.png')
ICON_COLOR = APP_DIR.joinpath('color.png')
ICON_CONTROL_PANEL = APP_DIR.joinpath('ControlPanel.png')
ICON_COPY = APP_DIR.joinpath('copy.png')
ICON_DELETE_FILE_FOLDER = APP_DIR.joinpath('deletefilefolder.png')
ICON_DISABLE = APP_DIR.joinpath('disable.png')
ICON_DOWN = APP_DIR.joinpath('down.png')
ICON_EXE = APP_DIR.joinpath('exe.png')
ICON_FILE = APP_DIR.joinpath('file.png')
ICON_FIND = APP_DIR.joinpath('find.png')
ICON_FOLDER = APP_DIR.joinpath('folder.png')
ICON_HISTORY = APP_DIR.joinpath('history.png')
ICON_IMAGE = APP_DIR.joinpath('image.png')
ICON_LOCK = APP_DIR.joinpath('lock.png')
ICON_LOGOFF = APP_DIR.joinpath('logoff.png')
ICON_OK = APP_DIR.joinpath('ok.png')
ICON_OPEN = APP_DIR.joinpath('open.png')
ICON_PICTURES = APP_DIR.joinpath('pictures.png')
ICON_PLUGIN = APP_DIR.joinpath('plugin.png')
ICON_PROGRAM = APP_DIR.joinpath('program.png')
ICON_RECYCLEBIN = APP_DIR.joinpath('recyclebin.png')
ICON_RESTART = APP_DIR.joinpath('restart.png')
ICON_SEARCH = APP_DIR.joinpath('search.png')
ICON_SETTINGS = APP_DIR.joinpath('settings.png')
ICON_SHELL = APP_DIR.joinpath('shell.png')
ICON_SHUTDOWN = APP_DIR.joinpath('shutdown.png')
ICON_SLEEP = APP_DIR.joinpath('sleep.png')
ICON_UP = APP_DIR.joinpath('up.png')
ICON_UPDATE = APP_DIR.joinpath('update.png')
ICON_URL = APP_DIR.joinpath('url.png')
ICON_USER = APP_DIR.joinpath('user.png')
ICON_WARNING = APP_DIR.joinpath('warning.png')
ICON_WEB_SEARCH = APP_DIR.joinpath('web_search.png')
ICON_WORK = APP_DIR.joinpath('work.png')
class Flox(Launcher):
def __init_subclass__(cls, api=API, app_dir=APP_DIR, user_dir=USER_DIR):
cls._debug = False
cls.appdir = APP_DIR
cls.user_dir = USER_DIR
cls.api = api
cls._start = time.time()
cls._results = []
cls._settings = None
cls.font_family = '/Resources/#Segoe Fluent Icons'
cls.issue_item_title = 'Report Issue'
cls.issue_item_subtitle = 'Report this issue to the developer'
@cached_property
def browser(self):
return Browser(self.app_settings)
def exception(self, exception):
self.exception_item(exception)
self.issue_item(exception)
def _query(self, query):
self.args = query.lower()
self.query(query)
def _context_menu(self, data):
self.context_menu(data)
def exception_item(self, exception):
self.add_item(
title=exception.__class__.__name__,
subtitle=str(exception),
icon=ICON_APP_ERROR,
method=self.change_query,
dont_hide=True
)
def issue_item(self, e):
trace = ''.join(traceback.format_exception(type(e), value=e, tb=e.__traceback__)).replace('\n', '%0A')
self.add_item(
title=self.issue_item_title,
subtitle=self.issue_item_subtitle,
icon=ICON_BROWSER,
method=self.create_github_issue,
parameters=[e.__class__.__name__, trace],
)
def create_github_issue(self, title, trace, log=None):
url = self.manifest['Website']
if 'github' in url.lower():
issue_body = f"Please+type+any+relevant+information+here%0A%0A%0A%0A%0A%0A%3Cdetails open%3E%3Csummary%3ETrace+Log%3C%2Fsummary%3E%0A%3Cp%3E%0A%0A%60%60%60%0A{trace}%0A%60%60%60%0A%3C%2Fp%3E%0A%3C%2Fdetails%3E"
url = f"{url}/issues/new?title={title}&body={issue_body}"
webbrowser.open(url)
def add_item(self, title:str, subtitle:str='', icon:str=None, method:Union[str, callable]=None, parameters:list=None, context:list=None, glyph:str=None, score:int=0, **kwargs):
icon = icon or self.icon
if not Path(icon).is_absolute():
icon = Path(self.plugindir, icon)
item = {
"Title": str(title),
"SubTitle": str(subtitle),
"IcoPath": str(icon),
"ContextData": context,
"Score": score,
"JsonRPCAction": {}
}
auto_complete_text = kwargs.pop("auto_complete_text", None)
item["AutoCompleteText"] = auto_complete_text or f'{self.user_keyword} {title}'.replace('* ', '')
if method:
item['JsonRPCAction']['method'] = getattr(method, "__name__", method)
item['JsonRPCAction']['parameters'] = parameters or []
item['JsonRPCAction']['dontHideAfterAction'] = kwargs.pop("dont_hide", False)
if glyph:
item['Glyph'] = {}
item['Glyph']['Glyph'] = glyph
font_family = kwargs.pop("font_family", self.font_family)
if font_family.startswith("#"):
font_family = str(Path(self.plugindir).joinpath(font_family))
item['Glyph']['FontFamily'] = font_family
for kw in kwargs:
item[kw] = kwargs[kw]
self._results.append(item)
return self._results[-1]
@cached_property
def plugindir(self):
potential_paths = [
os.path.abspath(os.getcwd()),
os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
]
for path in potential_paths:
while True:
if os.path.exists(os.path.join(path, PLUGIN_MANIFEST)):
return path
elif os.path.ismount(path):
return os.getcwd()
path = os.path.dirname(path)
@cached_property
def manifest(self):
with open(os.path.join(self.plugindir, PLUGIN_MANIFEST), 'r', encoding='utf-8') as f:
return json.load(f)
@cached_property
def id(self):
return self.manifest['ID']
@cached_property
def icon(self):
return self.manifest['IcoPath']
@cached_property
def action_keyword(self):
return self.manifest['ActionKeyword']
@cached_property
def version(self):
return self.manifest['Version']
@cached_property
def appdata(self):
# Userdata should be up two directories from plugin root
return os.path.dirname(os.path.dirname(self.plugindir))
@property
def app_settings(self):
with open(os.path.join(self.appdata, 'Settings', 'Settings.json'), 'r', encoding='utf-8') as f:
return json.load(f)
@property
def query_search_precision(self):
return self.app_settings.get('QuerySearchPrecision', 'Regular')
@cached_property
def user_keywords(self):
return self.app_settings['PluginSettings']['Plugins'].get(self.id, {}).get('UserKeywords', [self.action_keyword])
@cached_property
def user_keyword(self):
return self.user_keywords[0]
@cached_property
def appicon(self, icon):
return os.path.join(self.appdir, 'images', icon + '.png')
@property
def applog(self):
today = date.today().strftime('%Y-%m-%d')
file = f"{today}.txt"
return os.path.join(self.appdata, 'Logs', self.appversion, file)
@cached_property
def appversion(self):
return os.path.basename(self.appdir).replace('app-', '')
@cached_property
def logfile(self):
file = "plugin.log"
return os.path.join(self.plugindir, file)
@cached_property
def logger(self):
logger = logging.getLogger('')
formatter = logging.Formatter(
'%(asctime)s %(levelname)s (%(filename)s): %(message)s',
datefmt='%H:%M:%S')
logfile = logging.handlers.RotatingFileHandler(
self.logfile,
maxBytes=1024 * 2024,
backupCount=1)
logfile.setFormatter(formatter)
logger.addHandler(logfile)
logger.setLevel(logging.WARNING)
return logger
def logger_level(self, level):
if level == "info":
self.logger.setLevel(logging.INFO)
elif level == "debug":
self.logger.setLevel(logging.DEBUG)
elif level == "warning":
self.logger.setLevel(logging.WARNING)
elif level == "error":
self.logger.setLevel(logging.ERROR)
elif level == "critical":
self.logger.setLevel(logging.CRITICAL)
@cached_property
def api(self):
launcher = os.path.basename(os.path.dirname(self.appdir))
if launcher == 'FlowLauncher':
return FLOW_API
else:
return WOX_API
@cached_property
def name(self):
return self.manifest['Name']
@cached_property
def author(self):
return self.manifest['Author']
@cached_property
def settings_path(self):
dirname = self.name
setting_file = "Settings.json"
return os.path.join(self.appdata, 'Settings', 'Plugins', dirname, setting_file)
@cached_property
def settings(self):
if not os.path.exists(os.path.dirname(self.settings_path)):
os.mkdir(os.path.dirname(self.settings_path))
return Settings(self.settings_path)
def browser_open(self, url):
self.browser.open(url)
@cached_property
def python_dir(self):
return self.app_settings["PluginSettings"]["PythonDirectory"]
def log(self):
return self.logger