3131use App \Entity \Testcase ;
3232use App \Entity \User ;
3333use App \Utils \FreezeData ;
34+ use App \Utils \UpdateStrategy ;
3435use App \Utils \Utils ;
3536use DateTime ;
3637use Doctrine \ORM \EntityManagerInterface ;
3738use Doctrine \ORM \NonUniqueResultException ;
3839use Doctrine \ORM \NoResultException ;
3940use Doctrine \ORM \Query \Expr \Join ;
4041use Doctrine \ORM \QueryBuilder ;
42+ use Exception ;
4143use InvalidArgumentException ;
4244use Psr \Log \LoggerInterface ;
4345use ReflectionClass ;
46+ use Symfony \Component \Cache \Adapter \FilesystemAdapter ;
4447use Symfony \Component \DependencyInjection \Attribute \Autowire ;
4548use Symfony \Component \DependencyInjection \ParameterBag \ParameterBagInterface ;
4649use Symfony \Component \HttpFoundation \Cookie ;
6265use Symfony \Component \Security \Core \Authentication \Token \UsernamePasswordToken ;
6366use Symfony \Component \Security \Core \Authorization \AuthorizationCheckerInterface ;
6467use Symfony \Component \Security \Core \User \UserInterface ;
68+ use Symfony \Contracts \Cache \ItemInterface ;
6569use Twig \Environment ;
6670use ZipArchive ;
6771
@@ -70,6 +74,8 @@ class DOMJudgeService
7074 protected ?Executable $ defaultCompareExecutable = null ;
7175 protected ?Executable $ defaultRunExecutable = null ;
7276
77+ private string $ localVersionString = '' ;
78+
7379 final public const EVAL_DEFAULT = 0 ;
7480 final public const EVAL_LAZY = 1 ;
7581 final public const EVAL_FULL = 2 ;
@@ -108,6 +114,10 @@ public function __construct(
108114 protected string $ projectDir ,
109115 #[Autowire('%domjudge.vendordir% ' )]
110116 protected string $ vendorDir ,
117+ #[Autowire('%domjudge.version% ' )]
118+ protected readonly string $ domjudgeVersion ,
119+ #[Autowire('%domjudge.installmethod% ' )]
120+ protected readonly string $ domjudgeInstallMethod ,
111121 ) {}
112122
113123 /**
@@ -1715,4 +1725,98 @@ public function getTeamsForContest(?Contest $contest) : array {
17151725 ->getQuery ()
17161726 ->getResult ();
17171727 }
1728+
1729+ /**
1730+ * @return String[]|false
1731+ */
1732+ public function cacherCheckNewVersion (ItemInterface $ item ): array |false {
1733+ $ item ->expiresAfter (86400 );
1734+
1735+ $ versionUrl = 'https://versions.domjudge.org ' ;
1736+ $ options = ['http ' => ['timeout ' => 1 , 'method ' => 'GET ' , 'header ' => "User-Agent: DOMjudge# " . $ this ->domjudgeInstallMethod . "/ " . $ this ->localVersionString . "\r\n" ]];
1737+ $ context = stream_context_create ($ options );
1738+ $ response = @file_get_contents ($ versionUrl , false , $ context );
1739+ if ($ response === false ) {
1740+ return false ;
1741+ }
1742+ // Assume we get a one-level unordered JSON list with the released versions e.g. ["10.0.0", "9.11.0", "12.0.12", "10.0.1"]
1743+ $ tmp_versions = json_decode ($ response , true );
1744+ natsort ($ tmp_versions );
1745+ return array_reverse ($ tmp_versions );
1746+ }
1747+
1748+ /**
1749+ * Returns either the next strictly higher version or false when nothing is found/requested.
1750+ */
1751+ public function checkNewVersion (): string |false {
1752+ if ($ this ->config ->get ('check_new_version ' , false ) === UpdateStrategy::Strategy_none) {
1753+ return false ;
1754+ }
1755+ // The local version is something like "x.y.z / commit hash", e.g. "8.4.0DEV/4e25adb13" for development
1756+ // or 8.3.2 for a released version
1757+ // In case of development we remove the commit hash for some anonymity but keep the DEV to not count those as the (possibly) released version
1758+ $ this ->localVersionString = (string )strtok ($ this ->domjudgeVersion , "/ " );
1759+ $ localVersion = explode (". " , $ this ->localVersionString );
1760+ if (count ($ localVersion ) !== 3 ) {
1761+ // Unknown version, someone might have locally modified and used their own versioning
1762+ return false ;
1763+ }
1764+
1765+ $ cache = new FilesystemAdapter ();
1766+ try {
1767+ $ versions = $ cache ->get ('domjudge_versions ' , [$ this , 'cacherCheckNewVersion ' ]);
1768+ } catch (InvalidArgumentException $ e ) {
1769+ return false ;
1770+ }
1771+
1772+ if (!$ versions ) {
1773+ return false ;
1774+ }
1775+
1776+ preg_match ("/\d.\d.\d/ " , $ this ->domjudgeVersion , $ matches );
1777+ $ extractedLocalVersionString = $ matches [0 ];
1778+ if ($ this ->config ->get ('check_new_version ' , false ) === UpdateStrategy::Strategy_incremental) {
1779+ /* Steer towards the nearest highest patch release first
1780+ * So the expected path would be:
1781+ * DJ6.0.0 -> DJ6.0.6 -> DJ6.6.0 -> DJ9.1.2 instead of
1782+ * -> DJ6.0.[1..6] -> DJ6.[1..6].* -> DJ[7..9].*.*
1783+ * skipping all patch releases in between, when no patch release
1784+ * is available, try the highest minor and otherwise the highest Major
1785+ * instead of going to the latest release:
1786+ * DJ6.0.0 -> DJ9.1.2
1787+ */
1788+ $ patch = "/ " . $ localVersion [0 ] . ". " . $ localVersion [1 ] . ".\d/ " ;
1789+ $ minor = "/ " . $ localVersion [0 ] . ".\d.\d/ " ;
1790+ $ major = "/\d.\d.\d/ " ;
1791+ foreach ([$ patch , $ minor , $ major ] as $ regex ) {
1792+ foreach ($ versions as $ release ) {
1793+ if (preg_match ($ regex , $ release )) {
1794+ if (strnatcmp ($ release , $ extractedLocalVersionString ) === 1 ) {
1795+ return $ release ;
1796+ }
1797+ if (strnatcmp ($ release , $ extractedLocalVersionString ) === 0 && str_contains ($ this ->localVersionString , "DEV " )) {
1798+ // Special case, the development version is now released
1799+ return $ release ;
1800+ }
1801+ }
1802+ }
1803+ }
1804+ }
1805+ elseif ($ this ->config ->get ('check_new_version ' , false ) === UpdateStrategy::Strategy_major_release) {
1806+ /* Steer towards the latest version directly
1807+ * So the expected path would be:
1808+ * DJ6.0.0 -> DJ9.1.2
1809+ * This should be safe as doctrine migrations check for upgrades regardless of current DOMjudge release
1810+ */
1811+ $ latest = $ versions [0 ];
1812+ if (strnatcmp ($ latest , $ extractedLocalVersionString ) === 1 ) {
1813+ return $ latest ;
1814+ }
1815+ if (strnatcmp ($ latest , $ extractedLocalVersionString ) === 0 && str_contains ($ this ->localVersionString , "DEV " )) {
1816+ // Special case, the development version is now released
1817+ return $ latest ;
1818+ }
1819+ }
1820+ return false ;
1821+ }
17181822}
0 commit comments