diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/main.py b/main.py index d8d7a8e..8a7fdb1 100644 --- a/main.py +++ b/main.py @@ -1,53 +1,59 @@ #!/usr/bin/env python3 import sys +import os +import logging +import threading + +import task_manager + +APP_TITLE = "Org-mob" +DOCS_PATH = os.environ["ORG_PATH"] + import gi gi.require_version("Gtk", "4.0") gi.require_version('Polkit', '1.0') +gi.require_version(namespace='Adw', version='1') + +from gi.repository import Gtk, Polkit, GObject, Gio, Adw -from gi.repository import Gtk, Polkit, GObject, Gio class MainWindow(Gtk.Window): - def __init__(self, title, width, height, application=None): + def __init__(self, *, title, application, task_manager): super().__init__(title=title, application=application) + self.application = application + self.task_manager = task_manager + self.task_manager.get_task_list(self.on_task_list_ready) - task_list = Gtk.ListBox() - self.set_child(task_list) + self.task_list = Gtk.ListBox() + self.set_child(self.task_list) + + def on_button_clicked(self, button): + print('{} was clicked'.format(button)) + + def build_agenda_task_row(self, task): row = Gtk.ListBoxRow() hbox = Gtk.Box(spacing=6) - button = Gtk.Button.new_with_label("Click Me") - button.connect("clicked", self.on_click_me_clicked) - hbox.append(button) + + state_button = Gtk.Button.new_with_label(task.state or '') + state_button.connect("clicked", self.on_button_clicked) + hbox.append(state_button) + + task_name_label = Gtk.Entry(text=task.title) + hbox.append(task_name_label) + + row.set_child(hbox) - task_list.append(row) + return row - row = Gtk.ListBoxRow() - hbox = Gtk.Box(spacing=6) - button = Gtk.Button.new_with_mnemonic("_Open") - button.connect("clicked", self.on_open_clicked) - hbox.append(button) - row.set_child(hbox) - task_list.append(row) + def on_task_list_ready(self, agenda): + for item in agenda.with_hour: + self.task_list.append(self.build_agenda_task_row(item)) - row = Gtk.ListBoxRow() - hbox = Gtk.Box(spacing=6) - button = Gtk.Button.new_with_mnemonic("_Close") - button.connect("clicked", self.on_close_clicked) - hbox.append(button) - row.set_child(hbox) - task_list.append(row) - - def on_click_me_clicked(self, button): - print('"Click me" button was clicked') - - def on_open_clicked(self, button): - print('"Open" button was clicked') - - def on_close_clicked(self, button): - print("Closing application") - self.close() + for item in agenda.no_hour: + self.task_list.append(self.build_agenda_task_row(item)) class Application(Gtk.Application): @@ -56,16 +62,23 @@ class Application(Gtk.Application): def __init__(self): super().__init__(application_id='com.codigoparallevar.gtk4-organizer', flags=Gio.ApplicationFlags.FLAGS_NONE) + self.task_manager = task_manager.TaskManager(DOCS_PATH) def do_activate(self): win = self.props.active_window if not win: - win = MainWindow("My Gtk4 Application", 800, 800, application=self) + win = MainWindow( + title=APP_TITLE, + application=self, + task_manager=self.task_manager, + ) win.present() def main(): """ Run the main application""" + GObject.threads_init() + logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s") app = Application() return app.run(sys.argv) diff --git a/task_manager.py b/task_manager.py new file mode 100644 index 0000000..97f3501 --- /dev/null +++ b/task_manager.py @@ -0,0 +1,143 @@ +import logging +import os +import sys +import threading +from datetime import datetime +from typing import List + +import org_rw +from org_rw import OrgDoc, OrgTime + +EXTENSIONS = (".org", ".org.txt") + +from gi.repository import GObject + + +def is_today(ot: OrgTime): + now = datetime.now() + return ( + (ot.time.year == now.year) + and (ot.time.month == now.month) + and (ot.time.day == now.day) + ) + + +class Agenda: + def __init__( + self, + /, + with_hour: List[org_rw.Headline], + no_hour: List[org_rw.Headline], + ): + self.with_hour = with_hour + self.no_hour = no_hour + + def print(self): + for item in self.with_hour: + print(item.scheduled.time, item.state, item.title) + + if len(self.with_hour) > 0: + print("--------") + + for item in self.no_hour: + print(item.scheduled.time, item.state, item.title) + +class TaskManager: + docs: List[OrgDoc] + threads: List[threading.Thread] + + def __init__(self, docs_path: os.PathLike): + self.docs_path = docs_path + self.threads = [] + self.docs = None + + def load(self): + t0 = datetime.now() + top = os.path.abspath(self.docs_path) + + docs = [] + + for root, dirs, files in os.walk(top): + # Prune dirs + i = 0 + while i < len(dirs): + if dirs[i].startswith(".git"): + del dirs[i] + else: + i += 1 + + # Process files + for name in files: + if all(map(lambda ext: not name.endswith(ext), EXTENSIONS)): + continue + + path = os.path.join(root, name) + + try: + doc = org_rw.load(open(path), extra_cautious=True) + docs.append(doc) + except Exception as err: + import traceback + + traceback.print_exc() + print(f"== On {path}") + sys.exit(1) + + t1 = datetime.now() + logging.info("Loaded {} files in {}s".format(len(docs), t1 - t0)) + + self.docs = docs + + def get_task_list(self, callback): + def aux(): + if self.docs is None: + self.load() + result = self.get_agenda() + print("Result", result) + GObject.idle_add(callback, result) + + thread = threading.Thread(target=aux) + thread.start() + self.threads.append(thread) + + def get_agenda(self) -> Agenda: + headline_count = 0 + items_in_agenda = [] + now = datetime.now() + + for doc in self.docs: + for hl in doc.getAllHeadlines(): + headline_count += 1 + + if ( + hl.scheduled + and isinstance(hl.scheduled, OrgTime) + and hl.scheduled.time.active + ): + if is_today(hl.scheduled): + items_in_agenda.append(hl) + elif (hl.scheduled.time.to_datetime() < now) and hl.is_todo: + items_in_agenda.append(hl) + + logging.info("Read {} items".format(headline_count)) + logging.info("{} items in agenda today".format(len(items_in_agenda))) + + items_with_hour = [ + item + for item in items_in_agenda + if item.scheduled and is_today(item.scheduled) and item.scheduled.time.hour + ] + other_items = [ + item + for item in items_in_agenda + if not ( + item.scheduled and is_today(item.scheduled) and item.scheduled.time.hour + ) + ] + + logging.info("{} items today for a specific hour".format(len(items_with_hour))) + + return Agenda( + with_hour=sorted(items_with_hour, key=lambda x: x.scheduled.time), + no_hour=other_items, + )