From 3dd08888d0dea9a23d5fadf955a0eaa429fbfe4a Mon Sep 17 00:00:00 2001 From: javara999 Date: Wed, 1 Apr 2026 11:34:29 +0900 Subject: [PATCH] Add linkkf backend files --- linkkf/model.py | 273 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 linkkf/model.py diff --git a/linkkf/model.py b/linkkf/model.py new file mode 100644 index 0000000..4918b99 --- /dev/null +++ b/linkkf/model.py @@ -0,0 +1,273 @@ +# -*- coding: utf-8 -*- +import json +import os +from datetime import datetime + +from sqlalchemy import desc, or_ + +from .setup import * + + +ModelSetting = P.ModelSetting + + +class ModelLinkkfProgram(ModelBase): + P = P + __tablename__ = "linkkf_program" + __bind_key__ = P.package_name + + id = db.Column(db.Integer, primary_key=True) + contents_json = db.Column(db.JSON) + created_time = db.Column(db.DateTime) + programcode = db.Column(db.String) + save_folder = db.Column(db.String) + season = db.Column(db.Integer) + + def __init__(self, data): + self.created_time = datetime.now() + self.programcode = data["code"] + self.save_folder = data["title"] + self.season = data["season"] + + def set_info(self, data): + self.contents_json = data + self.programcode = data["code"] + self.save_folder = data["save_folder"] + self.season = data["season"] + + +class ModelLinkkf(ModelBase): + P = P + __tablename__ = "linkkf_auto_episode" + __bind_key__ = P.package_name + + id = db.Column(db.Integer, primary_key=True) + contents_json = db.Column(db.JSON) + created_time = db.Column(db.DateTime) + completed_time = db.Column(db.DateTime) + + programcode = db.Column(db.String) + episodecode = db.Column(db.String) + filename = db.Column(db.String) + duration = db.Column(db.Integer) + start_time = db.Column(db.DateTime) + end_time = db.Column(db.DateTime) + download_time = db.Column(db.Integer) + completed = db.Column(db.Boolean) + user_abort = db.Column(db.Boolean) + pf_abort = db.Column(db.Boolean) + etc_abort = db.Column(db.Integer) + ffmpeg_status = db.Column(db.Integer) + temp_path = db.Column(db.String) + save_path = db.Column(db.String) + pf = db.Column(db.Integer) + retry = db.Column(db.Integer) + filesize = db.Column(db.Integer) + filesize_str = db.Column(db.String) + download_speed = db.Column(db.String) + call = db.Column(db.String) + status = db.Column(db.String) + linkkf_info = db.Column(db.JSON) + + def __init__(self, call, info): + self.created_time = datetime.now() + self.completed = False + self.start_time = datetime.now() + self.user_abort = False + self.pf_abort = False + self.etc_abort = 0 + self.ffmpeg_status = -1 + self.pf = 0 + self.retry = 0 + self.call = call + self.set_info(info) + + def as_dict(self): + ret = super().as_dict() + if ret.get("status") in [None, ""]: + if self.completed is True: + ret["status"] = "completed" + elif self.user_abort is True: + ret["status"] = "canceled" + elif self.pf_abort is True or (self.etc_abort is not None and int(self.etc_abort) > 0): + ret["status"] = "error" + elif self.ffmpeg_status in [0, 5]: + ret["status"] = "downloading" + ret["created_time"] = self.created_time.strftime("%Y-%m-%d %H:%M:%S") + ret["completed_time"] = ( + self.completed_time.strftime("%Y-%m-%d %H:%M:%S") + if self.completed_time is not None + else None + ) + return ret + + def set_info(self, data): + self.contents_json = data + self.programcode = data["program_code"] + self.episodecode = data["code"] + self.filename = data.get("filename", self.filename) + self.linkkf_info = data + if self.status in [None, ""]: + self.status = "waiting" + + @staticmethod + def _normalize_json_data(data): + if isinstance(data, dict): + return data + if isinstance(data, str) and data.strip() != "": + try: + return json.loads(data) + except Exception: + return {} + return {} + + @classmethod + def sync_completed_from_filesystem(cls): + with F.app.app_context(): + changed = 0 + rows = F.db.session.query(cls).filter( + or_(cls.status != "completed", cls.status.is_(None), cls.completed.is_(False)) + ).all() + for row in rows: + info = cls._normalize_json_data(row.linkkf_info) or cls._normalize_json_data(row.contents_json) + save_path = row.save_path or info.get("save_path") + filename = row.filename or info.get("filename") + if not save_path or not filename: + continue + fullpath = os.path.join(save_path, filename) + if os.path.exists(fullpath) is False: + continue + row.completed = True + row.user_abort = False + row.pf_abort = False + row.etc_abort = 0 + row.ffmpeg_status = 7 if row.ffmpeg_status in [None, -1, 0, 5] else row.ffmpeg_status + row.status = "completed" + file_time = datetime.fromtimestamp(os.path.getmtime(fullpath)) + if row.end_time is None: + row.end_time = file_time + if row.completed_time is None: + row.completed_time = file_time + changed += 1 + if changed > 0: + F.db.session.commit() + return changed + + @classmethod + def migrate_existing_rows(cls): + with F.app.app_context(): + changed = 0 + rows = F.db.session.query(cls).all() + for row in rows: + info = cls._normalize_json_data(row.contents_json) or cls._normalize_json_data(row.linkkf_info) + updated = False + if row.programcode in [None, ""] and info.get("program_code"): + row.programcode = info.get("program_code") + updated = True + if row.episodecode in [None, ""] and info.get("code"): + row.episodecode = info.get("code") + updated = True + if row.filename in [None, ""] and info.get("filename"): + row.filename = info.get("filename") + updated = True + if row.linkkf_info in [None, {}] and info: + row.linkkf_info = info + updated = True + if row.status in [None, ""]: + if row.completed is True: + row.status = "completed" + elif row.user_abort is True: + row.status = "canceled" + elif row.pf_abort is True or (row.etc_abort is not None and int(row.etc_abort) > 0): + row.status = "error" + elif row.ffmpeg_status in [0, 5]: + row.status = "downloading" + else: + row.status = "waiting" + updated = True + if updated: + changed += 1 + if changed > 0: + F.db.session.commit() + changed += cls.sync_completed_from_filesystem() + return changed + + @classmethod + def web_list(cls, req): + with F.app.app_context(): + ret = {} + cls.sync_completed_from_filesystem() + page = int(req.form["page"]) if "page" in req.form else 1 + page_size = 30 + search = req.form["search_word"] if "search_word" in req.form else req.form.get("keyword", "") + option = req.form["option"] if "option" in req.form else req.form.get("option1", "finished") + order = req.form["order"] if "order" in req.form else "desc" + + query = cls.make_query(search=search, order=order, option=option) + count = query.count() + query = query.limit(page_size).offset((page - 1) * page_size) + lists = query.all() + ret["list"] = [item.as_dict() for item in lists] + ret["paging"] = cls.get_paging_info(count, page, page_size) + return ret + + @classmethod + def get_by_linkkf_id(cls, linkkf_id): + with F.app.app_context(): + return F.db.session.query(cls).filter_by(episodecode=linkkf_id).first() + + @classmethod + def make_query(cls, search="", order="desc", option="all"): + query = F.db.session.query(cls) + if search is not None and search != "": + if "|" in search: + conditions = [] + for token in [x.strip() for x in search.split("|") if x.strip()]: + conditions.append(cls.filename.like(f"%{token}%")) + conditions.append(cls.programcode.like(f"%{token}%")) + if conditions: + query = query.filter(or_(*conditions)) + elif "," in search: + for token in [x.strip() for x in search.split(",") if x.strip()]: + query = query.filter( + or_( + cls.filename.like(f"%{token}%"), + cls.programcode.like(f"%{token}%"), + ) + ) + else: + query = query.filter( + or_( + cls.filename.like(f"%{search}%"), + cls.programcode.like(f"%{search}%"), + ) + ) + if option == "completed": + query = query.filter(or_(cls.status == "completed", cls.completed.is_(True))) + elif option == "canceled": + query = query.filter(or_(cls.status == "canceled", cls.user_abort.is_(True))) + elif option == "error": + query = query.filter( + or_( + cls.status == "error", + cls.pf_abort.is_(True), + cls.etc_abort > 0, + ) + ) + elif option == "finished": + query = query.filter( + or_( + cls.status.in_(["completed", "error", "canceled"]), + cls.completed.is_(True), + cls.user_abort.is_(True), + cls.pf_abort.is_(True), + cls.etc_abort > 0, + ) + ) + elif option == "downloading": + query = query.filter(cls.status == "downloading") + if order == "desc": + query = query.order_by(desc(cls.id)) + else: + query = query.order_by(cls.id) + return query