diff --git a/Modules/Settings/Tabs/About.qml b/Modules/Settings/Tabs/About.qml index 53aa2df..3c97970 100644 --- a/Modules/Settings/Tabs/About.qml +++ b/Modules/Settings/Tabs/About.qml @@ -10,39 +10,13 @@ import qs.Widgets ColumnLayout { id: root - property string latestVersion: "Unknown" + property string latestVersion: Github.latestVersion property string currentVersion: "v1.2.1" // Fallback version - property var contributors: [] - property string githubDataPath: Settings.configDir + "github_data.json" + property var contributors: Github.contributors - function loadFromFile() { - const now = Date.now(); - const data = githubData; - if (!data.timestamp || (now - data.timestamp > 3.6e+06)) { - console.log("[About] Cache expired or missing, fetching new data from GitHub..."); - fetchFromGitHub(); - return ; - } - console.log("[About] Loading cached GitHub data (age: " + Math.round((now - data.timestamp) / 60000) + " minutes)"); - if (data.version) - root.latestVersion = data.version; - - if (data.contributors) { - root.contributors = data.contributors; - } - - } - - function fetchFromGitHub() { - versionProcess.running = true; - contributorsProcess.running = true; - } - - function saveData() { - githubData.timestamp = Date.now(); - Qt.callLater(() => { - githubDataFile.writeAdapter(); - }); + Component.onCompleted: { + // Initialize the Github service + Github.init(); } spacing: 0 @@ -71,86 +45,6 @@ ColumnLayout { } - FileView { - id: githubDataFile - - path: root.githubDataPath - blockLoading: true - printErrors: true - watchChanges: true - onFileChanged: githubDataFile.reload() - onLoaded: loadFromFile() - onLoadFailed: function(error) { - console.log("GitHub data file doesn't exist yet, creating it..."); - githubData.version = "Unknown"; - githubData.contributors = []; - githubData.timestamp = 0; - githubDataFile.writeAdapter(); - fetchFromGitHub(); - } - Component.onCompleted: { - if (path) - reload(); - - } - - JsonAdapter { - id: githubData - - property string version: "Unknown" - property var contributors: [] - property double timestamp: 0 - } - - } - - Process { - id: versionProcess - - command: ["curl", "-s", "https://api.github.com/repos/Ly-sec/Noctalia/releases/latest"] - - stdout: StdioCollector { - onStreamFinished: { - try { - const data = JSON.parse(text); - if (data.tag_name) { - const version = data.tag_name; - githubData.version = version; - root.latestVersion = version; - console.log("[About] Latest version fetched from GitHub:", version); - } else { - console.log("No tag_name in GitHub response"); - } - saveData(); - } catch (e) { - console.error("Failed to parse version:", e); - } - } - } - - } - - Process { - id: contributorsProcess - - command: ["curl", "-s", "https://api.github.com/repos/Ly-sec/Noctalia/contributors?per_page=100"] - - stdout: StdioCollector { - onStreamFinished: { - try { - const data = JSON.parse(text); - githubData.contributors = data || []; - root.contributors = githubData.contributors; - saveData(); - } catch (e) { - console.error("Failed to parse contributors:", e); - root.contributors = []; - } - } - } - - } - ScrollView { id: scrollView diff --git a/Services/Github.qml b/Services/Github.qml new file mode 100644 index 0000000..4d6e8f2 --- /dev/null +++ b/Services/Github.qml @@ -0,0 +1,182 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Services +pragma Singleton + +// GitHub API logic and caching +Singleton { + id: root + + property string githubDataFile: Quickshell.env("NOCTALIA_GITHUB_FILE") || (Settings.cacheDir + "github.json") + property int githubUpdateFrequency: 60 * 60 // 1 hour expressed in seconds + property var data: adapter // Used to access via Github.data.xxx.yyy + property bool isFetchingData: false + + // Public properties for easy access + property string latestVersion: "Unknown" + property var contributors: [] + + FileView { + objectName: "githubDataFileView" + path: githubDataFile + watchChanges: true + onFileChanged: reload() + onAdapterUpdated: writeAdapter() + Component.onCompleted: function () { + reload() + } + onLoaded: function () { + loadFromCache() + } + onLoadFailed: function (error) { + if (error.toString().includes("No such file") || error === 2) { + // File doesn't exist, create it with default values + console.log("[Github] Creating new cache file..."); + writeAdapter() + // Fetch data after a short delay to ensure file is created + Qt.callLater(() => { + fetchFromGitHub() + }) + } + } + + JsonAdapter { + id: adapter + + property string version: "Unknown" + property var contributors: [] + property double timestamp: 0 + } + } + + // -------------------------------- + function init() { + // does nothing but ensure the singleton is created + // do not remove + } + + // -------------------------------- + function loadFromCache() { + const now = Date.now(); + if (!data.timestamp || (now - data.timestamp > githubUpdateFrequency * 1000)) { + console.log("[Github] Cache expired or missing, fetching new data from GitHub..."); + fetchFromGitHub(); + return; + } + console.log("[Github] Loading cached GitHub data (age: " + Math.round((now - data.timestamp) / 60000) + " minutes)"); + + if (data.version) { + root.latestVersion = data.version; + } + if (data.contributors) { + root.contributors = data.contributors; + } + } + + // -------------------------------- + function fetchFromGitHub() { + if (isFetchingData) { + console.warn("[Github] GitHub data is still fetching") + return + } + + isFetchingData = true + versionProcess.running = true; + contributorsProcess.running = true; + } + + // -------------------------------- + function saveData() { + data.timestamp = Date.now(); + Qt.callLater(() => { + // Access the FileView's writeAdapter method + var fileView = root.children.find(child => child.objectName === "githubDataFileView"); + if (fileView) { + fileView.writeAdapter(); + } + }); + } + + // -------------------------------- + function resetCache() { + data.version = "Unknown" + data.contributors = [] + data.timestamp = 0 + + // Try to fetch immediately + fetchFromGitHub() + } + + Process { + id: versionProcess + + command: ["curl", "-s", "https://api.github.com/repos/Ly-sec/Noctalia/releases/latest"] + + stdout: StdioCollector { + onStreamFinished: { + try { + const response = text; + if (response && response.trim()) { + const data = JSON.parse(response); + if (data.tag_name) { + const version = data.tag_name; + root.data.version = version; + root.latestVersion = version; + console.log("[Github] Latest version fetched from GitHub:", version); + } else { + console.log("[Github] No tag_name in GitHub response"); + } + } else { + console.log("[Github] Empty response from GitHub API"); + } + } catch (e) { + console.error("[Github] Failed to parse version:", e); + } + + // Check if both processes are done + checkAndSaveData(); + } + } + } + + Process { + id: contributorsProcess + + command: ["curl", "-s", "https://api.github.com/repos/Ly-sec/Noctalia/contributors?per_page=100"] + + stdout: StdioCollector { + onStreamFinished: { + try { + const response = text; + if (response && response.trim()) { + const data = JSON.parse(response); + root.data.contributors = data || []; + root.contributors = root.data.contributors; + console.log("[Github] Contributors fetched from GitHub:", root.contributors.length); + } else { + console.log("[Github] Empty response from GitHub API for contributors"); + root.data.contributors = []; + root.contributors = []; + } + } catch (e) { + console.error("[Github] Failed to parse contributors:", e); + root.data.contributors = []; + root.contributors = []; + } + + // Check if both processes are done + checkAndSaveData(); + } + } + } + + // -------------------------------- + function checkAndSaveData() { + // Only save when both processes are finished + if (!versionProcess.running && !contributorsProcess.running) { + root.isFetchingData = false; + root.saveData(); + } + } +} \ No newline at end of file