Compare commits

..

3 Commits

12 changed files with 298 additions and 382 deletions

View File

@@ -44,6 +44,10 @@
"babel-preset-minify": "^0.5.2", "babel-preset-minify": "^0.5.2",
"better-docs": "^2.7.2", "better-docs": "^2.7.2",
"comlink-loader": "^2.0.0", "comlink-loader": "^2.0.0",
"compromise": "^13.11.3",
"compromise-dates": "^2.2.1",
"compromise-numbers": "^1.4.0",
"compromise-sentences": "^0.3.0",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"dayjs": "^1.10.6", "dayjs": "^1.10.6",
"ellipsize": "^0.1.0", "ellipsize": "^0.1.0",
@@ -134,6 +138,7 @@
"bulma": "^0.9.3", "bulma": "^0.9.3",
"clean-webpack-plugin": "^1.0.0", "clean-webpack-plugin": "^1.0.0",
"comlink": "^4.3.0", "comlink": "^4.3.0",
"compromise-strict": "^0.0.2",
"concurrently": "^4.0.0", "concurrently": "^4.0.0",
"copy-webpack-plugin": "^9.0.1", "copy-webpack-plugin": "^9.0.1",
"css-loader": "^5.1.2", "css-loader": "^5.1.2",
@@ -177,4 +182,4 @@
"resolutions": { "resolutions": {
"@storybook/react/webpack": "^5" "@storybook/react/webpack": "^5"
} }
} }

View File

@@ -4,10 +4,7 @@ import {
PriorityEnum, PriorityEnum,
SearchResponse, SearchResponse,
} from "threetwo-ui-typings"; } from "threetwo-ui-typings";
import { import { LIBRARY_SERVICE_BASE_URI, SEARCH_SERVICE_BASE_URI } from "../constants/endpoints";
LIBRARY_SERVICE_BASE_URI,
SEARCH_SERVICE_BASE_URI,
} from "../constants/endpoints";
import { import {
AIRDCPP_SEARCH_RESULTS_ADDED, AIRDCPP_SEARCH_RESULTS_ADDED,
AIRDCPP_SEARCH_RESULTS_UPDATED, AIRDCPP_SEARCH_RESULTS_UPDATED,
@@ -21,8 +18,6 @@ import {
IMS_COMIC_BOOK_DB_OBJECT_FETCHED, IMS_COMIC_BOOK_DB_OBJECT_FETCHED,
AIRDCPP_TRANSFERS_FETCHED, AIRDCPP_TRANSFERS_FETCHED,
LIBRARY_ISSUE_BUNDLES, LIBRARY_ISSUE_BUNDLES,
AIRDCPP_SOCKET_CONNECTED,
AIRDCPP_SOCKET_DISCONNECTED,
} from "../constants/action-types"; } from "../constants/action-types";
import { isNil } from "lodash"; import { isNil } from "lodash";
import axios from "axios"; import axios from "axios";
@@ -37,199 +32,190 @@ function sleep(ms: number): Promise<NodeJS.Timeout> {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }
export const toggleAirDCPPSocketConnectionStatus =
(status: String, payload?: any) => async (dispatch) => {
switch (status) {
case "connected":
dispatch({
type: AIRDCPP_SOCKET_CONNECTED,
data: payload,
});
break;
case "disconnected":
dispatch({
type: AIRDCPP_SOCKET_DISCONNECTED,
data: payload,
});
break;
default:
console.log("Can't set AirDC++ socket status.");
break;
}
};
export const search = export const search =
(data: SearchData, ADCPPSocket: any, credentials: any) => (data: SearchData, ADCPPSocket: any, credentials: any) =>
async (dispatch) => { async (dispatch) => {
try { try {
if (!ADCPPSocket.isConnected()) { if (!ADCPPSocket.isConnected()) {
await ADCPPSocket(); await ADCPPSocket.connect(
} credentials.username,
const instance: SearchInstance = await ADCPPSocket.post("search"); credentials.password,
dispatch({ true,
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}`,
); );
if (currentInstance.result_count === 0) { }
// ...nothing was received, show an informative message to the user const instance: SearchInstance = await ADCPPSocket.post("search");
console.log("No more search results."); dispatch({
} type: AIRDCPP_SEARCH_IN_PROGRESS,
});
// The search can now be considered to be "complete" // We want to get notified about every new result in order to make the user experience better
// If there's an "in progress" indicator in the UI, that could also be disabled here await ADCPPSocket.addListener(
dispatch({ `search`,
type: AIRDCPP_HUB_SEARCHES_SENT, "search_result_added",
searchInfo, async (groupedResult) => {
instance, // ...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)
},
instance.id, dispatch({
); type: AIRDCPP_SEARCH_RESULTS_ADDED,
// Finally, perform the actual search groupedResult,
await ADCPPSocket.post(`search/${instance.id}/hub_search`, data); });
} catch (error) { },
console.log(error); instance.id,
throw error; );
}
}; // 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 = export const downloadAirDCPPItem =
( (searchInstanceId: Number,
searchInstanceId: Number,
resultId: String, resultId: String,
comicObjectId: String, comicObjectId: String,
name: String, name: String, size: Number, type: any,
size: Number,
type: any,
ADCPPSocket: any, ADCPPSocket: any,
credentials: any, credentials: any,
): void => ): void =>
async (dispatch) => { async (dispatch) => {
try { try {
if (!ADCPPSocket.isConnected()) { if (!ADCPPSocket.isConnected()) {
await ADCPPSocket.connect(); await ADCPPSocket.connect(
} `${credentials.username}`,
let bundleDBImportResult = {}; `${credentials.password}`,
const downloadResult = await ADCPPSocket.post( true,
`search/${searchInstanceId}/results/${resultId}/download`, );
); }
let bundleDBImportResult = {};
const downloadResult = await ADCPPSocket.post(
`search/${searchInstanceId}/results/${resultId}/download`,
);
if (!isNil(downloadResult)) { if (!isNil(downloadResult)) {
bundleDBImportResult = await axios({ bundleDBImportResult = await axios({
method: "POST",
url: `${LIBRARY_SERVICE_BASE_URI}/applyAirDCPPDownloadMetadata`,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
data: {
bundleId: downloadResult.bundle_info.id,
comicObjectId,
name,
size,
type,
},
});
dispatch({
type: AIRDCPP_RESULT_DOWNLOAD_INITIATED,
downloadResult,
bundleDBImportResult,
});
dispatch({
type: IMS_COMIC_BOOK_DB_OBJECT_FETCHED,
comicBookDetail: bundleDBImportResult.data,
IMS_inProgress: false,
});
}
} 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", method: "POST",
url: `${LIBRARY_SERVICE_BASE_URI}/applyAirDCPPDownloadMetadata`, url: `${LIBRARY_SERVICE_BASE_URI}/getComicBookById`,
headers: { headers: {
"Content-Type": "application/json; charset=utf-8", "Content-Type": "application/json; charset=utf-8",
}, },
data: { data: {
bundleId: downloadResult.bundle_info.id, id: `${comicObjectId}`,
comicObjectId,
name,
size,
type,
}, },
}); });
// get only the bundles applicable for the comic
dispatch({ if (comicObject.data.acquisition.directconnect) {
type: AIRDCPP_RESULT_DOWNLOAD_INITIATED, const filteredBundles = comicObject.data.acquisition.directconnect.downloads.map(
downloadResult,
bundleDBImportResult,
});
dispatch({
type: IMS_COMIC_BOOK_DB_OBJECT_FETCHED,
comicBookDetail: bundleDBImportResult.data,
IMS_inProgress: false,
});
}
} catch (error) {
throw error;
}
};
export const getBundlesForComic =
(comicObjectId: string, ADCPPSocket: any, credentials: any) =>
async (dispatch) => {
try {
if (!ADCPPSocket.isConnected()) {
await ADCPPSocket.connect();
}
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
if (comicObject.data.acquisition.directconnect) {
const filteredBundles =
comicObject.data.acquisition.directconnect.downloads.map(
async ({ bundleId }) => { async ({ bundleId }) => {
return await ADCPPSocket.get(`queue/bundles/${bundleId}`); return await ADCPPSocket.get(`queue/bundles/${bundleId}`);
}, },
); );
dispatch({ dispatch({
type: AIRDCPP_BUNDLES_FETCHED, type: AIRDCPP_BUNDLES_FETCHED,
bundles: await Promise.all(filteredBundles), bundles: await Promise.all(filteredBundles),
}); });
}
} catch (error) {
throw error;
} }
} catch (error) { };
throw error;
}
};
export const getTransfers = export const getTransfers =
(ADCPPSocket: any, credentials: any) => async (dispatch) => { (ADCPPSocket: any, credentials: any) => async (dispatch) => {
try { try {
if (!ADCPPSocket.isConnected()) { if (!ADCPPSocket.isConnected()) {
await ADCPPSocket.connect(); await ADCPPSocket.connect(
`${credentials.username}`,
`${credentials.password}`,
true,
);
} }
const bundles = await ADCPPSocket.get("queue/bundles/1/85", {}); const bundles = await ADCPPSocket.get("queue/bundles/1/85", {});
if (!isNil(bundles)) { if (!isNil(bundles)) {
@@ -248,6 +234,7 @@ export const getTransfers =
type: LIBRARY_ISSUE_BUNDLES, type: LIBRARY_ISSUE_BUNDLES,
issue_bundles, issue_bundles,
}); });
} }
} catch (err) { } catch (err) {
throw err; throw err;

View File

@@ -62,10 +62,6 @@ pre {
margin-left: -300px; margin-left: -300px;
min-width: 500px; min-width: 500px;
} }
.airdcpp-status {
min-width: 300px;
line-height: 1.7rem;
}
body { body {
background: #454a59; background: #454a59;
} }

View File

@@ -137,4 +137,4 @@ export const App = (): ReactElement => {
); );
}; };
export default App; export default App;

View File

@@ -8,9 +8,10 @@ import { useDispatch, useSelector } from "react-redux";
import { searchIssue } from "../../actions/fileops.actions"; import { searchIssue } from "../../actions/fileops.actions";
import ellipsize from "ellipsize"; import ellipsize from "ellipsize";
/** /**
* Component that tabulates the contents of the user's ThreeTwo Library. * Component that tabulates the contents of the user's ThreeTwo Library.
* *
* @component * @component
* @example * @example
* <Library /> * <Library />
@@ -19,10 +20,9 @@ export const Library = (): ReactElement => {
const searchResults = useSelector( const searchResults = useSelector(
(state: RootState) => state.fileOps.libraryComics, (state: RootState) => state.fileOps.libraryComics,
); );
const searchError = useSelector((state: RootState) => { const searchError = useSelector(
console.log(state); (state: RootState) => state.fileOps.librarySearchError,
return state.fileOps.librarySearchError; );
});
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
dispatch( dispatch(
@@ -36,7 +36,7 @@ export const Library = (): ReactElement => {
from: 0, from: 0,
}, },
type: "all", type: "all",
trigger: "libraryPage", trigger: "libraryPage"
}, },
), ),
); );
@@ -89,67 +89,63 @@ export const Library = (): ReactElement => {
const WantedStatus = ({ value }) => { const WantedStatus = ({ value }) => {
return !value ? <span className="tag is-info is-light">Wanted</span> : null; return !value ? <span className="tag is-info is-light">Wanted</span> : null;
}; };
const columns = useMemo( const columns = useMemo(() => [
() => [ {
{ header: "Comic Metadata",
header: "Comic Metadata", footer: 1,
footer: 1, columns: [
columns: [ {
{ header: "File Details",
header: "File Details", id: "fileDetails",
id: "fileDetails", minWidth: 400,
minWidth: 400, accessorKey: "_source",
accessorKey: "_source", cell: info => {
cell: (info) => { return <MetadataPanel data={info.getValue()} />;
return <MetadataPanel data={info.getValue()} />;
},
}, },
{ },
header: "ComicInfo.xml", {
accessorKey: "_source.sourcedMetadata.comicInfo", header: "ComicInfo.xml",
align: "center", accessorKey: "_source.sourcedMetadata.comicInfo",
minWidth: 250, align: "center",
cell: (info) => minWidth: 250,
!isEmpty(info.getValue()) ? ( cell: info =>
<ComicInfoXML data={info.getValue()} /> !isEmpty(info.getValue()) ? (
) : ( <ComicInfoXML data={info.getValue()} />
<span className="tag mt-5">No ComicInfo.xml</span> ) : (
), <span className="tag mt-5">No ComicInfo.xml</span>
),
},
],
},
{
header: "Additional Metadata",
columns: [
{
header: "Publisher",
accessorKey:
"_source.sourcedMetadata.comicvine.volumeInformation",
cell: info => {
return (
!isNil(info.getValue()) && (
<h6 className="is-size-7 has-text-weight-bold">
{info.getValue().publisher.name}
</h6>
)
);
}, },
], },
}, {
{ header: "Something",
header: "Additional Metadata", accessorKey: "_source.acquisition.source.wanted",
columns: [ cell: info => {
{ !isUndefined(info.getValue()) ?
header: "Publisher", <WantedStatus value={info.getValue().toString()} /> : "Nothing";
accessorKey: "_source.sourcedMetadata.comicvine.volumeInformation",
cell: (info) => {
return (
!isNil(info.getValue()) && (
<h6 className="is-size-7 has-text-weight-bold">
{info.getValue().publisher.name}
</h6>
)
);
},
}, },
{ },
header: "Something", ],
accessorKey: "_source.acquisition.source.wanted", }
cell: (info) => { ], []);
!isUndefined(info.getValue()) ? (
<WantedStatus value={info.getValue().toString()} />
) : (
"Nothing"
);
},
},
],
},
],
[],
);
/** /**
* Pagination control that fetches the next x (pageSize) items * Pagination control that fetches the next x (pageSize) items
@@ -157,7 +153,7 @@ export const Library = (): ReactElement => {
* @param {number} pageIndex * @param {number} pageIndex
* @param {number} pageSize * @param {number} pageSize
* @returns void * @returns void
* *
**/ **/
const nextPage = useCallback((pageIndex: number, pageSize: number) => { const nextPage = useCallback((pageIndex: number, pageSize: number) => {
dispatch( dispatch(
@@ -177,6 +173,7 @@ export const Library = (): ReactElement => {
); );
}, []); }, []);
/** /**
* Pagination control that fetches the previous x (pageSize) items * Pagination control that fetches the previous x (pageSize) items
* based on the y (pageIndex) offset from the ThreeTwo Elasticsearch index * based on the y (pageIndex) offset from the ThreeTwo Elasticsearch index
@@ -202,7 +199,7 @@ export const Library = (): ReactElement => {
from, from,
}, },
type: "all", type: "all",
trigger: "libraryPage", trigger: "libraryPage"
}, },
), ),
); );
@@ -232,27 +229,25 @@ export const Library = (): ReactElement => {
/> />
</div> </div>
</div> </div>
) : ( ): <div className="columns">
<div className="columns"> <div className="column is-two-thirds">
<div className="column is-two-thirds"> <article className="message is-link">
<article className="message is-link"> <div className="message-body">
<div className="message-body"> No comics were found in the library, Elasticsearch reports no
No comics were found in the library, Elasticsearch reports no indices. Try importing a few comics into the library and come
indices. Try importing a few comics into the library and come back.
back. </div>
</div> </article>
</article> <pre>
<pre> {!isUndefined(searchError.data) &&
{!isUndefined(searchError.data) && JSON.stringify(
JSON.stringify( searchError.data.meta.body.error.root_cause,
searchError.data.meta.body.error.root_cause, null,
null, 4,
4, )}
)} </pre>
</pre>
</div>
</div> </div>
)} </div> }
</div> </div>
</section> </section>
); );

View File

@@ -3,23 +3,15 @@ import { SearchBar } from "./GlobalSearchBar/SearchBar";
import { DownloadProgressTick } from "./ComicDetail/DownloadProgressTick"; import { DownloadProgressTick } from "./ComicDetail/DownloadProgressTick";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { isUndefined } from "lodash"; import { isUndefined, isEmpty } from "lodash";
import { format, fromUnixTime } from "date-fns"; import { AirDCPPSocketContext } from "../context/AirDCPPSocket";
const Navbar: React.FunctionComponent = (props) => { const Navbar: React.FunctionComponent = (props) => {
const downloadProgressTick = useSelector( const downloadProgressTick = useSelector(
(state: RootState) => state.airdcpp.downloadProgressData, (state: RootState) => state.airdcpp.downloadProgressData,
); );
const airDCPPConfiguration = useContext(AirDCPPSocketContext);
const airDCPPSocketConnectionStatus = useSelector( console.log(airDCPPConfiguration)
(state: RootState) => state.airdcpp.isAirDCPPSocketConnected,
);
const airDCPPSessionInfo = useSelector(
(state: RootState) => state.airdcpp.airDCPPSessionInfo,
);
const socketDisconnectionReason = useSelector(
(state: RootState) => state.airdcpp.socketDisconnectionReason,
);
return ( return (
<nav className="navbar is-fixed-top"> <nav className="navbar is-fixed-top">
<div className="navbar-brand"> <div className="navbar-brand">
@@ -82,10 +74,12 @@ const Navbar: React.FunctionComponent = (props) => {
<div className="navbar-item has-dropdown is-hoverable"> <div className="navbar-item has-dropdown is-hoverable">
<a className="navbar-link is-arrowless"> <a className="navbar-link is-arrowless">
<i className="fa-solid fa-download"></i> <i className="fa-solid fa-download"></i>
{downloadProgressTick && <div className="pulsating-circle"></div>} {downloadProgressTick && (
<div className="pulsating-circle"></div>
)}
</a> </a>
{!isUndefined(downloadProgressTick) ? ( {!isUndefined(downloadProgressTick) ? (
<div className="navbar-dropdown is-right"> <div className="navbar-dropdown download-progress-meter">
<a className="navbar-item"> <a className="navbar-item">
<DownloadProgressTick data={downloadProgressTick} /> <DownloadProgressTick data={downloadProgressTick} />
</a> </div> </a> </div>
@@ -93,55 +87,18 @@ const Navbar: React.FunctionComponent = (props) => {
</div> </div>
{/* AirDC++ socket connection status */} {/* AirDC++ socket connection status */}
<div className="navbar-item has-dropdown is-hoverable"> <div className="navbar-item has-dropdown is-hoverable">
{airDCPPSocketConnectionStatus ? ( <a className="navbar-link is-arrowless has-text-success">
<> {!isEmpty(airDCPPConfiguration.airDCPPState.socketConnectionInformation) ? (
<a className="navbar-link is-arrowless has-text-success"> <i className="fa-solid fa-bolt"></i>) : null}
<i className="fa-solid fa-bolt"></i> </a>
</a> <div className="navbar-dropdown download-progress-meter">
<div className="navbar-dropdown pt-4 pr-2 pl-2 is-right airdcpp-status"> <a className="navbar-item">
{/* AirDC++ Session Information */} <pre>{JSON.stringify(airDCPPConfiguration.airDCPPState.socketConnectionInformation, null, 2)}</pre>
</a>
<p> </div>
Last login was{" "}
<span className="tag">
{format(
fromUnixTime(airDCPPSessionInfo.user.last_login),
"dd MMMM, yyyy",
)}
</span>
</p>
<hr className="navbar-divider" />
<p>
<span className="tag has-text-success">
{airDCPPSessionInfo.user.username}
</span>
connected to{" "}
<span className="tag has-text-success">
{airDCPPSessionInfo.system_info.client_version}
</span>{" "}
with session ID{" "}
<span className="tag has-text-success">
{airDCPPSessionInfo.session_id}
</span>
</p>
{/* <pre>{JSON.stringify(airDCPPSessionInfo, null, 2)}</pre> */}
</div>
</>
) : (
<>
<a className="navbar-link is-arrowless has-text-danger">
<i className="fa-solid fa-bolt"></i>
</a>
<div className="navbar-dropdown pr-2 pl-2 is-right">
<pre>
{JSON.stringify(socketDisconnectionReason, null, 2)}
</pre>
</div>
</>
)}
</div> </div>
<div className="navbar-item has-dropdown is-hoverable is-mega"> <div className="navbar-item has-dropdown is-hoverable is-mega">
<div className="navbar-link flex">Blog</div> <div className="navbar-link flex">Blog</div>
<div id="blogDropdown" className="navbar-dropdown"> <div id="blogDropdown" className="navbar-dropdown">

View File

@@ -115,8 +115,6 @@ export const AIRDCPP_FILE_DOWNLOAD_COMPLETED =
export const LS_SINGLE_IMPORT = "LS_SINGLE_IMPORT"; export const LS_SINGLE_IMPORT = "LS_SINGLE_IMPORT";
export const AIRDCPP_BUNDLES_FETCHED = "AIRDCPP_BUNDLES_FETCHED"; export const AIRDCPP_BUNDLES_FETCHED = "AIRDCPP_BUNDLES_FETCHED";
export const AIRDCPP_DOWNLOAD_PROGRESS_TICK = "AIRDCPP_DOWNLOAD_PROGRESS_TICK"; export const AIRDCPP_DOWNLOAD_PROGRESS_TICK = "AIRDCPP_DOWNLOAD_PROGRESS_TICK";
export const AIRDCPP_SOCKET_CONNECTED = "AIRDCPP_SOCKET_CONNECTED";
export const AIRDCPP_SOCKET_DISCONNECTED = "AIRDCPP_SOCKET_DISCONNECTED";
// Transfers // Transfers
export const AIRDCPP_TRANSFERS_FETCHED = "AIRDCPP_TRANSFERS_FETCHED"; export const AIRDCPP_TRANSFERS_FETCHED = "AIRDCPP_TRANSFERS_FETCHED";

View File

@@ -1,9 +1,7 @@
import { isEmpty, isUndefined } from "lodash"; import { isEmpty, isUndefined } from "lodash";
import React, { createContext, useEffect, useState } from "react"; import React, { createContext, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { toggleAirDCPPSocketConnectionStatus } from "../actions/airdcpp.actions";
import { getSettings } from "../actions/settings.actions"; import { getSettings } from "../actions/settings.actions";
import AirDCPPSocket from "../services/DcppSearchService"; import AirDCPPSocket from "../services/DcppSearchService";
const AirDCPPSocketContextProvider = ({ children }) => { const AirDCPPSocketContextProvider = ({ children }) => {
@@ -53,35 +51,17 @@ const AirDCPPSocketContextProvider = ({ children }) => {
client: { host }, client: { host },
}, },
} = configuration; } = configuration;
const initializedAirDCPPSocket = new AirDCPPSocket({ const initializedAirDCPPSocket = new AirDCPPSocket({
protocol: `${host.protocol}`, protocol: `${host.protocol}`,
hostname: `${host.hostname}:${host.port}`, hostname: `${host.hostname}:${host.port}`,
username: `${host.username}`,
password: `${host.password}`,
}); });
// connect and disconnect handlers let socketConnectionInformation = await initializedAirDCPPSocket.connect(
initializedAirDCPPSocket.onConnected = (sessionInfo) => { `${host.username}`,
dispatch(toggleAirDCPPSocketConnectionStatus("connected", sessionInfo)); `${host.password}`,
}; true,
initializedAirDCPPSocket.onDisconnected = async ( );
reason,
code,
wasClean,
) => {
dispatch(
toggleAirDCPPSocketConnectionStatus("disconnected", {
reason,
code,
wasClean,
}),
);
};
const socketConnectionInformation = await initializedAirDCPPSocket.connect();
// update the state with the new socket connection information
persistSettings({ persistSettings({
...airDCPPState, ...airDCPPState,
airDCPPState: { airDCPPState: {

View File

@@ -9,8 +9,6 @@ import {
AIRDCPP_BUNDLES_FETCHED, AIRDCPP_BUNDLES_FETCHED,
AIRDCPP_TRANSFERS_FETCHED, AIRDCPP_TRANSFERS_FETCHED,
LIBRARY_ISSUE_BUNDLES, LIBRARY_ISSUE_BUNDLES,
AIRDCPP_SOCKET_CONNECTED,
AIRDCPP_SOCKET_DISCONNECTED,
} from "../constants/action-types"; } from "../constants/action-types";
import { LOCATION_CHANGE } from "redux-first-history"; import { LOCATION_CHANGE } from "redux-first-history";
import { isNil, isUndefined } from "lodash"; import { isNil, isUndefined } from "lodash";
@@ -26,9 +24,6 @@ const initialState = {
downloadFileStatus: {}, downloadFileStatus: {},
bundles: [], bundles: [],
transfers: [], transfers: [],
isAirDCPPSocketConnected: false,
airDCPPSessionInfo: {},
socketDisconnectionReason: {},
}; };
function airdcppReducer(state = initialState, action) { function airdcppReducer(state = initialState, action) {
@@ -100,23 +95,8 @@ function airdcppReducer(state = initialState, action) {
...state, ...state,
transfers: action.bundles, transfers: action.bundles,
}; };
case AIRDCPP_SOCKET_CONNECTED:
return {
...state,
isAirDCPPSocketConnected: true,
airDCPPSessionInfo: action.data,
};
case AIRDCPP_SOCKET_DISCONNECTED:
return {
...state,
isAirDCPPSocketConnected: false,
socketDisconnectionReason: action.data,
};
case LOCATION_CHANGE: case LOCATION_CHANGE:
return { return {
...state,
searchResults: [], searchResults: [],
isAirDCPPSearchInProgress: false, isAirDCPPSearchInProgress: false,
searchInfo: null, searchInfo: null,
@@ -125,6 +105,7 @@ function airdcppReducer(state = initialState, action) {
bundleDBImportResult: null, bundleDBImportResult: null,
// bundles: [], // bundles: [],
}; };
default: default:
return state; return state;
} }

View File

@@ -10,7 +10,7 @@ class AirDCPPSocket {
} }
const options = { const options = {
url: `${socketProtocol}://${configuration.hostname}/api/v1/`, url: `${socketProtocol}://${configuration.hostname}/api/v1/`,
autoReconnect: true, autoReconnect: false,
reconnectInterval: 5, reconnectInterval: 5,
logLevel: "verbose", logLevel: "verbose",
ignoredListenerEvents: [ ignoredListenerEvents: [
@@ -18,8 +18,6 @@ class AirDCPPSocket {
"hash_statistics", "hash_statistics",
"hub_counts_updated", "hub_counts_updated",
], ],
username: `${configuration.username}`,
password: `${configuration.password}`,
}; };
const AirDCPPSocketInstance = Socket(options, window.WebSocket as any); const AirDCPPSocketInstance = Socket(options, window.WebSocket as any);
return AirDCPPSocketInstance; return AirDCPPSocketInstance;

View File

@@ -18,7 +18,7 @@ function sleep(ms: number): Promise<NodeJS.Timeout> {
} }
export const search = async (data: SearchData) => { export const search = async (data: SearchData) => {
await SocketService.connect(); await SocketService.connect("admin", "password");
const instance: SearchInstance = await SocketService.post("search"); const instance: SearchInstance = await SocketService.post("search");
const unsubscribe = await SocketService.addListener( const unsubscribe = await SocketService.addListener(
"search", "search",

View File

@@ -6686,6 +6686,13 @@ character-reference-invalid@^1.0.0:
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560"
integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==
chevrotain@7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/chevrotain/-/chevrotain-7.0.3.tgz#f9621971a726eba804f7763edace2183d157c5d7"
integrity sha512-G634q7M5EiqNNv+0MKcQES2jmqabbi4PvUDpzjG2t+i1XQFaMCz0o8BZ8lbQbZex4RqkzJ3pOy+UwNLFlQm4eg==
dependencies:
regexp-to-ast "0.5.0"
chokidar@^2.1.8: chokidar@^2.1.8:
version "2.1.8" version "2.1.8"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
@@ -7113,7 +7120,14 @@ compromise-sentences@^0.3.0:
resolved "https://registry.yarnpkg.com/compromise-sentences/-/compromise-sentences-0.3.0.tgz#61d34039323155bc20dad10f24340daaabf86e26" resolved "https://registry.yarnpkg.com/compromise-sentences/-/compromise-sentences-0.3.0.tgz#61d34039323155bc20dad10f24340daaabf86e26"
integrity sha512-L6d5PXmmyPyWRMoJURYOM0yILhtV37zCOy9ZW8Xd6KgY+2qQBTwuokekaGL9MDpSH3/grRDL+AHKytYpGrTEmA== integrity sha512-L6d5PXmmyPyWRMoJURYOM0yILhtV37zCOy9ZW8Xd6KgY+2qQBTwuokekaGL9MDpSH3/grRDL+AHKytYpGrTEmA==
compromise@^13.11.4: compromise-strict@^0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/compromise-strict/-/compromise-strict-0.0.2.tgz#e4bf4cc386e8795c6408d3606ebb2cd31cab9fbd"
integrity sha512-1ApkPa6kjmyS1ZFhDRZgttDcnVv+Ci8U0kIIsxRBLCN0vautyPm6FROvBwoLimlU3pgIH3hMIpdNdVfe2yj3Gw==
dependencies:
chevrotain "7.0.3"
compromise@^13.11.3, compromise@^13.11.4:
version "13.11.4" version "13.11.4"
resolved "https://registry.yarnpkg.com/compromise/-/compromise-13.11.4.tgz#3136e6d2c2cca822ba7cebd140c9f27f405b67f1" resolved "https://registry.yarnpkg.com/compromise/-/compromise-13.11.4.tgz#3136e6d2c2cca822ba7cebd140c9f27f405b67f1"
integrity sha512-nBITcNdqIHSVDDluaG6guyFFCSNXN+Hu87fU8VlhkE5Z0PwTZN1nro2O7a8JcUH88nB5EOzrxd9zKfXLSNFqcg== integrity sha512-nBITcNdqIHSVDDluaG6guyFFCSNXN+Hu87fU8VlhkE5Z0PwTZN1nro2O7a8JcUH88nB5EOzrxd9zKfXLSNFqcg==
@@ -11993,9 +12007,9 @@ json2mq@^0.2.0:
string-convert "^0.2.0" string-convert "^0.2.0"
json5@^1.0.1: json5@^1.0.1:
version "1.0.2" version "1.0.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
dependencies: dependencies:
minimist "^1.2.0" minimist "^1.2.0"
@@ -12959,9 +12973,9 @@ minimist-options@4.1.0:
kind-of "^6.0.3" kind-of "^6.0.3"
minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
version "1.2.7" version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
minipass-collect@^1.0.2: minipass-collect@^1.0.2:
version "1.0.2" version "1.0.2"
@@ -15841,6 +15855,11 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2" extend-shallow "^3.0.2"
safe-regex "^1.1.0" safe-regex "^1.1.0"
regexp-to-ast@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz#56c73856bee5e1fef7f73a00f1473452ab712a24"
integrity sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==
regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3: regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3:
version "1.4.3" version "1.4.3"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"