Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fbc6f4776 |
@@ -801,11 +801,12 @@ become `IMG_0001 (12345).JPG` and `IMG_0001 (12346).JPG`. A collection whose
|
||||
directory would have the name of another collection's JSON, again ignoring case,
|
||||
gets its ID added as well: beside an album `x`, whose JSON is `x.json`, an album
|
||||
named `x.json` gets the directory `x.json (12)/`. A name too long for a file
|
||||
name, which most file systems limit to 255 bytes, is cut short between two
|
||||
characters, just far enough that it fits with its ID added, and gets its ID; a
|
||||
collection's directory name also leaves room for the `.json` its JSON adds. IDs
|
||||
never change, so a name stays the same from run to run until such a clash
|
||||
appears or goes away.
|
||||
name, which most file systems limit to 255 bytes, gets its ID added too. A name
|
||||
with its ID added is cut short between two characters, just far enough that it
|
||||
fits: first the part before the ID, and once nothing is left of that, a
|
||||
symlink's extension. A collection's directory name also leaves room for the
|
||||
`.json` its JSON adds. IDs never change, so a name stays the same from run to
|
||||
run until such a clash appears or goes away.
|
||||
|
||||
A collection whose directory or JSON cannot be written, such as one whose
|
||||
directory name is taken by a file you put in `collections/`, does not stop the
|
||||
|
||||
@@ -17,9 +17,11 @@ pre-1.0
|
||||
|
||||
# Next Step
|
||||
|
||||
Three bugs are open: https://git.eeqj.de/sneak/quak/issues/196,
|
||||
https://git.eeqj.de/sneak/quak/issues/198 and
|
||||
https://git.eeqj.de/sneak/quak/issues/199. The check of quak against a live Ente
|
||||
Five bugs are open: https://git.eeqj.de/sneak/quak/issues/196,
|
||||
https://git.eeqj.de/sneak/quak/issues/198,
|
||||
https://git.eeqj.de/sneak/quak/issues/199,
|
||||
https://git.eeqj.de/sneak/quak/issues/205 and
|
||||
https://git.eeqj.de/sneak/quak/issues/206. The check of quak against a live Ente
|
||||
account, https://git.eeqj.de/sneak/quak/issues/187, needs an account only sneak
|
||||
has, so it waits on him.
|
||||
|
||||
|
||||
+11
-12
@@ -274,13 +274,14 @@ const rebuildSymlink = (linkPath: string, target: string): void => {
|
||||
// name is used as is unless another entry would get the same name, ignoring
|
||||
// case (two names that differ only in case are one entry on a case-insensitive
|
||||
// file system), or it is too long for a file name; then it gets ` (<id>)`,
|
||||
// before the extension when `beforeExtension` is set, with the name before it
|
||||
// cut short as far as it must be to fit. With `withJSON`, each entry also has
|
||||
// the JSON file `<name>.json` beside it, as an album does: a name then leaves
|
||||
// room for `.json`, and one that is another entry's JSON name gets its ID
|
||||
// added too. A name with an ID added can match another entry's own name
|
||||
// (`IMG (6).JPG`), so this repeats until no name is shared. IDs are stable, so
|
||||
// the names are too.
|
||||
// right before the extension when `beforeExtension` is set. To fit, the part
|
||||
// before the ID is cut short as far as it must be, and once nothing is left of
|
||||
// it, so is the extension. With `withJSON`, each entry also has the JSON file
|
||||
// `<name>.json` beside it, as an album does: a name then leaves room for
|
||||
// `.json`, and one that is another entry's JSON name gets its ID added too.
|
||||
// Two names with different IDs added never match, but one can match another
|
||||
// entry's own name (`IMG (6).JPG`), so this repeats until no name is shared.
|
||||
// IDs are stable, so the names are too.
|
||||
const uniqueNames = (
|
||||
entries: { id: number; name: string }[],
|
||||
opts: { beforeExtension?: boolean; withJSON?: boolean },
|
||||
@@ -288,11 +289,9 @@ const uniqueNames = (
|
||||
const maxBytes = MAX_NAME_BYTES - (opts.withJSON ? ".json".length : 0);
|
||||
const withID = (id: number, name: string): string => {
|
||||
const tag = ` (${id})`;
|
||||
let ext = opts.beforeExtension ? extname(name) : "";
|
||||
// An extension too long to leave room for the ID is cut short with
|
||||
// the rest of the name.
|
||||
if (Buffer.byteLength(tag + ext) > maxBytes) ext = "";
|
||||
const stem = name.slice(0, name.length - ext.length);
|
||||
const fullExt = opts.beforeExtension ? extname(name) : "";
|
||||
const stem = name.slice(0, name.length - fullExt.length);
|
||||
const ext = shortenToBytes(fullExt, maxBytes - Buffer.byteLength(tag));
|
||||
const room = maxBytes - Buffer.byteLength(tag + ext);
|
||||
return shortenToBytes(stem, room) + tag + ext;
|
||||
};
|
||||
|
||||
+56
-25
@@ -1703,31 +1703,6 @@ describe("backup album folders", () => {
|
||||
expect(readLedger(outDir).files["5"]).toMatchObject({ attempts: 2 });
|
||||
});
|
||||
|
||||
// The extension of this title is 303 bytes, so the ID cannot go before
|
||||
// it, and the whole title is cut short with the ID at its end. The
|
||||
// extension holds a "-", so the original is saved with `.bin`, a name
|
||||
// that fits.
|
||||
it("cuts a title short, extension and all, when its extension leaves no room for the ID", async () => {
|
||||
const outDir = join(root, "backup");
|
||||
const lib = libraryOf([
|
||||
{
|
||||
collection: collection(10, "Trip"),
|
||||
files: [file(1, 10, `a.${"L".repeat(300)}-x`)],
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await runBackup(lib, { downloadDirectory: outDir });
|
||||
|
||||
const link = `a.${"L".repeat(249)} (1)`;
|
||||
expect(Buffer.byteLength(link)).toBe(255);
|
||||
expect(result.failed).toBe(0);
|
||||
expect(tree(outDir)).toEqual([
|
||||
"Trip/",
|
||||
`Trip/${link} -> ${linkTo("1.bin")}`,
|
||||
"Trip.json",
|
||||
]);
|
||||
});
|
||||
|
||||
// The user has put a file in collections/ with the name of the album
|
||||
// Trip's folder, so the folder cannot be made.
|
||||
it("goes on to the next album when an album's folder cannot be made, and lists it as failed", async () => {
|
||||
@@ -1762,6 +1737,62 @@ describe("backup album folders", () => {
|
||||
expect(readLedger(outDir).files["5"]).toMatchObject({ attempts: 1 });
|
||||
});
|
||||
|
||||
// The extension of this title is 303 bytes, so it leaves no room for the
|
||||
// ID: nothing is left of the name before the ID, and the extension is cut
|
||||
// short after it. The extension holds a "-", so the original is saved
|
||||
// with `.bin`, a name that fits.
|
||||
it("cuts the extension of a title short when it leaves no room for the ID", async () => {
|
||||
const outDir = join(root, "backup");
|
||||
const lib = libraryOf([
|
||||
{
|
||||
collection: collection(10, "Trip"),
|
||||
files: [file(1, 10, `a.${"L".repeat(300)}-x`)],
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await runBackup(lib, { downloadDirectory: outDir });
|
||||
|
||||
const link = ` (1).${"L".repeat(250)}`;
|
||||
expect(Buffer.byteLength(link)).toBe(255);
|
||||
expect(result.failed).toBe(0);
|
||||
expect(tree(outDir)).toEqual([
|
||||
"Trip/",
|
||||
`Trip/${link} -> ${linkTo("1.bin")}`,
|
||||
"Trip.json",
|
||||
]);
|
||||
});
|
||||
|
||||
// Files 1 and 3 share a title, so each gets its ID before the
|
||||
// extension. File 2's title is too long, and its extension leaves no
|
||||
// room for the ID. Were the ID put at the end of file 2's title cut
|
||||
// short, its link would have file 1's name, and the link made second
|
||||
// would replace the first.
|
||||
it("keeps names unique when a title's extension leaves no room for the ID", async () => {
|
||||
const outDir = join(root, "backup");
|
||||
const shared = `x.${"Q".repeat(245)} (2)`;
|
||||
const lib = libraryOf([
|
||||
{
|
||||
collection: collection(10, "Trip"),
|
||||
files: [
|
||||
file(1, 10, shared),
|
||||
file(2, 10, `x (1).${"Q".repeat(250)}-`),
|
||||
file(3, 10, shared),
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await runBackup(lib, { downloadDirectory: outDir });
|
||||
|
||||
expect(result.failed).toBe(0);
|
||||
expect(tree(outDir)).toEqual([
|
||||
"Trip/",
|
||||
`Trip/ (2).${"Q".repeat(250)} -> ${linkTo("2.bin")}`,
|
||||
`Trip/x (1).${"Q".repeat(245)} (2) -> ${linkTo("1.bin")}`,
|
||||
`Trip/x (3).${"Q".repeat(245)} (2) -> ${linkTo("3.bin")}`,
|
||||
"Trip.json",
|
||||
]);
|
||||
});
|
||||
|
||||
it("changes nothing on a second run over an unchanged account", async () => {
|
||||
const outDir = join(root, "backup");
|
||||
const lib = libraryOf([
|
||||
|
||||
Reference in New Issue
Block a user