22from keyvaluestore import KeyValueStore , set_db_schema
33from models import Architecture , Changelog , Tag , EnvVar , Volume , Port , Config
44from models import Custom , SecurityOpt , Device , Cap , Hostname , MacAddress , Image
5- from models import Repository , ImagesData , ImagesResponse , IMAGES_SCHEMA_VERSION
5+ from models import Repository , ImagesData , ImagesResponse , IMAGES_SCHEMA_VERSION , SCARF_SCHEMA_VERSION
66
77import datetime
8+ import json
89import os
10+ import requests
911import time
12+ import traceback
1013
1114CI = os .environ .get ("CI" , None )
1215INVALIDATE_HOURS = int (os .environ .get ("INVALIDATE_HOURS" , "24" ))
16+ SCARF_TOKEN = os .environ .get ("SCARF_TOKEN" , None )
1317
1418
1519def get_tags (readme_vars ):
@@ -31,13 +35,18 @@ def get_architectures(readme_vars):
3135 archs .append (Architecture (arch = item ["arch" ], tag = item ["tag" ]))
3236 return archs
3337
34- def get_changelogs (readme_vars ):
38+ def get_changelog (readme_vars ):
3539 if "changelogs" not in readme_vars :
36- return None
37- changelogs = []
40+ return None , None
41+ changelog = []
3842 for item in readme_vars ["changelogs" ][0 :3 ]:
39- changelogs .append (Changelog (date = item ["date" ][0 :- 1 ], desc = item ["desc" ]))
40- return changelogs
43+ date = item ["date" ][0 :- 1 ]
44+ normalized_date = str (datetime .datetime .strptime (date , "%d.%m.%y" ).date ())
45+ changelog .append (Changelog (date = normalized_date , desc = item ["desc" ]))
46+ first_changelog = readme_vars ["changelogs" ][- 1 ]
47+ initial_date = first_changelog ["date" ][0 :- 1 ]
48+ normalized_initial_date = str (datetime .datetime .strptime (initial_date , "%d.%m.%y" ).date ())
49+ return changelog , normalized_initial_date
4150
4251def get_description (readme_vars ):
4352 description = readme_vars .get ("project_blurb" , "No description" )
@@ -136,7 +145,7 @@ def get_mac_address(readme_vars):
136145 hostname = readme_vars .get ("param_mac_address" , False )
137146 return MacAddress (mac_address = hostname , desc = readme_vars .get ("param_mac_address_desc" , "" ), optional = optional )
138147
139- def get_image (repo ):
148+ def get_image (repo , scarf_data ):
140149 print (f"Processing { repo .name } " )
141150 if not repo .name .startswith ("docker-" ) or repo .name .startswith ("docker-baseimage-" ):
142151 return None
@@ -153,6 +162,7 @@ def get_image(repo):
153162 application_setup = None
154163 if readme_vars .get ("app_setup_block_enabled" , False ):
155164 application_setup = f"{ repo .html_url } ?tab=readme-ov-file#application-setup"
165+ changelog , initial_date = get_changelog (readme_vars )
156166 config = Config (
157167 application_setup = application_setup ,
158168 readonly_supported = readme_vars .get ("readonly_supported" , None ),
@@ -171,8 +181,8 @@ def get_image(repo):
171181 )
172182 return Image (
173183 name = project_name ,
184+ initial_date = initial_date ,
174185 github_url = repo .html_url ,
175- stars = repo .stargazers_count ,
176186 project_url = readme_vars .get ("project_url" , None ),
177187 project_logo = readme_vars .get ("project_logo" , None ),
178188 description = get_description (readme_vars ),
@@ -181,40 +191,82 @@ def get_image(repo):
181191 category = categories ,
182192 stable = stable ,
183193 deprecated = deprecated ,
194+ stars = repo .stargazers_count ,
195+ monthly_pulls = scarf_data .get (project_name , None ),
184196 tags = tags ,
185197 architectures = get_architectures (readme_vars ),
186- changelog = get_changelogs ( readme_vars ) ,
198+ changelog = changelog ,
187199 config = config ,
188200 )
189201
190202def update_images ():
191203 with KeyValueStore (invalidate_hours = INVALIDATE_HOURS , readonly = False ) as kv :
192204 is_current_schema = kv .is_current_schema ("images" , IMAGES_SCHEMA_VERSION )
193205 if ("images" in kv and is_current_schema ) or CI == "1" :
194- print (f"{ datetime .datetime .now ()} - skipped - already updated" )
206+ print (f"{ datetime .datetime .now ()} - images skipped - already updated" )
195207 return
196208 print (f"{ datetime .datetime .now ()} - updating images" )
197209 images = []
210+ scarf_data = json .loads (kv ["scarf" ])
198211 repos = gh .get_repos ()
199212 for repo in sorted (repos , key = lambda repo : repo .name ):
200- image = get_image (repo )
213+ image = get_image (repo , scarf_data )
201214 if not image :
202215 continue
203216 images .append (image )
204217
205218 data = ImagesData (repositories = Repository (linuxserver = images ))
206- last_updated = datetime .datetime .now (datetime .timezone .utc ).isoformat (' ' , ' seconds' )
219+ last_updated = datetime .datetime .now (datetime .timezone .utc ).isoformat (" " , " seconds" )
207220 response = ImagesResponse (status = "OK" , last_updated = last_updated , data = data )
208221 new_state = response .model_dump_json (exclude_none = True )
209222 kv .set_value ("images" , new_state , IMAGES_SCHEMA_VERSION )
210223 print (f"{ datetime .datetime .now ()} - updated images" )
211224
225+ def get_monthly_pulls ():
226+ pulls_map = {}
227+ response = requests .get ("https://api.scarf.sh/v2/packages/linuxserver-ci/overview?per_page=1000" , headers = {"Authorization" : f"Bearer { SCARF_TOKEN } " })
228+ results = response .json ()["results" ]
229+ for result in results :
230+ name = result ["package" ]["name" ].replace ("linuxserver/" , "" )
231+ if "total_installs" not in result :
232+ continue
233+ monthly_pulls = result ["total_installs" ]
234+ pulls_map [name ] = monthly_pulls
235+ return pulls_map
236+
237+ def update_scarf ():
238+ with KeyValueStore (invalidate_hours = INVALIDATE_HOURS , readonly = False ) as kv :
239+ is_current_schema = kv .is_current_schema ("scarf" , SCARF_SCHEMA_VERSION )
240+ if ("scarf" in kv and is_current_schema ) or CI == "1" :
241+ print (f"{ datetime .datetime .now ()} - scarf skipped - already updated" )
242+ return
243+ print (f"{ datetime .datetime .now ()} - updating scarf" )
244+ pulls_map = get_monthly_pulls ()
245+ if not pulls_map :
246+ return
247+ new_state = json .dumps (pulls_map )
248+ kv .set_value ("scarf" , new_state , SCARF_SCHEMA_VERSION )
249+ print (f"{ datetime .datetime .now ()} - updated scarf" )
250+
251+ def update_status (status ):
252+ with KeyValueStore (invalidate_hours = 0 , readonly = False ) as kv :
253+ print (f"{ datetime .datetime .now ()} - updating status" )
254+ kv .set_value ("status" , status , 0 )
255+ print (f"{ datetime .datetime .now ()} - updated status" )
256+
212257def main ():
213- set_db_schema ()
214- while True :
215- gh .print_rate_limit ()
216- update_images ()
217- gh .print_rate_limit ()
258+ try :
259+ set_db_schema ()
260+ while True :
261+ gh .print_rate_limit ()
262+ update_scarf ()
263+ update_images ()
264+ gh .print_rate_limit ()
265+ update_status ("Success" )
266+ time .sleep (INVALIDATE_HOURS * 60 * 60 )
267+ except :
268+ print (traceback .format_exc ())
269+ update_status ("Failed" )
218270 time .sleep (INVALIDATE_HOURS * 60 * 60 )
219271
220272if __name__ == "__main__" :
0 commit comments