Use websocket proto for getting file stats and rate limits. Remove view_token

This commit is contained in:
2024-04-25 14:50:42 +02:00
parent c8f3f8222f
commit afd3f16181
10 changed files with 159 additions and 179 deletions

View File

@@ -1,65 +0,0 @@
import { readable } from 'svelte/store';
let limits = {
download_limit: 0,
download_limit_used: 0,
transfer_limit: 0,
transfer_limit_used: 0,
ipv6_bonus: 0,
loaded: false,
}
export const download_limits = readable(
limits,
function start(set) {
set_func = set;
loop();
return function stop() {
clearTimeout(timeout_ref);
};
}
);
let set_func;
let timeout_ref = 0;
let timeout_ms = 10000;
const loop = async () => {
let new_limits;
try {
let resp = await fetch(window.api_endpoint + "/misc/rate_limits")
if (resp.status >= 400) {
throw new Error(await resp.text())
}
new_limits = await resp.json()
new_limits.loaded = true
} catch (err) {
console.error("Failed to get rate limits: " + err)
timeout_ref = setTimeout(loop, 30000)
return
}
// If the usage has not changed we increase the timeout with one second. If
// it did change we halve the timeout
if (
new_limits.transfer_limit_used === limits.transfer_limit_used &&
new_limits.transfer_limit === limits.transfer_limit
) {
timeout_ms += 2000
if (timeout_ms > 60000) {
timeout_ms = 60000
}
} else {
timeout_ms /= 2
if (timeout_ms < 5000) {
timeout_ms = 5000
}
limits = new_limits
set_func(new_limits)
}
console.debug("Sleep", timeout_ms / 1000, "seconds")
timeout_ref = setTimeout(loop, timeout_ms)
}

View File

@@ -1,6 +1,6 @@
<script> <script>
import { onMount } from "svelte";
import { formatDataVolume, formatThousands } from "../util/Formatting.svelte" import { formatDataVolume, formatThousands } from "../util/Formatting.svelte"
import { set_file, stats } from "./StatsSocket"
export let file = { export let file = {
id: "", id: "",
@@ -10,98 +10,42 @@ export let file = {
bandwidth_used: 0, bandwidth_used: 0,
bandwidth_used_paid: 0, bandwidth_used_paid: 0,
} }
export let view_token = ""
let views = 0 let views = 0
let downloads = 0 let downloads = 0
let size = 0 let size = 0
let socket = null $: {
let error_msg = ""
$: update_stats(file.id)
let update_stats = async id => {
if (id === "" || id == "demo") {
return
}
views = file.views
if (file.size === 0) {
downloads = file.downloads
} else {
downloads = Math.round((file.bandwidth_used + file.bandwidth_used_paid) / file.size)
}
size = file.size size = file.size
send_watch_command() if ($stats.file_stats_init) {
} views = $stats.file_stats.views
const send_watch_command = () => {
if (socket !== null && socket.readyState === WebSocket.OPEN) {
socket.send(
JSON.stringify(
{cmd: "watch_file", a1: file.id, a2: view_token}
)
)
}
}
const init_socket = () => {
if (socket !== null && socket.readyState !== WebSocket.CLOSED) {
return
}
console.log("initializing socket")
socket = new WebSocket(location.origin.replace(/^http/, 'ws') + "/api/file_stats")
socket.onopen = () => send_watch_command()
socket.onmessage = msg => {
let j = JSON.parse(msg.data)
console.debug("WS update", j)
error_msg = ""
views = j.views
if (file.size === 0) { if (file.size === 0) {
downloads = j.downloads downloads = $stats.file_stats.downloads
} else { } else {
downloads = Math.round((j.bandwidth + j.bandwidth_paid) / file.size) downloads = Math.round(($stats.file_stats.bandwidth + $stats.file_stats.bandwidth_paid) / file.size)
} }
} } else {
socket.onerror = err => { views = file.views
if (socket === null) {
return
}
console.error("WS error", err)
socket.close()
socket = null
error_msg = "failed to get stats, retrying..."
window.setTimeout(init_socket, 2000) if (file.size === 0) {
downloads = file.downloads
} else {
downloads = Math.round((file.bandwidth_used + file.bandwidth_used_paid) / file.size)
}
} }
} }
onMount(() => { $: set_file(file.id)
init_socket()
return () => {
if (socket !== null) {
socket.close()
socket = null
}
}
})
</script> </script>
<div> <div>
{#if error_msg !== ""} <div class="label">Views</div>
{error_msg} <div class="stat">{formatThousands(views)}</div>
{:else} <div class="label">Downloads</div>
<div class="label">Views</div> <div class="stat">{formatThousands(downloads)}</div>
<div class="stat">{formatThousands(views)}</div> <div class="label">Size</div>
<div class="label">Downloads</div> <div class="stat">{formatDataVolume(size, 3)}</div>
<div class="stat">{formatThousands(downloads)}</div>
<div class="label">Size</div>
<div class="stat">{formatDataVolume(size, 3)}</div>
{/if}
</div> </div>
<style> <style>

View File

@@ -23,7 +23,6 @@ import CopyButton from "../layout/CopyButton.svelte";
let loading = true let loading = true
let embedded = false let embedded = false
let view_token = ""
let ads_enabled = false let ads_enabled = false
let view = "" // file or gallery let view = "" // file or gallery
@@ -88,8 +87,6 @@ onMount(() => {
toolbar_visible = false toolbar_visible = false
} }
view_token = viewer_data.view_token
if (viewer_data.type === "list") { if (viewer_data.type === "list") {
open_list(viewer_data.api_response) open_list(viewer_data.api_response)
} else { } else {
@@ -418,7 +415,7 @@ const keyboard_event = evt => {
<div class="file_preview_row"> <div class="file_preview_row">
<div class="toolbar" class:toolbar_visible> <div class="toolbar" class:toolbar_visible>
{#if view === "file"} {#if view === "file"}
<FileStats file={file} view_token={view_token}/> <FileStats file={file}/>
{:else if view === "gallery"} {:else if view === "gallery"}
<ListStats list={list}/> <ListStats list={list}/>
{/if} {/if}

View File

@@ -0,0 +1,115 @@
import { readable } from "svelte/store";
let results = {
connected: false,
file_stats_init: false,
file_stats: {
views: 0,
downloads: 0,
bandwidth: 0,
bandwidth_paid: 0,
},
limits_init: false,
limits: {
download_limit: 0,
download_limit_used: 0,
transfer_limit: 0,
transfer_limit_used: 0,
},
}
export const stats = readable(
results,
(set) => {
start_sock(set)
return stop_sock
},
);
let socket = null
const start_sock = (set_func) => {
if (socket !== null) {
return
}
console.log("initializing stats socket")
socket = new WebSocket(location.origin.replace(/^http/, 'ws') + "/api/file_stats")
socket.onopen = () => {
results.connected = true
set_func(results)
// Subscribe to the rate limit feed. This will also process any queued
// commands built up while the socket was down
send_cmd({ type: "limits" })
}
socket.onmessage = msg => {
let j = JSON.parse(msg.data)
console.debug("WS update", j)
if (j.type === "file_stats") {
results.file_stats = j.file_stats
results.file_stats_init = true
set_func(results)
} else if (j.type === "limits") {
results.limits = j.limits
results.limits_init = true
set_func(results)
} else {
console.error("Unknown ws message type", j.type, "data", msg.data)
}
}
socket.onerror = err => {
console.error("socket error", err)
stop_sock(set_func)
window.setTimeout(() => start_sock(set_func), 2000)
}
socket.onclose = () => {
stop_sock(set_func)
window.setTimeout(() => start_sock(set_func), 2000)
}
}
const stop_sock = (set_func) => {
if (socket === null) {
return
}
// Prevent error handlers from re-initializing the socket
socket.onerror = null
socket.onclose = null
// Close and delete the socket
socket.close()
socket = null
// Reset the state
results.connected = false
results.file_stats_init = false
results.limits_init = false
set_func(results)
}
export const set_file = file_id => {
send_cmd({
type: "file_stats",
data: { file_id: file_id },
})
}
let queued_commands = []
const send_cmd = cmd => {
if (socket !== null && socket.readyState === WebSocket.OPEN) {
// First empty the queue
while (queued_commands.length !== 0) {
socket.send(JSON.stringify(queued_commands.shift()))
}
// Send the requested command
socket.send(JSON.stringify(cmd))
} else if (cmd !== null) {
queued_commands.push(cmd)
console.debug("Socket is closed, command", cmd, "added to queue")
}
}

View File

@@ -1,28 +1,28 @@
<script> <script>
import { formatDataVolume } from "../util/Formatting.svelte"; import { formatDataVolume } from "../util/Formatting.svelte";
import { download_limits } from "./DownloadLimitStore.js" import { stats } from "./StatsSocket.js"
let percent = 0 let percent = 0
let title = "" let title = ""
$: { $: {
if ($download_limits.transfer_limit === 0) { if ($stats.limits.transfer_limit === 0) {
percent = 0 // Avoid division by 0 percent = 0 // Avoid division by 0
}else if ($download_limits.transfer_limit_used / $download_limits.transfer_limit > 1) { } else if ($stats.limits.transfer_limit_used / $stats.limits.transfer_limit > 1) {
percent = 100 percent = 100
} else { } else {
percent = ($download_limits.transfer_limit_used / $download_limits.transfer_limit) * 100 percent = ($stats.limits.transfer_limit_used / $stats.limits.transfer_limit) * 100
} }
title = "Download limit used: " + title = "Download limit used: " +
formatDataVolume($download_limits.transfer_limit_used, 3) + formatDataVolume($stats.limits.transfer_limit_used, 3) +
" of " + " of " +
formatDataVolume($download_limits.transfer_limit, 3); formatDataVolume($stats.limits.transfer_limit, 3);
} }
</script> </script>
<!-- Always show the outer bar to prevent layout shift --> <!-- Always show the outer bar to prevent layout shift -->
<div class="progress_bar_outer" title="{title}"> <div class="progress_bar_outer" title="{title}">
{#if $download_limits.loaded} {#if $stats.limits_init}
<div class="progress_bar_text"> <div class="progress_bar_text">
{title} {title}
</div> </div>

View File

@@ -2,16 +2,16 @@
import { formatDataVolume } from "../../util/Formatting.svelte"; import { formatDataVolume } from "../../util/Formatting.svelte";
import TextBlock from "./TextBlock.svelte"; import TextBlock from "./TextBlock.svelte";
import ProgressBar from "../../util/ProgressBar.svelte"; import ProgressBar from "../../util/ProgressBar.svelte";
import { download_limits } from "../DownloadLimitStore"; import { stats } from "../StatsSocket"
export let file = { export let file = {
size: 0, size: 0,
} }
$: transfer_left = $download_limits.transfer_limit - $download_limits.transfer_limit_used $: transfer_left = $stats.limits.transfer_limit - $stats.limits.transfer_limit_used
</script> </script>
{#if $download_limits.loaded} {#if $stats.limits_init}
<TextBlock center={true}> <TextBlock center={true}>
{#if file.size > transfer_left} {#if file.size > transfer_left}
<div class="highlight_yellow"> <div class="highlight_yellow">
@@ -24,8 +24,8 @@ $: transfer_left = $download_limits.transfer_limit - $download_limits.transfer_l
{/if} {/if}
<p> <p>
You have used {formatDataVolume($download_limits.transfer_limit_used, 3)} of You have used {formatDataVolume($stats.limits.transfer_limit_used, 3)} of
your daily {formatDataVolume($download_limits.transfer_limit, 3)} transfer your daily {formatDataVolume($stats.limits.transfer_limit, 3)} transfer
limit. When the transfer limit is exceeded your download speed will limit. When the transfer limit is exceeded your download speed will
be reduced. be reduced.
</p> </p>
@@ -39,6 +39,6 @@ $: transfer_left = $download_limits.transfer_limit - $download_limits.transfer_l
</strong> </strong>
</p> </p>
<ProgressBar total={$download_limits.transfer_limit} used={$download_limits.transfer_limit_used}></ProgressBar> <ProgressBar total={$stats.limits.transfer_limit} used={$stats.limits.transfer_limit_used}></ProgressBar>
</TextBlock> </TextBlock>
{/if} {/if}

View File

@@ -12,7 +12,7 @@ import { file_type } from "../FileUtilities.svelte";
import RateLimit from "./RateLimit.svelte"; import RateLimit from "./RateLimit.svelte";
import Torrent from "./Torrent.svelte"; import Torrent from "./Torrent.svelte";
import SpeedLimit from "./SpeedLimit.svelte"; import SpeedLimit from "./SpeedLimit.svelte";
import { download_limits } from "../DownloadLimitStore"; import { stats } from "../StatsSocket";
import Zip from "./Zip.svelte"; import Zip from "./Zip.svelte";
let viewer let viewer
@@ -60,7 +60,7 @@ export const toggle_playback = () => {
</div> </div>
{:else if viewer_type === "abuse"} {:else if viewer_type === "abuse"}
<Abuse bind:this={viewer} on:download></Abuse> <Abuse bind:this={viewer} on:download></Abuse>
{:else if !premium_download && $download_limits.transfer_limit_used > $download_limits.transfer_limit} {:else if !premium_download && $stats.limits.transfer_limit_used > $stats.limits.transfer_limit}
<SpeedLimit file={current_file} on:download></SpeedLimit> <SpeedLimit file={current_file} on:download></SpeedLimit>
{:else if viewer_type === "rate_limit"} {:else if viewer_type === "rate_limit"}
<RateLimit bind:this={viewer} on:download></RateLimit> <RateLimit bind:this={viewer} on:download></RateLimit>

View File

@@ -1,7 +1,7 @@
<script> <script>
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import { formatDataVolume } from "../../util/Formatting.svelte"; import { formatDataVolume } from "../../util/Formatting.svelte";
import { download_limits } from "../DownloadLimitStore"; import { stats } from "../StatsSocket";
import IconBlock from "./IconBlock.svelte"; import IconBlock from "./IconBlock.svelte";
import TextBlock from "./TextBlock.svelte"; import TextBlock from "./TextBlock.svelte";
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()
@@ -36,15 +36,15 @@ let file = {
</h1> </h1>
<p> <p>
You have reached your download limit for today. Without a pixeldrain You have reached your download limit for today. Without a pixeldrain
account you are limited to downloading {$download_limits.download_limit} files account you are limited to downloading {$stats.limits.download_limit} files
or {formatDataVolume($download_limits.transfer_limit, 3)} per 48 hours. This limit or {formatDataVolume($stats.limits.transfer_limit, 3)} per 48 hours. This limit
is counted per IP address, so if you're on a shared network it's is counted per IP address, so if you're on a shared network it's
possible that others have also contributed to this limit. possible that others have also contributed to this limit.
</p> </p>
<p> <p>
In the last 24 hours you have downloaded In the last 24 hours you have downloaded
{$download_limits.download_limit_used} files and used {$stats.limits.download_limit_used} files and used
{formatDataVolume($download_limits.transfer_limit_used, 3)} bandwidth. {formatDataVolume($stats.limits.transfer_limit_used, 3)} bandwidth.
</p> </p>
{/if} {/if}
<p> <p>

View File

@@ -1,7 +1,7 @@
<script> <script>
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import { formatDataVolume, formatDuration } from "../../util/Formatting.svelte"; import { formatDataVolume, formatDuration } from "../../util/Formatting.svelte";
import { download_limits } from "../DownloadLimitStore"; import { stats } from "../StatsSocket";
import IconBlock from "./IconBlock.svelte"; import IconBlock from "./IconBlock.svelte";
import TextBlock from "./TextBlock.svelte"; import TextBlock from "./TextBlock.svelte";
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()
@@ -20,10 +20,10 @@ export let file = {
<p> <p>
Pixeldrain's free tier is supported by my Patrons (be grateful). There's Pixeldrain's free tier is supported by my Patrons (be grateful). There's
only so much that you can do with the budget they provide. only so much that you can do with the budget they provide.
{formatDataVolume($download_limits.transfer_limit, 3)} per day is about {formatDataVolume($stats.limits.transfer_limit, 3)} per day is about
the most I can give away for free while keeping it fair for everyone, the most I can give away for free while keeping it fair for everyone,
and according to our records you have already downloaded and according to our records you have already downloaded
{formatDataVolume($download_limits.transfer_limit_used, 3)}. {formatDataVolume($stats.limits.transfer_limit_used, 3)}.
</p> </p>
<p> <p>
It's not that I want to withold this file from you, it's just that I It's not that I want to withold this file from you, it's just that I

View File

@@ -16,14 +16,6 @@ import (
blackfriday "github.com/russross/blackfriday/v2" blackfriday "github.com/russross/blackfriday/v2"
) )
func (wc *WebController) viewTokenOrBust() (t string) {
var err error
if t, err = wc.api.GetMiscViewToken(); err != nil && !wc.config.ProxyAPIRequests {
log.Error("Could not get viewtoken: %s", err)
}
return t
}
func browserCompat(ua string) bool { func browserCompat(ua string) bool {
return strings.Contains(ua, "MSIE") || strings.Contains(ua, "Trident/7.0") return strings.Contains(ua, "MSIE") || strings.Contains(ua, "Trident/7.0")
} }
@@ -32,7 +24,6 @@ type fileViewerData struct {
Type string `json:"type"` // file or list Type string `json:"type"` // file or list
APIResponse interface{} `json:"api_response"` APIResponse interface{} `json:"api_response"`
CaptchaKey string `json:"captcha_key"` CaptchaKey string `json:"captcha_key"`
ViewToken string `json:"view_token"`
Embedded bool `json:"embedded"` Embedded bool `json:"embedded"`
UserAdsEnabled bool `json:"user_ads_enabled"` UserAdsEnabled bool `json:"user_ads_enabled"`
ThemeURI template.URL `json:"theme_uri"` ThemeURI template.URL `json:"theme_uri"`
@@ -101,7 +92,6 @@ func (wc *WebController) serveFileViewer(w http.ResponseWriter, r *http.Request,
var vd = fileViewerData{ var vd = fileViewerData{
CaptchaKey: wc.captchaKey(), CaptchaKey: wc.captchaKey(),
ViewToken: wc.viewTokenOrBust(),
UserAdsEnabled: templateData.User.Subscription.ID == "", UserAdsEnabled: templateData.User.Subscription.ID == "",
} }
@@ -176,7 +166,6 @@ func (wc *WebController) serveListViewer(w http.ResponseWriter, r *http.Request,
var vd = fileViewerData{ var vd = fileViewerData{
Type: "list", Type: "list",
CaptchaKey: wc.captchaSiteKey, CaptchaKey: wc.captchaSiteKey,
ViewToken: wc.viewTokenOrBust(),
UserAdsEnabled: templateData.User.Subscription.ID == "", UserAdsEnabled: templateData.User.Subscription.ID == "",
APIResponse: list, APIResponse: list,
} }