Updating DirectorySearch to allow creation of new directories.

This commit is contained in:
Roland 2024-10-02 21:59:33 -06:00
parent 555cdab25c
commit 4b6fb1d37f
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
11 changed files with 227 additions and 247 deletions

View file

@ -96,7 +96,7 @@ export async function createChannel(channel) {
export async function deleteChannel(channelId) {
const response = await apiDelete(`${VIDEOS_API}/channels/${channelId}`);
if (response.status !== 204) {
const message = getErrorMessage(response, 'Failed to delete channel.');
const message = await getErrorMessage(response, 'Failed to delete channel.');
toast({
type: 'error',
title: 'Unexpected server response',
@ -112,7 +112,7 @@ export async function getChannels() {
if (response.status === 200) {
return (await response.json())['channels'];
} else {
const message = getErrorMessage(response, 'Failed to get channels.');
const message = await getErrorMessage(response, 'Failed to get channels.');
toast({
type: 'error',
title: 'Unexpected server response',
@ -153,7 +153,7 @@ export async function tagChannelInfo(channelId, tagName) {
if (response.ok) {
return (await response.json()).videos_destination;
}
const message = getErrorMessage(response, 'Failed to get channel tag info.');
const message = await getErrorMessage(response, 'Failed to get channel tag info.');
toast({
type: 'error',
title: 'Getting Channel Tag Info Failed',
@ -188,7 +188,7 @@ export async function searchVideos(offset, limit, channelId, searchStr, order_by
let data = await response.json();
return [data['file_groups'], data['totals']['file_groups']];
} else {
const message = getErrorMessage(response, 'Failed to search videos.');
const message = await getErrorMessage(response, 'Failed to search videos.');
toast({
type: 'error',
title: 'Searching Videos failed',
@ -218,7 +218,7 @@ export async function deleteVideos(videoIds) {
const i = videoIds.join(',');
const response = await apiDelete(`${VIDEOS_API}/video/${i}`);
if (response.status !== 204) {
const message = getErrorMessage(response, 'Failed to delete videos.');
const message = await getErrorMessage(response, 'Failed to delete videos.');
toast({
type: 'error',
title: 'Deleting Videos failed',
@ -258,7 +258,7 @@ export async function getStatus() {
if (response.status === 200) {
return await response.json();
} else {
const message = getErrorMessage(response, 'Could not get server status.');
const message = await getErrorMessage(response, 'Could not get server status.');
toast({
type: 'error',
title: 'Fetching Status Failed',
@ -273,7 +273,7 @@ export async function getSettings() {
if (response.ok) {
return await response.json();
} else {
const message = getErrorMessage(response, 'Could not get settings.');
const message = await getErrorMessage(response, 'Could not get settings.');
toast({
type: 'error',
title: 'Fetching Settings Failed',
@ -286,7 +286,7 @@ export async function getSettings() {
export async function saveSettings(settings) {
const response = await apiPatch(`${API_URI}/settings`, settings);
if (!response.ok) {
const message = getErrorMessage(response, 'Could not save settings.');
const message = await getErrorMessage(response, 'Could not save settings.');
toast({
type: 'error',
title: 'Saving Settings Failed',
@ -302,7 +302,7 @@ export async function getDownloads() {
if (response.ok) {
return await response.json();
} else {
const message = getErrorMessage(response, 'Could not get downloads.');
const message = await getErrorMessage(response, 'Could not get downloads.');
toast({
type: 'error',
title: 'Getting Downloads Failed',
@ -321,7 +321,7 @@ export async function getConfigs() {
configs: data.configs,
}
} else {
const message = getErrorMessage(response, 'Could not get configs.');
const message = await getErrorMessage(response, 'Could not get configs.');
toast({
type: 'error',
title: 'Getting Configs Failed',
@ -336,7 +336,7 @@ export async function postImportConfig(fileName) {
const body = {file_name: fileName};
const response = await apiPost(`${API_URI}/configs/import`, body);
if (!response.ok) {
const message = getErrorMessage(response, 'Could not import config.');
const message = await getErrorMessage(response, 'Could not import config.');
toast({
type: 'error',
title: 'Importing config Failed',
@ -351,7 +351,7 @@ export async function postSaveConfig(fileName) {
const body = {file_name: fileName, overwrite: true};
const response = await apiPost(`${API_URI}/configs/save`, body);
if (!response.ok) {
const message = getErrorMessage(response, 'Could not save config.');
const message = await getErrorMessage(response, 'Could not save config.');
toast({
type: 'error',
title: 'Saving config Failed',
@ -376,7 +376,7 @@ export async function getVideosStatistics() {
if (response.status === 200) {
return (await response.json())['statistics'];
} else {
const message = getErrorMessage(response, 'Could not get statistics.');
const message = await getErrorMessage(response, 'Could not get statistics.');
toast({
type: 'error',
title: 'Getting Statistics Failed',
@ -409,7 +409,7 @@ export async function createChannelDownload(channelId, url, frequency, title_inc
time: 5000,
});
} else {
const message = getErrorMessage(response, 'Could not create Channel download.');
const message = await getErrorMessage(response, 'Could not create Channel download.');
toast({
type: 'error',
title: 'Creating Channel Download Failed',
@ -443,7 +443,7 @@ export async function updateChannelDownload(channelId, downloadId, url, frequenc
time: 5000,
});
} else {
const message = getErrorMessage(response, 'Could not update Channel download.');
const message = await getErrorMessage(response, 'Could not update Channel download.');
toast({
type: 'error',
title: 'Updating Channel Download Failed',
@ -459,7 +459,7 @@ export async function refreshChannel(channelId) {
let url = `${VIDEOS_API}/channels/refresh/${channelId}`;
const response = await apiPost(url);
if (!response.ok) {
const message = getErrorMessage(response, "Failed to refresh this channel's directory");
const message = await getErrorMessage(response, "Failed to refresh this channel's directory");
toast({
type: 'error',
title: 'Failed to refresh',
@ -476,7 +476,7 @@ export async function encryptOTP(otp, plaintext) {
if (response.ok) {
return await response.json();
} else {
const message = getErrorMessage(response, 'Failed to encrypt OTP. See server logs.');
const message = await getErrorMessage(response, 'Failed to encrypt OTP. See server logs.');
toast({
type: 'error',
title: 'Failed to encrypt OTP',
@ -492,7 +492,7 @@ export async function decryptOTP(otp, ciphertext) {
if (response.ok) {
return await response.json();
} else {
const message = getErrorMessage(response, 'Failed to decrypt OTP. See server logs.');
const message = await getErrorMessage(response, 'Failed to decrypt OTP. See server logs.');
toast({
type: 'error',
title: 'Failed to decrypt OTP',
@ -507,7 +507,7 @@ export async function getCategories() {
if (response.ok) {
return (await response.json())['categories'];
} else {
const message = getErrorMessage(response, 'Could not get categories');
const message = await getErrorMessage(response, 'Could not get categories');
toast({
type: 'error', title: 'Unexpected server response', description: message, time: 5000,
});
@ -519,7 +519,7 @@ export async function getBrands() {
if (response.ok) {
return (await response.json())['brands'];
} else {
const message = getErrorMessage(response, 'Could not get brands');
const message = await getErrorMessage(response, 'Could not get brands');
toast({
type: 'error', title: 'Unexpected server response', description: message, time: 5000,
});
@ -531,7 +531,7 @@ export async function getInventories() {
if (response.ok) {
return (await response.json())['inventories'];
} else {
const message = getErrorMessage(response, 'Could not get inventories');
const message = await getErrorMessage(response, 'Could not get inventories');
toast({
type: 'error', title: 'Unexpected server response', description: message, time: 5000,
});
@ -543,7 +543,7 @@ export async function getInventory(inventoryId) {
if (response.ok) {
return await response.json();
} else {
const message = getErrorMessage(response, 'Could not get inventory');
const message = await getErrorMessage(response, 'Could not get inventory');
toast({
type: 'error', title: 'Unexpected server response', description: message, time: 5000,
});
@ -553,7 +553,7 @@ export async function getInventory(inventoryId) {
export async function saveInventory(inventory) {
const response = await apiPost(`${API_URI}/inventory`, inventory);
if (!response.ok) {
const message = getErrorMessage(response, 'Failed to save inventory');
const message = await getErrorMessage(response, 'Failed to save inventory');
toast({
type: 'error', title: 'Unexpected server response', description: message, time: 5000,
});
@ -565,7 +565,7 @@ export async function updateInventory(inventoryId, inventory) {
delete inventory['id'];
const response = await apiPut(`${API_URI}/inventory/${inventoryId}`, inventory);
if (!response.ok) {
const message = getErrorMessage(response, 'Failed to update inventory');
const message = await getErrorMessage(response, 'Failed to update inventory');
toast({
type: 'error', title: 'Unexpected server response', description: message, time: 5000,
});
@ -576,7 +576,7 @@ export async function updateInventory(inventoryId, inventory) {
export async function deleteInventory(inventoryId) {
const response = await apiDelete(`${API_URI}/inventory/${inventoryId}`);
if (!response.ok) {
const message = getErrorMessage(response, 'Failed to delete inventory');
const message = await getErrorMessage(response, 'Failed to delete inventory');
toast({
type: 'error', title: 'Unexpected server response', description: message, time: 5000,
});
@ -589,7 +589,7 @@ export async function getItems(inventoryId) {
if (response.ok) {
return await response.json();
} else {
const message = getErrorMessage(response, 'Failed to get items');
const message = await getErrorMessage(response, 'Failed to get items');
toast({
type: 'error', title: 'Unexpected server response', description: message, time: 5000,
});
@ -602,7 +602,7 @@ export async function saveItem(inventoryId, item) {
if (response.ok) {
return await response.json();
} else {
const message = getErrorMessage(response, 'Failed to save item');
const message = await getErrorMessage(response, 'Failed to save item');
toast({
type: 'error', title: 'Unexpected server response', description: message, time: 5000,
});
@ -616,7 +616,7 @@ export async function updateItem(itemId, item) {
if (response.ok) {
return await response.json();
} else {
const message = getErrorMessage(response, 'Failed to update item');
const message = await getErrorMessage(response, 'Failed to update item');
toast({
type: 'error', title: 'Unexpected server response', description: message, time: 5000,
});
@ -630,7 +630,7 @@ export async function deleteItems(itemIds) {
if (response.ok) {
return await response.json();
} else {
const message = getErrorMessage(response, 'Failed to delete items');
const message = await getErrorMessage(response, 'Failed to delete items');
toast({
type: 'error', title: 'Unexpected server response', description: message, time: 5000,
});
@ -651,7 +651,7 @@ export async function deleteArchives(archiveIds) {
let i = archiveIds.join(',');
const response = await apiDelete(`${API_URI}/archive/${i}`);
if (!response.ok) {
const message = getErrorMessage(response, 'Failed to delete archives');
const message = await getErrorMessage(response, 'Failed to delete archives');
toast({
type: 'error', title: 'Unexpected server response', description: message, time: 5000,
});
@ -687,7 +687,7 @@ export async function searchArchives(offset, limit, domain, searchStr, order, ta
let data = await response.json();
return [data['file_groups'], data['totals']['file_groups']];
} else {
const message = getErrorMessage(response, 'Cannot search archives. See server logs.');
const message = await getErrorMessage(response, 'Cannot search archives. See server logs.');
toast({
type: 'error',
title: 'Unable to search archives',
@ -704,7 +704,7 @@ export async function fetchDomains() {
let data = await response.json();
return [data['domains'], data['totals']['domains']];
} else {
const message = getErrorMessage(response, 'Unable to fetch Domains. See server logs.');
const message = await getErrorMessage(response, 'Unable to fetch Domains. See server logs.');
toast({
type: 'error',
title: 'Domains Error',
@ -720,7 +720,7 @@ export async function getArchive(archiveId) {
const data = await response.json();
return [data['file_group'], data['history']];
} else {
const message = getErrorMessage(response, 'Unable to get Archive. See server logs.');
const message = await getErrorMessage(response, 'Unable to get Archive. See server logs.');
toast({
type: 'error', title: 'Archive Error', description: message, time: 5000,
});
@ -773,7 +773,7 @@ export async function postDownload(
};
const response = await apiPost(`${API_URI}/download`, body);
if (!response.ok) {
const message = getErrorMessage(response, 'Unable to create Download. See server logs.');
const message = await getErrorMessage(response, 'Unable to create Download. See server logs.');
toast({
type: 'error', title: 'Download Error', description: message, time: 5000,
});
@ -809,7 +809,7 @@ export async function putDownload(
};
const response = await apiPut(`${API_URI}/download/${download_id}`, body);
if (!response.ok) {
const message = getErrorMessage(response, 'Unable to update Download. See server logs.');
const message = await getErrorMessage(response, 'Unable to update Download. See server logs.');
toast({
type: 'error', title: 'Download Error', description: message, time: 5000,
});
@ -820,7 +820,7 @@ export async function putDownload(
export async function killDownload(download_id) {
const response = await apiPost(`${API_URI}/download/${download_id}/kill`);
if (!response.ok) {
const message = getErrorMessage(response, 'Unable to stop Download. See server logs.');
const message = await getErrorMessage(response, 'Unable to stop Download. See server logs.');
toast({
type: 'error', title: 'Download Error', description: message, time: 5000,
});
@ -831,7 +831,7 @@ export async function killDownload(download_id) {
export async function restartDownload(download_id) {
const response = await apiPost(`${API_URI}/download/${download_id}/restart`);
if (!response.ok) {
const message = getErrorMessage(response, 'Unable to restart Download. See server logs.');
const message = await getErrorMessage(response, 'Unable to restart Download. See server logs.');
toast({
type: 'error', title: 'Download Error', description: message, time: 5000,
});
@ -842,7 +842,7 @@ export async function restartDownload(download_id) {
export async function killDownloads() {
const response = await apiPost(`${API_URI}/download/kill`);
if (!response.ok) {
const message = getErrorMessage(response, 'Unable to stop downloading. See server logs.');
const message = await getErrorMessage(response, 'Unable to stop downloading. See server logs.');
toast({
type: 'error', title: 'Download Error', description: message, time: 5000,
});
@ -853,7 +853,7 @@ export async function killDownloads() {
export async function startDownloads() {
const response = await apiPost(`${API_URI}/download/enable`);
if (!response.ok) {
const message = getErrorMessage(response, 'Unable to start downloading. See server logs.');
const message = await getErrorMessage(response, 'Unable to start downloading. See server logs.');
toast({
type: 'error', title: 'Download Error', description: message, time: 5000,
});
@ -875,7 +875,7 @@ export async function deleteDownload(downloadId) {
try {
const response = await apiDelete(`${API_URI}/download/${downloadId}`);
if (!response.ok) {
const message = getErrorMessage(response, 'Unable to delete the download. See server logs.');
const message = await getErrorMessage(response, 'Unable to delete the download. See server logs.');
toast({
type: 'error',
title: 'Download Error',
@ -919,7 +919,7 @@ export async function filesSearch(offset, limit, searchStr, mimetypes, model, ta
let [file_groups, total] = [data['file_groups'], data['totals']['file_groups']];
return [file_groups, total];
} else {
const message = getErrorMessage(response, 'Cannot search files. See server logs.');
const message = await getErrorMessage(response, 'Cannot search files. See server logs.');
toast({
type: 'error',
title: 'Unable to search files',
@ -942,7 +942,7 @@ export async function refreshFiles(paths) {
response = await apiPost(`${API_URI}/files/refresh`);
}
if (!response.ok) {
const message = getErrorMessage(response, 'Cannot refresh files. See server logs.');
const message = await getErrorMessage(response, 'Cannot refresh files. See server logs.');
toast({
type: 'error',
title: 'Files Error',
@ -957,7 +957,7 @@ export async function makeDirectory(path) {
const body = {path: path};
const response = await apiPost(`${API_URI}/files/directory`, body);
if (!response.ok) {
const message = getErrorMessage(response, 'Failed to create directory. See server logs.');
const message = await getErrorMessage(response, 'Failed to create directory. See server logs.');
toast({
type: 'error',
title: 'Unable to create directory',
@ -983,7 +983,7 @@ export async function getFile(path) {
const {file} = await response.json();
return file;
} else {
const message = getErrorMessage(response, 'Failed to get file data. See server logs.');
const message = await getErrorMessage(response, 'Failed to get file data. See server logs.');
toast({
type: 'error',
title: 'Files Error',
@ -997,7 +997,7 @@ export async function deleteFile(paths) {
let body = {paths: paths};
const response = await apiPost(`${API_URI}/files/delete`, body);
if (!response.ok) {
const message = getErrorMessage(response, 'Failed to delete file. See server logs.');
const message = await getErrorMessage(response, 'Failed to delete file. See server logs.');
toast({
type: 'error',
title: 'Files Error',
@ -1036,7 +1036,7 @@ export async function setHotspot(on) {
time: 5000,
});
} else {
const message = getErrorMessage(response, 'Could not modify hotspot. See server logs.');
const message = await getErrorMessage(response, 'Could not modify hotspot. See server logs.');
toast({
type: 'error',
title: 'Hotspot Error',
@ -1067,7 +1067,7 @@ export async function setThrottle(on) {
time: 5000,
});
} else {
const message = getErrorMessage(response, 'Could not modify throttle. See server logs.');
const message = await getErrorMessage(response, 'Could not modify throttle. See server logs.');
toast({type: 'error', title: 'Throttle Error', description: message, time: 5000});
}
}
@ -1078,7 +1078,7 @@ export async function getMapImportStatus() {
if (response.ok) {
return await response.json();
} else {
const message = getErrorMessage(response, 'Could not get import status');
const message = await getErrorMessage(response, 'Could not get import status');
toast({type: 'error', title: 'Map Error', description: message, time: 5000});
}
}
@ -1087,7 +1087,7 @@ export async function importMapFiles(paths) {
let body = {'files': paths};
const response = await apiPost(`${API_URI}/map/import`, body);
if (!response.ok) {
const message = getErrorMessage(response, 'Could not start import! See server logs.');
const message = await getErrorMessage(response, 'Could not start import! See server logs.');
toast({type: 'error', title: 'Map Error', description: message, time: 5000});
}
}
@ -1095,7 +1095,7 @@ export async function importMapFiles(paths) {
export async function clearCompletedDownloads() {
const response = await apiPost(`${API_URI}/download/clear_completed`);
if (!response.ok) {
const message = getErrorMessage(response, 'Could not clear completed downloads! See server logs.');
const message = await getErrorMessage(response, 'Could not clear completed downloads! See server logs.');
toast({type: 'error', title: 'Downloads Error', description: message, time: 5000});
}
return response
@ -1104,7 +1104,7 @@ export async function clearCompletedDownloads() {
export async function deleteOnceDownloads() {
const response = await apiPost(`${API_URI}/download/delete_once`);
if (!response.ok) {
const message = getErrorMessage(response, 'Could not delete once downloads! See server logs.');
const message = await getErrorMessage(response, 'Could not delete once downloads! See server logs.');
toast({type: 'error', title: 'Downloads Error', description: message, time: 5000});
}
return response
@ -1113,7 +1113,7 @@ export async function deleteOnceDownloads() {
export async function retryOnceDownloads() {
const response = await apiPost(`${API_URI}/download/retry_once`);
if (!response.ok) {
const message = getErrorMessage(response, 'Could not retry once downloads! See server logs.');
const message = await getErrorMessage(response, 'Could not retry once downloads! See server logs.');
toast({type: 'error', title: 'Downloads Error', description: message, time: 5000});
}
return response
@ -1124,7 +1124,7 @@ export async function getStatistics() {
if (response.ok) {
return await response.json();
} else {
const message = getErrorMessage(response, 'Unable to get file statistics');
const message = await getErrorMessage(response, 'Unable to get file statistics');
toast({type: 'error', title: 'Error', description: message, time: 5000});
}
}
@ -1168,7 +1168,7 @@ export async function tagFileGroup(fileGroup, name) {
const uri = `${API_URI}/files/tag`;
const response = await apiPost(uri, body);
if (!response.ok) {
const message = getErrorMessage(response, 'Unable to add tag');
const message = await getErrorMessage(response, 'Unable to add tag');
toast({type: 'error', title: 'Tag Error', description: message, time: 5000});
}
}
@ -1188,7 +1188,7 @@ export async function untagFileGroup(fileGroup, name) {
const uri = `${API_URI}/files/untag`;
const response = await apiPost(uri, body)
if (!response.ok) {
const message = getErrorMessage(response, 'Unable to untag');
const message = await getErrorMessage(response, 'Unable to untag');
toast({type: 'error', title: 'Tag Error', description: message, time: 5000});
}
}
@ -1209,7 +1209,7 @@ export async function saveTag(name, color, id) {
type: 'info', title: 'Created new tag', description: `Created new tag: ${name}`, time: 2000,
});
} else {
const message = getErrorMessage(response, 'Unable to save tag');
const message = await getErrorMessage(response, 'Unable to save tag');
toast({type: 'error', title: 'Tag Error', description: message, time: 5000});
}
}
@ -1219,7 +1219,7 @@ export async function deleteTag(id, name) {
const response = await apiDelete(uri);
if (response.status === 400) {
const content = await response.json();
const message = getErrorMessage(response, 'Cannot delete, Tag is used');
const message = await getErrorMessage(response, 'Cannot delete, Tag is used');
if (content['code'] === 'USED_TAG') {
toast({
type: 'error', title: 'Error!', description: message, time: 5000,
@ -1227,7 +1227,7 @@ export async function deleteTag(id, name) {
}
} else if (!response.ok) {
console.error('Failed to delete tag');
const message = getErrorMessage(response, `Unable to delete tag: ${name}`);
const message = await getErrorMessage(response, `Unable to delete tag: ${name}`);
toast({type: 'error', title: 'Tag Error', description: message, time: 5000});
}
}
@ -1241,7 +1241,7 @@ export async function fetchFile(path) {
return content['file'];
} else {
console.error('Unable to fetch file dict! See client logs.');
const message = getErrorMessage(response, 'Unable to get File');
const message = await getErrorMessage(response, 'Unable to get File');
toast({type: 'error', title: 'File Error', description: message, time: 5000});
}
}
@ -1252,20 +1252,26 @@ export async function sendNotification(message, url) {
if (response.status === 201) {
toast({type: 'success', title: 'Shared', description: 'Your share was sent', time: 2000});
} else {
const message = getErrorMessage(response, 'Your share failed to send!');
const message = await getErrorMessage(response, 'Your share failed to send!');
toast({type: 'error', title: 'Error', description: message, time: 5000});
}
}
export async function searchDirectories(name) {
const body = {name: name || ''};
export async function searchDirectories(path) {
const body = {path: path || ''};
const response = await apiPost(`${API_URI}/files/search_directories`, body);
if (response.status === 204) {
return [];
} else if (response.status === 200) {
return await response.json();
const content = await response.json();
return {
is_dir: content.is_dir,
directories: content.directories,
channel_directories: content.channel_directories,
domain_directories: content.domain_directories,
}
} else {
const message = getErrorMessage(response, 'Failed to search directories!');
const message = await getErrorMessage(response, 'Failed to search directories!');
toast({type: 'error', title: 'Error', description: message, time: 5000});
}
}
@ -1274,7 +1280,7 @@ export async function renamePath(path, newName) {
const body = {path, new_name: newName};
const response = await apiPost(`${API_URI}/files/rename`, body);
if (!response.ok) {
const message = getErrorMessage(response, 'Failed to rename!');
const message = await getErrorMessage(response, 'Failed to rename!');
toast({type: 'error', title: 'Error', description: message, time: 5000});
}
}
@ -1283,7 +1289,7 @@ export async function movePaths(destination, paths) {
const body = {destination, paths};
const response = await apiPost(`${API_URI}/files/move`, body);
if (!response.ok) {
const message = getErrorMessage(response, 'Failed to move!');
const message = await getErrorMessage(response, 'Failed to move!');
toast({type: 'error', title: 'Error', description: message, time: 5000});
}
}
@ -1292,7 +1298,7 @@ export async function ignoreDirectory(directory) {
const body = {path: directory};
const response = await apiPost(`${API_URI}/files/ignore_directory`, body);
if (!response.ok) {
const message = getErrorMessage(response, 'Failed to ignore directory!');
const message = await getErrorMessage(response, 'Failed to ignore directory!');
toast({type: 'error', title: 'Error', description: message, time: 5000});
}
}
@ -1301,7 +1307,7 @@ export async function unignoreDirectory(directory) {
const body = {path: directory};
const response = await apiPost(`${API_URI}/files/unignore_directory`, body);
if (!response.ok) {
const message = getErrorMessage(response, 'Failed to unignore directory!');
const message = await getErrorMessage(response, 'Failed to unignore directory!');
toast({type: 'error', title: 'Error', description: message, time: 5000});
}
}
@ -1314,7 +1320,7 @@ export async function fetchZims() {
zims: content['zims'],
}
} else {
const message = getErrorMessage(response, 'Cannot fetch Zims');
const message = await getErrorMessage(response, 'Cannot fetch Zims');
toast({type: 'error', title: 'Error', description: message, time: 5000});
}
}
@ -1329,7 +1335,7 @@ export async function fetchZimSubscriptions() {
iso_639_codes: content['iso_639_codes'],
}
} else {
const message = getErrorMessage(response, 'Cannot fetch Zim Subscriptions');
const message = await getErrorMessage(response, 'Cannot fetch Zim Subscriptions');
toast({type: 'error', title: 'Error', description: message, time: 5000});
}
}
@ -1356,7 +1362,7 @@ export async function tagZimEntry(zim_id, zim_entry, name) {
const uri = `${API_URI}/zim/tag`;
const response = await apiPost(uri, body);
if (!response.ok) {
const message = getErrorMessage(response, 'Failed to add zim tag');
const message = await getErrorMessage(response, 'Failed to add zim tag');
toast({type: 'error', title: 'Error', description: message, time: 5000});
}
}
@ -1367,7 +1373,7 @@ export async function untagZimEntry(zim_id, zim_entry, name) {
const uri = `${API_URI}/zim/untag`;
const response = await apiPost(uri, body);
if (!response.ok) {
const message = getErrorMessage(response, 'Failed to untag zim');
const message = await getErrorMessage(response, 'Failed to untag zim');
toast({type: 'error', title: 'Error', description: message, time: 5000});
}
}
@ -1472,7 +1478,7 @@ export async function getOutdatedZims() {
current: content['current'],
}
} else {
const message = getErrorMessage(response, 'Cannot fetch outdated Zims. See server logs.');
const message = await getErrorMessage(response, 'Cannot fetch outdated Zims. See server logs.');
toast({
type: 'error',
title: 'Error',

View file

@ -14,7 +14,7 @@ import {
import {
APIButton,
BackButton,
DirectoryInput,
DirectorySearch,
ErrorMessage,
frequencyOptions,
HelpHeader,
@ -370,10 +370,10 @@ function ChannelPage({create, header}) {
<label>
Directory <RequiredAsterisk/>
</label>
<DirectoryInput required
value={channel.directory}
setInput={value => changeValue('directory', value)}
placeholder='videos/channel directory'
<DirectorySearch
value={channel.directory}
setInput={value => changeValue('directory', value)}
placeholder='videos/channel directory'
/>
</FormField>
</FormGroup>

View file

@ -20,7 +20,6 @@ import {Link, NavLink, useNavigate, useSearchParams} from "react-router-dom";
import Message from "semantic-ui-react/dist/commonjs/collections/Message";
import {
useCPUTemperature,
useDirectories,
useHotspot,
useLoad,
useSearchDirectories,
@ -53,7 +52,6 @@ import {
import {FilePreviewContext} from "./FilePreview";
import _ from "lodash";
import {killDownloads, startDownloads} from "../api";
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
export const API_URI = process.env && process.env.REACT_APP_API_URI ? process.env.REACT_APP_API_URI : `https://${window.location.host}/api`;
export const VIDEOS_API = `${API_URI}/videos`;
@ -1213,16 +1211,21 @@ export function DirectorySearch({onSelect, value, ...props}) {
directories,
channelDirectories,
domainDirectories,
isDir,
loading,
} = useSearchDirectories(value);
const [results, setResults] = useState();
useEffect(() => {
if (
(directories && directories.length >= 0)
|| (channelDirectories && channelDirectories.length >= 0)
|| (domainDirectories && domainDirectories.length >= 0)) {
setResults({
if (directories && directories.length >= 0) {
const newDirectory = isDir ? {} : {
newDirectory: {
name: 'New Directory',
results: [{title: directoryName}],
}
};
const newResults = {
...newDirectory,
directories: {
name: 'Directories',
results: directories.map(i => {
@ -1240,8 +1243,9 @@ export function DirectorySearch({onSelect, value, ...props}) {
results: domainDirectories.map(i => {
return {title: i['path'], description: i['domain']};
}),
}
});
},
};
setResults(newResults);
}
}, [
JSON.stringify(directories),
@ -1254,9 +1258,6 @@ export function DirectorySearch({onSelect, value, ...props}) {
if (e) {
e.preventDefault();
}
if (onSelect) {
onSelect('');
}
setDirectoryName(data.value);
}
@ -1264,77 +1265,22 @@ export function DirectorySearch({onSelect, value, ...props}) {
if (e) {
e.preventDefault();
}
setDirectoryName(data.result.title);
// title is the relative path.
if (onSelect) {
onSelect(data.result.title);
}
setDirectoryName(data.result.title);
}
const handleClear = (e) => {
if (e) {
e.preventDefault();
}
onSelect('');
setDirectoryName('');
}
return <Grid>
<Grid.Row>
<Grid.Column mobile={13} tablet={14} computer={15}>
<Search category
placeholder='Search directory names...'
onSearchChange={handleSearchChange}
onResultSelect={handleResultSelect}
loading={loading}
value={directoryName}
results={results}
{...props}
/>
</Grid.Column>
<Grid.Column mobile={3} tablet={2} computer={1}>
<Button secondary icon='close' onClick={handleClear}/>
</Grid.Column>
</Grid.Row>
</Grid>
}
export function DirectoryInput({disabled, error, placeholder, setInput, value, required, isDirectory}) {
const {directory, directories, setDirectory, isDir} = useDirectories(value);
const {settings} = React.useContext(SettingsContext);
if (!directories || !settings) {
return <></>;
}
const localSetInput = (e, {value}) => {
setInput(value);
setDirectory(value);
}
let {media_directory} = settings;
if (media_directory && !media_directory.endsWith('/')) {
media_directory = `${media_directory}/`;
}
error = error || isDirectory && !isDir;
return <div>
<Input
action={{
color: error ? 'red' : 'green', labelPosition: 'left', icon: 'folder', content: media_directory,
}}
required={required}
disabled={disabled}
actionPosition='left'
value={directory}
onChange={localSetInput}
placeholder={placeholder}
list='directories'
/>
<datalist id='directories'>
{directories.map(i => <option key={i} value={i}>{i}</option>)}
</datalist>
</div>;
return <Search category
placeholder='Search directory names...'
onSearchChange={handleSearchChange}
onResultSelect={handleResultSelect}
loading={loading}
value={directoryName}
results={results}
{...props}
/>
}
export const BackButton = ({...props}) => {

View file

@ -70,7 +70,11 @@ export function Upload({disabled}) {
<Form>
<SForm.Field required>
<label>Destination</label>
<DirectorySearch onSelect={handleDestination} disabled={disabled}/>
<DirectorySearch
onSelect={handleDestination}
disabled={disabled}
style={{marginBottom: '0.5em'}}
/>
{tagsSelector}
</SForm.Field>
</Form>

View file

@ -8,7 +8,7 @@ import {
encodeMediaPath,
getParentDirectory,
humanFileSize,
humanNumber,
humanNumber, isoDatetimeToElapsedPopup,
isoDatetimeToString,
MultilineText,
PageContainer,
@ -367,7 +367,7 @@ function VideoPage({videoFile, prevFile, nextFile, fetchVideo, ...props}) {
<Segment>
<Header as='h2'>{title}</Header>
{videoFile.published_datetime && <h3>{isoDatetimeToString(videoFile.published_datetime)}</h3>}
{videoFile.published_datetime && <h3>{isoDatetimeToElapsedPopup(videoFile.published_datetime)}</h3>}
<h3>
{channel && <Link to={`/videos/channel/${channel.id}/video`}>
{channel.name}

View file

@ -8,7 +8,6 @@ import {
getChannel,
getChannels,
getConfigs,
getDirectories,
getDownloads,
getFiles,
getInventory,
@ -745,58 +744,22 @@ export const useThrottle = () => {
return {on, setOn, setThrottle: localSetThrottle};
}
export const useDirectories = (defaultDirectory) => {
const [directories, setDirectories] = useState();
const [directory, setDirectory] = useState(defaultDirectory);
const [exists, setExists] = useState();
const [isDir, setIsDir] = useState();
const [isFile, setIsFile] = useState();
const fetchDirectories = async () => {
if (defaultDirectory && directory === '') {
setDirectory(defaultDirectory);
}
try {
const {directories, is_dir, exists_, is_file} = await getDirectories(directory);
setDirectories(directories);
setIsDir(is_dir);
setExists(exists_);
setIsFile(is_file);
} catch (e) {
toast({
type: 'error',
title: 'Unexpected server response',
description: 'Could not get directories',
time: 5000,
});
}
}
useEffect(() => {
fetchDirectories();
}, [directory, defaultDirectory]);
useEffect(() => {
setDirectory(defaultDirectory);
}, [defaultDirectory])
return {directory, directories, setDirectory, exists, isDir, isFile};
}
export const useSearchDirectories = (value) => {
const [directoryName, setDirectoryName] = useState(value ?? '');
const [directories, setDirectories] = useState(null);
const [channelDirectories, setChannelDirectories] = useState(null);
const [domainDirectories, setDomainDirectories] = useState(null);
const [isDir, setIsDir] = useState(false);
const [loading, setLoading] = useState(false);
const localSearchDirectories = async () => {
setLoading(true);
try {
const {directories: dirs, channel_directories, domain_directories} = await searchDirectories(directoryName);
setDirectories(dirs);
setChannelDirectories(channel_directories);
setDomainDirectories(domain_directories);
const result = await searchDirectories(directoryName);
setDirectories(result.directories);
setChannelDirectories(result.channel_directories);
setDomainDirectories(result.domain_directories);
setIsDir(result.is_dir);
} finally {
setLoading(false);
}
@ -806,13 +769,19 @@ export const useSearchDirectories = (value) => {
localSearchDirectories();
}, [directoryName]);
useEffect(() => {
// Directory value was changed above.
setDirectoryName(value);
}, [value]);
return {
directoryName,
setDirectoryName,
directories,
loading,
channelDirectories,
domainDirectories
domainDirectories,
isDir,
}
}

View file

@ -5,7 +5,7 @@ After=network.target auditd.service
[Service]
# Delay startup so WROLPi API has time to start.
ExecStartPre=/bin/sleep 30
ExecStartPre=/bin/sleep 60
# Map is intense, but not a high priority.
Nice=19
ExecStart=/usr/bin/renderd -f

View file

@ -1,6 +1,5 @@
import pathlib
from http import HTTPStatus
from typing import List
import sanic.request
from sanic import response, Request, Blueprint
@ -127,31 +126,51 @@ def post_directories(_, body: schema.DirectoriesRequest):
@files_bp.post('/search_directories')
@openapi.definition(
summary='Get all directories whose name matches the provided name.',
summary='Get all directories whose name matches the provided name or path.',
body=schema.DirectoriesSearchRequest,
)
@validate(schema.DirectoriesSearchRequest)
async def post_search_directories(_, body: schema.DirectoriesSearchRequest):
if len(body.name) <= 1:
return response.empty()
if len(body.path) <= 1:
# Need more data before searching.
return json_response({
'is_dir': False,
'directories': [],
'channel_directories': [],
'domain_directories': [],
})
from modules.videos.channel import lib as channels_lib
channels = await channels_lib.search_channels_by_name(name=body.name)
path = get_media_directory() / body.path
try:
matching_directories = lib.get_matching_directories(str(path))
matching_directories = list(map(pathlib.Path, matching_directories))
except FileNotFoundError:
matching_directories = []
# Search Channels by name.
from modules.videos.channel.lib import search_channels_by_name
channels = await search_channels_by_name(name=body.path)
channel_directories = [dict(path=i.directory, name=i.name) for i in channels]
channel_paths = [i['path'] for i in channel_directories]
from modules.archive import lib as archives_lib
domains = await archives_lib.search_domains_by_name(name=body.name)
# Search Domains by name.
from modules.archive.lib import search_domains_by_name
domains = await search_domains_by_name(name=body.path)
domain_directories = [dict(path=i.directory, domain=i.domain) for i in domains]
domain_paths = [i['path'] for i in domain_directories]
# Get all directories that match but do not contain the above directories.
from wrolpi.files.models import Directory
directories: List[Directory] = await lib.search_directories_by_name(
name=body.name,
excluded=list(map(str, channel_paths + domain_paths)))
# Get all Directory that match but do not contain the above directories.
excluded = [str(i) for i in channel_paths + domain_paths + matching_directories]
directories = await lib.search_directories_by_name(name=body.path, excluded=excluded)
# Return only the top 20 directories.
directories = [i.__json__() for i in directories]
directories.extend([{'path': i, 'name': i.name} for i in matching_directories])
directories = list(sorted(directories, key=lambda i: i['path']))[:20]
body = {
'is_dir': path.is_dir(),
'directories': directories,
'channel_directories': channel_directories,
'domain_directories': domain_directories

View file

@ -50,7 +50,7 @@ class DirectoriesRequest:
@dataclass
class DirectoriesSearchRequest:
name: str
path: str
@dataclass

View file

@ -342,32 +342,54 @@ def test_file_group_tag(test_client):
assert 'tag_id' in response.json['error']
def test_search_directories(test_client, test_session, make_files_structure, assert_directories):
@pytest.mark.asyncio
async def test_search_directories(async_client, test_session, test_directory, make_files_structure,
assert_directories):
"""Directories can be searched by name."""
make_files_structure(['foo/one.txt', 'foo/two.txt', 'bar/one.txt'])
request, response = test_client.post('/api/files/refresh')
assert response.status_code == HTTPStatus.NO_CONTENT
await lib.refresh_files()
assert_directories({'foo', 'bar'})
# Create directory that was not refreshed.
(test_directory / 'baz').mkdir()
# More than one character required.
content = dict(name='f')
request, response = test_client.post('/api/files/search_directories', content=json.dumps(content))
assert response.status_code == HTTPStatus.NO_CONTENT
content = dict(path='f')
request, response = await async_client.post('/api/files/search_directories', content=json.dumps(content))
assert response.status_code == HTTPStatus.OK, response.content
assert response.json['directories'] == []
assert response.json['is_dir'] is False
content = dict(name='fo')
request, response = test_client.post('/api/files/search_directories', content=json.dumps(content))
# Can search using partial directory name.
content = dict(path='fo')
request, response = await async_client.post('/api/files/search_directories', content=json.dumps(content))
assert response.status_code == HTTPStatus.OK
assert [i['path'] for i in response.json['directories']] == ['foo', ]
assert response.json['is_dir'] is False
# Searching directory exactly.
content = dict(path='foo')
request, response = await async_client.post('/api/files/search_directories', content=json.dumps(content))
assert response.status_code == HTTPStatus.OK
assert [i['path'] for i in response.json['directories']] == ['foo', ]
assert response.json['is_dir'] is True
# Case is ignored.
content = dict(name='BAR')
request, response = test_client.post('/api/files/search_directories', content=json.dumps(content))
content = dict(path='BAR')
request, response = await async_client.post('/api/files/search_directories', content=json.dumps(content))
assert response.status_code == HTTPStatus.OK
assert [i['path'] for i in response.json['directories']] == ['bar', ]
assert response.json['is_dir'] is False
# Can search directories not yet in DB.
content = dict(path='ba')
request, response = await async_client.post('/api/files/search_directories', content=json.dumps(content))
assert response.status_code == HTTPStatus.OK
assert [i['path'] for i in response.json['directories']] == ['bar', 'baz']
assert response.json['is_dir'] is False
# Searching for something that does not exist.
content = dict(name='does not exist')
request, response = test_client.post('/api/files/search_directories', content=json.dumps(content))
content = dict(path='does not exist')
request, response = await async_client.post('/api/files/search_directories', content=json.dumps(content))
assert response.status_code == HTTPStatus.OK
assert [i['path'] for i in response.json['directories']] == []
@ -395,55 +417,69 @@ def test_search_directories(test_client, test_session, make_files_structure, ass
'foooooooooooooooooooooo/',
'fooooooooooooooooooooooo/',
])
test_client.post('/api/files/refresh')
content = dict(name='fo')
request, response = test_client.post('/api/files/search_directories', content=json.dumps(content))
await async_client.post('/api/files/refresh')
content = dict(path='fo')
request, response = await async_client.post('/api/files/search_directories', content=json.dumps(content))
assert response.status_code == HTTPStatus.OK
assert [i['path'] for i in response.json['directories']] == [f'f{"o" * i}' for i in range(2, 22)]
def test_post_search_directories(test_session, test_client, make_files_structure):
@pytest.mark.asyncio
async def test_post_search_directories(test_session, async_client, make_files_structure):
"""Directory names can be searched. This endpoint also returns Channel and Domain directories."""
channel_dir, domain_dir, _, _ = make_files_structure([
channel1_dir, channel2_dir, domain_dir, _ = make_files_structure([
'dir1/',
'dir2/',
'dir3/',
'dir4/',
])
request, response = test_client.post('/api/files/refresh')
request, response = await async_client.post('/api/files/refresh')
assert response.status_code == HTTPStatus.NO_CONTENT
from modules.videos.models import Channel
channel = Channel(directory=channel_dir, name='Channel Name')
channel1 = Channel(directory=channel1_dir, name='Channel Name')
channel2 = Channel(directory=channel2_dir, name='OtherChannel')
from modules.archive.models import Domain
domain = Domain(directory=domain_dir, domain='example.com')
test_session.add_all([channel, domain])
test_session.add_all([channel1, channel2, domain])
test_session.commit()
content = {'name': 'di'}
request, response = test_client.post('/api/files/search_directories', content=json.dumps(content))
assert response.status_code == HTTPStatus.OK
# All directories contain "di". The names of the Channel and Directory do not match.
content = {'path': 'di'}
request, response = await async_client.post('/api/files/search_directories', content=json.dumps(content))
assert response.status_code == HTTPStatus.OK
assert response.json['directories'] == [{'name': 'dir1', 'path': 'dir1'}, {'name': 'dir2', 'path': 'dir2'},
{'name': 'dir3', 'path': 'dir3'}, {'name': 'dir4', 'path': 'dir4'}]
assert response.json['channel_directories'] == []
assert response.json['domain_directories'] == []
content = {'name': 'Chan'}
request, response = test_client.post('/api/files/search_directories', content=json.dumps(content))
# Channel name matches.
content = {'path': 'Chan'}
request, response = await async_client.post('/api/files/search_directories', content=json.dumps(content))
assert response.status_code == HTTPStatus.OK
assert response.json['directories'] == []
assert response.json['channel_directories'] == [
{'name': 'Channel Name', 'path': 'dir1'},
{'name': 'OtherChannel', 'path': 'dir2'},
]
assert response.json['domain_directories'] == []
# "OtherChannel" matches even though the case is wrong, and it has a space.
content = {'path': 'other channel'}
request, response = await async_client.post('/api/files/search_directories', content=json.dumps(content))
assert response.status_code == HTTPStatus.OK
# Channel name matches.
assert response.json['directories'] == []
assert response.json['channel_directories'] == [{'name': 'Channel Name', 'path': 'dir1'}]
assert response.json['channel_directories'] == [{'name': 'OtherChannel', 'path': 'dir2'}]
assert response.json['domain_directories'] == []
content = {'name': 'exam'}
request, response = test_client.post('/api/files/search_directories', content=json.dumps(content))
content = {'path': 'exam'}
request, response = await async_client.post('/api/files/search_directories', content=json.dumps(content))
assert response.status_code == HTTPStatus.OK
# Domain name matches.
assert response.json['directories'] == []
assert response.json['channel_directories'] == []
assert response.json['domain_directories'] == [{'domain': 'example.com', 'path': 'dir2'}]
assert response.json['domain_directories'] == [{'domain': 'example.com', 'path': 'dir3'}]
def test_post_upload_directory(test_session, test_client, test_directory, make_files_structure, make_multipart_form):

View file

@ -61,7 +61,7 @@ class ScrapeHTMLDownloader(Downloader):
raise UnrecoverableDownloadError('Destination must be defined')
destination = pathlib.Path(destination)
if not destination.is_dir():
raise UnrecoverableDownloadError(f'Destination does not exist: {destination}')
destination.mkdir(parents=True)
suffix = suffix.lower()