From 1569d02aa38baa53d5442b3babdbf1a3aaa3aaa0 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 19 Oct 2020 14:44:55 +0200 Subject: Load thumbnails with multiple threads, use sha256 for saving image to path instead of base64 (filename limit is 256 on linux...) --- TODO | 5 - external/hash-library/LICENSE | 10 + external/hash-library/sha256.cpp | 411 +++++++++++++++++++++++++++++++++++++++ external/hash-library/sha256.h | 78 ++++++++ include/AsyncImageLoader.hpp | 32 ++- include/DownloadUtils.hpp | 1 + include/QuickMedia.hpp | 1 - src/AsyncImageLoader.cpp | 140 +++++++++---- src/Body.cpp | 2 +- src/DownloadUtils.cpp | 25 +++ src/QuickMedia.cpp | 20 +- 11 files changed, 663 insertions(+), 62 deletions(-) create mode 100644 external/hash-library/LICENSE create mode 100644 external/hash-library/sha256.cpp create mode 100644 external/hash-library/sha256.h diff --git a/TODO b/TODO index 72d4d2d..c77dc2c 100644 --- a/TODO +++ b/TODO @@ -21,15 +21,12 @@ Use fallback cjk font for regular sf::Text as well (search, tabs, chapter name w Fix some japanese fonts not rendering (full width alphanumeric?). Also add support for full chinese and korean range. Resize text vertex arrays to 0 when not visible on screen to reduce memory usage. Text already does this but its done incorrectly (copied from dchat codebase). (Is this really necessary?). -Speed up thumbnail creating (image resizing). Extract thumbnail from images that are being downloaded, while its downloading and show that while the full image is downloading (upscaled, or with blurhash). -Use one special thread to load cached files. Right now if there are multiple images on the screen and 1 needs to download while the others are cached, then the cached images wont load until that 1 image has downloaded. Add setting to disable sending typing events to the server (matrix). Support emoji (mainly for matrix), by readding Text code from dchat. Also do the same but for inline images, text editing and url colors and clicking (also clicking on inline images). Also take code from dchat to support gifs (inline in text). Use pixel buffer object for asynchronous texture transfer to gpu? is this necessary? When pressing backspace to delete text, auto search will kick in because the key repeat delay is longer on the first key. SearchBar should instead check of key press/key release state. -Set the thumbnail fallback image dimensions to the image dimensions we get from matrix. That means we can know the dimensions of images in matrix before they have finished downloading. This is good for removing popouts in the body. Add option to edit input in vim (using temporary file). Scrolling in images still messes up the |current| page sometimes, need a way to fix this. Add ctrl+i keybind when viewing an image on 4chan to reverse image search it (using google, yandex and saucenao). @@ -59,7 +56,6 @@ Support peertube (works with mpv, but need to implement search and related video Scroll to bottom when receiving a new message even if the selected message is not the last one. It should instead school if the last message is visible on the screen. Also add a tab for common directories and recently accessed files/directories (the directories would be the directory of used files). Provide a way to go to the first unread message in matrix and also show a marker in the body (maybe a red line?) where the first unread message is. -Load already downloaded images/thumbnails in a separate thread, to instantly load them instead of waiting for new downloads... Allow scrolling body item. A body item can be long and we wont be able to read all of it otherwise (such as a message on matrix). Pressing up/down should scroll such a large body item rather than moving to another one. Cleanup keybindings. Some require ctrl, some dont (4chan vs matrix for example). Add room topic beside room name in matrix (in messages tab). @@ -68,7 +64,6 @@ Add /me to matrix, emoji, reactions... Set the icon of the window to be the icon of the plugin. Nice for KDE, GNOME, etc with titlebars. Set a minimum wrap size for text. We dont want one line of text to fully fill the window vertically when the window size width is small. Its better to cut off the text and add eclipses. Support matrix html (for replies and text styling, such as greentext). -Use linear-interpolation for thumbnail creation. If --no-audio is used then music should be played with a lightweight music player instead. MPV is heavy even for music (60mb RAM). Optimize startup time. Update 4chan thread in real time, just like 4chan-x. diff --git a/external/hash-library/LICENSE b/external/hash-library/LICENSE new file mode 100644 index 0000000..e540da6 --- /dev/null +++ b/external/hash-library/LICENSE @@ -0,0 +1,10 @@ +zlib License + +Copyright (c) 2014,2015 Stephan Brumme + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. + If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/external/hash-library/sha256.cpp b/external/hash-library/sha256.cpp new file mode 100644 index 0000000..0c6138c --- /dev/null +++ b/external/hash-library/sha256.cpp @@ -0,0 +1,411 @@ +// ////////////////////////////////////////////////////////// +// sha256.cpp +// Copyright (c) 2014,2015 Stephan Brumme. All rights reserved. +// see http://create.stephan-brumme.com/disclaimer.html +// + +#include "sha256.h" + +// big endian architectures need #define __BYTE_ORDER __BIG_ENDIAN +#ifndef _MSC_VER +#include +#endif + + +/// same as reset() +SHA256::SHA256() +{ + reset(); +} + + +/// restart +void SHA256::reset() +{ + m_numBytes = 0; + m_bufferSize = 0; + + // according to RFC 1321 + m_hash[0] = 0x6a09e667; + m_hash[1] = 0xbb67ae85; + m_hash[2] = 0x3c6ef372; + m_hash[3] = 0xa54ff53a; + m_hash[4] = 0x510e527f; + m_hash[5] = 0x9b05688c; + m_hash[6] = 0x1f83d9ab; + m_hash[7] = 0x5be0cd19; +} + + +namespace +{ + inline uint32_t rotate(uint32_t a, uint32_t c) + { + return (a >> c) | (a << (32 - c)); + } + + inline uint32_t swap(uint32_t x) + { +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap32(x); +#endif +#ifdef MSC_VER + return _byteswap_ulong(x); +#endif + + return (x >> 24) | + ((x >> 8) & 0x0000FF00) | + ((x << 8) & 0x00FF0000) | + (x << 24); + } + + // mix functions for processBlock() + inline uint32_t f1(uint32_t e, uint32_t f, uint32_t g) + { + uint32_t term1 = rotate(e, 6) ^ rotate(e, 11) ^ rotate(e, 25); + uint32_t term2 = (e & f) ^ (~e & g); //(g ^ (e & (f ^ g))) + return term1 + term2; + } + + inline uint32_t f2(uint32_t a, uint32_t b, uint32_t c) + { + uint32_t term1 = rotate(a, 2) ^ rotate(a, 13) ^ rotate(a, 22); + uint32_t term2 = ((a | b) & c) | (a & b); //(a & (b ^ c)) ^ (b & c); + return term1 + term2; + } +} + + +/// process 64 bytes +void SHA256::processBlock(const void* data) +{ + // get last hash + uint32_t a = m_hash[0]; + uint32_t b = m_hash[1]; + uint32_t c = m_hash[2]; + uint32_t d = m_hash[3]; + uint32_t e = m_hash[4]; + uint32_t f = m_hash[5]; + uint32_t g = m_hash[6]; + uint32_t h = m_hash[7]; + + // data represented as 16x 32-bit words + const uint32_t* input = (uint32_t*) data; + // convert to big endian + uint32_t words[64]; + int i; + for (i = 0; i < 16; i++) +#if defined(__BYTE_ORDER) && (__BYTE_ORDER != 0) && (__BYTE_ORDER == __BIG_ENDIAN) + words[i] = input[i]; +#else + words[i] = swap(input[i]); +#endif + + uint32_t x,y; // temporaries + + // first round + x = h + f1(e,f,g) + 0x428a2f98 + words[ 0]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0x71374491 + words[ 1]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0xb5c0fbcf + words[ 2]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0xe9b5dba5 + words[ 3]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x3956c25b + words[ 4]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0x59f111f1 + words[ 5]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x923f82a4 + words[ 6]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0xab1c5ed5 + words[ 7]; y = f2(b,c,d); e += x; a = x + y; + + // secound round + x = h + f1(e,f,g) + 0xd807aa98 + words[ 8]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0x12835b01 + words[ 9]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0x243185be + words[10]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0x550c7dc3 + words[11]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x72be5d74 + words[12]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0x80deb1fe + words[13]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x9bdc06a7 + words[14]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0xc19bf174 + words[15]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 24 words + for (; i < 24; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // third round + x = h + f1(e,f,g) + 0xe49b69c1 + words[16]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0xefbe4786 + words[17]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0x0fc19dc6 + words[18]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0x240ca1cc + words[19]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x2de92c6f + words[20]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0x4a7484aa + words[21]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x5cb0a9dc + words[22]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0x76f988da + words[23]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 32 words + for (; i < 32; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // fourth round + x = h + f1(e,f,g) + 0x983e5152 + words[24]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0xa831c66d + words[25]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0xb00327c8 + words[26]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0xbf597fc7 + words[27]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0xc6e00bf3 + words[28]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0xd5a79147 + words[29]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x06ca6351 + words[30]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0x14292967 + words[31]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 40 words + for (; i < 40; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // fifth round + x = h + f1(e,f,g) + 0x27b70a85 + words[32]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0x2e1b2138 + words[33]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0x4d2c6dfc + words[34]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0x53380d13 + words[35]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x650a7354 + words[36]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0x766a0abb + words[37]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x81c2c92e + words[38]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0x92722c85 + words[39]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 48 words + for (; i < 48; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // sixth round + x = h + f1(e,f,g) + 0xa2bfe8a1 + words[40]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0xa81a664b + words[41]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0xc24b8b70 + words[42]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0xc76c51a3 + words[43]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0xd192e819 + words[44]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0xd6990624 + words[45]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0xf40e3585 + words[46]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0x106aa070 + words[47]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 56 words + for (; i < 56; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // seventh round + x = h + f1(e,f,g) + 0x19a4c116 + words[48]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0x1e376c08 + words[49]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0x2748774c + words[50]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0x34b0bcb5 + words[51]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x391c0cb3 + words[52]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0x4ed8aa4a + words[53]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x5b9cca4f + words[54]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0x682e6ff3 + words[55]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 64 words + for (; i < 64; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // eigth round + x = h + f1(e,f,g) + 0x748f82ee + words[56]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0x78a5636f + words[57]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0x84c87814 + words[58]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0x8cc70208 + words[59]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x90befffa + words[60]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0xa4506ceb + words[61]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0xbef9a3f7 + words[62]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0xc67178f2 + words[63]; y = f2(b,c,d); e += x; a = x + y; + + // update hash + m_hash[0] += a; + m_hash[1] += b; + m_hash[2] += c; + m_hash[3] += d; + m_hash[4] += e; + m_hash[5] += f; + m_hash[6] += g; + m_hash[7] += h; +} + + +/// add arbitrary number of bytes +void SHA256::add(const void* data, size_t numBytes) +{ + const uint8_t* current = (const uint8_t*) data; + + if (m_bufferSize > 0) + { + while (numBytes > 0 && m_bufferSize < BlockSize) + { + m_buffer[m_bufferSize++] = *current++; + numBytes--; + } + } + + // full buffer + if (m_bufferSize == BlockSize) + { + processBlock(m_buffer); + m_numBytes += BlockSize; + m_bufferSize = 0; + } + + // no more data ? + if (numBytes == 0) + return; + + // process full blocks + while (numBytes >= BlockSize) + { + processBlock(current); + current += BlockSize; + m_numBytes += BlockSize; + numBytes -= BlockSize; + } + + // keep remaining bytes in buffer + while (numBytes > 0) + { + m_buffer[m_bufferSize++] = *current++; + numBytes--; + } +} + + +/// process final block, less than 64 bytes +void SHA256::processBuffer() +{ + // the input bytes are considered as bits strings, where the first bit is the most significant bit of the byte + + // - append "1" bit to message + // - append "0" bits until message length in bit mod 512 is 448 + // - append length as 64 bit integer + + // number of bits + size_t paddedLength = m_bufferSize * 8; + + // plus one bit set to 1 (always appended) + paddedLength++; + + // number of bits must be (numBits % 512) = 448 + size_t lower11Bits = paddedLength & 511; + if (lower11Bits <= 448) + paddedLength += 448 - lower11Bits; + else + paddedLength += 512 + 448 - lower11Bits; + // convert from bits to bytes + paddedLength /= 8; + + // only needed if additional data flows over into a second block + unsigned char extra[BlockSize]; + + // append a "1" bit, 128 => binary 10000000 + if (m_bufferSize < BlockSize) + m_buffer[m_bufferSize] = 128; + else + extra[0] = 128; + + size_t i; + for (i = m_bufferSize + 1; i < BlockSize; i++) + m_buffer[i] = 0; + for (; i < paddedLength; i++) + extra[i - BlockSize] = 0; + + // add message length in bits as 64 bit number + uint64_t msgBits = 8 * (m_numBytes + m_bufferSize); + // find right position + unsigned char* addLength; + if (paddedLength < BlockSize) + addLength = m_buffer + paddedLength; + else + addLength = extra + paddedLength - BlockSize; + + // must be big endian + *addLength++ = (unsigned char)((msgBits >> 56) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 48) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 40) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 32) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 24) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 16) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 8) & 0xFF); + *addLength = (unsigned char)( msgBits & 0xFF); + + // process blocks + processBlock(m_buffer); + // flowed over into a second block ? + if (paddedLength > BlockSize) + processBlock(extra); +} + + +/// return latest hash as 64 hex characters +std::string SHA256::getHash() +{ + // compute hash (as raw bytes) + unsigned char rawHash[HashBytes]; + getHash(rawHash); + + // convert to hex string + std::string result; + result.reserve(2 * HashBytes); + for (int i = 0; i < HashBytes; i++) + { + static const char dec2hex[16+1] = "0123456789abcdef"; + result += dec2hex[(rawHash[i] >> 4) & 15]; + result += dec2hex[ rawHash[i] & 15]; + } + + return result; +} + + +/// return latest hash as bytes +void SHA256::getHash(unsigned char buffer[SHA256::HashBytes]) +{ + // save old hash if buffer is partially filled + uint32_t oldHash[HashValues]; + for (int i = 0; i < HashValues; i++) + oldHash[i] = m_hash[i]; + + // process remaining bytes + processBuffer(); + + unsigned char* current = buffer; + for (int i = 0; i < HashValues; i++) + { + *current++ = (m_hash[i] >> 24) & 0xFF; + *current++ = (m_hash[i] >> 16) & 0xFF; + *current++ = (m_hash[i] >> 8) & 0xFF; + *current++ = m_hash[i] & 0xFF; + + // restore old hash + m_hash[i] = oldHash[i]; + } +} + + +/// compute SHA256 of a memory block +std::string SHA256::operator()(const void* data, size_t numBytes) +{ + reset(); + add(data, numBytes); + return getHash(); +} + + +/// compute SHA256 of a string, excluding final zero +std::string SHA256::operator()(const std::string& text) +{ + reset(); + add(text.c_str(), text.size()); + return getHash(); +} diff --git a/external/hash-library/sha256.h b/external/hash-library/sha256.h new file mode 100644 index 0000000..aeaf314 --- /dev/null +++ b/external/hash-library/sha256.h @@ -0,0 +1,78 @@ +// ////////////////////////////////////////////////////////// +// sha256.h +// Copyright (c) 2014,2015 Stephan Brumme. All rights reserved. +// see http://create.stephan-brumme.com/disclaimer.html +// + +#pragma once + +//#include "hash.h" +#include + +// define fixed size integer types +#ifdef _MSC_VER +// Windows +typedef unsigned __int8 uint8_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +#else +// GCC +#include +#endif + + +/// compute SHA256 hash +/** Usage: + SHA256 sha256; + std::string myHash = sha256("Hello World"); // std::string + std::string myHash2 = sha256("How are you", 11); // arbitrary data, 11 bytes + + // or in a streaming fashion: + + SHA256 sha256; + while (more data available) + sha256.add(pointer to fresh data, number of new bytes); + std::string myHash3 = sha256.getHash(); + */ +class SHA256 //: public Hash +{ +public: + /// split into 64 byte blocks (=> 512 bits), hash is 32 bytes long + enum { BlockSize = 512 / 8, HashBytes = 32 }; + + /// same as reset() + SHA256(); + + /// compute SHA256 of a memory block + std::string operator()(const void* data, size_t numBytes); + /// compute SHA256 of a string, excluding final zero + std::string operator()(const std::string& text); + + /// add arbitrary number of bytes + void add(const void* data, size_t numBytes); + + /// return latest hash as 64 hex characters + std::string getHash(); + /// return latest hash as bytes + void getHash(unsigned char buffer[HashBytes]); + + /// restart + void reset(); + +private: + /// process 64 bytes + void processBlock(const void* data); + /// process everything left in the internal buffer + void processBuffer(); + + /// size of processed data in bytes + uint64_t m_numBytes; + /// valid bytes in m_buffer + size_t m_bufferSize; + /// bytes not processed yet + uint8_t m_buffer[BlockSize]; + + enum { HashValues = HashBytes / 4 }; + /// hash, stored as integers + uint32_t m_hash[HashValues]; +}; diff --git a/include/AsyncImageLoader.hpp b/include/AsyncImageLoader.hpp index 0b3f4bf..3189565 100644 --- a/include/AsyncImageLoader.hpp +++ b/include/AsyncImageLoader.hpp @@ -1,11 +1,16 @@ #pragma once -#include +#include "../include/Storage.hpp" #include #include #include +#include +#include #include #include +#include +#include +#include namespace QuickMedia { enum class LoadingState { @@ -23,15 +28,36 @@ namespace QuickMedia { sf::Clock texture_applied_time; }; + constexpr int NUM_IMAGE_LOAD_THREADS = 4; + class AsyncImageLoader { public: + AsyncImageLoader(); + ~AsyncImageLoader(); // Returns false if the image loader is already loading an image. In that case, this function should be called again later. // set |resize_target_size| to {0, 0} to disable resizing. // |thumbnail_data.loading_state| has to be LoadingState::NOT_LOADED when calling this! // Note: this method is not thread-safe - bool load_thumbnail(const std::string &url, bool local, sf::Vector2i resize_target_size, bool use_tor, std::shared_ptr thumbnail_data); + void load_thumbnail(const std::string &url, bool local, sf::Vector2i resize_target_size, bool use_tor, std::shared_ptr thumbnail_data); + private: + struct ThumbnailLoadData { + Path path; + Path thumbnail_path; + bool local; + std::shared_ptr thumbnail_data; + sf::Vector2i resize_target_size; + }; + + // Returns -1 if all threads are busy + int get_free_load_index() const; private: - bool loading_image = false; + bool loading_image[NUM_IMAGE_LOAD_THREADS]; + // TODO: Use curl single-threaded multi-download feature instead + std::thread download_image_thread[NUM_IMAGE_LOAD_THREADS]; std::thread load_image_thread; + std::mutex load_image_mutex; + std::condition_variable load_image_cv; + std::deque images_to_load; + bool running = true; }; } \ No newline at end of file diff --git a/include/DownloadUtils.hpp b/include/DownloadUtils.hpp index 52c5b80..e1e71df 100644 --- a/include/DownloadUtils.hpp +++ b/include/DownloadUtils.hpp @@ -18,5 +18,6 @@ namespace QuickMedia { DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector &additional_args, bool use_tor, bool use_browser_useragent = false, bool fail_on_error = true); DownloadResult download_to_string_cache(const std::string &url, std::string &result, const std::vector &additional_args, bool use_tor, bool use_browser_useragent = false); + DownloadResult download_to_file(const std::string &url, const std::string &destination_filepath, const std::vector &additional_args, bool use_tor, bool use_browser_useragent = false); DownloadResult download_to_json(const std::string &url, rapidjson::Document &result, const std::vector &additional_args, bool use_tor, bool use_browser_useragent = false, bool fail_on_error = true); } \ No newline at end of file diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp index b7e6814..955fc9c 100644 --- a/include/QuickMedia.hpp +++ b/include/QuickMedia.hpp @@ -122,7 +122,6 @@ namespace QuickMedia { bool no_video = false; bool use_system_mpv_config = false; UpscaleImageAction upscale_image_action = UpscaleImageAction::NO; - bool running = false; // TODO: Save this to config file when switching modes ImageViewMode image_view_mode = ImageViewMode::SINGLE; std::vector selected_files; diff --git a/src/AsyncImageLoader.cpp b/src/AsyncImageLoader.cpp index 00ca7ee..2f0f869 100644 --- a/src/AsyncImageLoader.cpp +++ b/src/AsyncImageLoader.cpp @@ -1,9 +1,8 @@ #include "../include/AsyncImageLoader.hpp" -#include "../include/base64_url.hpp" -#include "../include/Storage.hpp" #include "../include/DownloadUtils.hpp" #include "../include/ImageUtils.hpp" #include "../include/Scale.hpp" +#include "../external/hash-library/sha256.h" #include namespace QuickMedia { @@ -90,70 +89,141 @@ namespace QuickMedia { return ""; } - // TODO: Run in 5 different threads, and add download_to_file(_cache) to reduce memory usage in each (use -O curl option) - bool AsyncImageLoader::load_thumbnail(const std::string &url, bool local, sf::Vector2i resize_target_size, bool use_tor, std::shared_ptr thumbnail_data) { - if(loading_image) - return false; + static void create_thumbnail_if_thumbnail_smaller_than_image(const std::string &original_url, const Path &thumbnail_path, ThumbnailData *thumbnail_data, sf::Vector2i resize_target_size) { + sf::Vector2u new_image_size = to_vec2u( + clamp_to_size( + to_vec2f(thumbnail_data->image->getSize()), to_vec2f(resize_target_size))); + if(new_image_size.x < thumbnail_data->image->getSize().x || new_image_size.y < thumbnail_data->image->getSize().y) { + auto destination_image = std::make_unique(); + copy_resize(*thumbnail_data->image, *destination_image, new_image_size); + thumbnail_data->image = std::move(destination_image); + save_image_as_thumbnail_atomic(*thumbnail_data->image, thumbnail_path, get_ext(original_url)); + thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; + } + } - loading_image = true; + AsyncImageLoader::AsyncImageLoader() { + for(size_t i = 0; i < NUM_IMAGE_LOAD_THREADS; ++i) { + loading_image[i] = false; + } - assert(thumbnail_data->loading_state == LoadingState::NOT_LOADED); - thumbnail_data->loading_state = LoadingState::LOADING; + load_image_thread = std::thread([this]{ + ThumbnailLoadData thumbnail_load_data; + while(true) { + { + std::unique_lock lock(load_image_mutex); + while(images_to_load.empty() && running) load_image_cv.wait(lock); + if(!running) + break; + thumbnail_load_data = images_to_load.front(); + images_to_load.pop_front(); + } + + thumbnail_load_data.thumbnail_data->image = std::make_unique(); + if(thumbnail_load_data.thumbnail_data->image->loadFromFile(thumbnail_load_data.thumbnail_path.data)) { + fprintf(stderr, "Loaded %s from thumbnail cache\n", thumbnail_load_data.path.data.c_str()); + thumbnail_load_data.thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; + continue; + } + + if(thumbnail_load_data.local) { + if(thumbnail_load_data.thumbnail_data->image->loadFromFile(thumbnail_load_data.path.data) + && thumbnail_load_data.resize_target_size.x != 0 && thumbnail_load_data.resize_target_size.y != 0) + { + create_thumbnail_if_thumbnail_smaller_than_image(thumbnail_load_data.path.data, thumbnail_load_data.thumbnail_path, thumbnail_load_data.thumbnail_data.get(), thumbnail_load_data.resize_target_size); + } + thumbnail_load_data.thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; + } else { + thumbnail_load_data.thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; + } + } + }); + } + + AsyncImageLoader::~AsyncImageLoader() { + running = false; + { + std::unique_lock lock(load_image_mutex); + load_image_cv.notify_one(); + } + load_image_thread.join(); + } + + void AsyncImageLoader::load_thumbnail(const std::string &url, bool local, sf::Vector2i resize_target_size, bool use_tor, std::shared_ptr thumbnail_data) { + if(thumbnail_data->loading_state != LoadingState::NOT_LOADED) + return; if(url.empty()) { thumbnail_data->image = std::make_unique(); thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; - loading_image = false; - return true; + return; + } + + SHA256 sha256; + sha256.add(url.data(), url.size()); + Path thumbnail_path = get_cache_dir().join("thumbnails").join(sha256.getHash()); + if(get_file_type(thumbnail_path) == FileType::REGULAR) { + thumbnail_data->loading_state = LoadingState::LOADING; + std::unique_lock lock(load_image_mutex); + images_to_load.push_back({ url, thumbnail_path, local, thumbnail_data, resize_target_size }); + load_image_cv.notify_one(); + return; + } else if(local && get_file_type(url) == FileType::REGULAR) { + thumbnail_data->loading_state = LoadingState::LOADING; + std::unique_lock lock(load_image_mutex); + images_to_load.push_back({ url, thumbnail_path, true, thumbnail_data, resize_target_size }); + load_image_cv.notify_one(); + return; } - // TODO: Keep the thread running and use conditional variable instead to sleep until a new image should be loaded. Same in ImageViewer. - load_image_thread = std::thread([this, url, local, resize_target_size, thumbnail_data, use_tor]() mutable { - // TODO: Use sha256 instead of base64_url encoding - Path thumbnail_path = get_cache_dir().join("thumbnails").join(base64_url::encode(url)); + int free_index = get_free_load_index(); + if(free_index == -1) + return; + + loading_image[free_index] = true; + thumbnail_data->loading_state = LoadingState::LOADING; + // TODO: Keep the thread running and use conditional variable instead to sleep until a new image should be loaded. Same in ImageViewer. + download_image_thread[free_index] = std::thread([this, free_index, thumbnail_path, url, local, resize_target_size, thumbnail_data, use_tor]() mutable { thumbnail_data->image = std::make_unique(); if(thumbnail_data->image->loadFromFile(thumbnail_path.data)) { fprintf(stderr, "Loaded %s from thumbnail cache\n", url.c_str()); thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; - loading_image = false; + loading_image[free_index] = false; return; } else { if(local) { if(!thumbnail_data->image->loadFromFile(url)) { thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; - loading_image = false; + loading_image[free_index] = false; return; } } else { - std::string texture_data; - if(download_to_string_cache(url, texture_data, {}, use_tor, true) != DownloadResult::OK || !thumbnail_data->image->loadFromMemory(texture_data.data(), texture_data.size())) { + Path download_path = thumbnail_path; + download_path.append(".orig"); + if(download_to_file(url, download_path.data, {}, use_tor, true) != DownloadResult::OK || !thumbnail_data->image->loadFromFile(download_path.data)) { thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; - loading_image = false; + loading_image[free_index] = false; return; } } } - if(resize_target_size.x != 0 && resize_target_size.y != 0) { - sf::Vector2u new_image_size = to_vec2u(clamp_to_size(to_vec2f(thumbnail_data->image->getSize()), to_vec2f(resize_target_size))); - if(new_image_size.x < thumbnail_data->image->getSize().x || new_image_size.y < thumbnail_data->image->getSize().y) { - auto destination_image = std::make_unique(); - copy_resize(*thumbnail_data->image, *destination_image, new_image_size); - thumbnail_data->image = std::move(destination_image); - save_image_as_thumbnail_atomic(*thumbnail_data->image, thumbnail_path, get_ext(url)); - thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; - loading_image = false; - return; - } - } + if(resize_target_size.x != 0 && resize_target_size.y != 0) + create_thumbnail_if_thumbnail_smaller_than_image(url, thumbnail_path, thumbnail_data.get(), resize_target_size); thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; - loading_image = false; + loading_image[free_index] = false; return; }); - load_image_thread.detach(); + download_image_thread[free_index].detach(); + } - return true; + int AsyncImageLoader::get_free_load_index() const { + for(int i = 0; i < NUM_IMAGE_LOAD_THREADS; ++i) { + if(!loading_image[i]) + return i; + } + return -1; } } \ No newline at end of file diff --git a/src/Body.cpp b/src/Body.cpp index a22f3ff..0294d97 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -14,7 +14,7 @@ static const float padding_x = 10.0f; static const float image_padding_x = 5.0f; static const float padding_y = 5.0f; static const float embedded_item_padding_y = 0.0f; -static const double thumbnail_fade_duration_sec = 0.25; +static const double thumbnail_fade_duration_sec = 0.1; namespace QuickMedia { BodyItem::BodyItem(std::string _title) : diff --git a/src/DownloadUtils.cpp b/src/DownloadUtils.cpp index 8782020..fd7e7d1 100644 --- a/src/DownloadUtils.cpp +++ b/src/DownloadUtils.cpp @@ -80,6 +80,31 @@ namespace QuickMedia { } } + // TODO: Use this everywhere we want to save to file (such as manga download) + DownloadResult download_to_file(const std::string &url, const std::string &destination_filepath, const std::vector &additional_args, bool use_tor, bool use_browser_useragent) { + Path tmp_filepath = destination_filepath; + tmp_filepath.append(".tmp"); + + // TODO: Optimize with temporary '\0' + size_t dir_end = tmp_filepath.data.rfind('/'); + if(dir_end != std::string::npos && create_directory_recursive(tmp_filepath.data.substr(0, dir_end)) != 0) + return DownloadResult::ERR; + + std::vector args = additional_args; + args.push_back({ "-o", tmp_filepath.data.c_str() }); + + std::string dummy; + DownloadResult res = download_to_string(url, dummy, std::move(args), use_tor, use_browser_useragent); + if(res != DownloadResult::OK) return res; + + if(rename(tmp_filepath.data.c_str(), destination_filepath.c_str()) != 0) { + perror("rename"); + return DownloadResult::ERR; + } + + return DownloadResult::OK; + } + // TODO: Add timeout DownloadResult download_to_json(const std::string &url, rapidjson::Document &result, const std::vector &additional_args, bool use_tor, bool use_browser_useragent, bool fail_on_error) { sf::Clock timer; diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 54af0fa..659e59c 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -380,16 +380,6 @@ namespace QuickMedia { } Program::~Program() { - if(upscale_image_action != UpscaleImageAction::NO && running) { - running = false; - { - std::unique_lock lock(image_upscale_mutex); - image_upscale_cv.notify_one(); - } - image_upscale_thead.join(); - } else { - running = false; - } if(matrix) delete matrix; if(disp) @@ -506,15 +496,12 @@ namespace QuickMedia { return -2; } - running = true; image_upscale_thead = std::thread([this]{ CopyOp copy_op; - while(running) { + while(true) { { std::unique_lock lock(image_upscale_mutex); - while(images_to_upscale.empty() && running) image_upscale_cv.wait(lock); - if(!running) - break; + while(images_to_upscale.empty()) image_upscale_cv.wait(lock); copy_op = images_to_upscale.front(); images_to_upscale.pop_front(); } @@ -539,8 +526,7 @@ namespace QuickMedia { file_overwrite(copy_op.destination.data.c_str(), "1"); } }); - } else { - running = true; + image_upscale_thead.detach(); } if(strcmp(plugin_name, "file-manager") != 0 && start_dir) { -- cgit v1.2.3