From 2dac9a3a43b1be2c875a0d2a8132fb8f76109d79 Mon Sep 17 00:00:00 2001 From: qstokkink Date: Fri, 6 Sep 2024 15:04:13 +0200 Subject: [PATCH] Add GUI error handling per request --- src/tribler/ui/src/services/ipv8.service.ts | 45 +++-- src/tribler/ui/src/services/reporting.ts | 6 +- .../ui/src/services/tribler.service.ts | 178 +++++++++++------- 3 files changed, 145 insertions(+), 84 deletions(-) diff --git a/src/tribler/ui/src/services/ipv8.service.ts b/src/tribler/ui/src/services/ipv8.service.ts index 447727604b..cc19df0361 100644 --- a/src/tribler/ui/src/services/ipv8.service.ts +++ b/src/tribler/ui/src/services/ipv8.service.ts @@ -1,7 +1,7 @@ import { Circuit } from "@/models/circuit.model"; import { OverlayStats } from "@/models/overlay.model"; import axios, { AxiosInstance } from "axios"; -import { handleHTTPError } from "./reporting"; +import { handleHTTPError, handles } from "./reporting"; export class IPv8Service { @@ -13,40 +13,51 @@ export class IPv8Service { baseURL: this.baseURL, withCredentials: true, }); - this.http.interceptors.response.use(function (response) { return response; }, handleHTTPError); } async enableDrift(enable: boolean): Promise { - return (await this.http.put('/asyncio/drift', { enable })).data.success; + const response = await (this.http.put('/asyncio/drift', { enable: enable }, + handles(200, 400)).catch(handleHTTPError)); + if (response.status == 200) + return response.data.success; + return false; } async getDrift() { - return (await this.http.get('/asyncio/drift')).data.measurements; + const response = await (this.http.get('/asyncio/drift', handles(200, 404)).catch(handleHTTPError)); + if (response.status == 200) + return response.data.measurements; + return []; } async getTasks() { - return (await this.http.get('/asyncio/tasks')).data.tasks; + return (await (this.http.get('/asyncio/tasks', handles(200)).catch(handleHTTPError))).data.tasks; } async setAsyncioDebug(enable: boolean, slownessThreshold: number): Promise { - return (await this.http.put('/asyncio/debug', {enable: enable, slow_callback_duration: slownessThreshold})).data.success; + const response = await (this.http.put('/asyncio/debug', + {enable: enable, slow_callback_duration: slownessThreshold}, + handles(200)).catch(handleHTTPError)) + if (response.status == 200) + return response.data.success; + return false; } async getAsyncioDebug(): Promise { - return (await this.http.get('/asyncio/debug')).data; + return (await (this.http.get('/asyncio/debug', handles(200)).catch(handleHTTPError))).data; } async getOverlays() { - return (await this.http.get('/overlays')).data.overlays; + return (await (this.http.get('/overlays', handles(200)).catch(handleHTTPError))).data.overlays; } async getOverlayStatistics(): Promise { - return (await this.http.get('/overlays/statistics')).data.statistics; + return (await (this.http.get('/overlays/statistics', handles(200)).catch(handleHTTPError))).data.statistics; } async getTunnelPeers() { - return (await this.http.get('/tunnel/peers')).data.peers; + return (await (this.http.get('/tunnel/peers', handles(200)).catch(handleHTTPError))).data.peers; } async getCircuits(): Promise { @@ -54,27 +65,29 @@ export class IPv8Service { } async getRelays() { - return (await this.http.get('/tunnel/relays')).data.relays; + return (await (this.http.get('/tunnel/relays', handles(200)).catch(handleHTTPError))).data.relays; } async getExits() { - return (await this.http.get('/tunnel/exits')).data.exits; + return (await (this.http.get('/tunnel/exits', handles(200)).catch(handleHTTPError))).data.exits; } async getSwarms() { - return (await this.http.get('/tunnel/swarms')).data.swarms; + return (await (this.http.get('/tunnel/swarms', handles(200)).catch(handleHTTPError))).data.swarms; } async getDHTStatistics() { - return (await this.http.get('/dht/statistics')).data.statistics; + return (await (this.http.get('/dht/statistics', + handles(200)).catch(handleHTTPError))).data.statistics; // Crash in case of 404 } async getBuckets() { - return (await this.http.get('/dht/buckets')).data.buckets; + return (await (this.http.get('/dht/buckets', handles(200)).catch(handleHTTPError))).data.buckets; } async lookupDHTValue(hash: string) { - return this.http.get(`/dht/values/${hash}`); + return (await (this.http.get(`/dht/values/${hash}`, + handles(200)).catch(handleHTTPError))).data; // Crash in case of 404 } } diff --git a/src/tribler/ui/src/services/reporting.ts b/src/tribler/ui/src/services/reporting.ts index 3bd58b6fea..17506f1024 100644 --- a/src/tribler/ui/src/services/reporting.ts +++ b/src/tribler/ui/src/services/reporting.ts @@ -1,4 +1,4 @@ -import axios, { AxiosError } from "axios"; +import axios, { AxiosError, AxiosRequestConfig } from "axios"; export function handleHTTPError(error: Error | AxiosError) { const error_popup_text = document.querySelector("#error_popup_text"); @@ -17,3 +17,7 @@ export function handleHTTPError(error: Error | AxiosError) { } return Promise.reject(error); } + +export function handles(...handled: number[]): AxiosRequestConfig { + return { validateStatus: function (status: number) { return handled.includes(status); } } +} diff --git a/src/tribler/ui/src/services/tribler.service.ts b/src/tribler/ui/src/services/tribler.service.ts index d5d6ce36e8..34d1e1bb68 100644 --- a/src/tribler/ui/src/services/tribler.service.ts +++ b/src/tribler/ui/src/services/tribler.service.ts @@ -5,7 +5,7 @@ import { Path } from "@/models/path.model"; import { GuiSettings, Settings } from "@/models/settings.model"; import { Torrent } from "@/models/torrent.model"; import axios, { AxiosError, AxiosInstance } from "axios"; -import { handleHTTPError } from "./reporting"; +import { handleHTTPError, handles } from "./reporting"; const OnError = (event: MessageEvent) => { @@ -26,7 +26,6 @@ export class TriblerService { baseURL: this.baseURL, withCredentials: true, }); - this.http.interceptors.response.use(function (response) { return response; }, handleHTTPError); this.events = new EventSource(this.baseURL + '/events', { withCredentials: true }); this.addEventListener("tribler_exception", OnError); // Gets the GuiSettings @@ -50,185 +49,230 @@ export class TriblerService { // Downloads async getDownloads(infohash: string = '', getPeers: boolean = false, getPieces: boolean = false): Promise { - return (await this.http.get(`/downloads?infohash=${infohash}&get_peers=${+getPeers}&get_pieces=${+getPieces}`)).data.downloads; + return (await (this.http.get(`/downloads?infohash=${infohash}&get_peers=${+getPeers}&get_pieces=${+getPieces}`, + handles(200)).catch(handleHTTPError))).data.downloads; } async getDownloadFiles(infohash: string): Promise { - return (await this.http.get(`/downloads/${infohash}/files`)).data.files; + const response = await (this.http.get(`/downloads/${infohash}/files`, + handles(200, 404)).catch(handleHTTPError)); + if (response.status == 200) + return response.data.files; + return []; } async startDownload(uri: string, params: DownloadConfig = {}): Promise { - return (await this.http.put('/downloads', { ...params, uri: uri })).data.started; + const response = await (this.http.put('/downloads', { ...params, uri: uri }, + handles(200, 400, 500)).catch(handleHTTPError)); + if (response.status == 200) + return response.data.started; + return false; } async startDownloadFromFile(torrent: File, params: DownloadConfig = {}): Promise { - return (await this.http.put('/downloads', torrent, { - params: params, - headers: { - 'Content-Type': 'applications/x-bittorrent' - } - })).data.started; + const options = handles(200, 400, 500); + options['params'] = params; + options['headers'] = { 'Content-Type': 'applications/x-bittorrent' }; + const response = await (this.http.put('/downloads', torrent, options).catch(handleHTTPError)); + if (response.status == 200) + return response.data.started; + return false; } async stopDownload(infohash: string): Promise { - const response = await this.http.patch(`/downloads/${infohash}`, { state: 'stop' }); - return response.data.modified; + const response = await (this.http.patch(`/downloads/${infohash}`, { state: 'stop' }, + handles(200, 400, 404, 500)).catch(handleHTTPError)); + if (response.status == 200) + return response.data.modified; + return false; } async resumeDownload(infohash: string): Promise { - const response = await this.http.patch(`/downloads/${infohash}`, { state: 'resume' }); - return response.data.modified; + const response = await (this.http.patch(`/downloads/${infohash}`, { state: 'resume' }, + handles(200, 400, 404, 500)).catch(handleHTTPError)); + if (response.status == 200) + return response.data.modified; + return false; } async recheckDownload(infohash: string): Promise { - const response = await this.http.patch(`/downloads/${infohash}`, { state: 'recheck' }); - return response.data.modified; + const response = await (this.http.patch(`/downloads/${infohash}`, { state: 'recheck' }, + handles(200, 400, 404, 500)).catch(handleHTTPError)); + if (response.status == 200) + return response.data.modified; + return false; } async moveDownload(infohash: string, dest_dir: string): Promise { - const response = await this.http.patch(`/downloads/${infohash}`, { state: 'move_storage', dest_dir: dest_dir }); - return response.data.modified; + const response = await (this.http.patch(`/downloads/${infohash}`, { state: 'move_storage', dest_dir: dest_dir }, + handles(200, 400, 404, 500)).catch(handleHTTPError)); + if (response.status == 200) + return response.data.modified; + return false; } async setDownloadHops(infohash: string, anon_hops: number): Promise { - const response = await this.http.patch(`/downloads/${infohash}`, { anon_hops: anon_hops }); - return response.data.modified; + const response = await (this.http.patch(`/downloads/${infohash}`, { anon_hops: anon_hops }, + handles(200, 400, 404, 500)).catch(handleHTTPError)); + if (response.status == 200) + return response.data.modified; + return false; } async setDownloadFiles(infohash: string, selected_files: number[]): Promise { - const response = await this.http.patch(`/downloads/${infohash}`, { selected_files: selected_files }); - return response.data.modified; + const response = await (this.http.patch(`/downloads/${infohash}`, { selected_files: selected_files }, + handles(200, 400, 404, 500)).catch(handleHTTPError)); + if (response.status == 200) + return response.data.modified; + return false; } async removeDownload(infohash: string, removeData: boolean): Promise { - const response = await this.http.delete(`/downloads/${infohash}`, { data: { remove_data: (removeData) ? 1 : 0 } }); - return await response.data.removed; + const options = handles(200, 400, 404); + options['data'] = { remove_data: (removeData) ? 1 : 0 }; + const response = await (this.http.delete(`/downloads/${infohash}`, options).catch(handleHTTPError)); + if (response.status == 200) + return response.data.removed; + return false; } // Statistics async getIPv8Statistics() { - return (await this.http.get('/statistics/ipv8')).data.ipv8_statistics; + return (await (this.http.get('/statistics/ipv8', handles(200)))).data.ipv8_statistics; } async getTriblerStatistics() { - return (await this.http.get('/statistics/tribler')).data.tribler_statistics; + return (await (this.http.get('/statistics/tribler', handles(200)))).data.tribler_statistics; } // Torrents / search async getMetainfo(uri: string) { - try { - return (await this.http.get(`/torrentinfo?uri=${uri}`)).data; - } - catch (error) { - if (axios.isAxiosError(error)) { - return error.response?.data; - } - } + return (await (this.http.get(`/torrentinfo?uri=${uri}`, handles(200, 400, 500)).catch(handleHTTPError))).data; } async getMetainfoFromFile(torrent: File) { - return (await this.http.put('/torrentinfo', torrent, { - headers: { - 'Content-Type': 'applications/x-bittorrent' - } - })).data; + var options = handles(200); + options['headers'] = { 'Content-Type': 'applications/x-bittorrent' }; + return (await (this.http.put('/torrentinfo', torrent, options).catch(handleHTTPError))).data; } async getPopularTorrents(hide_xxx: boolean): Promise { - return (await this.http.get(`/metadata/torrents/popular?metadata_type=300&metadata_type=220&include_total=1&first=1&last=50&hide_xxx=${+hide_xxx}`)).data.results; + return (await (this.http.get(`/metadata/torrents/popular?metadata_type=300&metadata_type=220&include_total=1&first=1&last=50&hide_xxx=${+hide_xxx}`, + handles(200)).catch(handleHTTPError))).data.results; } async getTorrentHealth(infohash: string): Promise<{ infohash: string, num_seeders: number, num_leechers: number, last_tracker_check: number }> { - return (await this.http.get(`/metadata/torrents/${infohash}/health`)).data; + // TODO: The return value seems wrong. (200) => {'checking': bool} or (400) => {'error': string} + return (await (this.http.get(`/metadata/torrents/${infohash}/health`, + handles(200, 400)).catch(handleHTTPError))).data; } async getCompletions(txt_filter: string): Promise { - return (await this.http.get(`/metadata/search/completions?q=${txt_filter}`)).data.completions; + const response = await (this.http.get(`/metadata/search/completions?q=${txt_filter}`, + handles(200, 400)).catch(handleHTTPError)); + if (response.status == 200) + return response.data.completions; + return []; } async searchTorrentsLocal(txt_filter: string, hide_xxx: boolean): Promise { - return (await this.http.get(`/metadata/search/local?first=1&last=200&metadata_type=300&exclude_deleted=1&fts_text=${txt_filter}&hide_xxx=${+hide_xxx}`)).data.results; + const response = await (this.http.get(`/metadata/search/local?first=1&last=200&metadata_type=300&exclude_deleted=1&fts_text=${txt_filter}&hide_xxx=${+hide_xxx}`, + handles(200, 400, 404)).catch(handleHTTPError)); + if (response.status == 200) + return response.data.results; + return []; } async searchTorrentsRemote(txt_filter: string, hide_xxx: boolean): Promise<{ request_uuid: string, peers: string[] }> { - return (await this.http.put(`/search/remote?fts_text=${txt_filter}&hide_xxx=${+hide_xxx}&metadata_type=300&exclude_deleted=1`)).data; + return (await (this.http.put(`/search/remote?fts_text=${txt_filter}&hide_xxx=${+hide_xxx}&metadata_type=300&exclude_deleted=1`, + undefined, handles(200)).catch(handleHTTPError))).data; // Crash in case of 400 } // Settings async getSettings(): Promise { - const settings = (await this.http.get('/settings')).data.settings; + const settings = (await (this.http.get('/settings', handles(200)).catch(handleHTTPError))).data.settings; this.guiSettings = {...settings?.ui, ...this.guiSettings}; return settings } async setSettings(settings: Partial): Promise { this.guiSettings = {...settings?.ui, ...this.guiSettings}; - return (await this.http.post('/settings', settings)).data.modified; + return (await (this.http.post('/settings', settings, handles(200)).catch(handleHTTPError))).data.modified; } async getLibtorrentSession(hops: number) { - return (await this.http.get(`/libtorrent/session?hop=${hops}`)).data.session; + return (await (this.http.get(`/libtorrent/session?hop=${hops}`, + handles(200)).catch(handleHTTPError))).data.session; } async getLibtorrentSettings(hops: number) { - return (await this.http.get(`/libtorrent/settings?hop=${hops}`)).data.settings; + return (await (this.http.get(`/libtorrent/settings?hop=${hops}`, + handles(200)).catch(handleHTTPError))).data.settings; } // Versions async getVersion() { - return (await this.http.get(`/versioning/versions/current`)).data.version; + return (await (this.http.get(`/versioning/versions/current`, + handles(200)).catch(handleHTTPError))).data.version; } async getNewVersion() { - const version_info_json = (await this.http.get(`/versioning/versions/check`)).data; + const version_info_json = (await (this.http.get(`/versioning/versions/check`, + handles(200)).catch(handleHTTPError))).data; return (version_info_json.has_version ? version_info_json.new_version : false); } async getVersions() { - return (await this.http.get(`/versioning/versions`)).data; + return (await (this.http.get(`/versioning/versions`, handles(200)).catch(handleHTTPError))).data; } async canUpgrade() { - return (await this.http.get(`/versioning/upgrade/available`)).data.can_upgrade; + return (await (this.http.get(`/versioning/upgrade/available`, + handles(200)).catch(handleHTTPError))).data.can_upgrade; } async isUpgrading() { - return (await this.http.get(`/versioning/upgrade/working`)).data.running; + return (await (this.http.get(`/versioning/upgrade/working`, handles(200)).catch(handleHTTPError))).data.running; } async performUpgrade() { - return (await this.http.post(`/versioning/upgrade`)) + return await (this.http.post(`/versioning/upgrade`, undefined, handles(200)).catch(handleHTTPError)); } - async removeVersion(version_str: string) { - return (await this.http.delete(`/versioning/versions/${version_str}`)) + async removeVersion(version_str: string): Promise { + const response = await (this.http.delete(`/versioning/versions/${version_str}`, + handles(200, 400)).catch(handleHTTPError)); + if (response.status == 200) + return response.data.success; + return false; } // Misc async browseFiles(path: string, showFiles: boolean): Promise<{ current: string, paths: Path[] }> { - return (await this.http.get(`/files/browse?path=${path}&files=${+showFiles}`)).data; + return (await (this.http.get(`/files/browse?path=${path}&files=${+showFiles}`, + handles(200)).catch(handleHTTPError))).data; } async listFiles(path: string, recursively: boolean): Promise<{ paths: Path[] }> { - return (await this.http.get(`/files/list?path=${path}&recursively=${+recursively}`)).data; + return (await (this.http.get(`/files/list?path=${path}&recursively=${+recursively}`, + handles(200)).catch(handleHTTPError))).data; } async createTorrent(name: string, description: string, files: string[], exportDir: string, download: boolean) { - return (await this.http.post(`/createtorrent?download=${+download}`, { - name: name, - description: description, - files: files, - export_dir: exportDir - })).data.torrent; + return (await (this.http.post(`/createtorrent?download=${+download}`, { + name: name, + description: description, + files: files, + export_dir: exportDir + }, handles(200)).catch(handleHTTPError))).data.torrent; // Crash in case of 400 } async shutdown(): Promise { - return (await this.http.put(`/shutdown`)).data.shutdown; - + return (await (this.http.put(`/shutdown`, undefined, handles(200)).catch(handleHTTPError))).data.shutdown; } }