Skip to content

Commit d374447

Browse files
Auto-updater for the Tiled extension.
1 parent 6130bc7 commit d374447

File tree

5 files changed

+145
-0
lines changed

5 files changed

+145
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
*.png
22
*.tiled-session
3+
bundle.json
34
node_modules

extensions/includes/session.mjs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const ACCESS_TOKEN_PROPERTY = 'wikiAccessToken';
22
const LAST_LANGUAGE_PROPERTY = 'wikiLastLanguage';
3+
const DISABLE_UPDATE_CHECKS = 'wikiDisableUpdateChecks';
34

45
/**
56
* Retrieves the path to the Tiled session file.
@@ -75,3 +76,18 @@ export function storeLastLanguage(language) {
7576
setSessionProperty(LAST_LANGUAGE_PROPERTY, language);
7677
}
7778

79+
/**
80+
* Checks if automatic update checks are disabled.
81+
* @returns {boolean} True if automatic update checks are disabled
82+
*/
83+
export function areAutoUpdateChecksDisabled() {
84+
return getSessionProperty(DISABLE_UPDATE_CHECKS) === true;
85+
}
86+
87+
/**
88+
* Sets whether automatic update checks are disabled.
89+
* @param {boolean} disabled True to disable automatic update checks
90+
*/
91+
export function storeAutoUpdateChecksDisabled(disabled) {
92+
setSessionProperty(DISABLE_UPDATE_CHECKS, disabled);
93+
}

extensions/includes/update.mjs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { httpGet } from "./api.mjs";
2+
import { areAutoUpdateChecksDisabled, storeAutoUpdateChecksDisabled } from "./session.mjs";
3+
4+
// Conflict with the DOM File type.
5+
const TiledFile = /** @type {any} */ (File);
6+
7+
function isGit() {
8+
const p = new Process();
9+
p.workingDirectory = tiled.project.extensionsPath;
10+
const status = p.exec('git', ['status'], false);
11+
return status === 0;
12+
}
13+
14+
function pullGit() {
15+
const p = new Process();
16+
p.workingDirectory = tiled.project.extensionsPath;
17+
const status = p.exec('git', ['pull'], false);
18+
if (status !== 0) {
19+
tiled.alert(`Failed to pull updates from Git: ${p.readStdErr()}`);
20+
} else {
21+
tiled.log(p.readStdOut());
22+
}
23+
}
24+
25+
/**
26+
* Recursively retrieves all files in a directory and its subdirectories that
27+
* are relevant to the update process.
28+
* @param {string} dir Directory to inspect for files
29+
* @param {string[]} prefix Base directory for relative paths
30+
* @returns {Record<string, string>}
31+
*/
32+
function getAllUpdaterFiles(dir, prefix = []) {
33+
const /** @type {Record<string, string>} */ files = {};
34+
for (const fileName of TiledFile.directoryEntries(dir, 2, 0)) {
35+
if (!fileName.endsWith('.mjs')) {
36+
continue;
37+
}
38+
const file = new TextFile(`${dir}/${fileName}`, TextFile.ReadOnly);
39+
files[[...prefix, fileName].join('/')] = file.readAll();
40+
file.close();
41+
}
42+
for (const directory of TiledFile.directoryEntries(dir, 24577, 0)) {
43+
Object.assign(files, getAllUpdaterFiles(directory, [...prefix, directory]));
44+
}
45+
return files;
46+
}
47+
48+
function update() {
49+
if (areAutoUpdateChecksDisabled()) {
50+
return;
51+
}
52+
const scriptPath = FileInfo.joinPaths(tiled.project.extensionsPath, 'includes');
53+
const localFiles = getAllUpdaterFiles(scriptPath);
54+
httpGet('https://maps.undertale.wiki/bundle.json').then(bundle => {
55+
const onlyLocalFiles = Object.keys(localFiles).filter(path => !(path in bundle));
56+
const diffFiles = Object.entries(bundle).filter(([path, content]) => {
57+
const filePath = FileInfo.joinPaths(scriptPath, path);
58+
if (!TiledFile.exists(filePath)) {
59+
return true;
60+
}
61+
const file = new TextFile(filePath, TextFile.ReadOnly);
62+
const localContent = file.readAll();
63+
file.close();
64+
return localContent !== content;
65+
});
66+
if (onlyLocalFiles.length === 0 && diffFiles.length === 0) {
67+
tiled.log('Extension is up to date.');
68+
return;
69+
}
70+
if (isGit()) {
71+
if (tiled.confirm('Updates are available for the wiki extension, but you are using Git. Do you want to run git pull?')) {
72+
pullGit();
73+
}
74+
return;
75+
}
76+
if (!tiled.confirm(`Updates are available for the wiki extension! Do you want to update now?`)) {
77+
return;
78+
}
79+
for (const path of onlyLocalFiles) {
80+
TiledFile.remove(FileInfo.joinPaths(scriptPath, path));
81+
}
82+
for (const [path, content] of diffFiles) {
83+
const filePath = FileInfo.joinPaths(scriptPath, path);
84+
const file = new TextFile(filePath, TextFile.WriteOnly);
85+
file.write(content);
86+
file.commit();
87+
}
88+
tiled.alert('Extension updated successfully! Please restart Tiled to apply the updates.');
89+
}).catch(error => {
90+
tiled.alert(`Failed to check for updates: ${error.message}. Check the console for more details.`);
91+
tiled.log(`Error details: ${error.stack}`);
92+
});
93+
}
94+
95+
const enablePopup = tiled.registerAction('WikiDisableUpdateChecks', () => {
96+
storeAutoUpdateChecksDisabled(enablePopup.checked);
97+
});
98+
enablePopup.checkable = true;
99+
enablePopup.checked = areAutoUpdateChecksDisabled();
100+
enablePopup.iconVisibleInMenu = false;
101+
enablePopup.text = 'Wiki: Disable update checks';
102+
103+
tiled.extendMenu('Help', [
104+
{
105+
action: 'WikiDisableUpdateChecks'
106+
}
107+
]);
108+
109+
update();

pages/bundle.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {readdir, readFile, writeFile} from 'fs/promises';
2+
import {join} from 'path';
3+
import {fileURLToPath} from 'url';
4+
5+
const bundle = {};
6+
const scriptDir = join(fileURLToPath(import.meta.url), '..');
7+
const extensionsDir = join(scriptDir, '..', 'extensions', 'includes');
8+
for (const dir of await readdir(extensionsDir, {
9+
recursive: true,
10+
withFileTypes: true,
11+
})) {
12+
if (dir.isFile() && dir.name.endsWith('.mjs')) {
13+
const filePath = join(dir.parentPath, dir.name);
14+
const content = await readFile(filePath, 'utf-8');
15+
const relativePath = filePath.slice(extensionsDir.length + 1);
16+
bundle[relativePath] = content;
17+
}
18+
}
19+
await writeFile(join(scriptDir, 'bundle.json'), JSON.stringify(bundle));
File renamed without changes.

0 commit comments

Comments
 (0)