Compare commits

...

1 Commits

Author SHA1 Message Date
c5ee87535a WIP 2024-03-01 22:44:31 +01:00
9 changed files with 52 additions and 22 deletions

View File

@ -9,7 +9,8 @@ requests = "~=2.28"
requests-futures = "~=1.0" requests-futures = "~=1.0"
packaging = "~=23.0" packaging = "~=23.0"
loguru = "~=0.6" loguru = "~=0.6"
browser-cookie3 = "*" browser-cookie3 = "~=0.19"
pebble = "~=5.0"
[dev-packages] [dev-packages]
pyinstaller = "~=5.6" pyinstaller = "~=5.6"

17
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "236b0d1f3ca2625232ac2e64bf963e188ff5fa85fc570e46f8e877bf13cb98d3" "sha256": "b9f0e0869765dff4160bfe632eb361b03a8b93bd8d9b19e36d371f2ff76c73f1"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -204,6 +204,15 @@
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==23.2" "version": "==23.2"
}, },
"pebble": {
"hashes": [
"sha256:c8a0659b215ff6dcc974516018fcd95c5c626d8bb9a6668dcfbf85880e6390dc",
"sha256:e7f7ecfd0107ab7cec9f3bb411a856c4d7d552202d4e9a8b038e9a64ae31fd8c"
],
"index": "pypi",
"markers": "python_version >= '3.6'",
"version": "==5.0.6"
},
"pycryptodomex": { "pycryptodomex": {
"hashes": [ "hashes": [
"sha256:0daad007b685db36d977f9de73f61f8da2a7104e20aca3effd30752fd56f73e1", "sha256:0daad007b685db36d977f9de73f61f8da2a7104e20aca3effd30752fd56f73e1",
@ -355,11 +364,11 @@
}, },
"pyinstaller-hooks-contrib": { "pyinstaller-hooks-contrib": {
"hashes": [ "hashes": [
"sha256:131494f9cfce190aaa66ed82e82c78b2723d1720ce64d012fbaf938f4ab01d35", "sha256:43f3e084ae5f826415399d72ecf2e32328fe859ad7455c7cddfc09f1a61c90b7",
"sha256:51a51ea9e1ae6bd5ffa7ec45eba7579624bf4f2472ff56dba0edc186f6ed46a6" "sha256:8f5ac1acdafde9e553c82242aeae2d2f8fb65ec8e2b0ba416547948108a73e01"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==2024.1" "version": "==2024.2"
}, },
"pyproject-hooks": { "pyproject-hooks": {
"hashes": [ "hashes": [

View File

@ -24,6 +24,7 @@ install_requires =
packaging packaging
loguru loguru
browser_cookie3 browser_cookie3
pebble
[options.packages.find] [options.packages.find]
where = src where = src

View File

@ -1,3 +1,5 @@
from concurrent.futures import Executor
try: try:
from PySide6 import QtCore, QtGui, QtWidgets from PySide6 import QtCore, QtGui, QtWidgets
except ImportError: except ImportError:
@ -10,14 +12,14 @@ from fime.util import get_icon
class ImportTask(QtWidgets.QDialog): class ImportTask(QtWidgets.QDialog):
def __init__(self, config: Config, parent, *args, **kwargs): def __init__(self, config: Config, executor: Executor, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs) super().__init__(parent, *args, **kwargs)
self.setWindowTitle("New Tasks") self.setWindowTitle("New Tasks")
self.config = config self.config = config
self.line_edit = QtWidgets.QLineEdit(self) self.line_edit = QtWidgets.QLineEdit(self)
self.completer = TaskCompleter(config) self.completer = TaskCompleter(config, executor)
self.line_edit.setCompleter(self.completer) self.line_edit.setCompleter(self.completer)
self.line_edit.textChanged.connect(self.completer.update_picker) self.line_edit.textChanged.connect(self.completer.update_picker)
self.line_edit.setFocus() self.line_edit.setFocus()

View File

@ -23,7 +23,7 @@ from fime.import_task import ImportTask
from fime.report import ReportDialog from fime.report import ReportDialog
from fime.settings import Settings from fime.settings import Settings
from fime.task_edit import TaskEdit from fime.task_edit import TaskEdit
from fime.util import get_screen_height, get_icon from fime.util import get_screen_height, get_icon, CompatPool
class App: class App:
@ -41,7 +41,9 @@ class App:
self.menu = QtWidgets.QMenu(None) self.menu = QtWidgets.QMenu(None)
self.import_task = ImportTask(self.config, None) self.executor = CompatPool()
self.import_task = ImportTask(self.config, self.executor, None)
self.import_task.accepted.connect(self.new_task_imported) self.import_task.accepted.connect(self.new_task_imported)
self.taskEdit = TaskEdit(self.tasks, None) self.taskEdit = TaskEdit(self.tasks, None)
@ -50,7 +52,7 @@ class App:
self.reportDialog = ReportDialog(self.tasks, Report(lcd), None) self.reportDialog = ReportDialog(self.tasks, Report(lcd), None)
self.reportDialog.accepted.connect(self.log_edited) self.reportDialog.accepted.connect(self.log_edited)
self.worklogDialog = WorklogDialog(self.config, Worklog(lcd), None) self.worklogDialog = WorklogDialog(self.config, self.executor, Worklog(lcd), None)
self.worklogDialog.accepted.connect(self.log_edited) self.worklogDialog.accepted.connect(self.log_edited)
self.settings = Settings(self.config, None) self.settings = Settings(self.config, None)
@ -153,7 +155,7 @@ class App:
menu_items.append(("Settings", partial(self.open_new_dialog, self.settings))) menu_items.append(("Settings", partial(self.open_new_dialog, self.settings)))
menu_items.append(("About", partial(self.open_new_dialog, self.about))) menu_items.append(("About", partial(self.open_new_dialog, self.about)))
menu_items.append(("Close", self.app.quit)) menu_items.append(("Close", self.quit_handler))
if self.config.flip_menu: if self.config.flip_menu:
menu_items.reverse() menu_items.reverse()
@ -169,17 +171,23 @@ class App:
action.setIcon(get_icon("go-next")) action.setIcon(get_icon("go-next"))
action.triggered.connect(item[1]) action.triggered.connect(item[1])
def sigterm_handler(self, signo, _frame): def quit_handler(self, signo=None, _frame=None):
logger.debug(f'handling signal "{signal.strsignal(signo)}"') if signo:
logger.debug(f'handling signal "{signal.strsignal(signo)}"')
logger.debug("Quitting app")
self.app.quit() self.app.quit()
logger.debug("Shutting down HTTP requests executor")
self.executor.stop()
self.executor.join()
logger.debug("HTTP requests executor is shutdown")
def run(self): def run(self):
timer = QtCore.QTimer(None) timer = QtCore.QTimer(None)
# interrupt event loop regularly for signal handling # interrupt event loop regularly for signal handling
timer.timeout.connect(lambda: None) timer.timeout.connect(lambda: None)
timer.start(500) timer.start(500)
signal.signal(signal.SIGTERM, self.sigterm_handler) signal.signal(signal.SIGTERM, self.quit_handler)
signal.signal(signal.SIGINT, self.sigterm_handler) signal.signal(signal.SIGINT, self.quit_handler)
if PYSIDE_6: if PYSIDE_6:
self.app.exec() self.app.exec()
else: else:

View File

@ -1,6 +1,7 @@
import os import os
import re import re
import threading import threading
from concurrent.futures import Executor
from enum import Enum, auto from enum import Enum, auto
from functools import reduce, partial from functools import reduce, partial
from queue import Queue, Empty from queue import Queue, Empty
@ -27,11 +28,11 @@ class TaskCompleter(QtWidgets.QCompleter):
running = QtCore.Signal() running = QtCore.Signal()
stopped = QtCore.Signal() stopped = QtCore.Signal()
def __init__(self, config: Config, parent=None, *args, **kwargs): def __init__(self, config: Config, executor: Executor, parent=None, *args, **kwargs):
super().__init__([], parent, *args, **kwargs) super().__init__([], parent, *args, **kwargs)
self.setFilterMode(QtCore.Qt.MatchFlag.MatchContains) self.setFilterMode(QtCore.Qt.MatchFlag.MatchContains)
self.setCaseSensitivity(QtCore.Qt.CaseInsensitive) self.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.session = FuturesSession() self.session = FuturesSession(executor=executor)
self.session.headers["Accept"] = "application/json" self.session.headers["Accept"] = "application/json"
self.config = config self.config = config
self.picker_url = None self.picker_url = None

View File

@ -2,6 +2,7 @@ import enum
import browser_cookie3 import browser_cookie3
from loguru import logger from loguru import logger
from pebble import ProcessPool
from requests import Session from requests import Session
from fime.config import Config, AuthMethods, Browsers from fime.config import Config, AuthMethods, Browsers
@ -15,6 +16,11 @@ except ImportError:
import fime.icons import fime.icons
class CompatPool(ProcessPool):
def submit(self, fn, *args, **kwargs):
return self.schedule(fn, args=args, kwargs=kwargs)
def get_screen_height(qobject): def get_screen_height(qobject):
if hasattr(qobject, "screen"): if hasattr(qobject, "screen"):
return qobject.screen().size().height() return qobject.screen().size().height()

View File

@ -1,3 +1,4 @@
from concurrent.futures import Executor
from datetime import date from datetime import date
from functools import reduce, partial from functools import reduce, partial
from typing import List, Tuple from typing import List, Tuple
@ -95,10 +96,11 @@ class WorklogDialog(QtWidgets.QDialog):
if not self.return_ or self.editor.text() == self.initial_text: if not self.return_ or self.editor.text() == self.initial_text:
self.edit_finished_row.emit(row) self.edit_finished_row.emit(row)
def __init__(self, config: Config, worklog: Worklog, parent, *args, **kwargs): def __init__(self, config: Config, executor: Executor, worklog: Worklog, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs) super().__init__(parent, *args, **kwargs)
self.config = config self.config = config
self.executor = executor
self.rest = None self.rest = None
self._changing_items = False self._changing_items = False
@ -176,7 +178,7 @@ class WorklogDialog(QtWidgets.QDialog):
def showEvent(self, _): def showEvent(self, _):
# reinitialize to purge caches and pick up config changes # reinitialize to purge caches and pick up config changes
self.rest = WorklogRest(self.config) self.rest = WorklogRest(self.config, self.executor)
self._worklog.date = date.today() self._worklog.date = date.today()
self.update_all() self.update_all()
self.upload_button.setEnabled(False) self.upload_button.setEnabled(False)

View File

@ -1,5 +1,5 @@
import os import os
from concurrent.futures import Future from concurrent.futures import Future, Executor
from datetime import date, datetime, timedelta, time from datetime import date, datetime, timedelta, time
from functools import partial from functools import partial
from textwrap import dedent from textwrap import dedent
@ -16,13 +16,13 @@ from fime.util import Status, add_auth
class WorklogRest: class WorklogRest:
def __init__(self, config: Config): def __init__(self, config: Config, executor: Executor):
self.config = config self.config = config
self.user_url = os.path.join(config.jira_url, "rest/api/2/myself") self.user_url = os.path.join(config.jira_url, "rest/api/2/myself")
self.issue_url = os.path.join(config.jira_url, "rest/api/2/issue/{}") self.issue_url = os.path.join(config.jira_url, "rest/api/2/issue/{}")
self.worklog_url = os.path.join(config.jira_url, "rest/api/2/issue/{}/worklog") self.worklog_url = os.path.join(config.jira_url, "rest/api/2/issue/{}/worklog")
self.worklog_update_url = os.path.join(config.jira_url, "rest/api/2/issue/{issue_key}/worklog/{worklog_id}") self.worklog_update_url = os.path.join(config.jira_url, "rest/api/2/issue/{issue_key}/worklog/{worklog_id}")
self.session = FuturesSession() self.session = FuturesSession(executor=executor)
self.session.headers["Accept"] = "application/json" self.session.headers["Accept"] = "application/json"
add_auth(config, self.session) add_auth(config, self.session)
self._user = None self._user = None