AboutTab: fix arch commit detection

GitHubService: add optional TOKEN auth
This commit is contained in:
Ly-sec
2025-11-30 11:06:04 +01:00
parent 6aca04cddb
commit 4887be96f5
2 changed files with 108 additions and 22 deletions

View File

@@ -25,13 +25,13 @@ ColumnLayout {
spacing: Style.marginL
Component.onCompleted: {
GitHubService.ensureDataFresh();
if (root.isGitVersion) {
// First try to extract from Arch package version format (e.g., 3.4.0.r112.g3f00bec8-1)
var archHash = root.extractCommitFromArchVersion();
if (archHash) {
root.gitCommitHash = archHash;
} else {
// Fall back to git command for git-cloned installations
fetchGitCommit();
}
}
@@ -40,15 +40,18 @@ ColumnLayout {
function hasArchGitVersion() {
// Check if version matches Arch package format: X.Y.Z.rN.gHASH-REV
// Pattern: version.r<number>.g<hash>-<rev>
return /\.r\d+\.g[0-9a-fA-F]+-/.test(root.currentVersion);
// Handle both with and without "v" prefix: v3.4.0.r112.g3f00bec8-1 or 3.4.0.r112.g3f00bec8-1
var version = root.currentVersion.replace(/^v/, ""); // Remove "v" prefix if present
return /\.r\d+\.g[0-9a-fA-F]+-/.test(version);
}
function extractCommitFromArchVersion() {
// Extract commit hash from Arch package version format
// Format: 3.4.0.r112.g3f00bec8-1 or v3.4.0.r112.g3f00bec8-1
// We want to extract the hash after ".g" and before "-"
var match = root.currentVersion.match(/\.g([0-9a-fA-F]+)-/i);
if (match && match[1]) {
var version = root.currentVersion.replace(/^v/, ""); // Remove "v" prefix if present
var match = version.match(/\.g([0-9a-fA-F]+)-/i);
if (match && match[1] && match[1].length >= 7) {
// Return first 7 characters (short hash)
return match[1].substring(0, 7);
}

View File

@@ -14,6 +14,10 @@ Singleton {
property bool isFetchingData: false
readonly property alias data: adapter // Used to access via GitHubService.data.xxx.yyy
// GitHub API authentication
readonly property string githubToken: Quickshell.env("GITHUB_TOKEN") || ""
readonly property bool hasAuth: githubToken !== ""
// Public properties for easy access
property string latestVersion: I18n.tr("system.unknown-version")
property var contributors: []
@@ -44,10 +48,11 @@ Singleton {
}
onLoadFailed: function (error) {
if (error.toString().includes("No such file") || error === 2) {
// Fetch data after a short delay to ensure file is created
Qt.callLater(() => {
fetchFromGitHub();
});
// File doesn't exist - don't fetch on startup, just initialize empty
Logger.d("GitHub", "Cache file doesn't exist, will fetch when needed");
data.version = I18n.tr("system.unknown-version");
data.contributors = [];
data.timestamp = 0;
}
}
@@ -72,7 +77,7 @@ Singleton {
var needsRefetch = false;
if (!data.timestamp || (now >= data.timestamp + githubUpdateFrequency)) {
needsRefetch = true;
Logger.d("GitHub", "Cache expired or missing, scheduling fetch");
Logger.d("GitHub", "Cache expired or missing, will fetch when needed");
} else {
Logger.d("GitHub", "Loading cached GitHub data (age:", Math.round((now - data.timestamp) / 60), "minutes)");
}
@@ -84,7 +89,16 @@ Singleton {
root.contributors = data.contributors;
}
if (needsRefetch) {
// Don't fetch on startup - only fetch when explicitly requested (e.g., when About tab opens)
// This prevents rate limiting on startup
}
// --------------------------------
function ensureDataFresh() {
// Check if we need to fetch and do so if needed
const now = Time.timestamp;
if (!data.timestamp || (now >= data.timestamp + githubUpdateFrequency)) {
Logger.d("GitHub", "Cache expired, fetching fresh data");
fetchFromGitHub();
}
}
@@ -136,6 +150,19 @@ Singleton {
fetchFromGitHub();
}
// --------------------------------
function handleRateLimit() {
Logger.w("GitHub", "Rate limit hit - extending cache expiration to avoid further requests");
// Extend cache expiration by 1 hour to avoid hitting rate limit again
if (data.timestamp) {
data.timestamp = Time.timestamp + githubUpdateFrequency;
}
if (!root.hasAuth) {
Logger.w("GitHub", "Consider setting GITHUB_TOKEN environment variable to increase rate limit from 60 to 5000 requests/hour");
}
}
// --------------------------------
// Avatar Caching Functions
// --------------------------------
@@ -419,14 +446,42 @@ Singleton {
Process {
id: versionProcess
command: ["curl", "-s", "https://api.github.com/repos/noctalia-dev/noctalia-shell/releases/latest"]
command: root.hasAuth ? ["sh", "-c", "curl -s -w '\\n%{http_code}' -H 'Authorization: token " + root.githubToken + "' 'https://api.github.com/repos/noctalia-dev/noctalia-shell/releases/latest'"] : ["sh", "-c", "curl -s -w '\\n%{http_code}' 'https://api.github.com/repos/noctalia-dev/noctalia-shell/releases/latest'"]
stdout: StdioCollector {
onStreamFinished: {
try {
const response = text;
if (response && response.trim()) {
const data = JSON.parse(response);
var response = text;
if (!response || !response.trim()) {
Logger.w("GitHub", "Empty response from GitHub API");
checkAndSaveData();
return;
}
// Extract HTTP status code (last line) and response body
var lines = response.trim().split('\n');
var statusCode = lines.length > 1 ? parseInt(lines[lines.length - 1]) : 200;
var responseBody = lines.length > 1 ? lines.slice(0, -1).join('\n') : response;
// Check for rate limit status codes
if (statusCode === 403 || statusCode === 429) {
Logger.w("GitHub", "Rate limit hit (HTTP", statusCode + ")");
root.handleRateLimit();
checkAndSaveData();
return;
}
if (responseBody && responseBody.trim()) {
const data = JSON.parse(responseBody);
// Check for rate limit in response message
if (data.message && (data.message.includes("API rate limit") || data.message.includes("rate limit"))) {
Logger.w("GitHub", "Rate limit exceeded:", data.message);
root.handleRateLimit();
checkAndSaveData();
return;
}
if (data.tag_name) {
const version = data.tag_name;
root.data.version = version;
@@ -437,8 +492,6 @@ Singleton {
} else {
Logger.w("GitHub", "No tag_name in GitHub response");
}
} else {
Logger.w("GitHub", "Empty response from GitHub API");
}
} catch (e) {
Logger.e("GitHub", "Failed to parse version:", e);
@@ -453,21 +506,51 @@ Singleton {
Process {
id: contributorsProcess
command: ["curl", "-s", "https://api.github.com/repos/noctalia-dev/noctalia-shell/contributors?per_page=100"]
command: root.hasAuth ? ["sh", "-c", "curl -s -w '\\n%{http_code}' -H 'Authorization: token " + root.githubToken + "' 'https://api.github.com/repos/noctalia-dev/noctalia-shell/contributors?per_page=100'"] : ["sh", "-c", "curl -s -w '\\n%{http_code}' 'https://api.github.com/repos/noctalia-dev/noctalia-shell/contributors?per_page=100'"]
stdout: StdioCollector {
onStreamFinished: {
try {
const response = text;
var response = text;
Logger.d("GitHub", "Raw contributors response length:", response ? response.length : 0);
if (response && response.trim()) {
const data = JSON.parse(response);
if (!response || !response.trim()) {
Logger.w("GitHub", "Empty response from GitHub API for contributors");
root.data.contributors = [];
root.contributors = [];
checkAndSaveData();
return;
}
// Extract HTTP status code (last line) and response body
var lines = response.trim().split('\n');
var statusCode = lines.length > 1 ? parseInt(lines[lines.length - 1]) : 200;
var responseBody = lines.length > 1 ? lines.slice(0, -1).join('\n') : response;
// Check for rate limit status codes
if (statusCode === 403 || statusCode === 429) {
Logger.w("GitHub", "Rate limit hit (HTTP", statusCode + ")");
root.handleRateLimit();
checkAndSaveData();
return;
}
if (responseBody && responseBody.trim()) {
const data = JSON.parse(responseBody);
// Check for rate limit in response message
if (data.message && (data.message.includes("API rate limit") || data.message.includes("rate limit"))) {
Logger.w("GitHub", "Rate limit exceeded:", data.message);
root.handleRateLimit();
checkAndSaveData();
return;
}
Logger.d("GitHub", "Parsed contributors data type:", typeof data, "length:", Array.isArray(data) ? data.length : "not array");
root.data.contributors = data || [];
root.contributors = root.data.contributors;
Logger.d("GitHub", "Contributors fetched from GitHub:", root.contributors.length);
} else {
Logger.w("GitHub", "Empty response from GitHub API for contributors");
root.data.contributors = [];
root.contributors = [];
}