mirror of
https://github.com/bastienwirtz/homer.git
synced 2025-10-26 07:46:19 +01:00
feat(auto-refresh): centralized auto refresh System
This commit is contained in:
@@ -130,6 +130,11 @@ export default {
|
||||
DarkMode,
|
||||
DynamicTheme,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
config: () => this.config,
|
||||
};
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
loaded: false,
|
||||
|
||||
@@ -48,10 +48,10 @@ export default {
|
||||
};
|
||||
},
|
||||
created: function () {
|
||||
const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
|
||||
if (checkInterval > 0) {
|
||||
setInterval(() => this.fetchData(), checkInterval);
|
||||
}
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchData;
|
||||
|
||||
// Initial data fetch
|
||||
this.fetchData();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -40,10 +40,10 @@ export default {
|
||||
};
|
||||
},
|
||||
created: function () {
|
||||
const updateInterval = parseInt(this.item.updateInterval, 10) || 0;
|
||||
if (updateInterval > 0)
|
||||
setInterval(() => this.fetchConfig(), updateInterval);
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchConfig;
|
||||
|
||||
// Initial data fetch
|
||||
this.fetchConfig();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -29,10 +29,10 @@ export default {
|
||||
error: null,
|
||||
}),
|
||||
created() {
|
||||
const updateInterval = parseInt(this.item.updateInterval, 10) || 0;
|
||||
if (updateInterval > 0) {
|
||||
setInterval(() => this.fetchStat(), updateInterval);
|
||||
}
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchStat;
|
||||
|
||||
// Initial data fetch
|
||||
this.fetchStat();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -62,10 +62,10 @@ export default {
|
||||
},
|
||||
},
|
||||
created: function () {
|
||||
const updateInterval = parseInt(this.item.updateInterval, 10) || 0;
|
||||
if (updateInterval > 0) {
|
||||
setInterval(() => this.fetchConfig(), updateInterval);
|
||||
}
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchConfig;
|
||||
|
||||
// Initial data fetch
|
||||
this.fetchConfig();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -43,12 +43,11 @@ export default {
|
||||
serverError: false,
|
||||
};
|
||||
},
|
||||
created: function () {
|
||||
const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
|
||||
if (checkInterval > 0) {
|
||||
setInterval(() => this.fetchConfig(), checkInterval);
|
||||
}
|
||||
created() {
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchConfig;
|
||||
|
||||
// Initial data fetch
|
||||
this.fetchConfig();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -44,10 +44,10 @@ export default {
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const updateInterval = parseInt(this.item.updateInterval, 10) || 0;
|
||||
if (updateInterval > 0) {
|
||||
setInterval(() => this.fetchStatus(), updateInterval);
|
||||
}
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchStatus;
|
||||
|
||||
// Initial data fetch
|
||||
this.fetchStatus();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -39,8 +39,6 @@ export default {
|
||||
retryCount: 0,
|
||||
maxRetries: 3,
|
||||
retryDelay: 5000,
|
||||
localCheckInterval: 1000, // Default value or a fallback
|
||||
pollInterval: null,
|
||||
}),
|
||||
computed: {
|
||||
percentage: function () {
|
||||
@@ -57,18 +55,16 @@ export default {
|
||||
},
|
||||
created() {
|
||||
if (parseInt(this.item.apiVersion, 10) === 6) {
|
||||
// Set the interval to the checkInterval or default to 5 minutes
|
||||
this.localCheckInterval = parseInt(this.item.checkInterval, 10) || 300000;
|
||||
this.loadCachedSession();
|
||||
this.startStatusPolling();
|
||||
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchStatus;
|
||||
} else {
|
||||
this.fetchStatus_v5();
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (parseInt(this.item.apiVersion, 10) === 6) {
|
||||
this.stopStatusPolling();
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchStatus_v5();
|
||||
}
|
||||
// Initial data fetch
|
||||
this.autoUpdateMethod();
|
||||
},
|
||||
methods: {
|
||||
handleError: function (error, status) {
|
||||
@@ -76,21 +72,6 @@ export default {
|
||||
this.subtitle = error;
|
||||
this.status = status;
|
||||
},
|
||||
startStatusPolling: function () {
|
||||
this.fetchStatus();
|
||||
if (this.localCheckInterval < 1000) {
|
||||
this.localCheckInterval = 1000;
|
||||
}
|
||||
this.pollInterval = setInterval(
|
||||
this.fetchStatus,
|
||||
this.localCheckInterval,
|
||||
);
|
||||
},
|
||||
stopStatusPolling: function () {
|
||||
if (this.pollInterval) {
|
||||
clearInterval(this.pollInterval);
|
||||
}
|
||||
},
|
||||
loadCachedSession: function () {
|
||||
try {
|
||||
const cachedSession = localStorage.getItem(
|
||||
|
||||
@@ -41,11 +41,10 @@ export default {
|
||||
},
|
||||
},
|
||||
created() {
|
||||
const updateInterval = parseInt(this.item.updateInterval, 10) || 0;
|
||||
if (updateInterval > 0) {
|
||||
setInterval(this.fetchStatus, updateInterval);
|
||||
}
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchStatus;
|
||||
|
||||
// Initial data fetch
|
||||
this.fetchStatus();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -52,10 +52,10 @@ export default {
|
||||
};
|
||||
},
|
||||
created: function () {
|
||||
const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
|
||||
if (checkInterval > 0) {
|
||||
setInterval(() => this.fetchData(), checkInterval);
|
||||
}
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchData;
|
||||
|
||||
// Initial data fetch
|
||||
this.fetchData();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -36,12 +36,11 @@ export default {
|
||||
serverError: false,
|
||||
};
|
||||
},
|
||||
created: function () {
|
||||
const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
|
||||
if (checkInterval > 0) {
|
||||
setInterval(() => this.fetchConfig(), checkInterval);
|
||||
}
|
||||
created() {
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchConfig;
|
||||
|
||||
// Initial data fetch
|
||||
this.fetchConfig();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -51,12 +51,11 @@ export default {
|
||||
return this.item.legacyApi ? LEGACY_API : V3_API;
|
||||
},
|
||||
},
|
||||
created: function () {
|
||||
const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
|
||||
if (checkInterval > 0) {
|
||||
setInterval(() => this.fetchConfig(), checkInterval);
|
||||
}
|
||||
created() {
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchConfig;
|
||||
|
||||
// Initial data fetch
|
||||
this.fetchConfig();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -59,24 +59,18 @@ export default {
|
||||
},
|
||||
},
|
||||
created() {
|
||||
// Set intervals if configured so the rates and/or torrent count
|
||||
// will be updated.
|
||||
const rateInterval = parseInt(this.item.rateInterval, 10) || 0;
|
||||
const torrentInterval = parseInt(this.item.torrentInterval, 10) || 0;
|
||||
|
||||
if (rateInterval > 0) {
|
||||
setInterval(() => this.fetchRates(), rateInterval);
|
||||
}
|
||||
|
||||
if (torrentInterval > 0) {
|
||||
setInterval(() => this.fetchCount(), torrentInterval);
|
||||
}
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchAllData;
|
||||
|
||||
// Fetch the initial values.
|
||||
this.fetchRates();
|
||||
this.fetchCount();
|
||||
this.fetchAllData();
|
||||
},
|
||||
methods: {
|
||||
// Combined method for scheduler - fetches both rates and count
|
||||
fetchAllData: async function () {
|
||||
this.fetchRates();
|
||||
this.fetchCount();
|
||||
},
|
||||
// Perform two calls to the XML-RPC service and fetch download
|
||||
// and upload rates. Values are saved to the `ul` and `dl`
|
||||
// properties.
|
||||
|
||||
@@ -80,11 +80,10 @@ export default {
|
||||
},
|
||||
},
|
||||
created() {
|
||||
const downloadInterval = parseInt(this.item.downloadInterval, 10) || 0;
|
||||
if (downloadInterval > 0) {
|
||||
setInterval(() => this.fetchStatus(), downloadInterval);
|
||||
}
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchStatus;
|
||||
|
||||
// Initial data fetch
|
||||
this.fetchStatus();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -40,10 +40,10 @@ export default {
|
||||
};
|
||||
},
|
||||
created: function () {
|
||||
const updateInterval = parseInt(this.item.updateInterval, 10) || 0;
|
||||
if (updateInterval > 0) {
|
||||
setInterval(() => this.fetchSummary(), updateInterval);
|
||||
}
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchSummary;
|
||||
|
||||
// Initial data fetch
|
||||
this.fetchSummary();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -52,12 +52,11 @@ export default {
|
||||
return this.item.legacyApi ? LEGACY_API : V3_API;
|
||||
},
|
||||
},
|
||||
created: function () {
|
||||
const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
|
||||
if (checkInterval > 0) {
|
||||
setInterval(() => this.fetchConfig(), checkInterval);
|
||||
}
|
||||
created() {
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchConfig;
|
||||
|
||||
// Initial data fetch
|
||||
this.fetchConfig();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -41,11 +41,10 @@ export default {
|
||||
},
|
||||
},
|
||||
created() {
|
||||
const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
|
||||
if (checkInterval > 0) {
|
||||
setInterval(() => this.fetchStatus(), checkInterval);
|
||||
}
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchStatus;
|
||||
|
||||
// Initial data fetch
|
||||
this.fetchStatus();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -54,11 +54,10 @@ export default {
|
||||
},
|
||||
},
|
||||
created() {
|
||||
const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
|
||||
if (checkInterval > 0) {
|
||||
setInterval(() => this.fetchStatus(), checkInterval);
|
||||
}
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchStatus;
|
||||
|
||||
// Initial data fetch
|
||||
this.fetchStatus();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -61,19 +61,18 @@ export default {
|
||||
},
|
||||
},
|
||||
created() {
|
||||
const rateInterval = parseInt(this.item.rateInterval, 10) || 0;
|
||||
const torrentInterval = parseInt(this.item.torrentInterval, 10) || 0;
|
||||
if (rateInterval > 0) {
|
||||
setInterval(() => this.getRate(), rateInterval);
|
||||
}
|
||||
if (torrentInterval > 0) {
|
||||
setInterval(() => this.fetchCount(), torrentInterval);
|
||||
}
|
||||
// Set up auto-update method for the scheduler
|
||||
this.autoUpdateMethod = this.fetchAllData;
|
||||
|
||||
this.getRate();
|
||||
this.fetchCount();
|
||||
// Fetch initial values
|
||||
this.fetchAllData();
|
||||
},
|
||||
methods: {
|
||||
// Combined method for scheduler - fetches both rates and count
|
||||
fetchAllData: async function () {
|
||||
this.getRate();
|
||||
this.fetchCount();
|
||||
},
|
||||
fetchCount: async function () {
|
||||
try {
|
||||
const body = await this.fetch("/api/v2/torrents/info");
|
||||
|
||||
@@ -1,15 +1,35 @@
|
||||
import updateScheduler from "@/utils/updateScheduler.js";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
proxy: Object,
|
||||
},
|
||||
inject: {
|
||||
// Inject global config from parent components
|
||||
config: {
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
globalConfig() {
|
||||
return this.config() || {};
|
||||
},
|
||||
},
|
||||
created: function () {
|
||||
// custom service often consume info from an API using the item link (url) as a base url,
|
||||
// Custom service often consume info from an API using the item link (url) as a base url,
|
||||
// but sometimes the base url is different. An optional alternative URL can be provided with the "endpoint" key.
|
||||
this.endpoint = this.item.endpoint || this.item.url;
|
||||
|
||||
if (this.endpoint && this.endpoint.endsWith("/")) {
|
||||
this.endpoint = this.endpoint.slice(0, -1);
|
||||
}
|
||||
|
||||
// Initialize auto-update if configured
|
||||
this.initAutoUpdate();
|
||||
},
|
||||
beforeUnmount() {
|
||||
// Clean up auto-update registration
|
||||
updateScheduler.unregister(this);
|
||||
},
|
||||
methods: {
|
||||
fetch: function (path, init, json = true) {
|
||||
@@ -62,5 +82,79 @@ export default {
|
||||
return json ? response.json() : response.text();
|
||||
});
|
||||
},
|
||||
initAutoUpdate: function () {
|
||||
// Check if component has defined an auto-update method and interval
|
||||
const interval = this.getUpdateInterval();
|
||||
if (
|
||||
interval > 0 &&
|
||||
this.autoUpdateMethod &&
|
||||
typeof this.autoUpdateMethod === "function"
|
||||
) {
|
||||
updateScheduler.register(this, interval, this.autoUpdateMethod);
|
||||
}
|
||||
},
|
||||
getUpdateInterval: function () {
|
||||
// Check if auto-update is explicitly disabled for this service
|
||||
if (this.item.autoUpdateInterval === false) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Use service-specific interval if defined
|
||||
if (this.item.autoUpdateInterval) {
|
||||
return parseInt(this.item.autoUpdateInterval, 10) || 0;
|
||||
}
|
||||
|
||||
// Check for deprecated keys and warn users
|
||||
const deprecatedKeys = [
|
||||
"updateInterval",
|
||||
"checkInterval",
|
||||
"localCheckInterval",
|
||||
"downloadInterval",
|
||||
"rateInterval",
|
||||
"torrentInterval",
|
||||
];
|
||||
|
||||
for (const key of deprecatedKeys) {
|
||||
if (this.item[key]) {
|
||||
console.warn(
|
||||
`[DEPRECATED] Service "${this.item.name || "unknown"}" uses deprecated config key "${key}". ` +
|
||||
`Please use "autoUpdateInterval" instead. Support for "${key}" will be removed in a future version.`,
|
||||
);
|
||||
return parseInt(this.item[key], 10) || 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Use global auto-update configuration
|
||||
return this.getGlobalAutoUpdateInterval();
|
||||
},
|
||||
|
||||
getGlobalAutoUpdateInterval: function () {
|
||||
const globalAutoUpdate = this.globalConfig.autoUpdate;
|
||||
|
||||
// If auto-update is not configured globally, disable
|
||||
if (!globalAutoUpdate) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If global auto-update is explicitly disabled
|
||||
if (globalAutoUpdate.enabled === false) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If autoUpdate is just a number (simplified config)
|
||||
if (typeof globalAutoUpdate === "number") {
|
||||
return globalAutoUpdate;
|
||||
}
|
||||
|
||||
// If autoUpdate is an object, use defaultInterval
|
||||
if (
|
||||
typeof globalAutoUpdate === "object" &&
|
||||
globalAutoUpdate.defaultInterval
|
||||
) {
|
||||
return parseInt(globalAutoUpdate.defaultInterval, 10) || 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
131
src/utils/updateScheduler.js
Normal file
131
src/utils/updateScheduler.js
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* This module provides a single-timer solution for managing automatic data updates
|
||||
* across all service components in Homer. Instead of each service component creating
|
||||
* its own setInterval timer, all components register with this centralized scheduler.
|
||||
*
|
||||
*/
|
||||
class UpdateScheduler {
|
||||
constructor() {
|
||||
this.registeredComponents = new Map();
|
||||
this.globalTimer = null;
|
||||
this.tickCount = 0;
|
||||
this.isRunning = false;
|
||||
}
|
||||
|
||||
register(component, intervalMs, updateMethod) {
|
||||
if (!component || !updateMethod || intervalMs <= 0) {
|
||||
console.warn("UpdateScheduler: Invalid registration parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
const intervalSeconds = Math.floor(intervalMs / 1000);
|
||||
const componentId = this.generateComponentId(component);
|
||||
|
||||
this.registeredComponents.set(componentId, {
|
||||
component,
|
||||
interval: intervalSeconds,
|
||||
method: updateMethod,
|
||||
lastUpdate: 0,
|
||||
});
|
||||
|
||||
this.startGlobalTimer();
|
||||
console.log(
|
||||
`UpdateScheduler: Registered component with ${intervalSeconds}s interval`,
|
||||
);
|
||||
}
|
||||
|
||||
unregister(component) {
|
||||
const componentId = this.generateComponentId(component);
|
||||
const removed = this.registeredComponents.delete(componentId);
|
||||
|
||||
if (removed) {
|
||||
console.log("UpdateScheduler: Unregistered component");
|
||||
}
|
||||
|
||||
if (this.registeredComponents.size === 0) {
|
||||
this.stopGlobalTimer();
|
||||
}
|
||||
}
|
||||
|
||||
generateComponentId(component) {
|
||||
// Use component's unique identifier or Vue instance uid
|
||||
return component._uid || component.$.uid || Symbol("component");
|
||||
}
|
||||
|
||||
startGlobalTimer() {
|
||||
if (!this.globalTimer && !this.isRunning) {
|
||||
this.isRunning = true;
|
||||
this.tickCount = 0;
|
||||
|
||||
this.globalTimer = setInterval(() => {
|
||||
this.tickCount++;
|
||||
this.processUpdates();
|
||||
}, 1000);
|
||||
|
||||
console.log("UpdateScheduler: Global timer started");
|
||||
}
|
||||
}
|
||||
|
||||
stopGlobalTimer() {
|
||||
if (this.globalTimer) {
|
||||
clearInterval(this.globalTimer);
|
||||
this.globalTimer = null;
|
||||
this.isRunning = false;
|
||||
this.tickCount = 0;
|
||||
console.log("UpdateScheduler: Global timer stopped");
|
||||
}
|
||||
}
|
||||
|
||||
processUpdates() {
|
||||
for (const [, config] of this.registeredComponents) {
|
||||
try {
|
||||
if (this.tickCount - config.lastUpdate >= config.interval) {
|
||||
config.method.call(config.component);
|
||||
config.lastUpdate = this.tickCount;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("UpdateScheduler: Error during component update:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pause() {
|
||||
if (this.globalTimer) {
|
||||
clearInterval(this.globalTimer);
|
||||
this.globalTimer = null;
|
||||
this.isRunning = false;
|
||||
console.log("UpdateScheduler: Paused");
|
||||
}
|
||||
}
|
||||
|
||||
resume() {
|
||||
if (!this.globalTimer && this.registeredComponents.size > 0) {
|
||||
this.startGlobalTimer();
|
||||
console.log("UpdateScheduler: Resumed");
|
||||
}
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
return {
|
||||
isRunning: this.isRunning,
|
||||
registeredCount: this.registeredComponents.size,
|
||||
tickCount: this.tickCount,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export global singleton instance
|
||||
const updateScheduler = new UpdateScheduler();
|
||||
|
||||
// Pause updates when tab is hidden (power saving)
|
||||
if (typeof document !== "undefined") {
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (document.hidden) {
|
||||
updateScheduler.pause();
|
||||
} else {
|
||||
updateScheduler.resume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default updateScheduler;
|
||||
Reference in New Issue
Block a user