Add free transfer limit to filesystem

This commit is contained in:
2024-09-05 17:28:31 +02:00
parent 04efcb6505
commit 51afa2615c
30 changed files with 125 additions and 64 deletions

View File

@@ -1,6 +1,6 @@
<script>
import { formatDataVolume, formatThousands } from "../util/Formatting.svelte"
import { set_file, stats } from "./StatsSocket"
import { set_file, stats } from "src/util/StatsSocket"
export let file = {
id: "",

View File

@@ -1,115 +0,0 @@
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(set)
},
);
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,6 +1,6 @@
<script>
import { formatDataVolume } from "../util/Formatting.svelte";
import { stats } from "./StatsSocket.js"
import { stats } from "src/util/StatsSocket.js"
let percent = 0
let title = ""

View File

@@ -1,8 +1,8 @@
<script>
import { createEventDispatcher } from "svelte";
import IconBlock from "./IconBlock.svelte"
import TextBlock from "./TextBlock.svelte"
import FileTitle from "./FileTitle.svelte";
import IconBlock from "src/layout/IconBlock.svelte";
import TextBlock from "src/layout/TextBlock.svelte"
import FileTitle from "src/layout/FileTitle.svelte";
let dispatch = createEventDispatcher()

View File

@@ -1,7 +1,7 @@
<script>
import { createEventDispatcher, tick } from "svelte";
import BandwidthUsage from "./BandwidthUsage.svelte";
import FileTitle from "./FileTitle.svelte";
import FileTitle from "src/layout/FileTitle.svelte";
let dispatch = createEventDispatcher()
export let is_list = false

View File

@@ -1,8 +1,8 @@
<script>
import { formatDataVolume } from "../../util/Formatting.svelte";
import TextBlock from "./TextBlock.svelte";
import TextBlock from "src/layout/TextBlock.svelte"
import ProgressBar from "../../util/ProgressBar.svelte";
import { stats } from "../StatsSocket"
import { stats } from "src/util/StatsSocket.js"
export let file = {
size: 0,

View File

@@ -1,8 +1,8 @@
<script>
import { createEventDispatcher } from "svelte";
import BandwidthUsage from "./BandwidthUsage.svelte";
import IconBlock from "./IconBlock.svelte";
import FileTitle from "./FileTitle.svelte";
import IconBlock from "src/layout/IconBlock.svelte";
import FileTitle from "src/layout/FileTitle.svelte";
import { formatDataVolume } from "../../util/Formatting.svelte";
let dispatch = createEventDispatcher()

View File

@@ -11,9 +11,9 @@ import Abuse from "./Abuse.svelte";
import { file_type } from "../FileUtilities.svelte";
import RateLimit from "./RateLimit.svelte";
import Torrent from "./Torrent.svelte";
import SpeedLimit from "./SpeedLimit.svelte";
import { stats } from "../StatsSocket";
import { stats } from "src/util/StatsSocket.js"
import Zip from "./Zip.svelte";
import SlowDown from "src/layout/SlowDown.svelte";
let viewer
let viewer_type = "loading"
@@ -66,7 +66,13 @@ export const seek = delta => {
{:else if viewer_type === "abuse"}
<Abuse bind:this={viewer} on:download></Abuse>
{:else if !premium_download && $stats.limits.transfer_limit_used > $stats.limits.transfer_limit}
<SpeedLimit file={current_file} on:download></SpeedLimit>
<SlowDown
on:download
file_size={current_file.size}
file_name={current_file.name}
file_type={current_file.mime_type}
icon_href={current_file.icon_href}
/>
{:else if viewer_type === "rate_limit"}
<RateLimit bind:this={viewer} on:download></RateLimit>
{:else if viewer_type === "image"}

View File

@@ -1,12 +0,0 @@
<script>
export let title = ""
</script>
<h1>{title}</h1>
<style>
h1 {
text-shadow: 1px 1px 2px #000000;
line-break: anywhere;
}
</style>

View File

@@ -1,44 +0,0 @@
<script>
export let icon_href = ""
export let width = "750px"
</script>
<div class="block" style="width: {width}; max-width: 100%">
<img src={icon_href} alt="File icon" class="icon">
<div class="description">
<slot></slot>
</div>
</div>
<style>
.block {
display: flex;
flex-direction: row;
margin: 8px auto;
}
@media(max-width: 500px) {
.block {
flex-direction: column;
}
}
.icon {
flex: 0 0 auto;
margin-right: 8px;
border-radius: 8px;
/* Prevent icon from being stretched if text content is too large */
align-self: center;
width: 128px;
}
.description {
flex: 1 1 auto;
display: inline-block;
text-align: initial;
padding-left: 8px;
vertical-align: middle;
overflow-wrap: anywhere;
background-color: var(--shaded_background);
border-radius: 8px;
padding: 8px;
}
</style>

View File

@@ -1,6 +1,6 @@
<script>
import { createEventDispatcher } from "svelte"
import { swipe_nav } from "./SwipeNavigate";
import { swipe_nav } from "src/util/SwipeNavigate.ts";
let dispatch = createEventDispatcher()
export const set_file = f => {

View File

@@ -1,9 +1,9 @@
<script>
import { createEventDispatcher } from "svelte";
import { formatDataVolume } from "../../util/Formatting.svelte";
import { stats } from "../StatsSocket";
import IconBlock from "./IconBlock.svelte";
import TextBlock from "./TextBlock.svelte";
import { stats } from "src/util/StatsSocket.js"
import IconBlock from "src/layout/IconBlock.svelte";
import TextBlock from "src/layout/TextBlock.svelte"
let dispatch = createEventDispatcher()
export const set_file = f => file = f

View File

@@ -1,73 +0,0 @@
<script>
import { createEventDispatcher } from "svelte";
import { formatDataVolume, formatDuration } from "../../util/Formatting.svelte";
import { stats } from "../StatsSocket";
import IconBlock from "./IconBlock.svelte";
import TextBlock from "./TextBlock.svelte";
let dispatch = createEventDispatcher()
export let file = {
name: "",
mime_type: "",
availability: "",
size: 0,
download_speed_limit: 0,
}
</script>
<TextBlock>
<img src="/res/img/slow_down.webp" class="header_image" alt="Yea, I'm gonna need you to slow down a bit"/>
<p>
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.
{formatDataVolume($stats.limits.transfer_limit, 3)} per day is about
the most I can give away for free while keeping it fair for everyone,
and according to our records you have already downloaded
{formatDataVolume($stats.limits.transfer_limit_used, 3)}.
</p>
<p>
It's not that I want to withold this file from you, it's just that I
don't want pixeldrain to fall into bankruptcy like so many of the
websites that came before me. So if you really want this file you have a
few options:
</p>
<ul>
<li>
Come back tomorrow when your free transfer limit resets
</li>
<li>
Download the file at a rate of {file.download_speed_limit/(1<<10)}
kiB/s. This will take at least
{formatDuration((file.size/file.download_speed_limit)*1000)}
</li>
<li>
<a href="/#pro" target="_blank" class="button button_highlight">
<i class="icon">bolt</i> Upgrade your account
</a>
and earn my eternal gratitude
{#if !window.user_authenticated}
(you will need a <a href="/register">pixeldrain account</a> to
receive the benefits)
{/if}
</li>
</ul>
</TextBlock>
<IconBlock icon_href={file.icon_href}>
Name: {file.name}<br/>
Type: {file.mime_type}<br/>
<button on:click={() => {dispatch("download")}}>
<i class="icon">download</i> Download
</button>
</IconBlock>
<TextBlock>
Also, I believe you have my stapler. Please give it back.
</TextBlock>
<style>
.header_image {
width: 100%;
border-radius: 8px;
}
</style>

View File

@@ -1,97 +0,0 @@
// Dead zone before the swipe action gets detected
const swipe_inital_offset = 25
// Amount of pixels after which the navigation triggers
const swipe_trigger_offset = 75
export const swipe_nav = (node: HTMLElement, props: { enabled: boolean, prev: boolean, next: boolean }) => {
let start_x = 0
let start_y = 0
let render_offset = 0
let enabled = props.enabled === undefined ? true : props.enabled
let prev = props.prev === undefined ? true : props.prev
let next = props.next === undefined ? true : props.next
const touchstart = (e: TouchEvent) => {
start_x = e.touches[0].clientX
start_y = e.touches[0].clientY
render_offset = 0
}
const touchmove = (e: TouchEvent) => {
const offset_x = e.touches[0].clientX - start_x
if (!enabled || (offset_x < 0 && !next) || (offset_x > 0 && !prev)) {
return
}
const abs_x = Math.abs(offset_x)
const abs_y = Math.abs(e.touches[0].clientY - start_y)
const neg = offset_x < 0 ? -1 : 1
// The cursor must have moved at least 50 pixels and three times as much
// on the x axis than the y axis for it to count as a swipe
if (abs_x > swipe_inital_offset && abs_y < abs_x / 3) {
set_offset((abs_x - swipe_inital_offset) * neg, false)
} else {
set_offset(0, true)
}
}
const touchend = (e: TouchEvent) => {
if (!enabled) {
return
}
if (render_offset > swipe_trigger_offset) {
set_offset(1000, true)
node.dispatchEvent(new CustomEvent("prev"))
} else if (render_offset < -swipe_trigger_offset) {
set_offset(-1000, true)
node.dispatchEvent(new CustomEvent("next"))
} else {
set_offset(0, true)
}
}
const set_offset = (off: number, animate: boolean) => {
render_offset = off
if (off === 0) {
// Clear the transformation if the offset is zero
node.style.transform = ""
node.style.transition = ""
} else {
node.style.transform = "translateX(" + off + "px)"
if (animate) {
node.style.transition = "transform 400ms"
}
}
}
node.addEventListener("touchstart", touchstart)
node.addEventListener("touchmove", touchmove)
node.addEventListener("touchend", touchend)
// Get the child image so we can listen for the loaded event. When the
// loaded event fires we clear the transformations so that the image appears
// in the original position again
for (let i = 0; i < node.childNodes.length; i++) {
const child = node.childNodes.item(i)
if (child instanceof HTMLImageElement) {
child.addEventListener("load", () => set_offset(0, false))
}
}
return {
update(props: { enabled: boolean, prev: boolean, next: boolean }) {
enabled = props.enabled === undefined ? true : props.enabled
prev = props.prev === undefined ? true : props.prev
next = props.next === undefined ? true : props.next
set_offset(0, false)
},
destroy() {
node.removeEventListener("touchstart", touchstart)
node.removeEventListener("touchmove", touchmove)
node.removeEventListener("touchend", touchend)
}
}
}

View File

@@ -1,25 +0,0 @@
<script>
export let width = "750px"
export let center = false
</script>
<div class="block" class:center style="width: {width};">
<slot></slot>
</div>
<style>
.block {
display: block;
text-align: initial;
max-width: 99%;
overflow-wrap: anywhere;
margin: 8px auto;
background-color: var(--shaded_background);
border-radius: 8px;
padding: 8px;
}
.center {
text-align: center;
}
</style>

View File

@@ -1,12 +1,12 @@
<script>
import { createEventDispatcher } from "svelte";
import Magnet from "../../icons/Magnet.svelte";
import { formatDate } from "../../util/Formatting.svelte"
import IconBlock from "./IconBlock.svelte";
import TextBlock from "./TextBlock.svelte";
import Magnet from "src/icons/Magnet.svelte";
import { formatDate } from "src/util/Formatting.svelte"
import IconBlock from "src/layout/IconBlock.svelte";
import TextBlock from "src/layout/TextBlock.svelte";
import TorrentItem from "./TorrentItem.svelte"
import FileTitle from "./FileTitle.svelte";
import CopyButton from "../../layout/CopyButton.svelte";
import FileTitle from "src/layout/FileTitle.svelte";
import CopyButton from "src/layout/CopyButton.svelte";
let dispatch = createEventDispatcher()

View File

@@ -1,7 +1,7 @@
<script>
import { onMount, createEventDispatcher, tick } from "svelte";
import BandwidthUsage from "./BandwidthUsage.svelte";
import IconBlock from "./IconBlock.svelte";
import IconBlock from "src/layout/IconBlock.svelte";
let dispatch = createEventDispatcher()
export let is_list = false

View File

@@ -1,11 +1,11 @@
<script>
import { createEventDispatcher } from "svelte";
import { formatDataVolume, formatDate } from "../../util/Formatting.svelte"
import IconBlock from "./IconBlock.svelte";
import TextBlock from "./TextBlock.svelte";
import IconBlock from "src/layout/IconBlock.svelte";
import TextBlock from "src/layout/TextBlock.svelte"
import ZipItem from "./ZipItem.svelte";
import BandwidthUsage from "./BandwidthUsage.svelte";
import FileTitle from "./FileTitle.svelte";
import FileTitle from "src/layout/FileTitle.svelte";
let dispatch = createEventDispatcher()