feat(race): add supports_countdown to ContestListResult
Problem: `:CP race` on platforms without future contests (CSES, USACO) wastes time fetching the full contest list only to discover there is no `start_time`. The error message is also uninformative. Solution: Add `supports_countdown` bool to `ContestListResult` (default `True`). CSES and USACO set it to `False`. Cache the flag per-platform so subsequent calls skip the fetch entirely. `race.lua` checks the cached value first, then the scraper result, and shows `"<Platform> does not support :CP race"` instead of a generic error.
This commit is contained in:
parent
592f977296
commit
4e709c8470
7 changed files with 62 additions and 16 deletions
|
|
@ -392,7 +392,8 @@ end
|
|||
|
||||
---@param platform string
|
||||
---@param contests ContestSummary[]
|
||||
function M.set_contest_summaries(platform, contests)
|
||||
---@param opts? { supports_countdown?: boolean }
|
||||
function M.set_contest_summaries(platform, contests, opts)
|
||||
cache_data[platform] = cache_data[platform] or {}
|
||||
for _, contest in ipairs(contests) do
|
||||
cache_data[platform][contest.id] = cache_data[platform][contest.id] or {}
|
||||
|
|
@ -405,9 +406,22 @@ function M.set_contest_summaries(platform, contests)
|
|||
end
|
||||
end
|
||||
|
||||
if opts and opts.supports_countdown ~= nil then
|
||||
cache_data[platform].supports_countdown = opts.supports_countdown
|
||||
end
|
||||
|
||||
M.save()
|
||||
end
|
||||
|
||||
---@param platform string
|
||||
---@return boolean?
|
||||
function M.get_supports_countdown(platform)
|
||||
if not cache_data[platform] then
|
||||
return nil
|
||||
end
|
||||
return cache_data[platform].supports_countdown
|
||||
end
|
||||
|
||||
---@param platform string
|
||||
---@param contest_id string
|
||||
---@return integer?
|
||||
|
|
|
|||
|
|
@ -48,8 +48,10 @@ function M.get_platform_contests(platform, refresh)
|
|||
{ level = vim.log.levels.INFO, override = true, sync = true }
|
||||
)
|
||||
|
||||
local contests = scraper.scrape_contest_list(platform)
|
||||
cache.set_contest_summaries(platform, contests)
|
||||
local result = scraper.scrape_contest_list(platform)
|
||||
local contests = result and result.contests or {}
|
||||
local sc = result and result.supports_countdown
|
||||
cache.set_contest_summaries(platform, contests, { supports_countdown = sc })
|
||||
picker_contests = cache.get_contest_summaries(platform)
|
||||
|
||||
logger.log(
|
||||
|
|
|
|||
|
|
@ -35,20 +35,36 @@ function M.start(platform, contest_id, language)
|
|||
end
|
||||
|
||||
cache.load()
|
||||
|
||||
local display = constants.PLATFORM_DISPLAY_NAMES[platform] or platform
|
||||
local cached_countdown = cache.get_supports_countdown(platform)
|
||||
if cached_countdown == false then
|
||||
logger.log(('%s does not support :CP race'):format(display), { level = vim.log.levels.ERROR })
|
||||
return
|
||||
end
|
||||
|
||||
local start_time = cache.get_contest_start_time(platform, contest_id)
|
||||
|
||||
if not start_time then
|
||||
logger.log('Fetching contest list...', { level = vim.log.levels.INFO, override = true })
|
||||
local contests = scraper.scrape_contest_list(platform)
|
||||
if contests and #contests > 0 then
|
||||
cache.set_contest_summaries(platform, contests)
|
||||
start_time = cache.get_contest_start_time(platform, contest_id)
|
||||
logger.log('Fetching contest list...', { level = vim.log.levels.INFO, override = true, sync = true })
|
||||
local result = scraper.scrape_contest_list(platform)
|
||||
if result then
|
||||
local sc = result.supports_countdown
|
||||
if sc == false then
|
||||
cache.set_contest_summaries(platform, result.contests or {}, { supports_countdown = false })
|
||||
logger.log(('%s does not support :CP race'):format(display), { level = vim.log.levels.ERROR })
|
||||
return
|
||||
end
|
||||
if result.contests and #result.contests > 0 then
|
||||
cache.set_contest_summaries(platform, result.contests, { supports_countdown = sc })
|
||||
start_time = cache.get_contest_start_time(platform, contest_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not start_time then
|
||||
logger.log(
|
||||
('No start time found for %s contest %s'):format(
|
||||
('No start time found for %s contest "%s"'):format(
|
||||
constants.PLATFORM_DISPLAY_NAMES[platform] or platform,
|
||||
contest_id
|
||||
),
|
||||
|
|
|
|||
|
|
@ -257,9 +257,12 @@ function M.scrape_contest_list(platform)
|
|||
),
|
||||
{ level = vim.log.levels.ERROR }
|
||||
)
|
||||
return {}
|
||||
return nil
|
||||
end
|
||||
return result.data.contests
|
||||
return {
|
||||
contests = result.data.contests,
|
||||
supports_countdown = result.data.supports_countdown ~= false,
|
||||
}
|
||||
end
|
||||
|
||||
---@param platform string
|
||||
|
|
|
|||
|
|
@ -228,9 +228,13 @@ class CSESScraper(BaseScraper):
|
|||
cats = parse_categories(html)
|
||||
if not cats:
|
||||
return ContestListResult(
|
||||
success=False, error=f"{self.platform_name}: No contests found"
|
||||
success=False,
|
||||
error=f"{self.platform_name}: No contests found",
|
||||
supports_countdown=False,
|
||||
)
|
||||
return ContestListResult(success=True, error="", contests=cats)
|
||||
return ContestListResult(
|
||||
success=True, error="", contests=cats, supports_countdown=False
|
||||
)
|
||||
|
||||
async def login(self, credentials: dict[str, str]) -> LoginResult:
|
||||
username = credentials.get("username", "")
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ class MetadataResult(ScrapingResult):
|
|||
|
||||
class ContestListResult(ScrapingResult):
|
||||
contests: list[ContestSummary] = Field(default_factory=list)
|
||||
supports_countdown: bool = True
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
|
|
|||
|
|
@ -337,10 +337,16 @@ class USACOScraper(BaseScraper):
|
|||
contests.extend(await coro)
|
||||
|
||||
if not contests:
|
||||
return self._contests_error("No contests found")
|
||||
return ContestListResult(success=True, error="", contests=contests)
|
||||
return ContestListResult(
|
||||
success=False, error="No contests found", supports_countdown=False
|
||||
)
|
||||
return ContestListResult(
|
||||
success=True, error="", contests=contests, supports_countdown=False
|
||||
)
|
||||
except Exception as e:
|
||||
return self._contests_error(str(e))
|
||||
return ContestListResult(
|
||||
success=False, error=str(e), supports_countdown=False
|
||||
)
|
||||
|
||||
async def stream_tests_for_category_async(self, category_id: str) -> None:
|
||||
month_year, division = _parse_contest_id(category_id)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue