Skip to content

Commit 65876aa

Browse files
committed
feat: Add auto updater
1 parent 3e5c48c commit 65876aa

8 files changed

Lines changed: 517 additions & 11 deletions

File tree

CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@ cmake_minimum_required(VERSION 3.31.6)
33
project(Installer VERSION 1.0 LANGUAGES CXX)
44
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>" CACHE STRING "" FORCE)
55
add_compile_definitions(UNICODE _UNICODE)
6+
# Read the Millennium version from the version file
7+
file(STRINGS "version" VERSION_LINES LIMIT_COUNT 2)
8+
list(GET VERSION_LINES 1 MILLENNIUM_VERSION)
9+
string(STRIP "${MILLENNIUM_VERSION}" MILLENNIUM_VERSION)
10+
11+
# Make cmake re-run if the version file changes
12+
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "version")
13+
14+
# Generate version.h from version.h.in
15+
configure_file(src/include/version.h.in ${CMAKE_BINARY_DIR}/version.h)
16+
17+
add_compile_definitions(MILLENNIUM_VERSION="${MILLENNIUM_VERSION}")
618

719
# Specify the C++ standard
820
set(CMAKE_CXX_STANDARD 20)
@@ -37,6 +49,7 @@ set(SOURCES
3749
src/texture.cc
3850
src/wndproc.cc
3951
src/renderer.cc
52+
src/updater.cc
4053
src/routes/home.cc
4154
src/routes/install_prompt.cc
4255
src/routes/installer.cc
@@ -48,6 +61,7 @@ set(SOURCES
4861
src/animate.cc
4962
src/router.cc
5063
src/dpi.cc
64+
src/semver.cc
5165
src/installer/task_scheduler.cc
5266
src/installer/unzip.cc
5367
vendor/imgui/src/imgui.cpp

src/include/semver.h

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* ==================================================
3+
* _____ _ _ _ _
4+
* | |_| | |___ ___ ___|_|_ _ _____
5+
* | | | | | | | -_| | | | | | |
6+
* |_|_|_|_|_|_|___|_|_|_|_|_|___|_|_|_|
7+
*
8+
* ==================================================
9+
*
10+
* Copyright (c) 2025 Project Millennium
11+
*
12+
* Permission is hereby granted, free of charge, to any person obtaining a copy
13+
* of this software and associated documentation files (the "Software"), to deal
14+
* in the Software without restriction, including without limitation the rights
15+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16+
* copies of the Software, and to permit persons to whom the Software is
17+
* furnished to do so, subject to the following conditions:
18+
*
19+
* The above copyright notice and this permission notice shall be included in all
20+
* copies or substantial portions of the Software.
21+
*
22+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28+
* SOFTWARE.
29+
*/
30+
31+
#pragma once
32+
#include <string>
33+
34+
namespace Semver
35+
{
36+
struct SemverVersion
37+
{
38+
int major;
39+
int minor;
40+
int patch;
41+
std::string prerelease;
42+
std::string build;
43+
44+
SemverVersion(int maj = 0, int min = 0, int pat = 0, const std::string& pre = "", const std::string& bld = "");
45+
};
46+
47+
/**
48+
* Parse a semantic version string into its components
49+
* @param version The version string to parse (e.g., "1.2.3-alpha+build")
50+
* @return SemverVersion struct with parsed components
51+
* @throws std::invalid_argument if version format is invalid
52+
*/
53+
SemverVersion ParseSemver(const std::string& version);
54+
55+
/**
56+
* Compare two semantic version strings
57+
* @param v1 First version string
58+
* @param v2 Second version string
59+
* @return -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2
60+
* @throws std::invalid_argument if either version format is invalid
61+
*/
62+
int Compare(const std::string& v1, const std::string& v2);
63+
64+
/**
65+
* Compare prerelease identifiers according to semver rules
66+
* @param pre1 First prerelease string
67+
* @param pre2 Second prerelease string
68+
* @return -1 if pre1 < pre2, 0 if pre1 == pre2, 1 if pre1 > pre2
69+
*/
70+
int ComparePrereleases(const std::string& pre1, const std::string& pre2);
71+
} // namespace Semver

src/include/updater.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* ==================================================
3+
* _____ _ _ _ _
4+
* | |_| | |___ ___ ___|_|_ _ _____
5+
* | | | | | | | -_| | | | | | |
6+
* |_|_|_|_|_|_|___|_|_|_|_|_|___|_|_|_|
7+
*
8+
* ==================================================
9+
*
10+
* Copyright (c) 2025 Project Millennium
11+
*
12+
* Permission is hereby granted, free of charge, to any person obtaining a copy
13+
* of this software and associated documentation files (the "Software"), to deal
14+
* in the Software without restriction, including without limitation the rights
15+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16+
* copies of the Software, and to permit persons to whom the Software is
17+
* furnished to do so, subject to the following conditions:
18+
*
19+
* The above copyright notice and this permission notice shall be included in all
20+
* copies or substantial portions of the Software.
21+
*
22+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28+
* SOFTWARE.
29+
*/
30+
31+
#pragma once
32+
void CheckForAndDownloadUpdates();

src/include/version.h.in

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#ifndef VERSION_H
2+
#define VERSION_H
3+
4+
/** template header which exposes the Millennium version to windres */
5+
#define MILLENNIUM_VERSION "${MILLENNIUM_VERSION}"
6+
7+
#endif // VERSION_H

src/main.cc

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
#include <renderer.h>
5656
#include <stb_image.h>
5757
#include <util.h>
58+
#include "updater.h"
5859

5960
using namespace ImGui;
6061

@@ -107,10 +108,12 @@ void SetupImGuiScaling(GLFWwindow* window)
107108

108109
io.Fonts->Clear();
109110
io.Fonts->AddFontFromMemoryTTF((void*)GeistVariable, sizeof(GeistVariable), 16.0f * scaleFactor, &mem_cfg);
110-
if (std::filesystem::exists(fontPath)) io.Fonts->AddFontFromFileTTF(fontPath, 14.0f * scaleFactor, &cfg);
111-
111+
if (std::filesystem::exists(fontPath))
112+
io.Fonts->AddFontFromFileTTF(fontPath, 14.0f * scaleFactor, &cfg);
113+
112114
io.Fonts->AddFontFromMemoryTTF((void*)Geist_Bold, sizeof(Geist_Bold), 18.0f * scaleFactor, &mem_cfg);
113-
if (std::filesystem::exists(fontPath)) io.Fonts->AddFontFromFileTTF(fontPath, 14.0f * scaleFactor, &cfg);
115+
if (std::filesystem::exists(fontPath))
116+
io.Fonts->AddFontFromFileTTF(fontPath, 14.0f * scaleFactor, &cfg);
114117

115118
/** Explicitly set FreeType as the font loader to ensure color emoji support */
116119
io.Fonts->SetFontLoader(ImGuiFreeType::GetFontLoader());
@@ -216,12 +219,14 @@ void RenderBlur(HWND hwnd)
216219

217220
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
218221
{
219-
if (IsDebuggerPresent()) {
220-
AllocConsole();
221-
FILE* file;
222-
freopen_s(&file, "CONOUT$", "w", stdout);
223-
freopen_s(&file, "CONOUT$", "w", stderr);
224-
}
222+
if (IsDebuggerPresent()) {
223+
AllocConsole();
224+
FILE* file;
225+
freopen_s(&file, "CONOUT$", "w", stdout);
226+
freopen_s(&file, "CONOUT$", "w", stderr);
227+
}
228+
229+
CheckForAndDownloadUpdates();
225230

226231
MMRESULT result = timeBeginPeriod(1);
227232

src/semver.cc

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/**
2+
* ==================================================
3+
* _____ _ _ _ _
4+
* | |_| | |___ ___ ___|_|_ _ _____
5+
* | | | | | | | -_| | | | | | |
6+
* |_|_|_|_|_|_|___|_|_|_|_|_|___|_|_|_|
7+
*
8+
* ==================================================
9+
*
10+
* Copyright (c) 2025 Project Millennium
11+
*
12+
* Permission is hereby granted, free of charge, to any person obtaining a copy
13+
* of this software and associated documentation files (the "Software"), to deal
14+
* in the Software without restriction, including without limitation the rights
15+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16+
* copies of the Software, and to permit persons to whom the Software is
17+
* furnished to do so, subject to the following conditions:
18+
*
19+
* The above copyright notice and this permission notice shall be included in all
20+
* copies or substantial portions of the Software.
21+
*
22+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28+
* SOFTWARE.
29+
*/
30+
31+
#include "semver.h"
32+
#include <algorithm>
33+
#include <stdexcept>
34+
#include <cctype>
35+
#include <vector>
36+
37+
Semver::SemverVersion::SemverVersion(int maj, int min, int pat, const std::string& pre, const std::string& bld) : major(maj), minor(min), patch(pat), prerelease(pre), build(bld)
38+
{
39+
}
40+
41+
Semver::SemverVersion Semver::ParseSemver(const std::string& version)
42+
{
43+
if (version.empty()) {
44+
throw std::invalid_argument("Empty version string");
45+
}
46+
47+
std::string core = version;
48+
std::string build;
49+
std::string prerelease;
50+
51+
// Extract build metadata (everything after '+')
52+
size_t buildPos = core.find('+');
53+
if (buildPos != std::string::npos) {
54+
build = core.substr(buildPos + 1);
55+
core = core.substr(0, buildPos);
56+
}
57+
58+
// Extract prerelease (everything after '-')
59+
size_t prereleasePos = core.find('-');
60+
if (prereleasePos != std::string::npos) {
61+
prerelease = core.substr(prereleasePos + 1);
62+
core = core.substr(0, prereleasePos);
63+
}
64+
65+
// Parse major.minor.patch
66+
std::vector<int> parts;
67+
size_t start = 0;
68+
size_t end;
69+
70+
while ((end = core.find('.', start)) != std::string::npos) {
71+
std::string part = core.substr(start, end - start);
72+
if (part.empty() || !std::all_of(part.begin(), part.end(), ::isdigit)) {
73+
throw std::invalid_argument("Invalid numeric part in version");
74+
}
75+
parts.push_back(std::stoi(part));
76+
start = end + 1;
77+
}
78+
79+
// Parse the last part
80+
std::string lastPart = core.substr(start);
81+
if (lastPart.empty() || !std::all_of(lastPart.begin(), lastPart.end(), ::isdigit)) {
82+
throw std::invalid_argument("Invalid numeric part in version");
83+
}
84+
parts.push_back(std::stoi(lastPart));
85+
86+
if (parts.size() != 3) {
87+
throw std::invalid_argument("Semver requires exactly 3 numeric parts (major.minor.patch)");
88+
}
89+
90+
return Semver::SemverVersion(parts[0], parts[1], parts[2], prerelease, build);
91+
}
92+
93+
int Semver::ComparePrereleases(const std::string& pre1, const std::string& pre2)
94+
{
95+
// No prerelease has higher precedence than any prerelease
96+
if (pre1.empty() && pre2.empty())
97+
return 0;
98+
if (pre1.empty())
99+
return 1; // 1.0.0 > 1.0.0-alpha
100+
if (pre2.empty())
101+
return -1; // 1.0.0-alpha < 1.0.0
102+
103+
// Split prerelease identifiers by '.'
104+
auto split = [](const std::string& s) -> std::vector<std::string>
105+
{
106+
std::vector<std::string> parts;
107+
size_t start = 0;
108+
size_t end;
109+
110+
while ((end = s.find('.', start)) != std::string::npos) {
111+
parts.push_back(s.substr(start, end - start));
112+
start = end + 1;
113+
}
114+
parts.push_back(s.substr(start));
115+
return parts;
116+
};
117+
118+
std::vector<std::string> parts1 = split(pre1);
119+
std::vector<std::string> parts2 = split(pre2);
120+
121+
size_t minSize = std::min(parts1.size(), parts2.size());
122+
123+
for (size_t i = 0; i < minSize; ++i) {
124+
const std::string& p1 = parts1[i];
125+
const std::string& p2 = parts2[i];
126+
127+
bool p1IsNumeric = std::all_of(p1.begin(), p1.end(), ::isdigit) && !p1.empty();
128+
bool p2IsNumeric = std::all_of(p2.begin(), p2.end(), ::isdigit) && !p2.empty();
129+
130+
if (p1IsNumeric && p2IsNumeric) {
131+
// Both numeric: compare numerically
132+
int n1 = std::stoi(p1);
133+
int n2 = std::stoi(p2);
134+
if (n1 != n2)
135+
return (n1 < n2) ? -1 : 1;
136+
} else if (p1IsNumeric) {
137+
// Numeric identifiers have lower precedence than non-numeric
138+
return -1;
139+
} else if (p2IsNumeric) {
140+
// Non-numeric identifiers have higher precedence than numeric
141+
return 1;
142+
} else {
143+
// Both non-numeric: compare lexically
144+
if (p1 != p2)
145+
return (p1 < p2) ? -1 : 1;
146+
}
147+
}
148+
149+
// If all compared parts are equal, larger set of identifiers has higher precedence
150+
if (parts1.size() != parts2.size()) {
151+
return (parts1.size() < parts2.size()) ? -1 : 1;
152+
}
153+
154+
return 0;
155+
}
156+
157+
int Semver::Compare(const std::string& v1, const std::string& v2)
158+
{
159+
try {
160+
SemverVersion ver1 = ParseSemver(v1);
161+
SemverVersion ver2 = ParseSemver(v2);
162+
163+
// Compare major.minor.patch
164+
if (ver1.major != ver2.major) {
165+
return (ver1.major < ver2.major) ? -1 : 1;
166+
}
167+
if (ver1.minor != ver2.minor) {
168+
return (ver1.minor < ver2.minor) ? -1 : 1;
169+
}
170+
if (ver1.patch != ver2.patch) {
171+
return (ver1.patch < ver2.patch) ? -1 : 1;
172+
}
173+
174+
// If core versions are equal, compare prereleases
175+
// Build metadata is ignored in precedence comparison per semver spec
176+
return ComparePrereleases(ver1.prerelease, ver2.prerelease);
177+
178+
} catch (const std::exception&) {
179+
throw std::invalid_argument("Invalid semver format");
180+
}
181+
}

0 commit comments

Comments
 (0)