GameSentenceMiner 2.19.16__py3-none-any.whl → 2.20.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of GameSentenceMiner might be problematic. Click here for more details.
- GameSentenceMiner/__init__.py +39 -0
- GameSentenceMiner/anki.py +6 -3
- GameSentenceMiner/gametext.py +13 -2
- GameSentenceMiner/gsm.py +40 -3
- GameSentenceMiner/locales/en_us.json +4 -0
- GameSentenceMiner/locales/ja_jp.json +4 -0
- GameSentenceMiner/locales/zh_cn.json +4 -0
- GameSentenceMiner/obs.py +4 -1
- GameSentenceMiner/owocr/owocr/ocr.py +304 -134
- GameSentenceMiner/owocr/owocr/run.py +1 -1
- GameSentenceMiner/ui/anki_confirmation.py +4 -2
- GameSentenceMiner/ui/config_gui.py +12 -0
- GameSentenceMiner/util/configuration.py +6 -2
- GameSentenceMiner/util/cron/__init__.py +12 -0
- GameSentenceMiner/util/cron/daily_rollup.py +613 -0
- GameSentenceMiner/util/cron/jiten_update.py +397 -0
- GameSentenceMiner/util/cron/populate_games.py +154 -0
- GameSentenceMiner/util/cron/run_crons.py +148 -0
- GameSentenceMiner/util/cron/setup_populate_games_cron.py +118 -0
- GameSentenceMiner/util/cron_table.py +334 -0
- GameSentenceMiner/util/db.py +236 -49
- GameSentenceMiner/util/ffmpeg.py +23 -4
- GameSentenceMiner/util/games_table.py +340 -93
- GameSentenceMiner/util/jiten_api_client.py +188 -0
- GameSentenceMiner/util/stats_rollup_table.py +216 -0
- GameSentenceMiner/web/anki_api_endpoints.py +438 -220
- GameSentenceMiner/web/database_api.py +955 -1259
- GameSentenceMiner/web/jiten_database_api.py +1015 -0
- GameSentenceMiner/web/rollup_stats.py +672 -0
- GameSentenceMiner/web/static/css/dashboard-shared.css +75 -13
- GameSentenceMiner/web/static/css/overview.css +604 -47
- GameSentenceMiner/web/static/css/search.css +226 -0
- GameSentenceMiner/web/static/css/shared.css +762 -0
- GameSentenceMiner/web/static/css/stats.css +221 -0
- GameSentenceMiner/web/static/js/components/bar-chart.js +339 -0
- GameSentenceMiner/web/static/js/database-bulk-operations.js +320 -0
- GameSentenceMiner/web/static/js/database-game-data.js +390 -0
- GameSentenceMiner/web/static/js/database-game-operations.js +213 -0
- GameSentenceMiner/web/static/js/database-helpers.js +44 -0
- GameSentenceMiner/web/static/js/database-jiten-integration.js +750 -0
- GameSentenceMiner/web/static/js/database-popups.js +89 -0
- GameSentenceMiner/web/static/js/database-tabs.js +64 -0
- GameSentenceMiner/web/static/js/database-text-management.js +371 -0
- GameSentenceMiner/web/static/js/database.js +86 -718
- GameSentenceMiner/web/static/js/goals.js +79 -18
- GameSentenceMiner/web/static/js/heatmap.js +29 -23
- GameSentenceMiner/web/static/js/overview.js +1205 -339
- GameSentenceMiner/web/static/js/regex-patterns.js +100 -0
- GameSentenceMiner/web/static/js/search.js +215 -18
- GameSentenceMiner/web/static/js/shared.js +193 -39
- GameSentenceMiner/web/static/js/stats.js +1536 -179
- GameSentenceMiner/web/stats.py +1142 -269
- GameSentenceMiner/web/stats_api.py +2104 -0
- GameSentenceMiner/web/templates/anki_stats.html +4 -18
- GameSentenceMiner/web/templates/components/date-range.html +118 -3
- GameSentenceMiner/web/templates/components/html-head.html +40 -6
- GameSentenceMiner/web/templates/components/js-config.html +8 -8
- GameSentenceMiner/web/templates/components/regex-input.html +160 -0
- GameSentenceMiner/web/templates/database.html +564 -117
- GameSentenceMiner/web/templates/goals.html +41 -5
- GameSentenceMiner/web/templates/overview.html +159 -129
- GameSentenceMiner/web/templates/search.html +78 -9
- GameSentenceMiner/web/templates/stats.html +159 -5
- GameSentenceMiner/web/texthooking_page.py +280 -111
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/METADATA +43 -2
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/RECORD +70 -47
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Setup script to register and run the populate_games cron job once.
|
|
3
|
+
|
|
4
|
+
This script:
|
|
5
|
+
1. Checks if populate_games cron already exists
|
|
6
|
+
2. If not, creates it with schedule='once'
|
|
7
|
+
3. Runs it immediately to populate the games table
|
|
8
|
+
4. The cron will auto-disable after running (schedule='once' behavior)
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
python -m GameSentenceMiner.util.cron.setup_populate_games_cron
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import time
|
|
15
|
+
from GameSentenceMiner.util.cron_table import CronTable
|
|
16
|
+
from GameSentenceMiner.util.cron.populate_games import populate_games_table
|
|
17
|
+
from GameSentenceMiner.util.configuration import logger
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def setup_and_run_populate_games():
|
|
21
|
+
"""
|
|
22
|
+
Setup and run the populate_games cron job once.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Dictionary with setup and execution results
|
|
26
|
+
"""
|
|
27
|
+
logger.info("=" * 80)
|
|
28
|
+
logger.info("POPULATE GAMES CRON SETUP")
|
|
29
|
+
logger.info("=" * 80)
|
|
30
|
+
|
|
31
|
+
# Check if cron already exists
|
|
32
|
+
existing_cron = CronTable.get_by_name('populate_games')
|
|
33
|
+
|
|
34
|
+
if existing_cron:
|
|
35
|
+
if existing_cron.enabled:
|
|
36
|
+
logger.info(f"populate_games cron already exists and is enabled (id={existing_cron.id})")
|
|
37
|
+
logger.info("Running it now...")
|
|
38
|
+
else:
|
|
39
|
+
logger.info(f"populate_games cron already exists but is disabled (id={existing_cron.id})")
|
|
40
|
+
logger.info("This means it has already run once. Skipping...")
|
|
41
|
+
return {
|
|
42
|
+
'setup': 'skipped',
|
|
43
|
+
'reason': 'Cron already ran (disabled)',
|
|
44
|
+
'cron_id': existing_cron.id
|
|
45
|
+
}
|
|
46
|
+
else:
|
|
47
|
+
# Create new cron entry with schedule='once'
|
|
48
|
+
logger.info("Creating new populate_games cron entry...")
|
|
49
|
+
try:
|
|
50
|
+
new_cron = CronTable.create_cron_entry(
|
|
51
|
+
name='populate_games',
|
|
52
|
+
description='One-time auto-creation of game records from game_lines',
|
|
53
|
+
next_run=time.time(), # Run immediately
|
|
54
|
+
schedule='once', # Will auto-disable after running
|
|
55
|
+
enabled=True
|
|
56
|
+
)
|
|
57
|
+
logger.info(f"Created populate_games cron (id={new_cron.id})")
|
|
58
|
+
existing_cron = new_cron
|
|
59
|
+
except Exception as e:
|
|
60
|
+
logger.error(f"Failed to create cron entry: {e}")
|
|
61
|
+
return {
|
|
62
|
+
'setup': 'failed',
|
|
63
|
+
'error': str(e)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Run the populate_games function
|
|
67
|
+
logger.info("Executing populate_games_table()...")
|
|
68
|
+
try:
|
|
69
|
+
result = populate_games_table()
|
|
70
|
+
|
|
71
|
+
# Mark the cron as having run (will auto-disable since schedule='once')
|
|
72
|
+
CronTable.just_ran(existing_cron.id)
|
|
73
|
+
|
|
74
|
+
logger.info("=" * 80)
|
|
75
|
+
logger.info("POPULATE GAMES COMPLETED")
|
|
76
|
+
logger.info("=" * 80)
|
|
77
|
+
logger.info(f"Success: {result['success']}")
|
|
78
|
+
logger.info(f"Games created: {result['created']}")
|
|
79
|
+
logger.info(f"Lines linked: {result['linked_lines']}")
|
|
80
|
+
logger.info(f"Errors: {result['errors']}")
|
|
81
|
+
if result['error_message']:
|
|
82
|
+
logger.error(f"Error: {result['error_message']}")
|
|
83
|
+
logger.info("=" * 80)
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
'setup': 'success',
|
|
87
|
+
'cron_id': existing_cron.id,
|
|
88
|
+
'execution_result': result
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
logger.error(f"Failed to execute populate_games: {e}", exc_info=True)
|
|
93
|
+
return {
|
|
94
|
+
'setup': 'execution_failed',
|
|
95
|
+
'cron_id': existing_cron.id,
|
|
96
|
+
'error': str(e)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == '__main__':
|
|
101
|
+
result = setup_and_run_populate_games()
|
|
102
|
+
|
|
103
|
+
print("\n" + "=" * 80)
|
|
104
|
+
print("SETUP RESULT")
|
|
105
|
+
print("=" * 80)
|
|
106
|
+
print(f"Setup status: {result.get('setup')}")
|
|
107
|
+
if 'cron_id' in result:
|
|
108
|
+
print(f"Cron ID: {result['cron_id']}")
|
|
109
|
+
if 'execution_result' in result:
|
|
110
|
+
exec_result = result['execution_result']
|
|
111
|
+
print(f"Games created: {exec_result.get('created', 0)}")
|
|
112
|
+
print(f"Lines linked: {exec_result.get('linked_lines', 0)}")
|
|
113
|
+
print(f"Errors: {exec_result.get('errors', 0)}")
|
|
114
|
+
if 'error' in result:
|
|
115
|
+
print(f"Error: {result['error']}")
|
|
116
|
+
if 'reason' in result:
|
|
117
|
+
print(f"Reason: {result['reason']}")
|
|
118
|
+
print("=" * 80)
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from datetime import datetime, timedelta
|
|
3
|
+
from typing import Optional, List, Dict
|
|
4
|
+
|
|
5
|
+
from GameSentenceMiner.util.db import SQLiteDBTable
|
|
6
|
+
from GameSentenceMiner.util.configuration import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CronTable(SQLiteDBTable):
|
|
10
|
+
"""
|
|
11
|
+
Table for managing scheduled cron jobs in GSM.
|
|
12
|
+
Stores periodic tasks that need to be executed on a schedule.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
_table = "cron_table"
|
|
16
|
+
_fields = [
|
|
17
|
+
"name",
|
|
18
|
+
"description",
|
|
19
|
+
"last_run",
|
|
20
|
+
"next_run",
|
|
21
|
+
"enabled",
|
|
22
|
+
"created_at",
|
|
23
|
+
"schedule",
|
|
24
|
+
]
|
|
25
|
+
_types = [
|
|
26
|
+
int, # id (primary key)
|
|
27
|
+
str, # name
|
|
28
|
+
str, # description
|
|
29
|
+
float, # last_run (Unix timestamp)
|
|
30
|
+
float, # next_run (Unix timestamp)
|
|
31
|
+
bool, # enabled
|
|
32
|
+
float, # created_at (Unix timestamp)
|
|
33
|
+
str, # schedule (once, daily, weekly, monthly, yearly)
|
|
34
|
+
]
|
|
35
|
+
_pk = "id"
|
|
36
|
+
_auto_increment = True
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
id: Optional[int] = None,
|
|
41
|
+
name: Optional[str] = None,
|
|
42
|
+
description: Optional[str] = None,
|
|
43
|
+
last_run: Optional[float] = None,
|
|
44
|
+
next_run: Optional[float] = None,
|
|
45
|
+
enabled: bool = True,
|
|
46
|
+
created_at: Optional[float] = None,
|
|
47
|
+
schedule: str = "once",
|
|
48
|
+
):
|
|
49
|
+
"""
|
|
50
|
+
Initialize a CronTable entry.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
id: Primary key (auto-generated if None)
|
|
54
|
+
name: Unique name for the cron job
|
|
55
|
+
description: Human-readable description
|
|
56
|
+
last_run: Unix timestamp of last execution (None if never run)
|
|
57
|
+
next_run: Unix timestamp for next scheduled run
|
|
58
|
+
enabled: Whether the cron job is active
|
|
59
|
+
created_at: Unix timestamp of creation (defaults to now)
|
|
60
|
+
schedule: Schedule type ('once', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly')
|
|
61
|
+
"""
|
|
62
|
+
self.id = id
|
|
63
|
+
self.name = name if name else ""
|
|
64
|
+
self.description = description if description else ""
|
|
65
|
+
self.last_run = last_run # None if never run
|
|
66
|
+
self.next_run = next_run if next_run else time.time()
|
|
67
|
+
self.enabled = enabled
|
|
68
|
+
self.created_at = created_at if created_at else time.time()
|
|
69
|
+
self.schedule = (
|
|
70
|
+
schedule
|
|
71
|
+
if schedule in ["once", "minutely", "hourly", "daily", "weekly", "monthly", "yearly"]
|
|
72
|
+
else "once"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def create_cron_entry(
|
|
77
|
+
cls,
|
|
78
|
+
name: str,
|
|
79
|
+
description: str,
|
|
80
|
+
next_run: float,
|
|
81
|
+
schedule: str,
|
|
82
|
+
enabled: bool = True,
|
|
83
|
+
) -> "CronTable":
|
|
84
|
+
"""
|
|
85
|
+
Create a new cron entry and save it to the database.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
name: Unique name for the cron job
|
|
89
|
+
description: Human-readable description
|
|
90
|
+
next_run: Unix timestamp for next scheduled run
|
|
91
|
+
schedule: Schedule type ('once', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly')
|
|
92
|
+
enabled: Whether the cron job is active (default: True)
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
CronTable: The created cron entry
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
ValueError: If schedule type is invalid or name already exists
|
|
99
|
+
"""
|
|
100
|
+
# Validate schedule type
|
|
101
|
+
valid_schedules = ["once", "minutely", "hourly", "daily", "weekly", "monthly", "yearly"]
|
|
102
|
+
if schedule not in valid_schedules:
|
|
103
|
+
raise ValueError(
|
|
104
|
+
f"Invalid schedule type '{schedule}'. Must be one of: {', '.join(valid_schedules)}"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Check if name already exists
|
|
108
|
+
existing = cls.get_by_name(name)
|
|
109
|
+
if existing:
|
|
110
|
+
raise ValueError(f"Cron job with name '{name}' already exists")
|
|
111
|
+
|
|
112
|
+
# Create new entry
|
|
113
|
+
new_cron = cls(
|
|
114
|
+
name=name,
|
|
115
|
+
description=description,
|
|
116
|
+
next_run=next_run,
|
|
117
|
+
schedule=schedule,
|
|
118
|
+
enabled=enabled,
|
|
119
|
+
created_at=time.time(),
|
|
120
|
+
)
|
|
121
|
+
new_cron.save()
|
|
122
|
+
logger.debug(
|
|
123
|
+
f"Created cron job '{name}' with schedule '{schedule}', next run at {datetime.fromtimestamp(next_run)}"
|
|
124
|
+
)
|
|
125
|
+
return new_cron
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def get_due_crons(cls) -> List["CronTable"]:
|
|
129
|
+
"""
|
|
130
|
+
Get all enabled cron jobs that are due to run now or earlier.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
List[CronTable]: List of cron jobs that need to be executed, ordered by next_run
|
|
134
|
+
"""
|
|
135
|
+
now = time.time()
|
|
136
|
+
rows = cls._db.fetchall(
|
|
137
|
+
f"SELECT * FROM {cls._table} WHERE enabled=1 AND next_run <= ? ORDER BY next_run ASC",
|
|
138
|
+
(now,),
|
|
139
|
+
)
|
|
140
|
+
crons = [cls.from_row(row) for row in rows]
|
|
141
|
+
if crons:
|
|
142
|
+
logger.debug(f"Found {len(crons)} due cron job(s)")
|
|
143
|
+
return crons
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def get_by_name(cls, name: str) -> Optional["CronTable"]:
|
|
147
|
+
"""
|
|
148
|
+
Get a cron job by its unique name.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
name: The name of the cron job
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
CronTable: The cron job if found, None otherwise
|
|
155
|
+
"""
|
|
156
|
+
row = cls._db.fetchone(f"SELECT * FROM {cls._table} WHERE name=?", (name,))
|
|
157
|
+
return cls.from_row(row) if row else None
|
|
158
|
+
|
|
159
|
+
@classmethod
|
|
160
|
+
def get_all_enabled(cls) -> List["CronTable"]:
|
|
161
|
+
"""
|
|
162
|
+
Get all enabled cron jobs.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
List[CronTable]: List of all enabled cron jobs
|
|
166
|
+
"""
|
|
167
|
+
rows = cls._db.fetchall(
|
|
168
|
+
f"SELECT * FROM {cls._table} WHERE enabled=1 ORDER BY next_run ASC"
|
|
169
|
+
)
|
|
170
|
+
return [cls.from_row(row) for row in rows]
|
|
171
|
+
|
|
172
|
+
def update_last_run(self, timestamp: Optional[float] = None):
|
|
173
|
+
"""
|
|
174
|
+
Update the last_run timestamp for this cron job.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
timestamp: Unix timestamp to set (defaults to current time)
|
|
178
|
+
"""
|
|
179
|
+
self.last_run = timestamp if timestamp is not None else time.time()
|
|
180
|
+
self.save()
|
|
181
|
+
logger.debug(
|
|
182
|
+
f"Updated last_run for cron job '{self.name}' to {datetime.fromtimestamp(self.last_run)}"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def update_next_run(self, next_run: float):
|
|
186
|
+
"""
|
|
187
|
+
Update the next_run timestamp for this cron job.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
next_run: Unix timestamp for next scheduled run
|
|
191
|
+
"""
|
|
192
|
+
self.next_run = next_run
|
|
193
|
+
self.save()
|
|
194
|
+
logger.debug(
|
|
195
|
+
f"Updated next_run for cron job '{self.name}' to {datetime.fromtimestamp(next_run)}"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def enable(self):
|
|
199
|
+
"""Enable this cron job."""
|
|
200
|
+
self.enabled = True
|
|
201
|
+
self.save()
|
|
202
|
+
logger.debug(f"Enabled cron job '{self.name}'")
|
|
203
|
+
|
|
204
|
+
def disable(self):
|
|
205
|
+
"""Disable this cron job."""
|
|
206
|
+
self.enabled = False
|
|
207
|
+
self.save()
|
|
208
|
+
logger.debug(f"Disabled cron job '{self.name}'")
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def enable_cron(cls, cron_id: int):
|
|
212
|
+
"""
|
|
213
|
+
Enable a cron job by ID.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
cron_id: The ID of the cron job to enable
|
|
217
|
+
"""
|
|
218
|
+
cron = cls.get(cron_id)
|
|
219
|
+
if cron:
|
|
220
|
+
cron.enable()
|
|
221
|
+
else:
|
|
222
|
+
logger.warning(f"Cron job with id {cron_id} not found")
|
|
223
|
+
|
|
224
|
+
@classmethod
|
|
225
|
+
def disable_cron(cls, cron_id: int):
|
|
226
|
+
"""
|
|
227
|
+
Disable a cron job by ID.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
cron_id: The ID of the cron job to disable
|
|
231
|
+
"""
|
|
232
|
+
cron = cls.get(cron_id)
|
|
233
|
+
if cron:
|
|
234
|
+
cron.disable()
|
|
235
|
+
else:
|
|
236
|
+
logger.warning(f"Cron job with id {cron_id} not found")
|
|
237
|
+
|
|
238
|
+
@classmethod
|
|
239
|
+
def just_ran(cls, cron_id: int):
|
|
240
|
+
"""
|
|
241
|
+
Mark a cron job as having just run and calculate the next run time based on its schedule.
|
|
242
|
+
|
|
243
|
+
This is a convenience method that:
|
|
244
|
+
1. Sets last_run to current time
|
|
245
|
+
2. Calculates next_run based on the schedule type
|
|
246
|
+
3. Updates the database
|
|
247
|
+
|
|
248
|
+
For 'once' schedule, the cron job will be disabled after running.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
cron_id: The ID of the cron job that just ran
|
|
252
|
+
"""
|
|
253
|
+
cron = cls.get(cron_id)
|
|
254
|
+
if not cron:
|
|
255
|
+
logger.warning(f"Cron job with id {cron_id} not found")
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
# Set last_run to now
|
|
259
|
+
now = time.time()
|
|
260
|
+
cron.last_run = now
|
|
261
|
+
|
|
262
|
+
# Calculate next_run based on schedule
|
|
263
|
+
now_dt = datetime.fromtimestamp(now)
|
|
264
|
+
|
|
265
|
+
if cron.schedule == "once":
|
|
266
|
+
# For one-time jobs, disable after running
|
|
267
|
+
cron.enabled = False
|
|
268
|
+
cron.next_run = now # Set to now since it won't run again
|
|
269
|
+
logger.debug(
|
|
270
|
+
f"Cron job '{cron.name}' completed (one-time job) and has been disabled"
|
|
271
|
+
)
|
|
272
|
+
elif cron.schedule == "minutely":
|
|
273
|
+
# Schedule for 1 minute from now
|
|
274
|
+
next_run_dt = now_dt + timedelta(minutes=1)
|
|
275
|
+
cron.next_run = next_run_dt.timestamp()
|
|
276
|
+
logger.debug(
|
|
277
|
+
f"Cron job '{cron.name}' completed, next run scheduled for {next_run_dt}"
|
|
278
|
+
)
|
|
279
|
+
elif cron.schedule == "hourly":
|
|
280
|
+
# Schedule for 1 hour from now
|
|
281
|
+
next_run_dt = now_dt + timedelta(hours=1)
|
|
282
|
+
cron.next_run = next_run_dt.timestamp()
|
|
283
|
+
logger.debug(
|
|
284
|
+
f"Cron job '{cron.name}' completed, next run scheduled for {next_run_dt}"
|
|
285
|
+
)
|
|
286
|
+
elif cron.schedule == "daily":
|
|
287
|
+
# Schedule for 3am tomorrow
|
|
288
|
+
# If we schedule at + 24 hours
|
|
289
|
+
# imagine if user opens gsm at like 6pm first time, does some mining
|
|
290
|
+
# tomorrow they open gsm again but at 9am, but the cron is set to run at 6pm
|
|
291
|
+
# so they will have stats from yesterday not rolled up, as stats rollup did not run
|
|
292
|
+
# setting it to 3am means the user always has the full previous day rolled up when they open gsm
|
|
293
|
+
next_run_dt = (now_dt + timedelta(days=1)).replace(
|
|
294
|
+
hour=3, minute=0, second=0, microsecond=0
|
|
295
|
+
)
|
|
296
|
+
cron.next_run = next_run_dt.timestamp()
|
|
297
|
+
logger.debug(
|
|
298
|
+
f"Cron job '{cron.name}' completed, next run scheduled for {next_run_dt}"
|
|
299
|
+
)
|
|
300
|
+
elif cron.schedule == "weekly":
|
|
301
|
+
# Schedule for 3am next week (same day)
|
|
302
|
+
next_run_dt = (now_dt + timedelta(weeks=1)).replace(
|
|
303
|
+
hour=3, minute=0, second=0, microsecond=0
|
|
304
|
+
)
|
|
305
|
+
cron.next_run = next_run_dt.timestamp()
|
|
306
|
+
logger.debug(
|
|
307
|
+
f"Cron job '{cron.name}' completed, next run scheduled for {next_run_dt}"
|
|
308
|
+
)
|
|
309
|
+
elif cron.schedule == "monthly":
|
|
310
|
+
# Schedule for 3am approximately 30 days from now
|
|
311
|
+
next_run_dt = (now_dt + timedelta(days=30)).replace(
|
|
312
|
+
hour=3, minute=0, second=0, microsecond=0
|
|
313
|
+
)
|
|
314
|
+
cron.next_run = next_run_dt.timestamp()
|
|
315
|
+
logger.debug(
|
|
316
|
+
f"Cron job '{cron.name}' completed, next run scheduled for {next_run_dt}"
|
|
317
|
+
)
|
|
318
|
+
elif cron.schedule == "yearly":
|
|
319
|
+
# Schedule for 3am approximately 365 days from now
|
|
320
|
+
next_run_dt = (now_dt + timedelta(days=365)).replace(
|
|
321
|
+
hour=3, minute=0, second=0, microsecond=0
|
|
322
|
+
)
|
|
323
|
+
cron.next_run = next_run_dt.timestamp()
|
|
324
|
+
logger.debug(
|
|
325
|
+
f"Cron job '{cron.name}' completed, next run scheduled for {next_run_dt}"
|
|
326
|
+
)
|
|
327
|
+
else:
|
|
328
|
+
logger.warning(
|
|
329
|
+
f"Unknown schedule type '{cron.schedule}' for cron job '{cron.name}'"
|
|
330
|
+
)
|
|
331
|
+
return
|
|
332
|
+
|
|
333
|
+
# Save all changes
|
|
334
|
+
cron.save()
|