aboutsummaryrefslogtreecommitdiffstats
path: root/tiddlers/plugin-files-api-route.tid
blob: 62369265e55f5226bfc1395edf2e63d32cb1fc9c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
title: $:/plugins/local/files-api/get-files.js
type: application/javascript
module-type: route

/*\
title: $:/plugins/local/files-api/get-files.js
type: application/javascript
module-type: route

GET /api/files
\*/
"use strict";

var fs = require("fs"),
	path = require("path");

exports.methods = ["GET"];
exports.path = /^\/api\/files\/?$/;

exports.info = {
	priority: 100
};

function encodeFilePath(filename) {
	return filename
		.split(path.sep)
		.map(encodeURIComponent)
		.join("/");
}

async function scanDirectory(rootDirectory,directory,prefix) {
	var entries,
		files = [];

	try {
		entries = await fs.promises.readdir(directory,{
			withFileTypes: true
		});
	} catch(error) {
		if(error.code === "ENOENT") {
			return [];
		}
		throw error;
	}

	for(var index = 0; index < entries.length; index++) {
		var entry = entries[index];

		// 不公开隐藏文件。
		if(entry.name.charAt(0) === ".") {
			continue;
		}

		var absolutePath = path.join(directory,entry.name);

		// 忽略符号链接,避免扫描到 files/ 目录之外。
		if(entry.isDirectory()) {
			var childFiles = await scanDirectory(
				rootDirectory,
				absolutePath,
				path.join(prefix,entry.name)
			);
			files.push.apply(files,childFiles);
			continue;
		}

		if(!entry.isFile()) {
			continue;
		}

		var relativePath = path.join(prefix,entry.name),
			extension = path.extname(entry.name).toLowerCase(),
			extensionInfo = $tw.config.fileExtensionInfo[extension],
			stats;

		try {
			stats = await fs.promises.stat(absolutePath);
		} catch(error) {
			// 文件可能在扫描过程中被删除。
			continue;
		}

		files.push({
			name: entry.name,
			path: relativePath.split(path.sep).join("/"),
			url: (prefix === "" ? "" : "") +
				encodeFilePath(relativePath),
			type: extensionInfo ?
				extensionInfo.type :
				"application/octet-stream",
			size: stats.size,
			modified: stats.mtime.toISOString()
		});
	}

	return files;
}

exports.handler = function(request,response,state) {
	var rootDirectory = path.resolve(state.boot.wikiPath,"files"),
		pathPrefix = state.pathPrefix || "";

	scanDirectory(rootDirectory,rootDirectory,"")
		.then(function(files) {
			files.sort(function(left,right) {
				return left.path.localeCompare(right.path);
			});

			files.forEach(function(file) {
				file.url = pathPrefix + "/files/" + file.url;
			});

			state.sendResponse(
				200,
				{"Content-Type": "application/json; charset=utf-8"},
				JSON.stringify(files),
				"utf8"
			);
		})
		.catch(function(error) {
			console.error("Unable to list files directory:",error);

			state.sendResponse(
				500,
				{"Content-Type": "application/json; charset=utf-8"},
				JSON.stringify({
					error: "Unable to list files directory"
				}),
				"utf8"
			);
		});
};