Skip to content

Commit a10e418

Browse files
KAMI911claude
andcommitted
refactor(calendarium@kami911): resolve system location asynchronously
Address the best-practices scanner warnings on PR #1895: the system timezone / coordinate lookup used synchronous GLib.file_get_contents() and Gio query_info(), which block the main loop. Replace them with Gio.File.load_contents_async() and query_info_async(). The lookup now runs once at startup via _resolveSystemLocationAsync(); until it completes _systemLocation() returns the Budapest default, and a redraw is triggered when the real result arrives (only when the primary location is auto-detected). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PNasJnrStmVjJgJ8MULzcG
1 parent 7434274 commit a10e418

1 file changed

Lines changed: 93 additions & 62 deletions

File tree

  • calendarium@kami911/files/calendarium@kami911

calendarium@kami911/files/calendarium@kami911/desklet.js

Lines changed: 93 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ CalendariumDesklet.prototype = {
130130
global.logError("Calendarium: _setupUI crash [" + e.message + "] stack:\n" + (e.stack || "(no stack)"));
131131
throw e;
132132
}
133+
// Detect the computer's location from its system timezone (async).
134+
this._resolveSystemLocationAsync();
133135
// Load locale data files asynchronously; refresh again when done.
134136
this._loadNamedayData(() => this._refresh());
135137
},
@@ -339,32 +341,99 @@ CalendariumDesklet.prototype = {
339341
},
340342

341343
/**
342-
* The computer's own IANA timezone name (e.g. "Europe/Budapest"), or null
343-
* if it cannot be determined.
344+
* Read a text file asynchronously; cb(text) with the contents as a string,
345+
* or cb(null) on any failure. Never blocks the main loop.
344346
*/
345-
_systemTimezoneName: function() {
346-
try {
347-
let id = GLib.TimeZone.new_local().get_identifier();
348-
if (id && id.indexOf("/") !== -1) return id;
349-
} catch (e) {}
350-
try {
351-
let [ok, contents] = GLib.file_get_contents("/etc/timezone");
352-
if (ok) {
353-
let s = ((contents instanceof Uint8Array)
347+
_readTextFileAsync: function(path, cb) {
348+
let f = Gio.File.new_for_path(path);
349+
f.load_contents_async(null, function(obj, res) {
350+
try {
351+
let [ok, contents] = f.load_contents_finish(res);
352+
if (!ok) { cb(null); return; }
353+
cb((contents instanceof Uint8Array)
354354
? new TextDecoder().decode(contents)
355-
: imports.byteArray.toString(contents)).trim();
356-
if (s.indexOf("/") !== -1) return s;
355+
: imports.byteArray.toString(contents));
356+
} catch (e) {
357+
cb(null);
357358
}
358-
} catch (e) {}
359+
});
360+
},
361+
362+
/**
363+
* Resolve the computer's approximate location from its system IANA
364+
* timezone, fully asynchronously, and cache it in this._sysLoc as
365+
* { lat, lon, tz }. Triggers a redraw when done if the primary location
366+
* is auto-detected. Falls back to DEFAULT_LAT/DEFAULT_LON (Budapest).
367+
*/
368+
_resolveSystemLocationAsync: function() {
369+
if (this._sysLoc) return;
370+
let self = this;
371+
372+
let finish = function(tzName) {
373+
let loc = { lat: DEFAULT_LAT, lon: DEFAULT_LON, tz: tzName || null };
374+
let apply = function(coords) {
375+
if (coords) { loc.lat = coords.lat; loc.lon = coords.lon; }
376+
self._sysLoc = loc;
377+
if (!self._isDestroyed && !self.use_manual_location) {
378+
self._onSettingChanged();
379+
}
380+
};
381+
if (tzName) self._tzTabCoordsAsync(tzName, apply);
382+
else apply(null);
383+
};
384+
385+
// 1. GLib.TimeZone (no blocking file read).
359386
try {
360-
let target = Gio.File.new_for_path("/etc/localtime")
361-
.query_info("standard::symlink-target",
362-
Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null)
363-
.get_symlink_target();
364-
let m = target && target.match(/zoneinfo\/(.+)$/);
365-
if (m) return m[1];
387+
let id = GLib.TimeZone.new_local().get_identifier();
388+
if (id && id.indexOf("/") !== -1) { finish(id); return; }
366389
} catch (e) {}
367-
return null;
390+
391+
// 2. /etc/timezone (Debian/Ubuntu/Mint).
392+
this._readTextFileAsync("/etc/timezone", function(text) {
393+
let s = text && text.trim();
394+
if (s && s.indexOf("/") !== -1) { finish(s); return; }
395+
396+
// 3. /etc/localtime symlink target.
397+
let f = Gio.File.new_for_path("/etc/localtime");
398+
f.query_info_async("standard::symlink-target",
399+
Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
400+
GLib.PRIORITY_DEFAULT, null, function(obj, res) {
401+
let tzName = null;
402+
try {
403+
let target = f.query_info_finish(res).get_symlink_target();
404+
let m = target && target.match(/zoneinfo\/(.+)$/);
405+
if (m) tzName = m[1];
406+
} catch (e) {}
407+
finish(tzName);
408+
});
409+
});
410+
},
411+
412+
/**
413+
* Look up approximate coordinates for an IANA timezone name in the tzdata
414+
* zone1970.tab / zone.tab table, asynchronously. cb({lat, lon}) or cb(null).
415+
*/
416+
_tzTabCoordsAsync: function(tzName, cb) {
417+
let self = this;
418+
let paths = ["/usr/share/zoneinfo/zone1970.tab",
419+
"/usr/share/zoneinfo/zone.tab"];
420+
let tryPath = function(idx) {
421+
if (idx >= paths.length) { cb(null); return; }
422+
self._readTextFileAsync(paths[idx], function(text) {
423+
if (!text) { tryPath(idx + 1); return; }
424+
let lines = text.split("\n");
425+
for (let i = 0; i < lines.length; i++) {
426+
if (!lines[i] || lines[i][0] === "#") continue;
427+
let cols = lines[i].split("\t");
428+
if (cols.length < 3) continue;
429+
if (cols[2].trim() !== tzName) continue;
430+
cb(self._parseIso6709(cols[1].trim()));
431+
return;
432+
}
433+
tryPath(idx + 1);
434+
});
435+
};
436+
tryPath(0);
368437
},
369438

370439
/**
@@ -384,51 +453,13 @@ CalendariumDesklet.prototype = {
384453
lon: Math.round(lon * 10000) / 10000 };
385454
},
386455

387-
/**
388-
* Approximate coordinates of a system IANA timezone, looked up in the
389-
* tzdata zone1970.tab / zone.tab table. Returns { lat, lon } or null.
390-
*/
391-
_tzTabCoords: function(tzName) {
392-
let paths = ["/usr/share/zoneinfo/zone1970.tab",
393-
"/usr/share/zoneinfo/zone.tab"];
394-
for (let p = 0; p < paths.length; p++) {
395-
let text;
396-
try {
397-
let [ok, contents] = GLib.file_get_contents(paths[p]);
398-
if (!ok) continue;
399-
text = (contents instanceof Uint8Array)
400-
? new TextDecoder().decode(contents)
401-
: imports.byteArray.toString(contents);
402-
} catch (e) { continue; }
403-
let lines = text.split("\n");
404-
for (let i = 0; i < lines.length; i++) {
405-
if (!lines[i] || lines[i][0] === "#") continue;
406-
let cols = lines[i].split("\t");
407-
if (cols.length < 3) continue;
408-
if (cols[2].trim() !== tzName) continue;
409-
return this._parseIso6709(cols[1].trim());
410-
}
411-
}
412-
return null;
413-
},
414-
415456
/**
416457
* Approximate location of the computer, derived from its system IANA
417-
* timezone. Result is { lat, lon, tz }; cached for the desklet's lifetime.
418-
* Falls back to DEFAULT_LAT/DEFAULT_LON (Budapest) when the timezone
419-
* cannot be resolved to coordinates.
458+
* timezone by _resolveSystemLocationAsync(). Returns the cached
459+
* { lat, lon, tz }, or the Budapest default until resolution completes.
420460
*/
421461
_systemLocation: function() {
422-
if (this._sysLoc) return this._sysLoc;
423-
let loc = { lat: DEFAULT_LAT, lon: DEFAULT_LON, tz: null };
424-
let tzName = this._systemTimezoneName();
425-
loc.tz = tzName;
426-
if (tzName) {
427-
let coords = this._tzTabCoords(tzName);
428-
if (coords) { loc.lat = coords.lat; loc.lon = coords.lon; }
429-
}
430-
this._sysLoc = loc;
431-
return loc;
462+
return this._sysLoc || { lat: DEFAULT_LAT, lon: DEFAULT_LON, tz: null };
432463
},
433464

434465
/**

0 commit comments

Comments
 (0)