🔧 Refactoring AirDC++ downloads

This commit is contained in:
2022-04-22 09:13:44 -07:00
parent a40b08c990
commit 544d359501
5 changed files with 740 additions and 780 deletions

View File

@@ -53,6 +53,7 @@
"pretty-bytes": "^5.6.0",
"react": "^18.0.0",
"react-collapsible": "^2.8.3",
"react-comic-viewer": "^0.3.5",
"react-datepicker": "^4.5.0",
"react-dom": "^18.0.0",
"react-fast-compare": "^3.2.0",

View File

@@ -31,84 +31,84 @@ function sleep(ms: number): Promise<NodeJS.Timeout> {
export const search =
(data: SearchData, ADCPPSocket: any, credentials: any) =>
async (dispatch) => {
try {
if (!ADCPPSocket.isConnected()) {
await ADCPPSocket.connect(
credentials.username,
credentials.password,
true,
);
}
const instance: SearchInstance = await ADCPPSocket.post("search");
dispatch({
type: AIRDCPP_SEARCH_IN_PROGRESS,
});
// We want to get notified about every new result in order to make the user experience better
await ADCPPSocket.addListener(
`search`,
"search_result_added",
async (groupedResult) => {
// ...add the received result in the UI
// (it's probably a good idea to have some kind of throttling for the UI updates as there can be thousands of results)
dispatch({
type: AIRDCPP_SEARCH_RESULTS_ADDED,
groupedResult,
});
},
instance.id,
);
// We also want to update the existing items in our list when new hits arrive for the previously listed files/directories
await ADCPPSocket.addListener(
`search`,
"search_result_updated",
async (groupedResult) => {
// ...update properties of the existing result in the UI
dispatch({
type: AIRDCPP_SEARCH_RESULTS_UPDATED,
groupedResult,
});
},
instance.id,
);
// We need to show something to the user in case the search won't yield any results so that he won't be waiting forever)
// Wait for 5 seconds for any results to arrive after the searches were sent to the hubs
await ADCPPSocket.addListener(
`search`,
"search_hub_searches_sent",
async (searchInfo) => {
await sleep(5000);
// Check the number of received results (in real use cases we should know that even without calling the API)
const currentInstance = await ADCPPSocket.get(
`search/${instance.id}`,
async (dispatch) => {
try {
if (!ADCPPSocket.isConnected()) {
await ADCPPSocket.connect(
credentials.username,
credentials.password,
true,
);
if (currentInstance.result_count === 0) {
// ...nothing was received, show an informative message to the user
console.log("No more search results.");
}
}
const instance: SearchInstance = await ADCPPSocket.post("search");
dispatch({
type: AIRDCPP_SEARCH_IN_PROGRESS,
});
// The search can now be considered to be "complete"
// If there's an "in progress" indicator in the UI, that could also be disabled here
dispatch({
type: AIRDCPP_HUB_SEARCHES_SENT,
searchInfo,
instance,
});
},
instance.id,
);
// Finally, perform the actual search
await ADCPPSocket.post(`search/${instance.id}/hub_search`, data);
} catch (error) {
console.log(error);
throw error;
}
};
// We want to get notified about every new result in order to make the user experience better
await ADCPPSocket.addListener(
`search`,
"search_result_added",
async (groupedResult) => {
// ...add the received result in the UI
// (it's probably a good idea to have some kind of throttling for the UI updates as there can be thousands of results)
dispatch({
type: AIRDCPP_SEARCH_RESULTS_ADDED,
groupedResult,
});
},
instance.id,
);
// We also want to update the existing items in our list when new hits arrive for the previously listed files/directories
await ADCPPSocket.addListener(
`search`,
"search_result_updated",
async (groupedResult) => {
// ...update properties of the existing result in the UI
dispatch({
type: AIRDCPP_SEARCH_RESULTS_UPDATED,
groupedResult,
});
},
instance.id,
);
// We need to show something to the user in case the search won't yield any results so that he won't be waiting forever)
// Wait for 5 seconds for any results to arrive after the searches were sent to the hubs
await ADCPPSocket.addListener(
`search`,
"search_hub_searches_sent",
async (searchInfo) => {
await sleep(5000);
// Check the number of received results (in real use cases we should know that even without calling the API)
const currentInstance = await ADCPPSocket.get(
`search/${instance.id}`,
);
if (currentInstance.result_count === 0) {
// ...nothing was received, show an informative message to the user
console.log("No more search results.");
}
// The search can now be considered to be "complete"
// If there's an "in progress" indicator in the UI, that could also be disabled here
dispatch({
type: AIRDCPP_HUB_SEARCHES_SENT,
searchInfo,
instance,
});
},
instance.id,
);
// Finally, perform the actual search
await ADCPPSocket.post(`search/${instance.id}/hub_search`, data);
} catch (error) {
console.log(error);
throw error;
}
};
export const downloadAirDCPPItem =
(
@@ -118,126 +118,134 @@ export const downloadAirDCPPItem =
ADCPPSocket: any,
credentials: any,
): void =>
async (dispatch) => {
try {
if (!ADCPPSocket.isConnected()) {
await ADCPPSocket.connect(
`${credentials.username}`,
`${credentials.password}`,
true,
async (dispatch) => {
try {
if (!ADCPPSocket.isConnected()) {
await ADCPPSocket.connect(
`${credentials.username}`,
`${credentials.password}`,
true,
);
}
let bundleDBImportResult = {};
const downloadResult = await ADCPPSocket.post(
`search/${instanceId}/results/${resultId}/download`,
);
}
let bundleDBImportResult = {};
const downloadResult = await ADCPPSocket.post(
`search/${instanceId}/results/${resultId}/download`,
);
let bundleId;
let directoryIds;
if (!isNil(downloadResult.bundle_info)) {
bundleId = downloadResult.bundle_info.id;
}
if (!isNil(downloadResult.directory_download_ids)) {
directoryIds = downloadResult.directory_download_ids.map(
(item) => item.id,
// download status check
await ADCPPSocket.addListener(
`queue`,
"queue_file_status",
async (searchInfo) => {
console.log("HERE", searchInfo);
},
);
}
if (!isNil(downloadResult)) {
bundleDBImportResult = await axios({
let bundleId;
let directoryIds;
if (!isNil(downloadResult.bundle_info)) {
bundleId = downloadResult.bundle_info.id;
}
if (!isNil(downloadResult.directory_download_ids)) {
directoryIds = downloadResult.directory_download_ids.map(
(item) => item.id,
);
}
if (!isNil(downloadResult)) {
bundleDBImportResult = await axios({
method: "POST",
url: `${LIBRARY_SERVICE_BASE_URI}/applyAirDCPPDownloadMetadata`,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
data: {
resultId,
comicObjectId,
searchInstanceId: instanceId,
bundleId,
directoryIds,
},
});
dispatch({
type: AIRDCPP_RESULT_DOWNLOAD_INITIATED,
downloadResult: downloadResult,
bundleDBImportResult,
});
dispatch({
type: IMS_COMIC_BOOK_DB_OBJECT_FETCHED,
comicBookDetail: bundleDBImportResult.data,
IMS_inProgress: false,
});
}
} catch (error) {
throw error;
}
};
export const getDownloadProgress =
(comicObjectId: string, ADCPPSocket: any, credentials: any): void =>
async (dispatch) => {
try {
if (!ADCPPSocket.isConnected()) {
await ADCPPSocket.connect(
`${credentials.username}`,
`${credentials.password}`,
true,
);
}
await ADCPPSocket.addListener(
`queue`,
"queue_bundle_tick",
async (downloadProgressData) => {
dispatch({
type: AIRDCPP_DOWNLOAD_PROGRESS_TICK,
downloadProgressData,
});
},
);
// File status listener
await ADCPPSocket.addListener(
`queue`,
"queue_file_status",
async (data) => console.log("FILE STATUS", data),
);
} catch (error) {
throw error;
}
};
export const getBundlesForComic =
(comicObjectId: string, ADCPPSocket: any, credentials: any) =>
async (dispatch) => {
try {
if (!ADCPPSocket.isConnected()) {
await ADCPPSocket.connect(
`${credentials.username}`,
`${credentials.password}`,
true,
);
}
const comicObject = await axios({
method: "POST",
url: `${LIBRARY_SERVICE_BASE_URI}/applyAirDCPPDownloadMetadata`,
url: `${LIBRARY_SERVICE_BASE_URI}/getComicBookById`,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
data: {
resultId,
comicObjectId,
searchInstanceId: instanceId,
bundleId,
directoryIds,
id: `${comicObjectId}`,
},
});
dispatch({
type: AIRDCPP_RESULT_DOWNLOAD_INITIATED,
downloadResult: downloadResult,
bundleDBImportResult,
});
dispatch({
type: IMS_COMIC_BOOK_DB_OBJECT_FETCHED,
comicBookDetail: bundleDBImportResult.data,
IMS_inProgress: false,
});
}
} catch (error) {
throw error;
}
};
export const getDownloadProgress =
(comicObjectId: string, ADCPPSocket: any, credentials: any): void =>
async (dispatch) => {
try {
if (!ADCPPSocket.isConnected()) {
await ADCPPSocket.connect(
`${credentials.username}`,
`${credentials.password}`,
true,
// get only the bundles applicable for the comic
const filteredBundles = comicObject.data.acquisition.directconnect.map(
async ({ bundleId }) => {
return await ADCPPSocket.get(`queue/bundles/${bundleId}`);
},
);
dispatch({
type: AIRDCPP_BUNDLES_FETCHED,
bundles: await Promise.all(filteredBundles),
});
} catch (error) {
throw error;
}
await ADCPPSocket.addListener(
`queue`,
"queue_bundle_tick",
async (downloadProgressData) => {
dispatch({
type: AIRDCPP_DOWNLOAD_PROGRESS_TICK,
downloadProgressData,
});
},
);
// File status listener
await ADCPPSocket.addListener(
`queue`,
"queue_file_status",
async (data) => console.log("FILE STATUS", data),
);
} catch (error) {
throw error;
}
};
export const getBundlesForComic =
(comicObjectId: string, ADCPPSocket: any, credentials: any) =>
async (dispatch) => {
try {
if (!ADCPPSocket.isConnected()) {
await ADCPPSocket.connect(
`${credentials.username}`,
`${credentials.password}`,
true,
);
}
const comicObject = await axios({
method: "POST",
url: `${LIBRARY_SERVICE_BASE_URI}/getComicBookById`,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
data: {
id: `${comicObjectId}`,
},
});
// get only the bundles applicable for the comic
const filteredBundles = comicObject.data.acquisition.directconnect.map(
async ({ bundleId }) => {
return await ADCPPSocket.get(`queue/bundles/${bundleId}`);
},
);
dispatch({
type: AIRDCPP_BUNDLES_FETCHED,
bundles: await Promise.all(filteredBundles),
});
} catch (error) {
throw error;
}
};
};

View File

@@ -201,7 +201,7 @@ pre {
.card {
margin: 0 0 15px 0;
.partial-rounded-card-image {
img {
border-top-left-radius: 0.4rem;
@@ -246,7 +246,6 @@ pre {
margin-top: 10px;
}
}
}
.is-divider {
margin-top: 1.5rem;
@@ -271,17 +270,17 @@ pre {
}
.issue-metadata {
background-color: #FBFFEE;
background-color: #fbffee;
padding: 0.8em;
border-radius: 0.5rem;
.name {
font-size: 0.95rem;
color: #4A4F50;
color: #4a4f50;
}
}
.comicInfo-metadata {
background-color: #F7EBDD;
background-color: #f7ebdd;
padding: 0.8rem;
border-radius: 0.5rem;
}
@@ -368,7 +367,7 @@ pre {
.tabs {
.download-icon-labels {
.downloads-count {
margin: 0 1em 0 0.4em;
margin: 0 1em -1px 0.4em;
border: 1px solid #ccc;
}
}

View File

@@ -58,27 +58,33 @@ export const DownloadsPanel = (
return (
<div className="column is-half">
{JSON.stringify(props.data.downloadProgressTick)}
<progress
className="progress is-small is-success"
value={props.data.downloaded_bytes}
max={props.data.size}
>
{(parseInt(props.data.downloaded_bytes) / parseInt(props.data.size)) *
100}
%
</progress>
<div className="card">
<div className="card-content is-size-7">
<dl>
<dt>{props.data.name}</dt>
<dt className="is-size-6">{props.data.name}</dt>
<dd>
{prettyBytes(props.data.downloaded_bytes)} of{" "}
{prettyBytes(props.data.size)}
<span className="is-size-3 has-text-weight-semibold">
{prettyBytes(props.data.downloaded_bytes)}/
{prettyBytes(props.data.size)}{" "}
</span>
<progress
className="progress is-small is-success"
value={props.data.downloaded_bytes}
max={props.data.size}
>
{(parseInt(props.data.downloaded_bytes) /
parseInt(props.data.size)) *
100}
%
</progress>
</dd>
<dd>{prettyBytes(props.data.speed)} per second.</dd>
<dd>
<dd className="is-size-5">
{prettyBytes(props.data.speed)} per second.
</dd>
<dd className="is-size-5">
Time left:
{parseInt(props.data.seconds_left) / 60}
{Math.round(parseInt(props.data.seconds_left) / 60)}
</dd>
<dd>{props.data.target}</dd>
</dl>

1090
yarn.lock

File diff suppressed because it is too large Load Diff