@@ -3,6 +3,15 @@ import { parseSize } from "../shared.js"
33
44export type PasteLocation = "KV" | "R2"
55
6+ // since CF does not allow expiration shorter than 60s, extend the expiration to 70s
7+ const PASTE_EXPIRE_SPECIFIED_MIN = 70
8+
9+ /* Since we need the metadata stored in KV to perform R2 cleanup,
10+ the paste in KV should not be deleted until it is cleaned in R2.
11+ We extend the lifetime by 2 days to avoid it being cleaned in VK too early
12+ */
13+ const PASTE_EXPIRE_EXTENSION_FOR_R2 = 2 * 24 * 60 * 60
14+
615// TODO: allow admin to upload permanent paste
716// TODO: add filename length check
817export type PasteMetadata = {
@@ -102,7 +111,7 @@ export async function getPaste(env: Env, short: string, ctx: ExecutionContext):
102111 if ( metadata . location === "R2" ) {
103112 const object = await env . R2 . get ( short )
104113 if ( object === null ) {
105- throw new WorkerError ( 404 , `cannot find R2 bucket of name ' ${ short } '` )
114+ return null
106115 }
107116 return { paste : object . body , metadata }
108117 } else {
@@ -143,10 +152,12 @@ export async function updatePaste(
143152 } ,
144153) {
145154 const expirationUnix = dateToUnix ( options . now ) + options . expirationSeconds
146- // since CF does not allow expiration shorter than 60s, extend the expiration to 70s
147- const expirationUnixSpecified = dateToUnix ( options . now ) + Math . max ( options . expirationSeconds , 70 )
155+ let expirationUnixSpecified =
156+ dateToUnix ( options . now ) + Math . max ( options . expirationSeconds , PASTE_EXPIRE_SPECIFIED_MIN )
148157
149158 if ( originalMetadata . location === "R2" ) {
159+ expirationUnixSpecified = expirationUnixSpecified + PASTE_EXPIRE_EXTENSION_FOR_R2
160+
150161 await env . R2 . put ( pasteName , content )
151162 }
152163 const metadata : PasteMetadata = {
@@ -182,11 +193,13 @@ export async function createPaste(
182193) {
183194 const expirationUnix = dateToUnix ( options . now ) + options . expirationSeconds
184195
185- // since CF does not allow expiration shorter than 60s, extend the expiration to 70s
186- const expirationUnixSpecified = dateToUnix ( options . now ) + Math . max ( options . expirationSeconds , 70 )
196+ let expirationUnixSpecified =
197+ dateToUnix ( options . now ) + Math . max ( options . expirationSeconds , PASTE_EXPIRE_SPECIFIED_MIN )
187198
188199 const location = options . contentLength > parseSize ( env . R2_THRESHOLD ) ! ? "R2" : "KV"
189200 if ( location === "R2" ) {
201+ expirationUnixSpecified = expirationUnixSpecified + PASTE_EXPIRE_EXTENSION_FOR_R2
202+
190203 await env . R2 . put ( pasteName , content )
191204 }
192205
@@ -226,3 +239,49 @@ export async function deletePaste(env: Env, pasteName: string, originalMetadata:
226239 await env . R2 . delete ( pasteName )
227240 }
228241}
242+
243+ export async function cleanExpiredInR2 ( env : Env , controller : ScheduledController ) {
244+ // types generated by wrangler somehow not working, so cast manually
245+ type Listed = {
246+ list_complete : false
247+ keys : KVNamespaceListKey < PasteMetadataInStorage , string > [ ]
248+ cursor : string
249+ cacheStatus : string | null
250+ }
251+
252+ const nowUnix = controller . scheduledTime / 1000
253+
254+ let numCleaned = 0
255+ const r2NamesToClean : string [ ] = [ ]
256+
257+ async function clean ( ) {
258+ await env . R2 . delete ( r2NamesToClean )
259+ numCleaned += r2NamesToClean . length
260+ r2NamesToClean . length = 0
261+ }
262+
263+ let cursor : string | null = null
264+ while ( true ) {
265+ const listed = ( await env . PB . list < PasteMetadataInStorage > ( { cursor } ) ) as Listed
266+
267+ cursor = listed . cursor
268+
269+ for ( const key of listed . keys ) {
270+ if ( key . metadata !== undefined ) {
271+ const metadata = migratePasteMetadata ( key . metadata )
272+ if ( metadata . location === "R2" && metadata . willExpireAtUnix < nowUnix ) {
273+ r2NamesToClean . push ( key . name )
274+
275+ if ( r2NamesToClean . length === 1000 ) {
276+ await clean ( )
277+ }
278+ }
279+ }
280+ }
281+
282+ if ( listed . list_complete ) break
283+ }
284+ await clean ( )
285+
286+ console . log ( `${ numCleaned } buckets cleaned` )
287+ }
0 commit comments