Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion root/app/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from keyvaluestore import KeyValueStore
from models import ImagesResponse
from models import ImagesResponse, StatsResponse
from pydantic import ValidationError
import json
import os
Expand Down Expand Up @@ -55,5 +55,19 @@ async def images(include_config: bool = False, include_deprecated: bool = False)
print(traceback.format_exc())
raise HTTPException(status_code=404, detail="Not found")

async def get_stats():
with KeyValueStore() as kv:
return kv["stats"]

@api.get("/stats", response_model=StatsResponse, summary="Get the stats")
async def stats():
try:
response = await get_stats()
stats_response = StatsResponse.model_validate_json(response)
return stats_response
except Exception:
print(traceback.format_exc())
raise HTTPException(status_code=404, detail="Not found")

if __name__ == "__main__":
api.run()
6 changes: 6 additions & 0 deletions root/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Increment when updating schema or forcing an update on start
IMAGES_SCHEMA_VERSION = 3
SCARF_SCHEMA_VERSION = 1
STATS_SCHEMA_VERSION = 1


class Tag(BaseModel):
Expand Down Expand Up @@ -128,3 +129,8 @@ def exclude_config(self):
def exclude_deprecated(self):
images = self.data.repositories.linuxserver
self.data.repositories.linuxserver = list(filter(lambda image: not image.deprecated, images))

class StatsResponse(BaseModel):
total_images: int
total_stars: int
total_monthly_pulls: int
24 changes: 24 additions & 0 deletions root/app/updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from models import Architecture, Changelog, Tag, EnvVar, Volume, Port, Config
from models import Custom, SecurityOpt, Device, Cap, Hostname, MacAddress, Image
from models import Repository, ImagesData, ImagesResponse, IMAGES_SCHEMA_VERSION, SCARF_SCHEMA_VERSION
from models import StatsResponse, STATS_SCHEMA_VERSION

import datetime
import json
Expand Down Expand Up @@ -258,6 +259,28 @@ def update_status(status):
kv.set_value("status", status, 0)
print(f"{datetime.datetime.now()} - updated status")

def update_stats():
stats_response = StatsResponse(total_images=0, total_monthly_pulls=0, total_stars=0)

with KeyValueStore(invalidate_hours=INVALIDATE_HOURS, readonly=False) as kv:
is_current_schema = kv.is_current_schema("stats", STATS_SCHEMA_VERSION)
if ("stats" in kv and is_current_schema) or CI == "1":
print(f"{datetime.datetime.now()} - stats skipped - already updated")
return
print(f"{datetime.datetime.now()} - updating stats")
images_json = kv["images"]
images_response = ImagesResponse.model_validate_json(images_json)
images = images_response.data.repositories.linuxserver

for image in images:
stats_response.total_monthly_pulls += image.monthly_pulls or 0
stats_response.total_images += 1
stats_response.total_stars += image.stars

new_state = stats_response.model_dump_json(exclude_none=True)
kv.set_value("stats", new_state, STATS_SCHEMA_VERSION)
print(f"{datetime.datetime.now()} - updated stats")

def main():
try:
set_db_schema()
Expand All @@ -266,6 +289,7 @@ def main():
update_scarf()
update_images()
gh.print_rate_limit()
update_stats()
update_status("Success")
time.sleep(INVALIDATE_HOURS*60*60)
except Exception as ex:
Expand Down
Loading