Adding https/file support for mounts
This commit is contained in:
parent
fcf712ccc7
commit
cce5be43e1
@ -2,8 +2,10 @@ Space mounting in `MOUNTS`
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- path: file:/Users/zef/git/blog
|
- path: file:/Users/zef/git/blog
|
||||||
prefix: 📖
|
prefix: blog/
|
||||||
perm: ro,rw #default rw
|
perm: ro,rw #default rw
|
||||||
|
- path: http://someIP:3000
|
||||||
|
prefix: prod/
|
||||||
```
|
```
|
||||||
|
|
||||||
Features
|
Features
|
||||||
@ -13,6 +15,8 @@ To do:
|
|||||||
* [ ] Handle queries
|
* [ ] Handle queries
|
||||||
* `page` and `link` query needs to dynamically add/remove a `and name =~ /^🚪 PREFIX/` clause)
|
* `page` and `link` query needs to dynamically add/remove a `and name =~ /^🚪 PREFIX/` clause)
|
||||||
* `task` same but with `page` check
|
* `task` same but with `page` check
|
||||||
|
* [x] Add `file:` support
|
||||||
|
* [x] Add `http:`/`https:` support
|
||||||
|
|
||||||
* Due to namespacing, the mounted space needs to be namespaced somehow
|
* Due to namespacing, the mounted space needs to be namespaced somehow
|
||||||
* Could be an emoji, could be a page prefix (now using `name`)
|
* Could be an emoji, could be a page prefix (now using `name`)
|
||||||
|
@ -26,6 +26,9 @@ I know, right?
|
|||||||
[[🔨 Development]]
|
[[🔨 Development]]
|
||||||
[[🗺 Roadmap]]
|
[[🗺 Roadmap]]
|
||||||
|
|
||||||
|
## Proposals
|
||||||
|
[[Mounts]]
|
||||||
|
|
||||||
## Installing and running Silver Bullet
|
## Installing and running Silver Bullet
|
||||||
To run a release version, you need to have a recent version of npm (8+) and node.js (16+) installed as well as some basic build infrastructure (make, cpp). Silver Bullet has only been tested on MacOS and Linux thus far.
|
To run a release version, you need to have a recent version of npm (8+) and node.js (16+) installed as well as some basic build infrastructure (make, cpp). Silver Bullet has only been tested on MacOS and Linux thus far.
|
||||||
|
|
||||||
|
@ -20,6 +20,8 @@ const globalMountPrefix = "🚪 ";
|
|||||||
type MountPoint = {
|
type MountPoint = {
|
||||||
prefix: string;
|
prefix: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
password?: string;
|
||||||
|
protocol: "file" | "http";
|
||||||
perm: "rw" | "ro";
|
perm: "rw" | "ro";
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,11 +46,12 @@ async function updateMountPoints() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mountsYaml = codeTextNode.children![0].text;
|
let mountsYaml = codeTextNode.children![0].text;
|
||||||
let mountList = YAML.parse(mountsYaml!);
|
let mountList: Partial<MountPoint>[] = YAML.parse(mountsYaml!);
|
||||||
if (!Array.isArray(mountList)) {
|
if (!Array.isArray(mountList)) {
|
||||||
console.error("Invalid MOUNTS file, should have array of mount points");
|
console.error("Invalid MOUNTS file, should have array of mount points");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Let's validate this and fill in some of the blanks
|
||||||
for (let mountPoint of mountList) {
|
for (let mountPoint of mountList) {
|
||||||
if (!mountPoint.prefix) {
|
if (!mountPoint.prefix) {
|
||||||
console.error("Invalid mount point, no prefix specified", mountPoint);
|
console.error("Invalid mount point, no prefix specified", mountPoint);
|
||||||
@ -61,9 +64,16 @@ async function updateMountPoints() {
|
|||||||
if (!mountPoint.perm) {
|
if (!mountPoint.perm) {
|
||||||
mountPoint.perm = "rw";
|
mountPoint.perm = "rw";
|
||||||
}
|
}
|
||||||
|
if (mountPoint.path.startsWith("file:")) {
|
||||||
|
mountPoint.protocol = "file";
|
||||||
|
mountPoint.path = mountPoint.path.substring("file:".length);
|
||||||
|
} else {
|
||||||
|
mountPoint.protocol = "http";
|
||||||
|
mountPoint.path += "/fs"; // Add the /fs suffix to the path
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mountPointCache = mountList;
|
mountPointCache = mountList as MountPoint[];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function translateLinksWithPrefix(
|
async function translateLinksWithPrefix(
|
||||||
@ -120,15 +130,39 @@ export async function readPageMounted(
|
|||||||
): Promise<{ text: string; meta: PageMeta }> {
|
): Promise<{ text: string; meta: PageMeta }> {
|
||||||
await updateMountPoints();
|
await updateMountPoints();
|
||||||
let { resolvedPath, mountPoint } = lookupMountPoint(name);
|
let { resolvedPath, mountPoint } = lookupMountPoint(name);
|
||||||
let { text, meta } = await readFile(`${resolvedPath}.md`);
|
if (mountPoint.protocol === "file") {
|
||||||
return {
|
let { text, meta } = await readFile(`${resolvedPath}.md`);
|
||||||
text: await translateLinksWithPrefix(text, mountPoint.prefix),
|
return {
|
||||||
meta: {
|
text: await translateLinksWithPrefix(text, mountPoint.prefix),
|
||||||
name: name,
|
meta: {
|
||||||
lastModified: meta.lastModified,
|
name: name,
|
||||||
perm: mountPoint.perm,
|
lastModified: meta.lastModified,
|
||||||
},
|
perm: mountPoint.perm,
|
||||||
};
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
let res = await fetch(resolvedPath, {
|
||||||
|
method: "GET",
|
||||||
|
headers: authHeaders(mountPoint),
|
||||||
|
});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to read page: ${res.status}: ${await res.text()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (res.headers.get("X-Status") === "404") {
|
||||||
|
throw new Error(`Page not found`);
|
||||||
|
}
|
||||||
|
let text = await res.text();
|
||||||
|
return {
|
||||||
|
text: await translateLinksWithPrefix(text, mountPoint.prefix),
|
||||||
|
meta: {
|
||||||
|
name: name,
|
||||||
|
lastModified: +(res.headers.get("Last-Modified") || "0"),
|
||||||
|
perm: (res.headers.get("X-Permission") as "rw" | "ro") || "rw",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function writePageMounted(
|
export async function writePageMounted(
|
||||||
@ -138,52 +172,124 @@ export async function writePageMounted(
|
|||||||
await updateMountPoints();
|
await updateMountPoints();
|
||||||
let { resolvedPath, mountPoint } = lookupMountPoint(name);
|
let { resolvedPath, mountPoint } = lookupMountPoint(name);
|
||||||
text = await translateLinksWithoutPrefix(text, mountPoint.prefix);
|
text = await translateLinksWithoutPrefix(text, mountPoint.prefix);
|
||||||
let meta = await writeFile(`${resolvedPath}.md`, text);
|
if (mountPoint.protocol === "file") {
|
||||||
return {
|
let meta = await writeFile(`${resolvedPath}.md`, text);
|
||||||
name: name,
|
return {
|
||||||
lastModified: meta.lastModified,
|
name: name,
|
||||||
perm: mountPoint.perm,
|
lastModified: meta.lastModified,
|
||||||
};
|
perm: mountPoint.perm,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
let res = await fetch(resolvedPath, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: authHeaders(mountPoint),
|
||||||
|
body: text,
|
||||||
|
});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to write page: ${res.status}: ${await res.text()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: name,
|
||||||
|
lastModified: +(res.headers.get("Last-Modified") || "0"),
|
||||||
|
perm: (res.headers.get("X-Permission") as "rw" | "ro") || "rw",
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deletePageMounted(name: string): Promise<void> {
|
export async function deletePageMounted(name: string): Promise<void> {
|
||||||
await updateMountPoints();
|
await updateMountPoints();
|
||||||
let { resolvedPath, mountPoint } = lookupMountPoint(name);
|
let { resolvedPath, mountPoint } = lookupMountPoint(name);
|
||||||
if (mountPoint.perm === "rw") {
|
if (mountPoint.protocol === "file") {
|
||||||
await deleteFile(`${resolvedPath}.md`);
|
if (mountPoint.perm === "rw") {
|
||||||
|
await deleteFile(`${resolvedPath}.md`);
|
||||||
|
} else {
|
||||||
|
throw new Error("Deleting read-only page");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Deleting read-only page");
|
throw new Error("Not yet implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function authHeaders(mountPoint: MountPoint): HeadersInit {
|
||||||
|
return {
|
||||||
|
Authorization: mountPoint.password ? `Bearer ${mountPoint.password}` : "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function getPageMetaMounted(name: string): Promise<PageMeta> {
|
export async function getPageMetaMounted(name: string): Promise<PageMeta> {
|
||||||
await updateMountPoints();
|
await updateMountPoints();
|
||||||
let { resolvedPath, mountPoint } = lookupMountPoint(name);
|
let { resolvedPath, mountPoint } = lookupMountPoint(name);
|
||||||
let meta = await getFileMeta(`${resolvedPath}.md`);
|
if (mountPoint.protocol === "file") {
|
||||||
return {
|
let meta = await getFileMeta(`${resolvedPath}.md`);
|
||||||
name,
|
return {
|
||||||
lastModified: meta.lastModified,
|
name,
|
||||||
perm: mountPoint.perm,
|
lastModified: meta.lastModified,
|
||||||
};
|
perm: mountPoint.perm,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// http/https
|
||||||
|
let res = await fetch(resolvedPath, {
|
||||||
|
method: "OPTIONS",
|
||||||
|
headers: authHeaders(mountPoint),
|
||||||
|
});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to list pages: ${res.status}: ${await res.text()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (res.headers.get("X-Status") === "404") {
|
||||||
|
throw new Error(`Page not found`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: name,
|
||||||
|
lastModified: +(res.headers.get("Last-Modified") || "0"),
|
||||||
|
perm: (res.headers.get("X-Permission") as "rw" | "ro") || "rw",
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listPagesMounted(): Promise<PageMeta[]> {
|
export async function listPagesMounted(): Promise<PageMeta[]> {
|
||||||
await updateMountPoints();
|
await updateMountPoints();
|
||||||
let allPages: PageMeta[] = [];
|
let allPages: PageMeta[] = [];
|
||||||
for (let mp of mountPointCache) {
|
for (let mp of mountPointCache) {
|
||||||
let files = await listFiles(mp.path, true);
|
if (mp.protocol === "file") {
|
||||||
for (let file of files) {
|
let files = await listFiles(mp.path, true);
|
||||||
if (!file.name.endsWith(".md")) {
|
for (let file of files) {
|
||||||
continue;
|
if (!file.name.endsWith(".md")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
allPages.push({
|
||||||
|
name: `${globalMountPrefix}${mp.prefix}${file.name.substring(
|
||||||
|
0,
|
||||||
|
file.name.length - 3
|
||||||
|
)}`,
|
||||||
|
lastModified: file.lastModified,
|
||||||
|
perm: mp.perm,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
allPages.push({
|
} else {
|
||||||
name: `${globalMountPrefix}${mp.prefix}${file.name.substring(
|
let res = await fetch(mp.path, {
|
||||||
0,
|
headers: authHeaders(mp),
|
||||||
file.name.length - 3
|
|
||||||
)}`,
|
|
||||||
lastModified: file.lastModified,
|
|
||||||
perm: mp.perm,
|
|
||||||
});
|
});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to list pages: ${res.status}: ${await res.text()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let remotePages: PageMeta[] = await res.json();
|
||||||
|
// console.log("Remote pages", remotePages);
|
||||||
|
for (let pageMeta of remotePages) {
|
||||||
|
if (pageMeta.name.startsWith("_")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
allPages.push({
|
||||||
|
name: `${globalMountPrefix}${mp.prefix}${pageMeta.name}`,
|
||||||
|
lastModified: pageMeta.lastModified,
|
||||||
|
perm: mp.perm,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return allPages;
|
return allPages;
|
||||||
|
Loading…
Reference in New Issue
Block a user