This commit is contained in:
candux 2025-01-09 15:43:16 +08:00 committed by GitHub
commit 6319bdfdaa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 80 additions and 0 deletions

View file

@ -37,6 +37,7 @@ Example:
"pbfAlias": "pbf", "pbfAlias": "pbf",
"serveAllFonts": false, "serveAllFonts": false,
"serveAllStyles": false, "serveAllStyles": false,
"watchMbtiles": false,
"serveStaticMaps": true, "serveStaticMaps": true,
"allowRemoteMarkerIcons": true, "allowRemoteMarkerIcons": true,
"allowInlineMarkerImages": true, "allowInlineMarkerImages": true,
@ -171,6 +172,13 @@ If this option is enabled, all the styles from the ``paths.styles`` will be serv
The process will also watch for changes in this directory and remove/add more styles dynamically. The process will also watch for changes in this directory and remove/add more styles dynamically.
It is recommended to also use the ``serveAllFonts`` option when using this option. It is recommended to also use the ``serveAllFonts`` option when using this option.
``watchMbtiles``
------------------------
If this option is enabled, all the opened Mbtiles are watched for changes and automatically reloaded.
The new data is then severed immediately. There is a small downtime for rendered endpoints.
Mbtiles have to be replaced atomically. i.e. moving the file from the same filesystem. Modifying an existing file will crash the server.
``serveStaticMaps`` ``serveStaticMaps``
------------------------ ------------------------

View file

@ -111,6 +111,7 @@ const startWithInputFile = async (inputFile) => {
const config = { const config = {
options: { options: {
watchMbtiles: true,
paths: { paths: {
root: styleDir, root: styleDir,
fonts: 'fonts', fonts: 'fonts',

View file

@ -365,6 +365,9 @@ export const serve_data = {
return app; return app;
}, },
remove: (repo, id) => {
delete repo[id];
},
add: async (options, repo, params, id, publicUrl) => { add: async (options, repo, params, id, publicUrl) => {
let inputFile; let inputFile;
let inputType; let inputType;

View file

@ -883,6 +883,7 @@ export const serve_rendered = {
renderersStatic: [], renderersStatic: [],
sources: {}, sources: {},
sourceTypes: {}, sourceTypes: {},
styleFile: '',
}; };
let styleJSON; let styleJSON;
@ -1080,6 +1081,7 @@ export const serve_rendered = {
}; };
const styleFile = params.style; const styleFile = params.style;
map.styleFile = styleFile;
const styleJSONPath = path.resolve(options.paths.styles, styleFile); const styleJSONPath = path.resolve(options.paths.styles, styleFile);
try { try {
styleJSON = JSON.parse(await fsp.readFile(styleJSONPath)); styleJSON = JSON.parse(await fsp.readFile(styleJSONPath));
@ -1300,6 +1302,9 @@ export const serve_rendered = {
item.map.renderersStatic.forEach((pool) => { item.map.renderersStatic.forEach((pool) => {
pool.close(); pool.close();
}); });
Object.entries(item.map.sources).forEach(([key, source]) => {
delete item.map.sources[key];
});
} }
delete repo[id]; delete repo[id];
}, },

View file

@ -292,6 +292,69 @@ function start(opts) {
startupPromises.push( startupPromises.push(
serve_data.add(options, serving.data, item, id, opts.publicUrl), serve_data.add(options, serving.data, item, id, opts.publicUrl),
); );
if (options.watchMbtiles) {
console.log(`Watching Mbtile "${item.mbtiles}" for changes...`);
const watcher = chokidar.watch(
path.join(options.paths.mbtiles, item.mbtiles),
{
ignoreInitial: true,
// wait 10 seconds after the last change before updating. Otherwise, cases where a file is constantly replaced
// will create race conditions and can crash the server
awaitWriteFinish: { stabilityThreshold: 10000 },
},
);
watcher.on('all', (eventType, filename) => {
if (filename) {
if (eventType === 'add' || eventType === 'change') {
console.log(`MBTiles "${filename}" changed, updating...`);
serve_data.remove(serving.data, id);
let newItem = {
mbtiles: filename,
};
serve_data.add(options, serving.data, newItem, id, opts.publicUrl);
if (!isLight) {
Object.entries(serving.rendered).forEach(
([serving_key, serving_value]) => {
// check if source is used in serving
Object.values(serving_value.map.sources).forEach(
(source_value) => {
const newFileInode = fs.statSync(filename).ino;
// we check if the filename is the same and the inode has changed
// the inode check is necessary because it could be that multiple mbtiles
// were changed and we already changed to the new file. This can lead to race-conditions
if (
source_value.filename === filename &&
source_value._stat.ino !== newFileInode
) {
// remove from serving and add back
serve_style.remove(serving.styles, serving_key);
serve_rendered.remove(serving.rendered, serving_key);
const item = {
style: serving_value.map.styleFile,
};
addStyle(serving_key, item, false, false);
}
},
);
},
);
}
}
// we intentionally don't handle the 'unlink' event here. If the file is deleted, the file descriptor is still valid,
// everything will continue to work
}
});
watcher.on('error', (error) => {
console.error(`Failed to watch file: ${error}`);
});
}
} }
if (options.serveAllStyles) { if (options.serveAllStyles) {