From 2bd8f42eb0624d456dfa28aac9e1cd8010a861b0 Mon Sep 17 00:00:00 2001 From: Ethan Dalool Date: Sat, 1 Oct 2022 14:30:08 -0700 Subject: [PATCH] Move http functions to new javascript file http.js. --- frontends/ycdl_flask/static/js/api.js | 94 +++++---- frontends/ycdl_flask/static/js/common.js | 140 +------------ frontends/ycdl_flask/static/js/http.js | 210 +++++++++++++++++++ frontends/ycdl_flask/templates/channel.html | 1 + frontends/ycdl_flask/templates/channels.html | 1 + 5 files changed, 280 insertions(+), 166 deletions(-) create mode 100644 frontends/ycdl_flask/static/js/http.js diff --git a/frontends/ycdl_flask/static/js/api.js b/frontends/ycdl_flask/static/js/api.js index 190384d..b8d0c7a 100644 --- a/frontends/ycdl_flask/static/js/api.js +++ b/frontends/ycdl_flask/static/js/api.js @@ -6,80 +6,100 @@ api.channels = {}; api.channels.add_channel = function add_channel(channel_id, callback) { - const url = "/add_channel"; - const data = {"channel_id": channel_id}; - return common.post(url, data, callback); + return http.post({ + url: "/add_channel", + data: {"channel_id": channel_id}, + callback: callback, + }); } api.channels.delete_channel = function delete_channel(channel_id, callback) { - const url = `/channel/${channel_id}/delete`; - const data = {}; - return common.post(url, data, callback); + return http.post({ + url: `/channel/${channel_id}/delete`, + data: {}, + callback: callback, + }); } api.channels.refresh_channel = function refresh_channel(channel_id, force, callback) { - const url = `/channel/${channel_id}/refresh`; - const data = {"force": force}; - return common.post(url, data, callback); + return http.post({ + url: `/channel/${channel_id}/refresh`, + data: {"force": force}, + callback: callback, + }); } api.channels.refresh_all_channels = function refresh_all_channels(force, callback) { - const url = "/refresh_all_channels"; - const data = {"force": force}; - return common.post(url, data, callback); + return http.post({ + url: "/refresh_all_channels", + data: {"force": force}, + callback: callback, + }); } api.channels.set_automark = function set_automark(channel_id, state, callback) { - const url = `/channel/${channel_id}/set_automark`; - const data = {"state": state}; - return common.post(url, data, callback); + return http.post({ + url: `/channel/${channel_id}/set_automark`, + data: {"state": state}, + callback: callback, + }); } api.channels.set_autorefresh = function set_autorefresh(channel_id, autorefresh, callback) { - const url = `/channel/${channel_id}/set_autorefresh`; - const data = {"autorefresh": autorefresh}; - return common.post(url, data, callback); + return http.post({ + url: `/channel/${channel_id}/set_autorefresh`, + data: {"autorefresh": autorefresh}, + callback: callback, + }); } api.channels.set_download_directory = function set_download_directory(channel_id, download_directory, callback) { - const url = `/channel/${channel_id}/set_download_directory`; - const data = {"download_directory": download_directory}; - return common.post(url, data, callback); + return http.post({ + url: `/channel/${channel_id}/set_download_directory`, + data: {"download_directory": download_directory}, + callback: callback, + }); } api.channels.set_name = function set_name(channel_id, name, callback) { - const url = `/channel/${channel_id}/set_name`; - const data = {"name": name}; - return common.post(url, data, callback); + return http.post({ + url: `/channel/${channel_id}/set_name`, + data: {"name": name}, + callback: callback, + }); } api.channels.set_queuefile_extension = function set_queuefile_extension(channel_id, extension, callback) { - const url = `/channel/${channel_id}/set_queuefile_extension`; - const data = {"extension": extension}; - return common.post(url, data, callback); + return http.post({ + url: `/channel/${channel_id}/set_queuefile_extension`, + data: {"extension": extension}, + callback: callback, + }); } api.channels.show_download_directory = function show_download_directory(channel_id, callback) { - const url = `/channel/${channel_id}/show_download_directory`; - return common.post(url, null, callback); + return http.post({ + url: `/channel/${channel_id}/show_download_directory`, + callback: callback, + }); } api.channels.callback_go_to_channels = @@ -101,15 +121,19 @@ api.videos = {}; api.videos.mark_state = function mark_state(video_ids, state, callback) { - const url = "/mark_video_state"; - const data = {"video_ids": video_ids, "state": state}; - return common.post(url, data, callback); + return http.post({ + url: "/mark_video_state", + data: {"video_ids": video_ids, "state": state}, + callback: callback, + }); } api.videos.start_download = function start_download(video_ids, callback) { - const url = "/start_download"; - const data = {"video_ids": video_ids}; - return common.post(url, data, callback); + return http.post({ + url: "/start_download", + data: {"video_ids": video_ids}, + callback: callback, + }); } diff --git a/frontends/ycdl_flask/static/js/common.js b/frontends/ycdl_flask/static/js/common.js index 3dbaf83..20b1c90 100644 --- a/frontends/ycdl_flask/static/js/common.js +++ b/frontends/ycdl_flask/static/js/common.js @@ -34,6 +34,12 @@ function is_wide_mode() return getComputedStyle(document.documentElement).getPropertyValue("--wide").trim() === "1"; } +common.go_to_root = +function go_to_root() +{ + window.location.href = "/"; +} + common.refresh = function refresh() { @@ -51,146 +57,18 @@ function refresh_or_alert(response) window.location.reload(); } -//////////////////////////////////////////////////////////////////////////////////////////////////// -// HTTP //////////////////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -common.formdata = -function formdata(data) -{ - fd = new FormData(); - for (let [key, value] of Object.entries(data)) - { - if (value === undefined) - { - continue; - } - if (value === null) - { - value = ''; - } - fd.append(key, value); - } - return fd; -} - -common._request = -function _request(method, url, callback) -{ - /* - Perform an HTTP request and call the `callback` with the response. - - The response will have the following structure: - { - "meta": { - "completed": true / false, - "status": If the connection failed or request otherwise could not - complete, `status` will be 0. If the request completed, - `status` will be the HTTP response code. - "json_ok": If the server responded with parseable json, `json_ok` - will be true, and that data will be in `response.data`. If the - server response was not parseable json, `json_ok` will be false - and `response.data` will be undefined. - "request_url": The URL exactly as given to this call. - } - "data": {JSON parsed from server response if json_ok}. - } - - So, from most lenient to most strict, error catching might look like: - if response.meta.completed - if response.meta.json_ok - if response.meta.status === 200 - if response.meta.status === 200 and response.meta.json_ok - */ - const request = new XMLHttpRequest(); - const response = { - "meta": { - "completed": false, - "status": 0, - "json_ok": false, - "request_url": url, - }, - }; - - request.onreadystatechange = function() - { - /* - readystate values: - 0 UNSENT / ABORTED - 1 OPENED - 2 HEADERS_RECEIVED - 3 LOADING - 4 DONE - */ - if (request.readyState != 4) - {return;} - - if (callback == null) - {return;} - - response.meta.status = request.status; - - if (request.status != 0) - { - response.meta.completed = true; - try - { - response.data = JSON.parse(request.responseText); - response.meta.json_ok = true; - } - catch (exc) - { - response.meta.json_ok = false; - } - } - callback(response); - }; - const asynchronous = true; - request.open(method, url, asynchronous); - return request; -} - -common.get = -function get(url, callback) -{ - request = common._request("GET", url, callback); - request.send(); - return request; -} - -common.post = -function post(url, data, callback) -{ - /* - `data`: - a FormData object which you have already filled with values, or a - dictionary from which a FormData will be made, using common.formdata. - */ - if (data instanceof FormData || data === null) - { - ; - } - else - { - data = common.formdata(data); - } - request = common._request("POST", url, callback); - request.send(data); - return request; -} - //////////////////////////////////////////////////////////////////////////////////////////////////// // STRING TOOLS //////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// common.join_and_trail = -function join_and_trail(l, s) +function join_and_trail(list, separator) { - if (l.length === 0) + if (list.length === 0) { return ""; } - return l.join(s) + s + return list.join(separator) + separator } //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/frontends/ycdl_flask/static/js/http.js b/frontends/ycdl_flask/static/js/http.js new file mode 100644 index 0000000..42547cf --- /dev/null +++ b/frontends/ycdl_flask/static/js/http.js @@ -0,0 +1,210 @@ +const http = {}; + +http.HEADERS = {}; + +http.requests_in_flight = 0; + +http.request_queue = {}; +http.request_queue.array = []; + +http.request_queue.push = +function request_queue_push(func) +{ + http.request_queue.array.push(func) + if (http.requests_in_flight == 0) + { + http.request_queue_next(); + } +} + +http.request_queue.unshift = +function request_queue_unshift(func) +{ + http.request_queue.array.unshift(func) + if (http.requests_in_flight == 0) + { + http.request_queue_next(); + } +} +http.request_queue.pushleft = http.request_queue.unshift; + +http.request_queue_next = +function request_queue_next() +{ + if (http.requests_in_flight > 0) + { + return; + } + if (http.request_queue.array.length === 0) + { + return; + } + const func = http.request_queue.array.shift(); + func(); +} + +http.formdata = +function formdata(data) +{ + const fd = new FormData(); + for (let [key, value] of Object.entries(data)) + { + if (value === undefined) + { + continue; + } + if (value === null) + { + value = ''; + } + fd.append(key, value); + } + return fd; +} + +http._request = +function _request(kwargs) +{ + /* + Perform an HTTP request and call the `callback` with the response. + + The response will have the following structure: + { + "meta": { + "request": the XMLHttpRequest object, + "completed": true / false, + "status": If the connection failed or request otherwise could not + complete, `status` will be 0. If the request completed, + `status` will be the HTTP response code. + "json_ok": If the server responded with parseable json, `json_ok` + will be true, and that data will be in `response.data`. If the + server response was not parseable json, `json_ok` will be false + and `response.data` will be undefined. + "kwargs": The kwargs exactly as given to this call. + } + "data": {JSON parsed from server response if json_ok}, + "retry": function you can call to retry the request. + } + + So, from most lenient to most strict, error catching might look like: + if response.meta.completed + if response.meta.json_ok + if response.meta.status === 200 + if response.meta.status === 200 and response.meta.json_ok + */ + const request = new XMLHttpRequest(); + const response = { + "meta": { + "request": request, + "completed": false, + "status": 0, + "json_ok": false, + "kwargs": kwargs, + }, + "retry": function(){http._request(kwargs)}, + }; + + request.onreadystatechange = function() + { + /* + readystate values: + 0 UNSENT / ABORTED + 1 OPENED + 2 HEADERS_RECEIVED + 3 LOADING + 4 DONE + */ + if (request.readyState != 4) + { + return; + } + + http.requests_in_flight -= 1; + setTimeout(http.request_queue_next, 0); + + if (kwargs["callback"] == null) + { + return; + } + + response.meta.status = request.status; + + if (request.status != 0) + { + response.meta.completed = true; + try + { + response.data = JSON.parse(request.responseText); + response.meta.json_ok = true; + } + catch (exc) + { + response.meta.json_ok = false; + } + } + kwargs["callback"](response); + }; + + // Headers + + const asynchronous = "asynchronous" in kwargs ? kwargs["asynchronous"] : true; + request.open(kwargs["method"], kwargs["url"], asynchronous); + + for (const [header, value] of Object.entries(http.HEADERS)) + { + request.setRequestHeader(header, value); + } + + const more_headers = kwargs["headers"] || {}; + for (const [header, value] of Object.entries(more_headers)) + { + request.setRequestHeader(header, value); + } + + if (kwargs["with_credentials"]) + { + request.withCredentials = true; + } + + // Send + + let data = kwargs["data"]; + if (data === undefined || data === null) + { + request.send(); + } + else if (data instanceof FormData) + { + request.send(data); + } + else if (typeof(data) === "string" || data instanceof String) + { + request.send(data); + } + else + { + request.send(http.formdata(data)); + } + http.requests_in_flight += 1; + + return request; +} + +http.get = +function get(kwargs) +{ + kwargs["method"] = "GET"; + return http._request(kwargs); +} + +http.post = +function post(kwargs) +{ + /* + `data`: + a FormData object which you have already filled with values, or a + dictionary from which a FormData will be made, using http.formdata. + */ + kwargs["method"] = "POST"; + return http._request(kwargs); +} diff --git a/frontends/ycdl_flask/templates/channel.html b/frontends/ycdl_flask/templates/channel.html index 023439c..76b8d77 100644 --- a/frontends/ycdl_flask/templates/channel.html +++ b/frontends/ycdl_flask/templates/channel.html @@ -11,6 +11,7 @@ +