123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- from pathlib import Path
- from flask import Flask, Response, render_template, abort, send_file, jsonify
- import markdown as md
- from dataclasses import dataclass
- from dataclasses_json import DataClassJsonMixin
- app = Flask(__name__)
- BUILDS_PATH = Path("../builds")
- ARTIFACTS_FOLDER = "artifacts"
- INFO_FILE = "info.json"
- @dataclass
- class ArtifactItem(DataClassJsonMixin):
- file: str
- description: str
- @dataclass
- class Artifact(DataClassJsonMixin):
- id: str
- date: str
- changelog: str
- artifacts: list[ArtifactItem]
- hash: str | None = None
- short_hash: str | None = None
- @dataclass
- class ProjectInfo(DataClassJsonMixin):
- name: str
- commit_url: str
- @dataclass
- class Project:
- id: str
- info: ProjectInfo
- def _get_artifact_paths(self) -> list[Path]:
- artifacts_path = BUILDS_PATH / self.id / ARTIFACTS_FOLDER
- if not artifacts_path.exists():
- return []
- return sorted(
- [p for p in artifacts_path.iterdir() if p.is_dir()],
- reverse=True,
- )
- def get_artifacts(self) -> list[Artifact]:
- result = []
- for artifact_folder in self._get_artifact_paths():
- info_file_path = artifact_folder / INFO_FILE
- artifact = self._parse_artifact(info_file_path)
- if artifact:
- result.append(artifact)
- return result
- def get_lastest_artifact(self) -> Artifact | None:
- latest_artifact_folder = max(self._get_artifact_paths(), default=None)
- if not latest_artifact_folder:
- return None
- return self._parse_artifact(latest_artifact_folder / INFO_FILE)
- def get_artifact(self, artifact_id: str) -> Artifact | None:
- artifact_folder = BUILDS_PATH / self.id / ARTIFACTS_FOLDER / artifact_id
- if not artifact_folder.is_dir():
- return None
- return self._parse_artifact(artifact_folder / INFO_FILE)
- def _parse_artifact(self, info_file_path: Path) -> Artifact | None:
- if not info_file_path.exists():
- return None
- try:
- with info_file_path.open("r", encoding="utf-8") as f:
- artifact = Artifact.from_json(f.read())
- artifact.date = artifact.date.strip()
- return artifact
- except:
- return None
- def get_projects() -> list[Project]:
- result = []
- projects = [p for p in BUILDS_PATH.iterdir() if p.is_dir()]
- for project in projects:
- info_path = project / INFO_FILE
- if not info_path.exists():
- continue
- try:
- *_, proj_id = project.parts
- with info_path.open("r", encoding="utf-8") as f:
- proj_info = ProjectInfo.from_json(f.read())
- result.append(Project(proj_id, proj_info))
- except:
- continue
- return result
- @app.get("/api/projects/<string:project_id>/artifacts/latest")
- def get_latest_artifact(project_id: str) -> Response:
- projects = get_projects()
- selected_project = next(
- (project for project in projects if project.id == project_id), None
- )
- if not selected_project:
- return abort(404)
- artifact = selected_project.get_lastest_artifact()
- if not artifact:
- return abort(404)
- return jsonify(artifact)
- @app.get("/api/projects/<string:project_id>/artifacts/<string:artifact_id>")
- def get_artifact_by_id(project_id: str, artifact_id: str) -> Response:
- projects = get_projects()
- selected_project = next(
- (project for project in projects if project.id == project_id), None
- )
- if not selected_project:
- return abort(404)
- artifact = selected_project.get_artifact(artifact_id)
- if not artifact:
- return abort(404)
- return jsonify(artifact)
- @app.get("/projects/<string:project_id>/<string:artifact_id>/<string:download_item>")
- def download_item(project_id: str, artifact_id: str, download_item: str) -> Response:
- file_path = (
- BUILDS_PATH / project_id / ARTIFACTS_FOLDER / artifact_id / download_item
- )
- if not file_path.exists():
- return abort(404)
- return send_file(file_path)
- @app.get("/projects/<string:project_id>")
- def display_project(project_id: str) -> Response | str:
- projects = get_projects()
- selected_project = next(
- (project for project in projects if project.id == project_id), None
- )
- if not selected_project:
- return abort(404)
- info_path = BUILDS_PATH / selected_project.id / "info.md"
- readme = None
- try:
- if info_path.exists():
- with info_path.open("r", encoding="utf-8") as f:
- readme = md.markdown(f.read())
- except:
- readme = None
- artifacts = selected_project.get_artifacts()
- return render_template(
- "project_view.html",
- projects=projects,
- selected_project=selected_project,
- readme=readme,
- artifacts=artifacts,
- )
- @app.get("/")
- def index() -> Response | str:
- projects = get_projects()
- return render_template("main.html", projects=projects)
- if __name__ == "__main__":
- app.run(host="0.0.0.0")
|