diff options
-rwxr-xr-x | Makefile | 8 | ||||
-rw-r--r-- | html/index.html | 7 | ||||
-rw-r--r-- | html/whiteboard.js | 158 | ||||
-rw-r--r-- | whiteboard.cpp | 148 |
4 files changed, 147 insertions, 174 deletions
@@ -36,9 +36,15 @@ ifeq ($(CXXFLAGS),) CXXFLAGS=-g -O2 endif -CXXFLAGS+=-Wall -fPIE -std=c++20 -Wpedantic -gdwarf-4 +CXXFLAGS+=-Wall -fPIE -Wpedantic -gdwarf-4 LDFLAGS+=-pie +ifeq ($(CXX),g++-9) +CXXFLAGS+=-std=c++17 +else +CXXFLAGS+=-std=c++20 +endif + ifeq ($(CXX),clang++-10) LIBS+= \ -fuse-ld=lld-10 \ diff --git a/html/index.html b/html/index.html index 780660a..64eae90 100644 --- a/html/index.html +++ b/html/index.html @@ -14,12 +14,7 @@ <textarea rows="30" cols="80" id="board" name="board"></textarea> <br/> <br/> - <h2>Contact</h2> - Roland Reichwein<br/> - Hauptstr. 101a<br/> - 82008 Unterhaching<br/> - <a href="mailto:mail@reichwein.it">mail@reichwein.it</a><br/> - <a href="https://www.reichwein.it">https://www.reichwein.it</a><br/> + Reichwein.IT Whiteboard by <a href="https://www.reichwein.it">https://www.reichwein.it</a><br/> </div> <a id="download-a" hidden></a> diff --git a/html/whiteboard.js b/html/whiteboard.js index dd81ff0..2b35180 100644 --- a/html/whiteboard.js +++ b/html/whiteboard.js @@ -1,40 +1,81 @@ // started on main page load function init() { - - // Connect "Enter" in text field with Button click - var url = document.getElementById("url"); - url.addEventListener("keyup", function(event) { - if (event.keyCode === 13) { - event.preventDefault(); - on_start(); - } - }); + init_board(); } -function set_status(message) { - if (message == "") - message = " "; +function init_board() { + var xhr = new XMLHttpRequest(); + + // run on data received back + xhr.onreadystatechange = function() { + if (this.readyState == 3) { + //set_status("Please wait while downloading " + filename + " ..."); + return; + } + if (this.readyState != 4) { + return; + } + if (this.status != 200) { + //set_status("Server Error while retrieving " + filename + ", status: " + this.status + " " + this.statusText); + return; + } + + var file = new Blob([this.response]); + reader = new FileReader(); + reader.onload = function() { + var board = document.getElementById("board"); + board.innerHTML = reader.result; + + // Initialization done. Now we can start modifying. + board.addEventListener("input", function() {on_modify(); }); + + setInterval(function() {checkupdate();}, 2000); + } + + reader.readAsBinaryString(file); + + //set_status(""); // OK + } + + var parser = new DOMParser(); + var xmlDocument = parser.parseFromString("<request></request>", "text/xml"); + + var requestElement = xmlDocument.getElementsByTagName("request")[0]; + + var commandElement = xmlDocument.createElement("command"); + commandElement.appendChild(document.createTextNode("getfile")); + requestElement.appendChild(commandElement); + + var idElement = xmlDocument.createElement("id"); + idElement.appendChild(document.createTextNode("id1")); + requestElement.appendChild(idElement); + + xhr.open("POST", "whiteboard.fcgi", true); + xhr.setRequestHeader("Content-type", "text/xml"); + xhr.responseType = 'blob'; + xhr.send(xmlDocument); - document.getElementById("status").innerHTML = message; + //set_status("Please wait while server prepares " + filename + " ..."); } -// started on button click: get filename -function on_start() { +function on_modify() { var xhr = new XMLHttpRequest(); - + // run on data received back xhr.onreadystatechange = function() { + if (this.readyState == 3) { + //set_status("Please wait while downloading " + filename + " ..."); + return; + } if (this.readyState != 4) { return; } if (this.status != 200) { - set_status("Server Error while retrieving filename, " + filename + ", status: " + this.status + " " + this.statusText); + //set_status("Server Error while retrieving " + filename + ", status: " + this.status + " " + this.statusText); return; } - var filename = this.responseText; - - get_file(filename); + //set_status(""); // OK } var parser = new DOMParser(); @@ -43,50 +84,65 @@ function on_start() { var requestElement = xmlDocument.getElementsByTagName("request")[0]; var commandElement = xmlDocument.createElement("command"); - commandElement.appendChild(document.createTextNode("getfilename")); + commandElement.appendChild(document.createTextNode("modify")); requestElement.appendChild(commandElement); - var urlElement = xmlDocument.createElement("url"); - urlElement.appendChild(document.createTextNode(document.getElementById("url").value)); - requestElement.appendChild(urlElement); + var idElement = xmlDocument.createElement("id"); + idElement.appendChild(document.createTextNode("id1")); + requestElement.appendChild(idElement); + + var dataElement = xmlDocument.createElement("data"); + dataElement.appendChild(document.createTextNode(document.getElementById("board").value)); + requestElement.appendChild(dataElement); - var formatElement = xmlDocument.createElement("format"); - formatElement.appendChild(document.createTextNode(document.getElementById("mp3").checked ? "mp3" : "mp4")); - requestElement.appendChild(formatElement); - xhr.open("POST", "whiteboard.fcgi", true); xhr.setRequestHeader("Content-type", "text/xml"); - xhr.responseType = 'text'; + xhr.responseType = 'blob'; xhr.send(xmlDocument); - set_status("Please wait while retrieving filename..."); + //set_status("Please wait while server prepares " + filename + " ..."); } -// started on button click: get file -function get_file(filename) { - var xhr = new XMLHttpRequest(); +// checksum of string +function checksum32(s) { + var result = 0; + for (var i = 0; i < s.length; i++) { + result = ((((result >>> 1) | ((result & 1) << 31)) | 0) ^ (s.charCodeAt(i) & 0xFF)) | 0; + } + return (result & 0x7FFFFFFF) | 0; +} +// gets called by regular polling +function checkupdate() { + var xhr = new XMLHttpRequest(); + // run on data received back xhr.onreadystatechange = function() { if (this.readyState == 3) { - set_status("Please wait while downloading " + filename + " ..."); + //set_status("Please wait while downloading " + filename + " ..."); return; } if (this.readyState != 4) { return; } if (this.status != 200) { - set_status("Server Error while retrieving " + filename + ", status: " + this.status + " " + this.statusText); + //set_status("Server Error while retrieving " + filename + ", status: " + this.status + " " + this.statusText); return; } - var a = document.getElementById("download-a"); - a.setAttribute("download", filename); - var file = new Blob([this.response]); - a.href = window.URL.createObjectURL(file); - a.click(); - - set_status(""); // OK + // no change if response is text/plain + if (this.getResponseHeader("Content-Type") == "application/octet-stream") { + var file = new Blob([this.response]); + reader = new FileReader(); + reader.onload = function() { + var board = document.getElementById("board"); + board.value = reader.result; + } + + reader.readAsBinaryString(file); + } + + //set_status(""); // OK } var parser = new DOMParser(); @@ -95,22 +151,22 @@ function get_file(filename) { var requestElement = xmlDocument.getElementsByTagName("request")[0]; var commandElement = xmlDocument.createElement("command"); - commandElement.appendChild(document.createTextNode("getfile")); + commandElement.appendChild(document.createTextNode("checkupdate")); requestElement.appendChild(commandElement); - var urlElement = xmlDocument.createElement("url"); - urlElement.appendChild(document.createTextNode(document.getElementById("url").value)); - requestElement.appendChild(urlElement); + var idElement = xmlDocument.createElement("id"); + idElement.appendChild(document.createTextNode("id1")); + requestElement.appendChild(idElement); + + var checksumElement = xmlDocument.createElement("checksum"); + checksumElement.appendChild(document.createTextNode(checksum32(document.getElementById("board").value))); + requestElement.appendChild(checksumElement); - var formatElement = xmlDocument.createElement("format"); - formatElement.appendChild(document.createTextNode(document.getElementById("mp3").checked ? "mp3" : "mp4")); - requestElement.appendChild(formatElement); - xhr.open("POST", "whiteboard.fcgi", true); xhr.setRequestHeader("Content-type", "text/xml"); xhr.responseType = 'blob'; xhr.send(xmlDocument); - set_status("Please wait while server prepares " + filename + " ..."); + //set_status("Please wait while server prepares " + filename + " ..."); } diff --git a/whiteboard.cpp b/whiteboard.cpp index cecee9e..b6b3a8d 100644 --- a/whiteboard.cpp +++ b/whiteboard.cpp @@ -25,38 +25,15 @@ using namespace std::string_literals; namespace fs = std::filesystem; namespace { - - class TempDir - { - private: - fs::path m_path; - fs::path m_oldpath; - public: - TempDir() - { - char templ[] = "/tmp/downtubeXXXXXX"; - char *temp = mkdtemp(templ); - if (temp == nullptr) { - throw std::runtime_error("Can't create temporary directory."); - } - - m_path = temp; - m_oldpath = fs::current_path(); - fs::current_path(m_path); - } - ~TempDir() - { - fs::current_path(m_oldpath); - fs::remove_all(m_path); + uint32_t checksum32(const std::string& s) { + uint32_t result{0}; + for (int i = 0; i < s.size(); i++) { + result = ((result >> 1) | ((result & 1) << 31)) ^ (s[i] & 0xFF); } - - const fs::path& getPath() const {return m_path;} - }; - - std::regex re{"https?://(www\\.youtube\\.com/watch\\?v=|youtu\\.be/)[[:alnum:]_&=-]+", std::regex::extended}; // www.youtube.com/watch?v=ItjMIxS3-rI or https://youtu.be/ItjMIxS3-rI - -} // anonymous namespace + return result & 0x7FFFFFFF; + } +} int main(void) { @@ -81,6 +58,8 @@ int main(void) return 1; } + std::string filedata {""}; // initial content + while (FCGX_Accept_r(&request) >= 0) { try { char* method = FCGX_GetParam("REQUEST_METHOD", request.envp); @@ -99,107 +78,44 @@ int main(void) pt::ptree xml; std::istringstream ss{xmlData}; - pt::xml_parser::read_xml(ss, xml, pt::xml_parser::no_comments | pt::xml_parser::trim_whitespace); + pt::xml_parser::read_xml(ss, xml); - std::string url {xml.get<std::string>("request.url")}; - std::string format {xml.get<std::string>("request.format")}; std::string command {xml.get<std::string>("request.command")}; + std::string id {xml.get<std::string>("request.id")}; - //FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out); - //FCGX_FPrintF(request.out, "url: %s\r\n", url.c_str()); - //FCGX_FPrintF(request.out, "format: %s\r\n", format.c_str()); // mp3, mp4 - - if (format != "mp3" && format != "mp4") { - throw std::runtime_error("Bad format: "s + format); - } - - if (!std::regex_match(url, re)) { - throw std::runtime_error("Bad URL"); - } - - // remove trailing "&..." - size_t and_pos {url.find('&')}; - if (and_pos != std::string::npos) - url = url.substr(0, and_pos); - - //FCGX_FPrintF(request.out, "command: %s\r\n", command.c_str()); - - TempDir tempDir; - - //FCGX_FPrintF(request.out, "path: %s\r\n", tempDir.getPath().string().c_str()); - - if (command == "getfilename") { - //std::string cmd{"youtube-dl -o '%(title)s."s + format + "' --get-filename --no-call-home --restrict-filenames "s + url + " > filename.txt"}; - // Recoding to MP4 is too slow currently. So keep original format for now - std::string cmd{"xyoutube-dl -o '%(title)s.%(ext)s' --get-filename --no-call-home --restrict-filenames "s + url + " > filename.txt"}; - if (system(cmd.c_str())) - throw std::runtime_error("Can't guess filename"); - - std::string filename {File::getFile("filename.txt")}; - boost::algorithm::trim(filename); - - if (format == "mp3") - filename = fs::path{filename}.stem().string() + ".mp3"; - + if (command == "modify") { + std::string data {xml.get<std::string>("request.data")}; + filedata = data; FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out); - FCGX_FPrintF(request.out, "%s", filename.c_str()); } else if (command == "getfile") { - if (format == "mp3") { - std::string cmd{"xyoutube-dl --no-warnings --no-call-home --no-progress -x --audio-format mp3 -o 'audio.%(ext)s' --restrict-filenames "s + url}; - system(cmd.c_str()); // Ignore error - "ERROR: Stream #1:0 -> #0:1 (copy)" - seems to be ok - - std::string filedata {File::getFile("audio.mp3")}; // may throw - if (filedata.size() > 30000000) - throw std::runtime_error("File too big"); - - FCGX_PutS("Content-Type: application/octet-stream\r\n", request.out); - FCGX_FPrintF(request.out, "Content-Length: %d\r\n\r\n", filedata.size()); - FCGX_PutStr(filedata.c_str(), filedata.size(), request.out); - } else if (format == "mp4") { - //std::string cmd{"youtube-dl --no-warnings --no-call-home --no-progress --recode-video mp4 -o video.mp4 --restrict-filenames "s + url}; - // Recoding to MP4 is too slow currently. So keep original format for now - std::string cmd{"xyoutube-dl --no-warnings --no-call-home --no-progress -o video.mp4 --restrict-filenames "s + url}; - system(cmd.c_str()); // Ignore error - - // youtube-dl gets it wrong and creates, e.g. video.mkv. - // So find it and load it - fs::directory_iterator di{fs::current_path()}; - fs::path filename; - for (const auto& i: di) { - if (boost::algorithm::starts_with(i.path().filename().string(), "video."s)) { - filename = i.path().filename().string(); - break; - } - } - - if (filename.empty()) { - throw std::runtime_error("No video file found."); - } - - std::string filedata {File::getFile(filename)}; // may throw - - if (filedata.size() > 300000000) - throw std::runtime_error("File too big"); - + if (filedata.size() > 30000000) + throw std::runtime_error("File too big"); + + FCGX_PutS("Content-Type: application/octet-stream\r\n", request.out); + FCGX_FPrintF(request.out, "Content-Length: %d\r\n\r\n", filedata.size()); + FCGX_PutStr(filedata.c_str(), filedata.size(), request.out); + } else if (command == "checkupdate") { + std::string checksum_s {xml.get<std::string>("request.checksum")}; + uint32_t checksum{static_cast<uint32_t>(stoul(checksum_s))}; + + //std::cout << "Checksum JS: " << checksum_s << std::endl; + //std::cout << "Checksum C++: " << checksum32(filedata) << std::endl; + + if (checksum != checksum32(filedata)) { + //std::cout << "Sending change..." << std::endl; FCGX_PutS("Content-Type: application/octet-stream\r\n", request.out); FCGX_FPrintF(request.out, "Content-Length: %d\r\n\r\n", filedata.size()); FCGX_PutStr(filedata.c_str(), filedata.size(), request.out); } else { - throw std::runtime_error("Bad format for unknown reason: "s + format); // should have been caught above already! + //std::cout << "No change..." << std::endl; + FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out); + FCGX_PutS("No change.\r\n", request.out); } } else { throw std::runtime_error("Bad command: "s + command); } - // Name: - // youtube-dl -o "%(title)s.mp3" --get-filename --no-call-home --restrict-filenames $SOURCE > filename.txt - // - // MP3: - // youtube-dl --no-warnings --no-call-home --no-progress -x --audio-format mp3 --embed-thumbnail -o "%(title)s.(ext)s" --restrict-filenames $SOURCE - // - // MP4: - // youtube-dl --no-warnings --no-call-home --no-progress --recode-video mp4 -o "%(title)s.(ext)s" --restrict-filenames $SOURCE } else { throw std::runtime_error("Unsupported method.\r\n"); } |