-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstudytrack.py
More file actions
executable file
·152 lines (137 loc) · 3.96 KB
/
Copy pathstudytrack.py
File metadata and controls
executable file
·152 lines (137 loc) · 3.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#!/usr/bin/env python3
"""
StudyTrack v1 - single-file CLI launcher that delegates to webapp package.
Usage:
studytrack --start # start detached server
studytrack --stop # stop server
studytrack --status # show running status
"""
import os
import sys
import argparse
import time
import signal
from pathlib import Path
from subprocess import Popen
import sqlite3
# Attempt to import Flask via webapp package. If imports fail, exit with helpful message.
try:
# webapp package will import Flask
import webapp
except Exception:
pass
# Config
PORT = 8080
DATA_DIR = Path.home() / ".studytrack"
DATA_DIR.mkdir(parents=True, exist_ok=True)
PID_FILE = DATA_DIR / "studytrack.pid"
LOG_FILE = DATA_DIR / "studytrack.log"
SCRIPT = os.path.abspath(__file__)
# Helpers
def write_pid(pid):
PID_FILE.write_text(str(pid))
def read_pid():
if not PID_FILE.exists():
return None
try:
return int(PID_FILE.read_text().strip())
except Exception:
return None
def remove_pid():
try:
PID_FILE.unlink()
except Exception:
pass
def is_running(pid):
try:
os.kill(pid, 0)
except OSError:
return False
return True
def kill_group(pid):
try:
os.killpg(os.getpgid(pid), signal.SIGTERM)
return True
except Exception:
try:
os.kill(pid, signal.SIGTERM)
return True
except Exception:
return False
# CLI actions
def start():
pid = read_pid()
if pid and is_running(pid):
print(f"StudyTrack already running (pid {pid})")
return
python = sys.executable
cmd = [python, SCRIPT, "--runserver"]
try:
p = Popen(cmd, stdout=open(LOG_FILE, "a"), stderr=open(LOG_FILE, "a"), preexec_fn=os.setsid, close_fds=True)
write_pid(p.pid)
print(f"StudyTrack started on http://localhost:{PORT} (pid {p.pid})")
print("You can close this terminal. To stop: studytrack --stop")
except Exception as e:
print("Failed to start StudyTrack:", e)
def stop():
pid = read_pid()
if not pid:
print("StudyTrack is not running (no pid file).")
return
if not is_running(pid):
print("Stale pid file found; removing.")
remove_pid()
return
ok = kill_group(pid)
if ok:
time.sleep(0.3)
remove_pid()
print("StudyTrack stopped.")
else:
print("Failed to stop StudyTrack. Try: sudo pkill -f studytrack.py")
def status():
pid = read_pid()
if not pid:
print("StudyTrack is not running.")
return
if is_running(pid):
print(f"StudyTrack running (pid {pid}) at http://localhost:{PORT}")
else:
print("PID exists but process not running. Remove pid file and try again.")
# When launched as runserver, import and run webapp.app
def runserver():
# import local webapp package and start app
# keep import here so venv activation is required earlier
try:
from webapp.routes import create_app
app = create_app()
# FORCE DEBUG MODE
app.run(host='127.0.0.1', port=PORT, threaded=True, debug=True, use_reloader=False)
except ImportError as e:
print(f"Error: Failed to import webapp. {e}")
print("Please ensure your venv is active and all files are saved.")
except Exception as e:
print(f"An error occurred: {e}")
# Argparse
def main():
parser = argparse.ArgumentParser(prog='studytrack')
parser.add_argument('--start', action='store_true')
parser.add_argument('--stop', action='store_true')
parser.add_argument('--status', action='store_true')
parser.add_argument('--runserver', action='store_true', help=argparse.SUPPRESS)
args = parser.parse_args()
if args.runserver:
runserver()
return
if args.start:
start()
return
if args.stop:
stop()
return
if args.status:
status()
return
parser.print_help()
if __name__ == '__main__':
main()