Commit e1b6920c authored by Saswat's avatar Saswat

makefile; readme

parent 34711ae9
...@@ -2,7 +2,6 @@ build/ ...@@ -2,7 +2,6 @@ build/
data/ data/
pkgs/ pkgs/
dist/ dist/
venv/
atvenv/ atvenv/
src/__pycache/ src/__pycache__/
*.spec *.spec
...@@ -5,6 +5,7 @@ APP_NAME = "ActivityTracker" ...@@ -5,6 +5,7 @@ APP_NAME = "ActivityTracker"
VERSION = "1.0" VERSION = "1.0"
ARCH = "amd64" ARCH = "amd64"
PKG_NAME = "$(APP_NAME)_$(VERSION)_$(ARCH)" PKG_NAME = "$(APP_NAME)_$(VERSION)_$(ARCH)"
INTERVAL = 300
PKGS_UTILS=\ PKGS_UTILS=\
scripts/activitytrackerd.service\ scripts/activitytrackerd.service\
...@@ -24,24 +25,28 @@ GUI=\ ...@@ -24,24 +25,28 @@ GUI=\
src/plotter.py\ src/plotter.py\
src/gui.py\ src/gui.py\
all: venv pkg all: venv run
@echo Started Virtual Environment @echo Started Virtual Environment
@echo Built deb package in pkg/$(PKG_NAME).deb @echo Built deb package in pkg/$(PKG_NAME).deb
venv: $(VENV)/bin/activate venv: $(VENV)/bin/activate
@echo Virtual Environment is Set @echo Virtual Environment is Set
# $(PYTHON) src/tracking_deamon.py &
# $(PYTHON) src/gui.py
$(VENV)/bin/activate: requirements.txt $(VENV)/bin/activate: requirements.txt
@echo Creating Virtual Environment @echo Creating Virtual Environment
python3 -m venv $(VENV) python3 -m venv $(VENV)
$(PIP) install -r requirements.txt $(PIP) install -r requirements.txt
pkg: dist/ActivityTracker dist/ActivityTrackerd $(PKGS_UTILS) appdata/config.json run: daemon gui
daemon: $(DAEMON) $(UTILS)
$(PYTHON) src/trackingdaemon.py -d -i 10 &> log/deamon.log &
gui: $(GUI) $(UTILS)
$(PYTHON) src/gui.py -d &> deamon.log
pkg: dist/ActivityTracker dist/ActivityTrackerd $(PKGS_UTILS)
@echo Building Package @echo Building Package
mkdir -p pkgs/$(PKG_NAME)/etc/$(APP_NAME)/
cp appdata/config.json pkgs/$(PKG_NAME)/etc/$(APP_NAME)/
mkdir -p pkgs/$(PKG_NAME)/DEBIAN/ mkdir -p pkgs/$(PKG_NAME)/DEBIAN/
cp scripts/control pkgs/$(PKG_NAME)/DEBIAN/ cp scripts/control pkgs/$(PKG_NAME)/DEBIAN/
...@@ -65,6 +70,10 @@ dist/activitytrackerd: $(DAEMON) $(UTILS) ...@@ -65,6 +70,10 @@ dist/activitytrackerd: $(DAEMON) $(UTILS)
clean: clean-pkg clean-venv clean: clean-pkg clean-venv
@echo Clean completed @echo Clean completed
clean-run:
@echo cleaning previous run
rm log/*
clean-pkg: clean-pkg:
@echo Cleaning Package related folders @echo Cleaning Package related folders
rm -rf build rm -rf build
......
# Activity Tracker
### A utility to track your daily activities in the desktop.
#####Saswat Meher, Suraj Munjani
---
## Description
This program helps in keep track of which windows are being opened through it's daemon process activitytrackerd and provides a gui interface to visualise those activities through activitytracker.
For more details about the project please check the report file.
## How to run
Following are the instruction to run both the daemon and gui. First clone the repo into the local machine then proceed with the instructions.
We will make use of Makefile to run these programmes. Makefile internally make usage of debug mode in the programme and output redirection which is suitable for debugging purpose.
### Setting up a Virtual Environment
All the required libraries are mentioned in the `requirements.txt` file. You can be use Makefile to create a virtual env and install these dependencies.
```bash
make venv
```
This will will create a new virtual environment named `atvenv` and activate the same.
### Run daemon
The data about activities are collected using a daemon process. To start the daemon process make use of the make file.
```bash
make daemon
```
This will start a daemon process in the background that will start collecting user activity data dump it in the database file `data/activity_data.db`.
The interval at which the data is dumped into the disk can be also specified throught the makefile. E.g. to set interval to 10 Sec use command mentioned below.
```bash
make daemon interval=10
```
Data is dumped to the databse only after an event is detected after the interval of 10 sec.
Logs about the data being dumped into the database can be seen in `log/daemon.log`.
### Run GUI
The GUI utility to visualise the activity data can also be started using makefile.
```bash
make gui
```
Logs for the gui can be seen in `log/gui.log`.
### Packaging Instructions
This project can be packaged to generate a debian package os that it can be directly installed in a debian machine.
```bash
make pkgs
```
This command will generate binary executables independent of the python dependencies. It will use those binaries with various scripts needed for packaging to create a package structure.
Finally it will create a debian package inside pkg folder `pkg/ActivityTracker_1.0_amd64.deb` using dpkg-deb.
### Install the package
The above generated debian package can be installed using dpkg.
```bash
sudo dpkg -i <path_to_deb_package>
```
After installation the daemon should register itself as a systemd service and start automatically as a result of postinst script.
> **_NOTE:_** Currently after the installation the daemon is failing to start. So need to start `activitytrackerd` manually.
The GUI of program can be opened with the shell using the following command in shell.
```bash
activitytracker
```
### uninstall the program
The programme can be uninstalled using dpkg.
```bash
sudo dpkg -r activitytracker
```
{
"INTERVAL": 300,
"DATABASE_FILE": "/home/saswat/.ActivityTracker/activity_data.db"
}
\ No newline at end of file
Current deamon pid: 11802
Using DB file: data/activity_data.db
INSERT INTO activity VALUES (1669106240, '', 0)
INSERT INTO activity VALUES (1669106240, 'Visual Studio Code ', 6)
INSERT INTO activity VALUES (1669106240, 'Brave ', 1)
INSERT INTO activity VALUES (1669106240, 'Mozilla Firefox ', 3)
INSERT INTO activity VALUES (1669106250, 'Mozilla Firefox ', 5)
INSERT INTO activity VALUES (1669106250, 'Visual Studio Code ', 5)
INSERT INTO activity VALUES (1669106260, 'Visual Studio Code ', 9)
INSERT INTO activity VALUES (1669106260, 'Mozilla Firefox ', 1)
INSERT INTO activity VALUES (1669106270, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106280, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106290, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106300, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106310, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106320, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106330, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106340, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106350, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106360, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106370, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106380, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106390, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106400, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106410, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106420, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106430, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106440, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106450, 'Visual Studio Code ', 10)
X protocol error:
<class 'Xlib.error.BadWindow'>: code = 3, resource_id = <class 'Xlib.xobject.resource.Resource'>(0x05800027), sequence_number = 120, major_opcode = 2, minor_opcode = 0
INSERT INTO activity VALUES (1669106460, 'Visual Studio Code ', 2)
INSERT INTO activity VALUES (1669106460, 'Activity Tracker ', 8)
INSERT INTO activity VALUES (1669106470, 'Activity Tracker ', 10)
INSERT INTO activity VALUES (1669106480, 'Activity Tracker ', 8)
INSERT INTO activity VALUES (1669106480, 'Visual Studio Code ', 2)
INSERT INTO activity VALUES (1669106490, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106500, 'Visual Studio Code ', 1)
INSERT INTO activity VALUES (1669106500, 'Mozilla Firefox ', 9)
INSERT INTO activity VALUES (1669106510, 'Mozilla Firefox ', 9)
INSERT INTO activity VALUES (1669106510, 'Visual Studio Code ', 1)
INSERT INTO activity VALUES (1669106520, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106530, 'Visual Studio Code ', 8)
INSERT INTO activity VALUES (1669106530, 'Mozilla Firefox ', 2)
INSERT INTO activity VALUES (1669106540, 'Mozilla Firefox ', 8)
INSERT INTO activity VALUES (1669106540, 'Visual Studio Code ', 2)
INSERT INTO activity VALUES (1669106550, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106560, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106570, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106580, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106590, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106600, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106610, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106620, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106630, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106640, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106650, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106660, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106670, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106680, 'Visual Studio Code ', 3)
INSERT INTO activity VALUES (1669106680, 'Mozilla Firefox ', 7)
INSERT INTO activity VALUES (1669106690, 'Mozilla Firefox ', 10)
INSERT INTO activity VALUES (1669106700, 'Mozilla Firefox ', 10)
INSERT INTO activity VALUES (1669106710, 'Mozilla Firefox ', 10)
INSERT INTO activity VALUES (1669106720, 'Mozilla Firefox ', 10)
INSERT INTO activity VALUES (1669106730, 'Mozilla Firefox ', 8)
INSERT INTO activity VALUES (1669106730, 'Brave ', 1)
INSERT INTO activity VALUES (1669106730, '', 1)
INSERT INTO activity VALUES (1669106740, '', 2)
INSERT INTO activity VALUES (1669106740, 'Visual Studio Code ', 8)
INSERT INTO activity VALUES (1669106750, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106760, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106770, 'Visual Studio Code ', 10)
INSERT INTO activity VALUES (1669106780, 'Visual Studio Code ', 10)
Using DB file: data/activity_data.db
2022-11-22 1 hr 53 min
2022-11-21 3 hr 29 min
2022-11-20 6 hr 41 min
2022-11-19 0 hr 10 min
2022-11-20 6 hr 41 min
2022-11-21 3 hr 29 min
2022-11-22 1 hr 54 min
sudo systemctl daemon-reload
sudo systemctl stop activitytrackerd.service
sudo systemctl disable activitytrackerd.service
sudo systemctl enable activitytrackerd.service
sudo systemctl start activitytrackerd.service
sudo systemctl status activitytrackerd.service
import sqlite3 import sqlite3
from datetime import datetime, timedelta from datetime import datetime, timedelta
import os import sys
class DBHandler: class DBHandler:
""" """
...@@ -49,6 +49,7 @@ class DBHandler: ...@@ -49,6 +49,7 @@ class DBHandler:
cursor = self.conn.execute("SELECT * from ACTIVITY ") cursor = self.conn.execute("SELECT * from ACTIVITY ")
for row in cursor: for row in cursor:
print(row) print(row)
sys.stdout.flush()
return return
def write_interval(self, interval_start, app_name, duration): def write_interval(self, interval_start, app_name, duration):
...@@ -56,7 +57,8 @@ class DBHandler: ...@@ -56,7 +57,8 @@ class DBHandler:
Write an entry for the amount of time an application was active in an interval. Write an entry for the amount of time an application was active in an interval.
""" """
query = f"INSERT INTO activity VALUES ({interval_start}, '{app_name}', {duration})" query = f"INSERT INTO activity VALUES ({interval_start}, '{app_name}', {duration})"
#print(query) print(query)
sys.stdout.flush()
self.conn.execute(query) self.conn.execute(query)
self.conn.commit() self.conn.commit()
...@@ -65,7 +67,7 @@ class DBHandler: ...@@ -65,7 +67,7 @@ class DBHandler:
Provided a date instance return all data present corresponding the whole week Provided a date instance return all data present corresponding the whole week
for the date. for the date.
""" """
print(date) sys.stdout.flush()
date = datetime(date.year, date.month, date.day, 0, 0, 0) date = datetime(date.year, date.month, date.day, 0, 0, 0)
start = date - timedelta(days=date.weekday()) start = date - timedelta(days=date.weekday())
end = start + timedelta(days=7) end = start + timedelta(days=7)
......
...@@ -5,15 +5,19 @@ import plotter ...@@ -5,15 +5,19 @@ import plotter
from datetime import datetime, timedelta from datetime import datetime, timedelta
from tkinter.ttk import * from tkinter.ttk import *
from tkcalendar import DateEntry from tkcalendar import DateEntry
import sys
import getopt
DEBUG = False
class ActivityTracker: class ActivityTracker:
""" """
This class stores all the variables needed for the gui. This class stores all the variables needed for the gui.
Also updates graphs and details when the date is changed. Also updates graphs and details when the date is changed.
""" """
def __init__(self) -> None: def __init__(self, debug) -> None:
self.root = Tk() # A tikinter object to create the root of the window self.root = Tk() # A tikinter object to create the root of the window
self.plotter = plotter.ActivityPlotter() self.plotter = plotter.ActivityPlotter(debug)
self.root.title('Activity Tracker') self.root.title('Activity Tracker')
width = self.root.winfo_screenwidth() width = self.root.winfo_screenwidth()
...@@ -135,9 +139,35 @@ class ActivityTracker: ...@@ -135,9 +139,35 @@ class ActivityTracker:
self.__refresh_app() self.__refresh_app()
return return
Usage = """
Usage: <executable> [-h|-d]
-h | --help : help
-d | --debug : Run application in debug mode
"""
def parse_cmdargs():
global DEBUG
argv = sys.argv[1:]
short_opts = "hd"
long_opts = ["help", "debug"]
try:
args, _ = getopt.getopt(argv, short_opts, long_opts)
for opt, arg in args:
if opt in ["-h", "--help"]:
print(Usage)
elif opt in["-d", "--debug"]:
DEBUG = True
except getopt.error as err:
print (str(err))
print(Usage)
exit(1)
if __name__ == '__main__': if __name__ == '__main__':
""" """
Execution starts from here, Creating a ActivityTracker object, then it is set on mainloop(). Execution starts from here, Creating a ActivityTracker object, then it is set on mainloop().
""" """
tracker = ActivityTracker() parse_cmdargs()
tracker = ActivityTracker(DEBUG)
tracker.root.mainloop() tracker.root.mainloop()
\ No newline at end of file
...@@ -9,14 +9,13 @@ class ActivityPlotter: ...@@ -9,14 +9,13 @@ class ActivityPlotter:
""" """
Class to help plotting grpahs in GUI. Class to help plotting grpahs in GUI.
""" """
def __init__(self) -> None: def __init__(self, debug:False) -> None:
""" """
Init database handler and various constant variables Init database handler and various constant variables
""" """
config = utils.load_config() db_path = utils.get_db_path(debug)
plt.rcParams.update({'font.size': 16}) plt.rcParams.update({'font.size': 16})
self.db = dbhandler.DBHandler(config['DATABASE_FILE']) self.db = dbhandler.DBHandler(db_path)
print(config['DATABASE_FILE'])
self.week_days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] self.week_days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
self.hours = ['12am', '3am', '6am', '9am', '12pm', '3pm', '6pm', '9pm', '12am'] self.hours = ['12am', '3am', '6am', '9am', '12pm', '3pm', '6pm', '9pm', '12am']
...@@ -97,7 +96,6 @@ class ActivityPlotter: ...@@ -97,7 +96,6 @@ class ActivityPlotter:
curr_date_time = self.perday[week_day] curr_date_time = self.perday[week_day]
screen_time_text = str(int(curr_date_time/3600)) + " hr " + str(int((curr_date_time%3600)/60)) + " min" screen_time_text = str(int(curr_date_time/3600)) + " hr " + str(int((curr_date_time%3600)/60)) + " min"
print(screen_time_text)
return figure, screen_time_text return figure, screen_time_text
def __get_day_plot(self): def __get_day_plot(self):
...@@ -149,5 +147,5 @@ class ActivityPlotter: ...@@ -149,5 +147,5 @@ class ActivityPlotter:
week_fig, screen_time_text = self.__get_week_plot() week_fig, screen_time_text = self.__get_week_plot()
day_fig = self.__get_day_plot() day_fig = self.__get_day_plot()
app_fig = self.__get_app_plot() app_fig = self.__get_app_plot()
print(self.curr_date, screen_time_text)
return week_fig, day_fig, app_fig, screen_time_text return week_fig, day_fig, app_fig, screen_time_text
from contextlib import contextmanager from contextlib import contextmanager
from typing import Any, Dict, Optional, Tuple, Union
from Xlib import X from Xlib import X
from Xlib.display import Display from Xlib.display import Display
from Xlib.error import XError from Xlib.error import XError
...@@ -10,11 +8,21 @@ from Xlib.xobject.drawable import Window ...@@ -10,11 +8,21 @@ from Xlib.xobject.drawable import Window
import os import os
import signal import signal
import sys import sys
import getopt
import eventhandler import eventhandler
import utils import utils
event_handler = None event_handler = None
DEBUG = False
INTERVAL = 300
Usage ="""
Usage: <executable> [-h|-d|-i:]
-h | --help : help
-d | --debug : Run application in debug mode
-i | --interval: Interval to dump data to the disk
"""
@contextmanager @contextmanager
def create_window_obj(disp, window_id): def create_window_obj(disp, window_id):
...@@ -159,21 +167,43 @@ def init_eventhandler(): ...@@ -159,21 +167,43 @@ def init_eventhandler():
Initialise the EventHandler to handle a change in the name of the window Initialise the EventHandler to handle a change in the name of the window
""" """
global event_handler global event_handler
config = utils.load_config() db_file = utils.get_db_path(DEBUG)
event_handler = eventhandler.EventHandler(config["INTERVAL"], config["DATABASE_FILE"]) event_handler = eventhandler.EventHandler(INTERVAL, db_file)
return event_handler return event_handler
def parse_cmdargs():
global DEBUG
global INTERVAL
argv = sys.argv[1:]
short_opts = "hdi:"
long_opts = ["help", "debug", "interval="]
try:
args, _ = getopt.getopt(argv, short_opts, long_opts)
for opt, arg in args:
if opt in ["-h", "--help"]:
print(Usage)
elif opt in["-d", "--debug"]:
DEBUG = True
elif opt in ["-i", "--interval"]:
INTERVAL = int(arg)
except getopt.error as err:
print (str(err))
print(Usage)
exit(1)
if __name__ == '__main__': if __name__ == '__main__':
""" """
Main method that starts the process that starts detecting changes in the active window using EventFetcher. Main method that starts the process that starts detecting changes in the active window using EventFetcher.
Handle any changes using EventHandler class. Handle any changes using EventHandler class.
""" """
parse_cmdargs()
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGTERM, signal_handler)
print("Current deamon pid:", os.getpid()) print("Current deamon pid:", os.getpid())
sys.stdout.flush()
event_handler = init_eventhandler() event_handler = init_eventhandler()
event_fetcher = EventFetcher() event_fetcher = EventFetcher()
......
from curses.ascii import isalnum from curses.ascii import isalnum
import json import os, sys
import os
def extract_app_name(title): def extract_app_name(title):
""" """
...@@ -18,29 +17,21 @@ def extract_app_name(title): ...@@ -18,29 +17,21 @@ def extract_app_name(title):
i -= 1 i -= 1
return app_name return app_name
def load_config(): def get_db_path(debug):
""" DEV_DB_PATH = "data/activity_data.db"
Load the config file. This contains the interval time, Location for database, etc. user_path = os.path.expanduser('~')
""" DB_PATH = user_path+"/.activitytracker/activity_data.db"
local_file = "../appdata/config.json"
global_file = "/etc/ActivityTracker/config.json" if debug:
if os.path.exists(global_file): if os.path.exists(os.path.dirname(DEV_DB_PATH)) == False:
f = open(global_file) os.mkdir(os.path.dirname(DEV_DB_PATH))
config = json.load(f) print("Using DB file: ", DEV_DB_PATH)
f.close() sys.stdout.flush()
print("Using global config") return DEV_DB_PATH
elif os.path.exists(local_file):
f = open(local_file)
config = json.load(f)
f.close()
print("Using dev config")
else: else:
config = dict() if os.path.exists(os.path.dirname(DB_PATH)) == False:
config['INTERVAL'] = 300 os.mkdir(os.path.dirname(DB_PATH))
deafult_db_file_folder = os.path.expanduser('~') + "/.ActivityTracker/" print("Using DB file: ", DB_PATH)
if os.path.exists(deafult_db_file_folder) == False: sys.stdout.flush()
os.mkdir(deafult_db_file_folder) return DB_PATH
config['DATABASE_FILE'] = deafult_db_file_folder + "activity_data.db"
print("Using default config") \ No newline at end of file
print(config)
return config
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment