diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4ea5b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +tasks.json +log.txt diff --git a/README.md b/README.md index 36afdac..7f0bd8d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,12 @@ simple_task_manager =============== +### Usage +``` +curl -X POST http://localhost:5000/schedule \ + -H "Content-Type: application/json" \ + -d '{ + "url": "http://example.com/notify", + "json_data": {"event": "backup"}, + "run_at": "2025-04-15 16:00:00" + }' +``` diff --git a/manager.py b/manager.py new file mode 100644 index 0000000..d4ce3f8 --- /dev/null +++ b/manager.py @@ -0,0 +1,130 @@ +import asyncio +import aiohttp +import json +import os +from flask import Flask, request, jsonify +from datetime import datetime +import threading + +TASKS_FILE = "tasks.json" +LOG_FILE = "log.txt" +MAX_LOG_LINES = 100 + +app = Flask(__name__) +tasks = [] # список задач + + +class Task: + def __init__(self, url: str, json_data: dict, run_at: str, created_at: str): + self.url = url + self.json_data = json_data + self.run_at = datetime.strptime(run_at, "%Y-%m-%d %H:%M:%S") + self.created_at = datetime.strptime(created_at, "%Y-%m-%d %H:%M:%S") + self.executed = False + + def to_dict(self): + return { + "url": self.url, + "json_data": self.json_data, + "run_at": self.run_at.strftime("%Y-%m-%d %H:%M:%S"), + "created_at": self.created_at.strftime("%Y-%m-%d %H:%M:%S"), + "executed": self.executed + } + + @staticmethod + def from_dict(data): + task = Task( + url=data["url"], + json_data=data["json_data"], + run_at=data["run_at"], + created_at=data["created_at"] + ) + task.executed = data.get("executed", False) + return task + + async def execute(self): + try: + async with aiohttp.ClientSession() as session: + async with session.post(self.url, json=self.json_data) as resp: + print(f"[{datetime.now()}] Executed task to {self.url}, status: {resp.status}") + self.executed = True + log_task(self) + except Exception as e: + print(f"[{datetime.now()}] Failed to execute task to {self.url}: {e}") + + +def save_tasks(): + with open(TASKS_FILE, "w") as f: + json.dump([task.to_dict() for task in tasks if not task.executed], f, indent=2) + + +def load_tasks(): + if os.path.exists(TASKS_FILE): + with open(TASKS_FILE, "r") as f: + data = json.load(f) + return [Task.from_dict(t) for t in data] + return [] + + +def log_task(task: Task): + log_line = f"[{task.created_at.strftime('%Y-%m-%d %H:%M:%S')}]: {task.url}; {json.dumps(task.json_data)}; выполнено в {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n" + try: + if os.path.exists(LOG_FILE): + with open(LOG_FILE, "r") as f: + lines = f.readlines() + else: + lines = [] + + lines.append(log_line) + lines = lines[-MAX_LOG_LINES:] + + with open(LOG_FILE, "w") as f: + f.writelines(lines) + except Exception as e: + print(f"[!] Ошибка записи лога: {e}") + + +@app.route('/schedule', methods=['POST']) +def schedule_task(): + try: + data = request.get_json() + url = data['url'] + json_data = data.get('json_data', {}) + run_at_str = data['run_at'] # "YYYY-MM-DD HH:MM:SS" + + # Проверка формата + run_at = datetime.strptime(run_at_str, "%Y-%m-%d %H:%M:%S") + created_at = datetime.now() + + task = Task(url, json_data, run_at_str, created_at.strftime("%Y-%m-%d %H:%M:%S")) + tasks.append(task) + save_tasks() + + return jsonify({"status": "scheduled", "run_at": run_at_str}) + except Exception as e: + return jsonify({"error": str(e)}), 400 + + +async def task_scheduler(): + while True: + now = datetime.now() + changed = False + for task in tasks: + if not task.executed and now >= task.run_at: + await task.execute() + changed = True + if changed: + save_tasks() + await asyncio.sleep(1) + + +def start_async_loop(): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(task_scheduler()) + + +if __name__ == '__main__': + tasks = load_tasks() + threading.Thread(target=start_async_loop, daemon=True).start() + app.run(host='0.0.0.0', port=5000) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e1450f7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,16 @@ +aiohappyeyeballs==2.6.1 +aiohttp==3.11.16 +aiosignal==1.3.2 +attrs==25.3.0 +blinker==1.9.0 +click==8.1.8 +Flask==3.1.0 +frozenlist==1.5.0 +idna==3.10 +itsdangerous==2.2.0 +Jinja2==3.1.6 +MarkupSafe==3.0.2 +multidict==6.4.3 +propcache==0.3.1 +Werkzeug==3.1.3 +yarl==1.19.0