31
31
use App \Entity \Testcase ;
32
32
use App \Entity \User ;
33
33
use App \Utils \FreezeData ;
34
+ use App \Utils \UpdateStrategy ;
34
35
use App \Utils \Utils ;
35
36
use DateTime ;
36
37
use Doctrine \ORM \EntityManagerInterface ;
37
38
use Doctrine \ORM \NonUniqueResultException ;
38
39
use Doctrine \ORM \NoResultException ;
39
40
use Doctrine \ORM \Query \Expr \Join ;
40
41
use Doctrine \ORM \QueryBuilder ;
42
+ use Exception ;
41
43
use InvalidArgumentException ;
42
44
use Psr \Log \LoggerInterface ;
43
45
use ReflectionClass ;
46
+ use Symfony \Component \Cache \Adapter \FilesystemAdapter ;
44
47
use Symfony \Component \DependencyInjection \Attribute \Autowire ;
45
48
use Symfony \Component \DependencyInjection \ParameterBag \ParameterBagInterface ;
46
49
use Symfony \Component \HttpFoundation \Cookie ;
62
65
use Symfony \Component \Security \Core \Authentication \Token \UsernamePasswordToken ;
63
66
use Symfony \Component \Security \Core \Authorization \AuthorizationCheckerInterface ;
64
67
use Symfony \Component \Security \Core \User \UserInterface ;
68
+ use Symfony \Contracts \Cache \ItemInterface ;
65
69
use Twig \Environment ;
66
70
use ZipArchive ;
67
71
@@ -70,6 +74,8 @@ class DOMJudgeService
70
74
protected ?Executable $ defaultCompareExecutable = null ;
71
75
protected ?Executable $ defaultRunExecutable = null ;
72
76
77
+ private string $ localVersionString = '' ;
78
+
73
79
final public const EVAL_DEFAULT = 0 ;
74
80
final public const EVAL_LAZY = 1 ;
75
81
final public const EVAL_FULL = 2 ;
@@ -108,6 +114,10 @@ public function __construct(
108
114
protected string $ projectDir ,
109
115
#[Autowire('%domjudge.vendordir% ' )]
110
116
protected string $ vendorDir ,
117
+ #[Autowire('%domjudge.version% ' )]
118
+ protected readonly string $ domjudgeVersion ,
119
+ #[Autowire('%domjudge.installmethod% ' )]
120
+ protected readonly string $ domjudgeInstallMethod ,
111
121
) {}
112
122
113
123
/**
@@ -1715,4 +1725,98 @@ public function getTeamsForContest(?Contest $contest) : array {
1715
1725
->getQuery ()
1716
1726
->getResult ();
1717
1727
}
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
+ }
1718
1822
}
0 commit comments