added cal project
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -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
100
CLAUDE.md
Normal 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
5
dmapp/.env
Normal 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
0
dmapp/__init__.py
Normal 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 ~
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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]]:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
from dmweb import create_app
|
from dmweb import create_app
|
||||||
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
Reference in New Issue
Block a user