added cal project

This commit is contained in:
buenosairesam
2025-08-03 06:21:14 -03:00
parent a3e3e6a565
commit 2d7de863f3
12 changed files with 123 additions and 21 deletions

3
.gitignore vendored
View File

@@ -3,4 +3,5 @@ __pycache__
nohup.out nohup.out
dm.out dm.out
dm.err dm.err
sample_task_file sample_task_file
dmfnt

100
CLAUDE.md Normal file
View File

@@ -0,0 +1,100 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Deskmeter is a productivity tool that measures time spent across desktop workspaces. It consists of three main components:
- **dmcore**: Core tracking daemon (`dmapp/dmcore/main.py`) that monitors active workspace and task changes
- **dmweb**: Flask web application (`dmapp/dmweb/`) for viewing productivity data
- **dmfnt**: Angular frontend (`dmapp/dmfnt/`) for enhanced UI (in development)
## Architecture
### Core Components
**Task Management System**:
- Task definitions stored in files, tracked via `dmapp/dmcore/task.py`
- File modification time monitoring for automatic task switching
- Hierarchical task organization under workspace paths (e.g., `work/default`, `work/dlt`)
**Workspace Tracking**:
- Uses `wmctrl` to detect active X11 workspace (requires XORG, not Wayland)
- Maps workspace indices to labels in `dmapp/dmcore/main.py:desktops`
- Special handling for "work" desktops with automatic task enforcement
**Data Storage**:
- MongoDB backend storing workspace switches with timestamps and durations
- State persistence in `dmapp/dmcore/state.py` for current workspace/task tracking
### Web Interface Structure
**Flask Routes** (`dmapp/dmweb/dm.py`):
- `/` - Today's productivity summary
- `/day/<month>/<day>` - Single day view
- `/calendar` - Monthly calendar view via `dmapp/dmweb/dmcal.py`
- `/totals` - All-time statistics
## Development Commands
### Python Backend
```bash
# Install dependencies
pip install -r requirements.txt
# Start MongoDB service
sudo systemctl start mongod.service
# Run core tracking daemon
cd dmapp/dmcore
python3 main.py
# Run Flask web server
cd dmapp/dmweb
python3 run.py
# Run tests
cd dmapp/tests
python3 test_dmapp.py
```
### Angular Frontend
```bash
cd dmapp/dmfnt
# Install dependencies
npm install
# Development server
npm run start
# Build for production
npm run build
# Run tests
npm run test
```
### System Setup
The application requires:
- MongoDB running locally
- wmctrl installed (`apt install wmctrl`)
- X11 desktop environment (not Wayland)
- Python virtual environment recommended
## Key Configuration
**Workspace Labels**: Edit `desktops` tuple in `dmapp/dmcore/main.py:12`
**Work Desktop Mapping**: Configure `work_desktops` dict in `dmapp/dmcore/main.py:13`
**Timezone**: Set in `dmapp/dmcore/main.py:19` using zoneinfo
## Development Notes
- The system enforces task constraints within work desktops - switching to a non-work task automatically reverts to the designated work task
- Task files are monitored for changes to enable automatic context switching
- Web interface runs on port 10000 by default
- Core daemon sleeps for 2 seconds between workspace checks

5
dmapp/.env Normal file
View File

@@ -0,0 +1,5 @@
// dmcore
task_file = "/home/mariano/LETRAS/adm/task/main"
desktops = ("Plan", "Think", "Work", "Other", "Away", "Work", "Work", "Work")
work_desktops = {2: "default", 5: "dlt", 6: "vhs", 7: "cal"}
unlabeled = "Away"

0
dmapp/__init__.py Normal file
View File

View File

@@ -1,9 +1,9 @@
sudo systemctl start mongod.service sudo systemctl start mongod.service
. /home/mariano/wdir/venv/dm/bin/activate . /home/mariano/wdir/venv/dm/bin/activate
cd /home/mariano/wdir/def/deskmeter cd /home/mariano/wdir/def/deskmeter/dmapp/dmcore
nohup python3 dmmain.py >dm.out 2>dm.err & nohup python3 main.py >dm.out 2>dm.err &
cd /home/mariano/wdir/def/deskmeter/dmapp cd /home/mariano/wdir/def/deskmeter/dmapp/dmweb
nohup python3 run.py >dm.out 2>dm.err & nohup python3 run.py >dm.out 2>dm.err &
cd ~ cd ~

View File

@@ -9,8 +9,8 @@ import task
from config import logger, switches from config import logger, switches
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
desktops = ("Plan", "Think", "Work", "Other", "Away", "Work", "Work") desktops = ("Plan", "Think", "Work", "Other", "Away", "Work", "Work", "Work")
work_desktops = {2: "default", 5: "dlt", 6: "vhs"} work_desktops = {2: "default", 5: "dlt", 6: "vhs", 7: "cal"}
unlabeled = "Away" unlabeled = "Away"
@@ -60,7 +60,6 @@ while True:
current_mtime = state.retrieve("current").get("filetime") current_mtime = state.retrieve("current").get("filetime")
file_mtime = task.get_file_mtime(None) file_mtime = task.get_file_mtime(None)
logger.debug(f"current_mtime: {current_mtime}, file_mtime:{file_mtime}")
# First handle file changes # First handle file changes
if current_mtime != file_mtime: if current_mtime != file_mtime:
task_id = task.read_and_extract(None) task_id = task.read_and_extract(None)
@@ -86,9 +85,6 @@ while True:
f"work/{work_desktops[current_workspace]}" f"work/{work_desktops[current_workspace]}"
) )
work_task_ids = {t["task_id"] for t in workspace_tasks if "task_id" in t} work_task_ids = {t["task_id"] for t in workspace_tasks if "task_id" in t}
logger.debug(
f"work_task_ids:{work_task_ids}, current_work_task: {current_work_task},current_task: {current_task}"
)
# if current_task in work_task_ids and current_task != current_work_task: # if current_task in work_task_ids and current_task != current_work_task:
if current_task not in work_task_ids: if current_task not in work_task_ids:
@@ -97,6 +93,7 @@ while True:
current_task = current_work_task current_task = current_work_task
state.save("current", task=current_task) state.save("current", task=current_task)
task.db_to_file_as_is(None) task.db_to_file_as_is(None)
elif current_task != current_work_task: elif current_task != current_work_task:
# Update work state when switching to a different valid task # Update work state when switching to a different valid task
state.update_work_state(work_desktops[current_workspace], current_task) state.update_work_state(work_desktops[current_workspace], current_task)

View File

@@ -7,7 +7,7 @@ import state
from bson import ObjectId from bson import ObjectId
from config import logger, tasks from config import logger, tasks
task_file = "/home/mariano/wdir/def/deskmeter/dmapp/dmcore/sample_task_file" task_file = "/home/mariano/LETRAS/adm/task/main"
def parse_line(line: str) -> tuple[Optional[str], Optional[str]]: def parse_line(line: str) -> tuple[Optional[str], Optional[str]]:

View File

@@ -1,13 +1,12 @@
from flask import Flask from flask import Flask
from dmweb import dm, dmcal
from . import dm, dmcal
def create_app(): def create_app():
app = Flask("deskmeter")
app = Flask("deskmeter")
app.debug = True app.debug = True
app.register_blueprint(dm.dmbp) app.register_blueprint(dm.dmbp)
return app return app

View File

@@ -24,6 +24,7 @@ def index(task=None):
end = datetime.today().replace(hour=23, minute=59, second=59, tzinfo=timezone) end = datetime.today().replace(hour=23, minute=59, second=59, tzinfo=timezone)
rows = get_period_totals(start, end, task) rows = get_period_totals(start, end, task)
print(rows)
return render_template("main.html", rows=rows) return render_template("main.html", rows=rows)

View File

@@ -2,10 +2,10 @@ import calendar
from datetime import datetime from datetime import datetime
from pprint import pprint from pprint import pprint
# import pytz
from dmweb.dm import dmbp
from flask import Blueprint, render_template from flask import Blueprint, render_template
# import pytz
from .dm import dmbp
from .get_period_times import ( from .get_period_times import (
get_period_totals, get_period_totals,
read_and_extract, read_and_extract,
@@ -77,6 +77,8 @@ class DMHTMLCalendar(calendar.HTMLCalendar):
rows = get_period_totals(start, end, self.task) rows = get_period_totals(start, end, self.task)
print(rows)
returnstr = "<table class='totaltable'>" returnstr = "<table class='totaltable'>"
for row in rows: for row in rows:
returnstr += "<tr><td>{}</td><td>{}</td></tr>".format( returnstr += "<tr><td>{}</td><td>{}</td></tr>".format(

View File

@@ -178,8 +178,6 @@ def get_period_totals(start, end, task=None):
aux_results = list(switches.aggregate(pipeline_before_after)) aux_results = list(switches.aggregate(pipeline_before_after))
print(aux_results)
bfirst = aux_results[0]["before_first"] bfirst = aux_results[0]["before_first"]
if bfirst: if bfirst:

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from dmweb import create_app from dmweb import create_app
app = create_app() app = create_app()