Compare commits

..

No commits in common. "c5ee87535ae9238bf2e7395b630d8534f2ac1e9b" and "a2976cdcc29a40cea228866a3f712b8f7ae00cfe" have entirely different histories.

12 changed files with 34 additions and 85 deletions

View File

@ -9,7 +9,6 @@ variables:
value: "0"
cache:
key: cache-$CI_COMMIT_REF_SLUG-$WINDOWS_PYTHON_PACKAGE_NAME
paths:
- .cache/pip
- .venv/
@ -57,8 +56,10 @@ update_windows_deps:
- python --version
- pip install pipenv
script:
- cp Pipfile.lock Pipfile.lock.bak
- pipenv lock --dev
- cp Pipfile.lock Pipfile.lock.windows
- mv Pipfile.lock.bak Pipefile.lock
artifacts:
paths:
- Pipfile.lock.windows
@ -117,7 +118,7 @@ package_windows:
- refreshenv
- python --version
- pip install pipenv
- mv -force Pipfile.lock.windows Pipfile.lock
- mv Pipenv.lock.windows Pipenv.lock
- pipenv sync --dev
script:
- pipenv run pyinstaller --onefile --windowed --name "${WINDOWS_AMD64_BINARY}" -i icon.ico src/fime/main.py

View File

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

17
Pipfile.lock generated
View File

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

View File

@ -379,11 +379,11 @@
},
"pyinstaller-hooks-contrib": {
"hashes": [
"sha256:43f3e084ae5f826415399d72ecf2e32328fe859ad7455c7cddfc09f1a61c90b7",
"sha256:8f5ac1acdafde9e553c82242aeae2d2f8fb65ec8e2b0ba416547948108a73e01"
"sha256:131494f9cfce190aaa66ed82e82c78b2723d1720ce64d012fbaf938f4ab01d35",
"sha256:51a51ea9e1ae6bd5ffa7ec45eba7579624bf4f2472ff56dba0edc186f6ed46a6"
],
"markers": "python_version >= '3.7'",
"version": "==2024.2"
"version": "==2024.1"
},
"pyproject-hooks": {
"hashes": [

View File

@ -28,10 +28,6 @@ The Jira URL will just be the base URL to you're Jira Instance (e.g. `https://ji
The Jira Token is a Personal Access Token. See [here](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html) on how to get one.
There is a new authentication method, which uses the cookies from your browser (most popular browsers should be
supported). To use it, you configure it and log into your Jira instance in your browser. If it doesn't work, try closing
your browser, to force it to write the cookies to the disk
## License
Licensed under MIT license. See License.md for more details.

View File

@ -23,8 +23,6 @@ install_requires =
requests-futures
packaging
loguru
browser_cookie3
pebble
[options.packages.find]
where = src

View File

@ -1,5 +1,3 @@
from concurrent.futures import Executor
try:
from PySide6 import QtCore, QtGui, QtWidgets
except ImportError:
@ -12,14 +10,14 @@ from fime.util import get_icon
class ImportTask(QtWidgets.QDialog):
def __init__(self, config: Config, executor: Executor, parent, *args, **kwargs):
def __init__(self, config: Config, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.setWindowTitle("New Tasks")
self.config = config
self.line_edit = QtWidgets.QLineEdit(self)
self.completer = TaskCompleter(config, executor)
self.completer = TaskCompleter(config)
self.line_edit.setCompleter(self.completer)
self.line_edit.textChanged.connect(self.completer.update_picker)
self.line_edit.setFocus()
@ -91,7 +89,7 @@ class ImportTask(QtWidgets.QDialog):
def showEvent(self, _):
self.auto_change_task_check_box.setChecked(self.config.import_auto_change_task)
# pick up config changes
self.completer.update()
self.completer.update_urls()
self.line_edit.setText("")
self.raise_()
self.line_edit.setFocus()

View File

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

View File

@ -1,7 +1,6 @@
import os
import re
import threading
from concurrent.futures import Executor
from enum import Enum, auto
from functools import reduce, partial
from queue import Queue, Empty
@ -28,18 +27,19 @@ class TaskCompleter(QtWidgets.QCompleter):
running = QtCore.Signal()
stopped = QtCore.Signal()
def __init__(self, config: Config, executor: Executor, parent=None, *args, **kwargs):
def __init__(self, config: Config, parent=None, *args, **kwargs):
super().__init__([], parent, *args, **kwargs)
self.setFilterMode(QtCore.Qt.MatchFlag.MatchContains)
self.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.session = FuturesSession(executor=executor)
self.session = FuturesSession()
self.session.headers["Accept"] = "application/json"
add_auth(config, self.session)
self.config = config
self.picker_url = None
self.search_url = None
self.issue_url_tmpl = None
self.issue_key_regex = re.compile(r"^[a-zA-Z0-9]+-[0-9]+")
self.update()
self.update_urls()
self.text = ""
self.response_text = ""
self.model_data = set()
@ -53,12 +53,10 @@ class TaskCompleter(QtWidgets.QCompleter):
self.rif_counter_lock = threading.Lock()
self.last_rif_state = TaskCompleter.RifState.STOPPED
def update(self):
def update_urls(self):
self.picker_url = os.path.join(self.config.jira_url, "rest/api/2/issue/picker")
self.search_url = os.path.join(self.config.jira_url, "rest/api/2/search")
self.issue_url_tmpl = os.path.join(self.config.jira_url, "rest/api/2/issue/{}")
self.session = FuturesSession()
add_auth(self.config, self.session)
@QtCore.Slot()
def process_response(self):

View File

@ -1,11 +1,9 @@
import enum
import browser_cookie3
from loguru import logger
from pebble import ProcessPool
from requests import Session
from fime.config import Config, AuthMethods, Browsers
from fime.config import Config, AuthMethods
try:
from PySide6 import QtCore, QtGui, QtWidgets
@ -16,11 +14,6 @@ except ImportError:
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):
if hasattr(qobject, "screen"):
return qobject.screen().size().height()
@ -68,21 +61,6 @@ def add_auth(config: Config, session: Session):
case AuthMethods.TOKEN:
session.headers["Authorization"] = f"Bearer {config.jira_token}"
case AuthMethods.COOKIES:
match config.cookie_source:
case Browsers.AUTO:
cookie_jar = browser_cookie3.load()
case Browsers.FIREFOX:
cookie_jar = browser_cookie3.firefox()
case Browsers.CHROME:
cookie_jar = browser_cookie3.chrome()
case Browsers.CHROMIUM:
cookie_jar = browser_cookie3.chromium()
case Browsers.EDGE:
cookie_jar = browser_cookie3.edge()
case Browsers.OPERA:
cookie_jar = browser_cookie3.opera()
case _:
raise AssertionError("Unknown cookie_source")
session.cookies = cookie_jar
raise NotImplemented
case _:
raise AssertionError("Unknown auth method")

View File

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

View File

@ -1,5 +1,5 @@
import os
from concurrent.futures import Future, Executor
from concurrent.futures import Future
from datetime import date, datetime, timedelta, time
from functools import partial
from textwrap import dedent
@ -16,13 +16,13 @@ from fime.util import Status, add_auth
class WorklogRest:
def __init__(self, config: Config, executor: Executor):
def __init__(self, config: Config):
self.config = config
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.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.session = FuturesSession(executor=executor)
self.session = FuturesSession()
self.session.headers["Accept"] = "application/json"
add_auth(config, self.session)
self._user = None