-
-
Notifications
You must be signed in to change notification settings - Fork 62
/
Copy pathdata.js
176 lines (151 loc) · 5.64 KB
/
data.js
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import { IndexedDB } from '@sveltia/utils/storage';
import { allAssets, getAssetFoldersByPath } from '$lib/services/assets';
import { parseAssetFiles } from '$lib/services/assets/parser';
import {
allEntries,
dataLoaded,
entryParseErrors,
getEntryFoldersByPath,
} from '$lib/services/contents';
import { parseEntryFiles } from '$lib/services/contents/parser';
/** @type {RepositoryInfo} */
export const repositoryProps = {
service: '',
label: '',
owner: '',
repo: '',
branch: '',
};
/**
* Parse a list of all files on the repository/filesystem to create entry and asset lists, with the
* relevant collection/file configuration added.
* @param {BaseFileListItem[]} files - Unfiltered file list.
* @returns {{
* entryFiles: BaseEntryListItem[],
* assetFiles: BaseAssetListItem[],
* allFiles: (BaseEntryListItem | BaseAssetListItem)[],
* count: number,
* }} File
* list, including both entries and assets.
*/
export const createFileList = (files) => {
/** @type {BaseEntryListItem[]} */
const entryFiles = [];
/** @type {BaseAssetListItem[]} */
const assetFiles = [];
files.forEach((fileInfo) => {
const { path } = fileInfo;
const [entryFolder] = getEntryFoldersByPath(path);
const [assetFolder] = getAssetFoldersByPath(path);
if (entryFolder) {
entryFiles.push({ ...fileInfo, type: 'entry', folder: entryFolder });
}
// Exclude files already listed as entries. These files can appear in the file list when a
// relative media path is configured for a collection
if (assetFolder && !entryFiles.find((e) => e.path === path)) {
assetFiles.push({ ...fileInfo, type: 'asset', folder: assetFolder });
}
});
const allFiles = [...entryFiles, ...assetFiles];
return { entryFiles, assetFiles, allFiles, count: allFiles.length };
};
/**
* Fetch file list from a backend service, download/parse all the entry files, then cache them in
* the {@link allEntries} and {@link allAssets} stores.
* @param {object} args - Arguments.
* @param {RepositoryInfo} args.repository - Repository info.
* @param {() => Promise<string>} args.fetchDefaultBranchName - Function to fetch the repository’s
* default branch name.
* @param {() => Promise<string>} args.fetchLastCommitHash - Function to fetch the latest commit’s
* SHA-1 hash.
* @param {() => Promise<BaseFileListItem[]>} args.fetchFileList - Function to fetch the
* repository’s complete file list.
* @param {(fetchingFiles: (BaseEntryListItem | BaseAssetListItem)[]) =>
* Promise<RepositoryContentsMap>} args.fetchFileContents - Function to fetch the metadata of
* entry/asset files as well as text file contents.
*/
export const fetchAndParseFiles = async ({
repository,
fetchDefaultBranchName,
fetchLastCommitHash,
fetchFileList,
fetchFileContents,
}) => {
const { databaseName, branch: branchName } = repository;
const metaDB = new IndexedDB(/** @type {string} */ (databaseName), 'meta');
const cacheDB = new IndexedDB(/** @type {string} */ (databaseName), 'file-cache');
const cachedHash = await metaDB.get('last_commit_hash');
const cachedFileEntries = await cacheDB.entries();
let branch = branchName;
let fileList;
if (!branch) {
branch = await fetchDefaultBranchName();
repository.branch = branch;
}
// This has to be done after the branch is determined
const lastHash = await fetchLastCommitHash();
if (cachedHash && cachedHash === lastHash && cachedFileEntries.length) {
// Skip fetching the file list if the cached hash matches the latest. But don’t skip if the file
// cache is empty; something probably went wrong the last time the files were fetched.
fileList = createFileList(cachedFileEntries.map(([path, data]) => ({ path, ...data })));
} else {
// Get a complete file list first, and filter what’s managed in CMS
fileList = createFileList(await fetchFileList());
metaDB.set('last_commit_hash', lastHash);
}
// Skip fetching files if no files found
if (!fileList.count) {
allEntries.set([]);
allAssets.set([]);
dataLoaded.set(true);
return;
}
const { entryFiles, assetFiles, allFiles } = fileList;
const cachedFiles = Object.fromEntries(cachedFileEntries);
// Restore cached text and commit info
allFiles.forEach(({ sha, path }, index) => {
if (cachedFiles[path]?.sha === sha) {
Object.assign(allFiles[index], cachedFiles[path]);
}
});
const fetchingFiles = allFiles.filter(({ meta }) => !meta);
const fetchedFileMap = fetchingFiles.length ? await fetchFileContents(fetchingFiles) : {};
const { entries, errors } = parseEntryFiles(
entryFiles.map((file) => {
const { text, meta } = fetchedFileMap[file.path] ?? {};
return {
...file,
text: file.text ?? text,
meta: file.meta ?? meta,
};
}),
);
allEntries.set(entries);
entryParseErrors.set(errors);
allAssets.set(
parseAssetFiles(
assetFiles.map((file) => {
const { meta, text, size } = fetchedFileMap[file.path] ?? {};
return {
...file,
name: file.path.split('/').pop(),
meta: file.meta ?? meta,
// The size and text are only available in the 2nd request (`fetchFileContents`) on GitLab
size: file.size ?? size,
text: file.text ?? text,
};
}),
),
);
dataLoaded.set(true);
const usedPaths = allFiles.map(({ path }) => path);
const unusedPaths = Object.keys(cachedFiles).filter((path) => !usedPaths.includes(path));
// Save new entry caches
if (fetchingFiles.length) {
await cacheDB.saveEntries(Object.entries(fetchedFileMap));
}
// Delete old entry caches
if (unusedPaths.length) {
cacheDB.deleteEntries(unusedPaths);
}
};