Skip to content

Commit

Permalink
Add sort query to directory index
Browse files Browse the repository at this point in the history
  • Loading branch information
Arlen22 committed Nov 21, 2021
1 parent 49f0734 commit 7d89d52
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 40 deletions.
13 changes: 2 additions & 11 deletions src/server/generate-directory-listing.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DirectoryIndexOptions, DirectoryIndexListing } from './server-types';
import { sortBySelector } from './utils-functions';

const { sortBySelector } = require("./utils");
const fixPutSaver = `javascript:((saver) => {
if (typeof saver !== 'number' || saver < 0) return;
$tw.saverHandler.savers[saver].__proto__.uri = function () { return decodeURI(encodeURI(document.location.toString().split('#')[0])); };
Expand All @@ -20,16 +20,7 @@ export function generateDirectoryListing(directory: DirectoryIndexListing, optio
let isError = directory.type === 403 || directory.type === 404;

function listEntries(entries: DirectoryIndexListing["entries"]) {
return entries
.slice()
.sort(
sortBySelector(
e =>
(options.mixFolders ? "" : e.type === "folder" ? "0-" : "1-") +
e.name.toLocaleLowerCase()
)
)
.map((entry, index) => {
return entries.map((entry, index) => {
const showSize = entry.type === "file";
return `
<li>
Expand Down
16 changes: 9 additions & 7 deletions src/server/generate-directory-rss.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { extname } from "path";
import { basename, extname } from "path";
import { pathToFileURL } from "url";
import { DirectoryIndexListing, DirectoryIndexOptions } from "./server-types";
import { expandNodeTree, toXML } from "./utils-xml";
import { mime } from "send";
import { sortBySelector } from "./utils-functions";


export function generateDirectoryRSS(def: DirectoryIndexListing, opts: DirectoryIndexOptions) {
let rss = {
title: def.path,
description: def.path,
title: basename(def.path),
description: opts.isLoggedIn ? "Logged in as " + opts.isLoggedIn : "Not logged in",
link: def.path,
item: def.entries.map(e => ({
title: e.name,
link:"/" + e.path,
description: e.type === "file" ? mime.lookup(e.name, "") : ("index/" + e.type),
link: "/" + e.path,
description: e.type === "file" ? `${e.mime} (${e.size})` : e.type,
pubDate: new Date(e.modified).toISOString(),
guid: "/" + e.path + "@" + e.modified
}))
}
};
return '<?xml version="1.0" encoding="UTF-8" ?>\n'
+ '<rss version="2.0">\n'
+ toXML(expandNodeTree(rss, "channel")[0])
+ '\n</rss>\n';
}
}
4 changes: 2 additions & 2 deletions src/server/server-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ declare const __non_webpack_require__: NodeRequire | undefined;
const nodeRequire =
typeof __non_webpack_require__ !== "undefined" ? __non_webpack_require__ : require;
import { oc } from "./optional-chaining";

import { homedir as gethomedir } from "os";
import { fromXML, toXML } from "./utils-xml";
import { safeJSON, tryParseJSON } from "./utils-functions";
import { readFileSync } from 'fs';
Expand All @@ -13,7 +13,7 @@ function format(str: string, ...args: any[]) {
args.unshift(str);
return args.join(",");
}
const homedir = require("os").homedir();
const homedir = gethomedir();
function pathResolveWithUser(settingsDir: string, str: string) {
if (str.startsWith("~")) return path.join(homedir, str.slice(1));
else return path.resolve(settingsDir, str);
Expand Down
61 changes: 57 additions & 4 deletions src/server/server-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as http from "http";
import * as url from "url";
import * as fs from "fs";
import * as path from "path";

import mime = require('mime');
import { format, promisify } from "util";
import * as JSON5 from "json5";
import * as send from "send";
Expand All @@ -28,7 +28,9 @@ import {
Config,
OptionsConfig,
} from "./server-config";
import { JsonError } from "./utils";

import { JsonError, keys } from "./utils-functions";
// import { JsonError } from "./utils";
import { checkServerConfigSchema } from "./interface-checker";
export {
Config,
Expand Down Expand Up @@ -116,8 +118,9 @@ export type ServerEvents = {
"serverClose": readonly [string]
}
export type ServerEventEmitter = EventEmitter<ServerEvents>;
export const TAGS = ["B", "KB", "MB", "GB", "TB", "PB"] as const;
export function getHumanSize(size: number) {
const TAGS = ["B", "KB", "MB", "GB", "TB", "PB"];

let power = 0;
while (size >= 1024) {
size /= 1024;
Expand Down Expand Up @@ -301,6 +304,7 @@ export type DirectoryIndexListing = {
icon: string;
type: "error" | "folder" | "datafolder" | "file" | "group";
size: string;
mime: string;
modified: number;
}[]
type: "group" | "folder" | 403 | 404
Expand All @@ -312,7 +316,19 @@ export type DirectoryIndexOptions = {
mixFolders: boolean;
isLoggedIn: string | false;
extIcons: { [ext_mime: string]: string };
sort: string[]
};
export type DirectoryIndexEntry = DirectoryIndexListing["entries"][number];
export const DirectoryIndexKeys = keys<{ [K in keyof DirectoryIndexEntry]: undefined }>({
name: undefined,
size: undefined,
icon: undefined,
mime: undefined,
modified: undefined,
path: undefined,
type: undefined
});

export async function sendDirectoryIndex(_r: DirectoryIndexData, options: DirectoryIndexOptions) {
let { keys, paths, dirpath, type } = _r;
// let pairs = keys.map((k, i) => [k, paths[i]] as [string, string | boolean]);
Expand All @@ -329,11 +345,14 @@ export async function sendDirectoryIndex(_r: DirectoryIndexData, options: Direct
? (nameparts && options.extIcons[nameparts] || "other.png")
: (stat.itemtype as string + ".png"),
size: stat && stat.stat ? getHumanSize(stat.stat.size) : "",
modified: stat?.stat?.mtimeMs || 0
mime: (stat?.itemtype === "file") ? mime.lookup(statpath, "") : "",
modified: stat?.stat?.mtimeMs || 0,

};
return list;
})
);
sortDirectoryEntries(entries, options)
if (options.format === "json") {
return JSON.stringify({ path: dirpath, entries, type, options }, null, 2);
} else if (options.format === "rss") {
Expand All @@ -344,7 +363,41 @@ export async function sendDirectoryIndex(_r: DirectoryIndexData, options: Direct
return generateDirectoryListing(def, options);
}
}
export const directorySorters: {
[K in keyof DirectoryIndexEntry]: (
// e: DirectoryIndexListing["entries"][number],
a: DirectoryIndexListing["entries"][number],
b: DirectoryIndexListing["entries"][number],
opts: DirectoryIndexOptions
) => number
} = {
name: (a, b, opts) =>
(opts.mixFolders ? 0 : directorySorters.type(a,b,opts))
|| a.name.localeCompare(b.name),
size: (a, b, opts) =>
TAGS.findIndex(e => a.size.endsWith(e)) - TAGS.findIndex(e => b.size.endsWith(e))
|| +a.size - +b.size,
modified: (a,b,opts) => b.modified - a.modified,
path: (a,b,opts) => a.path.localeCompare(b.path),
icon: (a,b,opts) => a.icon.localeCompare(b.icon),
mime: (a,b,opts) => a.mime.localeCompare(b.mime),
type: (a,b,opts) => +(a.type === "file") - +(b.type === "file"),
};


/** sort directory entries in place according to sort array*/
export function sortDirectoryEntries(entries: DirectoryIndexListing["entries"], opts: DirectoryIndexOptions) {
var { sort } = opts;
return entries.sort((a, b) => {
for (var i = 0, diff = 0, e = sort[i], reverse = false; i < sort.length && !diff; (i++, e = sort[i])) {
reverse = e.startsWith("-");
if (reverse) e = e.substr(1);
diff = directorySorters[e](a, b, opts);
}
if(reverse) return -diff;
else return diff;
});
}
/**
* If the path
*/
Expand Down
27 changes: 22 additions & 5 deletions src/server/tiddlyserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ import {
IStatPathResult,
getStatPathResult,
DirectoryIndexOptions,
DirectoryIndexKeys,
DirectoryIndexEntry,
} from "./server-types";
import { StateObject } from "./state-object";
import { RequestEvent } from "./request-event";
import { parse } from "url";
import { generateDirectoryListing } from './generate-directory-listing';
import { contains, first, keys } from "./utils-functions";
import { contains, first, firstArray, keys } from "./utils-functions";


// it isn't pretty but I can't find a way to improve it - 2020/04/10
Expand All @@ -45,9 +47,12 @@ function generateErrorPage(type: 403 | 404, path: string, state: {
upload: false,
mkdir: false,
mixFolders: state.settings.directoryIndex.mixFolders,
isLoggedIn: state.username ? state.username + " (group " + state.authAccountKey + ")" : (false as false),
isLoggedIn: state.username
? state.username + " (group " + state.authAccountKey + ")"
: (false as false),
format: "html",
extIcons: state.settings.directoryIndex.types
extIcons: state.settings.directoryIndex.types,
sort: []
});
}

Expand Down Expand Up @@ -254,13 +259,14 @@ export class TreeStateObject<STATPATH extends StatPathResult = StatPathResult> e

//generate the index using generateDirectoryListing.js
const options = this.getDirectoryIndexOptions(isFolder);

let contentType: { [K in DirectoryIndexOptions["format"]]: string } = {
html: "text/html",
json: "application/json",
rss: "application/rss"
};
const format = first(state.url.query.format);
if (contains(keys(contentType), format)) {
if (format && contains(keys(contentType), format)) {
options.format = format;
}
let e = await state.getTreePathFiles();
Expand Down Expand Up @@ -295,6 +301,16 @@ export class TreeStateObject<STATPATH extends StatPathResult = StatPathResult> e


private getDirectoryIndexOptions(isFolder: boolean): DirectoryIndexOptions {
let sort = firstArray(this.url.query.sort);
if (!sort.every((e) =>
contains(DirectoryIndexKeys, e.startsWith("-") ? e.substr(1) : e)
)) {
this.throwReason(400, "sort argument must be one of "
+ DirectoryIndexKeys.join(", ")
+ "; optionally prefixed with a minus sign (-)");
throw false;
}

return {
upload: isFolder && this.allow.upload,
mkdir: isFolder && this.allow.mkdir,
Expand All @@ -303,7 +319,8 @@ export class TreeStateObject<STATPATH extends StatPathResult = StatPathResult> e
? this.username + " (group " + this.authAccountKey + ")"
: (false as false),
format: this.treeOptions.index.defaultType as "html" | "json",
extIcons: this.settings.directoryIndex.types
extIcons: this.settings.directoryIndex.types,
sort
};
}

Expand Down
30 changes: 22 additions & 8 deletions src/server/utils-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// export function tryParseJSON(str: string, errObj?: ((e: JsonError) => T | void)): T;

import * as JSON5 from "json5";
import { DirectoryIndexEntry, DirectoryIndexListing, DirectoryIndexOptions } from "./server-types";

/**
* Calls the onerror handler if there is a JSON error. Returns whatever the error handler
Expand Down Expand Up @@ -60,28 +61,41 @@ export function padLeft(str: any, pad: number | string, padStr?: string): string
//pad: 000000 val: 6543210 => 654321
return pad.substr(0, Math.max(pad.length - item.length, 0)) + item;
}
export function sortBySelector<T extends { [k: string]: string }>(key: (e: T) => any) {
export function sortBySelector<T, R extends any[]>(key: (e: T, ...opts: R) => any, ...opts: R) {
return function (a: T, b: T) {
var va = key(a);
var vb = key(b);
var va = key(a, ...opts);
var vb = key(b, ...opts);

if (va > vb) return 1;
else if (va < vb) return -1;
else return 0;
};
}
export function sortByKey(key: string) {
return sortBySelector(e => e[key]);
export function sortBySelectorArray<T, R extends any[]>(key: (e: T, ...opts: R) => any, ...opts: R) {
return function (a: T, b: T) {
var va = key(a, ...opts);
var vb = key(b, ...opts);

if (va > vb) return 1;
else if (va < vb) return -1;
else return 0;
};
}
export function sortByKey<T extends {}>(key: any) {
return sortBySelector<T, []>(e => e[key]);
}

export function safeJSON(key, value) {
if (key === "__proto__" || key === "constructor") return undefined;
else return value;
}

export function first<T>(item: T | T[]): T{
return Array.isArray(item) ? item[0] : item;
export function first<T>(item: T | T[]): T | undefined {
return Array.isArray(item) ? (item.length ? item[0] : undefined) : item;
}
export function firstArray<T>(item: T | T[]): T[] {
return Array.isArray(item) ? item : item === undefined ? [] : [item];
}
export function contains<T extends string>(arr: T[], test: string): test is T {
return arr.indexOf(test as any) !== -1;
}
}
6 changes: 3 additions & 3 deletions src/server/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ export namespace colors {
export const BgWhite = "\x1b[47m";
}

export * from "./utils-functions";
export * from "./utils-config";
export * from "./utils-xml";
// export * from "./utils-functions";
// export * from "./utils-config";
// export * from "./utils-xml";

0 comments on commit 7d89d52

Please sign in to comment.