Compare commits

..

No commits in common. "cf74ffb0e26e257f03fc467f2f0d1846533ba0df" and "29007b0e29c5ecff6a7b6824086bb08c6758ac71" have entirely different histories.

13 changed files with 364 additions and 594 deletions

View file

@ -6,24 +6,24 @@
"" ""
"Downloaded pages go into `archive/` next to the executable." "Downloaded pages go into `archive/` next to the executable."
"Database goes into `archiver.db*` next to the executable." "Database goes into `archiver.db*` next to the executable."
"The database is necessary to store your download progress and resume where you left off if the process is interrupted.")) "The database is necessary to store your download progress and resume where you left off if the process is interrupted.")
(ps ""
"Default output style is `progress` in a tty and `lines` otherwise."))
(flag (output-quiet?) (flag (output-quiet?)
("-q" "--output-quiet" "disable progress output") ("-q" "--output-quiet" "disable progress output")
(output-quiet? #t)) (output-quiet? #t))
(flag (output-progress?)
("-p" "--output-progress" "progress output for terminals (default in a tty)")
(output-progress? #t))
(flag (output-lines?) (flag (output-lines?)
("-l" "--output-lines" "output the name of each file downloaded (default outside of a tty)") ("-l" "--output-lines" "output the name of each file downloaded")
(output-lines? #t)) (output-lines? #t))
(flag (output-progress?)
("-p" "--output-progress" "progress output for terminals")
(output-progress? #t))
(constraint (one-of output-quiet? output-lines? output-progress?)) (constraint (one-of output-quiet? output-lines? output-progress?))
(program (program
(start [wikiname "wikiname to download"]) (start [wikiname "wikiname to download"])
;; set up arguments ;; set up arguments
@ -42,27 +42,30 @@
;; check ;; check
(when (or (not wikiname) (equal? wikiname "")) (when (or (not wikiname) (equal? wikiname ""))
(raise-user-error "Please specify the wikiname to download on the command line.")) (raise-user-error "Please specify the wikiname to download on the command line."))
;; progress reporting based on selected mode ;; stage 1
(define (report-progress a b c) (cond [(output-lines?) (displayln "Downloading list of pages...")]
(define basename (basename->name-for-query c)) [(output-progress?) (printf "Downloading list of pages... \r")])
(cond (if-necessary-download-list-of-pages
[(output-lines?) wikiname
(displayln basename)] (λ (a b c)
[(output-progress?) (cond [(output-progress?) (printf "Downloading list of pages... [~a/~b]\r" a b)])))
(when (eq? (modulo a 20) 0) ;; stage 2
(thread (λ () (update-width)))) (save-each-page
(define prefix (format "[~a] [~a/~a] " wikiname a b)) wikiname
(define rest (- width (string-length prefix))) (λ (a b c)
(define real-width (min (string-length basename) rest)) (define basename (basename->name-for-query c))
(define spare-width (- rest real-width)) (cond
(define name-display (substring basename 0 real-width)) [(output-lines?)
(define whitespace (make-string spare-width #\ )) (displayln basename)]
(printf "~a~a~a\r" prefix name-display whitespace)])) [(output-progress?)
;; download all stages (when (eq? (modulo a 20) 0)
(for ([stage all-stages] (thread (λ () (update-width))))
[i (in-naturals 1)]) (define prefix (format "[~a/~a] " a b))
(printf "> Stage ~a/~a~n" i (length all-stages)) (define rest (- width (string-length prefix)))
(stage wikiname report-progress) (define real-width (min (string-length basename) rest))
(displayln ""))) (define spare-width (- rest real-width))
(define name-display (substring basename 0 real-width))
(define whitespace (make-string spare-width #\ ))
(printf "~a~a~a\r" prefix name-display whitespace)]))))
(run start) (run start)

View file

@ -1,7 +1,7 @@
#lang racket/base #lang racket/base
(require racket/file (require racket/list
racket/list
racket/path racket/path
racket/runtime-path
racket/string racket/string
json json
json-pointer json-pointer
@ -9,16 +9,9 @@
"../lib/syntax.rkt") "../lib/syntax.rkt")
(provide (provide
get-slc slc)
query-exec*
query-rows*
query-list*
query-value*
query-maybe-value*
query-maybe-row*)
(define storage-path (anytime-path ".." "storage")) (define-runtime-path database-file "../storage/archiver.db")
(define database-file (build-path storage-path "archiver.db"))
(define migrations (define migrations
(wrap-sql (wrap-sql
@ -32,50 +25,23 @@
(query-exec slc "alter table wiki add column license_text TEXT") (query-exec slc "alter table wiki add column license_text TEXT")
(query-exec slc "alter table wiki add column license_url TEXT")))) (query-exec slc "alter table wiki add column license_url TEXT"))))
(define slc (box #f)) (define slc (sqlite3-connect #:database database-file #:mode 'create))
(define (get-slc) (query-exec slc "PRAGMA journal_mode=WAL")
(define slc* (unbox slc)) (define database-version
(cond (with-handlers ([exn:fail:sql?
[slc* slc*] (λ (exn)
[else ; need to set up the database
(make-directory* storage-path) (query-exec slc "create table database_version (version integer, primary key (version))")
(define slc* (sqlite3-connect #:database database-file #:mode 'create)) (query-exec slc "insert into database_version values (0)")
(query-exec slc* "PRAGMA journal_mode=WAL") 0)])
(define database-version (query-value slc "select version from database_version")))
(with-handlers ([exn:fail:sql?
(λ (exn)
; need to set up the database
(query-exec slc* "create table database_version (version integer, primary key (version))")
(query-exec slc* "insert into database_version values (0)")
0)])
(query-value slc* "select version from database_version")))
(let do-migrate-step () (let do-migrate-step ()
(when (database-version . < . (length migrations)) (when (database-version . < . (length migrations))
(call-with-transaction (call-with-transaction
slc* slc
(list-ref migrations database-version)) (list-ref migrations database-version))
(set! database-version (add1 database-version)) (set! database-version (add1 database-version))
(query-exec slc* "update database_version set version = $1" database-version) (query-exec slc "update database_version set version = $1" database-version)
(do-migrate-step))) (do-migrate-step)))
(set-box! slc slc*)
slc*]))
(define (query-exec* . args)
(apply query-exec (get-slc) args))
(define (query-rows* . args)
(apply query-rows (get-slc) args))
(define (query-list* . args)
(apply query-list (get-slc) args))
(define (query-value* . args)
(apply query-value (get-slc) args))
(define (query-maybe-value* . args)
(apply query-maybe-value (get-slc) args))
(define (query-maybe-row* . args)
(apply query-maybe-row (get-slc) args))

View file

@ -1,128 +1,89 @@
#lang racket/base #lang racket/base
(require racket/class (require racket/class
racket/draw
racket/format
racket/list racket/list
racket/port racket/port
racket/set racket/set
racket/splicing
racket/string racket/string
db db
net/http-easy net/http-easy
memo
(only-in racket/gui timer%)
racket/gui/easy racket/gui/easy
racket/gui/easy/operator racket/gui/easy/operator
(only-in pict bitmap)
images/icons/style
images/icons/control
images/icons/stickman
images/icons/symbol
"archiver-database.rkt" "archiver-database.rkt"
"archiver.rkt" "archiver.rkt"
"../lib/url-utils.rkt" "../lib/url-utils.rkt"
"../lib/xexpr-utils.rkt") "../lib/xexpr-utils.rkt")
(default-icon-material rubber-icon-material) (define active-threads (mutable-seteq))
(require (for-syntax racket/base racket/match racket/set racket/string))
(define-syntax (@> stx)
(define form (cdr (syntax->datum stx)))
(match form
[(list form) ; (@> (fn @obs))
;; identify the observables and replace with non-@ symbols
(define collection (mutable-set))
(define updated
(let loop ([sexp form])
(cond [(symbol? sexp)
(let ([as-s (symbol->string sexp)])
(if (string-prefix? as-s "@")
(let ([without-@ (string->symbol (substring as-s 1))])
(set-add! collection (cons sexp without-@))
without-@)
sexp))]
[(pair? sexp) (cons (loop (car sexp)) (loop (cdr sexp)))]
[#t sexp])))
(define collection-l (set->list collection))
;; return obs-combine -> updated-form
(datum->syntax stx `(obs-combine (λ (,@(map cdr collection-l)) ,updated) ,@(map car collection-l)))]
[(list (? string? str) args ...) ; (@> "Blah: ~a/~a" @arg1 arg2)
;; identify the observables and replace with non-@ symbols
(define collection-l
(for/list ([arg args])
(if (symbol? arg)
(let ([as-s (symbol->string arg)])
(if (string-prefix? as-s "@")
(let ([without-@ (string->symbol (substring as-s 1))])
(cons arg without-@))
(cons #f arg)))
(cons #f arg))))
(define collection-lo (filter car collection-l))
;; return obs-combine -> format
(datum->syntax stx `(obs-combine (λ (,@(map cdr collection-lo)) (format ,str ,@(map cdr collection-l))) ,@(map car collection-lo)))]))
(define/obs @auto-retry #f) (define/obs @auto-retry #f)
(define/obs @wikiname "")
(define/obs @state 'waiting)
(define/obs @num-pages 1)
(define/obs @done-pages 0)
(define/obs @just-done "")
(define/obs @queue '())
(define @title
(obs-combine
(λ (state queue num-pages done-pages)
(define suffix (if (pair? queue)
(format " +~a" (length queue))
""))
(define progress (if (eq? num-pages 0)
" 0%"
(format " ~a%" (round (inexact->exact (* (/ done-pages num-pages) 100))))))
(case state
[(waiting stage-0) (format "Fandom Archiver~a" suffix)]
[(stage-1) (format "Fandom Archiver 0%~a" suffix)]
[(stage-2) (format "Fandom Archiver~a~a" progress suffix)]
[(err) "ERROR Fandom Archiver"]
[(done) "Fandom Archiver 100%"]))
@state @queue @num-pages (obs-throttle @done-pages #:duration 5000)))
(define-struct qi^ (wikiname st stage progress max-progress eta th) #:transparent) ;; queue item (define-syntax-rule (t body ...)
(set-add! active-threads (thread (λ () body ...))))
(define rows (query-rows* "select wikiname, progress from wiki where progress < 4")) (define (do-start-or-queue)
(define/obs @queue null) (define wikiname (obs-peek @wikiname))
(define (add-wikiname-to-queue wikiname st stage) (:= @wikiname "")
(@queue . <~ . (λ (queue) (when (not (equal? (string-trim wikiname) ""))
(define already-exists? (findf (λ (qi) (equal? (qi^-wikiname qi) wikiname)) queue)) (@queue . <~ . (λ (q) (append q (list wikiname))))
(if already-exists? (shift-queue-maybe)))
queue
(append queue (list (qi^ wikiname st stage 0 1 "..." #f)))))))
(for ([row rows])
(add-wikiname-to-queue (vector-ref row 0)
(if (= (vector-ref row 1) 4)
'complete
'queued)
(vector-ref row 1)))
(define status-icon-size 32) (define (shift-queue-maybe)
(define status-icon-min-width 36) (when (memq (obs-peek @state) '(waiting done))
(define button-icon-size 12) (define q (obs-peek @queue))
(cond
[(pair? q)
(define wikiname (car q))
(:= @queue (cdr q))
(do-start-stage1 wikiname)]
[#t (:= @state 'done)])))
(define color-green (make-color 90 212 68)) (define (do-start-stage1 wikiname)
(:= @just-done "")
(:= @done-pages 0)
(:= @num-pages 1)
(t (with-handlers ([exn:fail? (handle-graphical-exn wikiname)])
(:= @state 'stage-0)
(if-necessary-download-list-of-pages wikiname (λ (now-done num-pages just-done-name)
(:= @num-pages num-pages)
(:= @done-pages now-done)
(:= @just-done just-done-name)
(:= @state 'stage-1)))
(do-start-stage2 wikiname))))
(define/obs @input "") (define (do-start-stage2 wikiname)
(:= @just-done "")
(splicing-let ([frame-count 30]) (:= @num-pages 1)
(define stickman-frames (:= @done-pages 0)
(for/vector ([s (in-range 0 1 (/ 1 frame-count))]) (t (with-handlers ([exn:fail? (handle-graphical-exn wikiname)])
(running-stickman-icon (save-each-page wikiname (λ (now-done num-pages just-done-path)
s (:= @num-pages num-pages)
#:height status-icon-size (:= @done-pages now-done)
#:material (default-icon-material)))) (:= @just-done just-done-path)))
(:= @state 'waiting)
(define/obs @stick-frame-no 0) (shift-queue-maybe)))
(define stick-timer (:= @state 'stage-2))
(new timer%
[notify-callback (λ () (@stick-frame-no . <~ . add1))]
[interval (truncate (/ 1000 frame-count))]))
(define/obs @stick
(@stick-frame-no . ~> . (λ (n) (vector-ref stickman-frames
(modulo n (vector-length stickman-frames)))))))
(define status-icons
(hasheq 'queued (stop-icon #:color syntax-icon-color #:height status-icon-size)
'paused (continue-forward-icon #:color syntax-icon-color #:height status-icon-size)
'running @stick
'error (x-icon #:height status-icon-size)
'complete (check-icon #:color color-green #:height status-icon-size)))
(define action-icons
(hasheq 'pause (pause-icon #:color syntax-icon-color #:height button-icon-size)
'resume (play-icon #:color color-green #:height button-icon-size)))
(define (bitmap-view @the-bitmap [min-width 1])
(pict-canvas #:min-size (@> (list (max min-width (send @the-bitmap get-width)) (send @the-bitmap get-height))) #;(if min-size (list min-size min-size) #f)
#:stretch '(#f #f)
#:style '(transparent)
@the-bitmap
bitmap))
(define (exn->string e) (define (exn->string e)
(with-output-to-string (with-output-to-string
@ -135,15 +96,13 @@
(printf ": ~a" (car item))) (printf ": ~a" (car item)))
(displayln ""))))) (displayln "")))))
(define ((handle-graphical-exn @qi) e) (define ((handle-graphical-exn wikiname) e)
(displayln (exn->string e) (current-error-port)) (displayln (exn->string e) (current-error-port))
(cond (cond
[(obs-peek @auto-retry) [(obs-peek @auto-retry)
(void) ;; TODO (do-retry-end wikiname)]
#;(do-retry-end wikiname)]
[#t [#t
(update-qi @qi [st 'error]) (:= @state 'err)
(do-try-unpause-next-entry)
(thread (thread
(λ () (λ ()
(define/obs @visible? #t) (define/obs @visible? #t)
@ -157,177 +116,89 @@
(input #:style '(multiple hscroll) (input #:style '(multiple hscroll)
#:min-size '(#f 200) #:min-size '(#f 200)
(exn->string e)) (exn->string e))
;; TODO (button "Retry Now" (λ () (:= @visible? #f) (do-retry-now wikiname)))
#;(button "Retry Now" (λ () (:= @visible? #f) (do-retry-now wikiname))) (button "Retry Round-Robin" (λ () (:= @visible? #f) (do-retry-end wikiname)))
#;(button "Retry Round-Robin" (λ () (:= @visible? #f) (do-retry-end wikiname))) (button "Skip Wiki" (λ () (:= @visible? #f) (do-continue)))
#;(button "Skip Wiki" (λ () (:= @visible? #f) (do-continue))) (button "Use Auto-Retry" (λ ()
#;(button "Use Auto-Retry" (λ () (:= @auto-retry #t)
(:= @auto-retry #t) (:= @visible? #f)
(:= @visible? #f) (do-retry-end wikiname)))
(do-retry-end wikiname))) (text "Be careful not to auto-retry an infinite loop!")))
#;(text "Be careful not to auto-retry an infinite loop!")))
main-window))) main-window)))
(sleep) (sleep)
; make sure the broken thread is gone ; make sure the old broken threads are all gone
(define th (qi^-th (obs-peek @qi))) (for ([th active-threads]) (kill-thread th))
(when th (kill-thread th))])) (set-clear! active-threads)]))
(define segments (define (do-retry-now wikiname)
(list (@queue . <~ . (λ (q) (append (list wikiname) q)))
(list 5/100 (make-color 0 223 217)) (:= @state 'waiting)
(list 88/100 color-green) (shift-queue-maybe))
(list 2/100 (make-color 0 223 217))
(list 5/100 color-green)))
(define segment-spacing 2)
(unless (= (apply + (map car segments)) 1)
(error 'segments "segments add up to ~a, not 1" (apply + (map car segments))))
;; return the new bitmap, which can be drawn on a dc<%> (define (do-retry-end wikiname)
(define/memoize (ray-trace width height stage progress max-progress) (@queue . <~ . (λ (q) (append q (list wikiname))))
;; (printf "rendering ~a ~a/~a at ~a~n" stage progress max-progress (current-inexact-milliseconds)) (:= @state 'waiting)
(define bm (make-object bitmap% width height #f #t)) (shift-queue-maybe))
(define dc (make-object bitmap-dc% bm))
(define width-available (- width (* (length segments) segment-spacing)))
(send dc set-smoothing 'unsmoothed)
(send dc set-pen "black" 0 'transparent)
(for/fold ([offset 0])
([segment segments]
[i (in-naturals 0)]) ;; zero indexed stages?
;; calculate start and end locations of grey bar
(define-values (segment-proportion segment-color) (apply values segment))
(define segment-start (if (= offset 0) 0 (+ offset segment-spacing)))
(define segment-width (* width-available segment-proportion))
;; draw grey bar
(send dc set-brush (make-color 180 180 180 0.4) 'solid)
(send dc draw-rectangle segment-start 0 segment-width height)
;; draw solid bar according to the current item's progress
(define proportion
(cond [(stage . < . i) 0]
[(stage . > . i) 1]
[(max-progress . <= . 0) 0]
[(progress . < . 0) 0]
[(progress . >= . max-progress) 1]
[else (progress . / . max-progress)]))
(send dc set-brush segment-color 'solid)
(send dc draw-rectangle segment-start 0 (* proportion segment-width) height)
(+ segment-start segment-width))
(bitmap-render-icon bm 6/8))
;; get ray traced bitmap (possibly from cache) and draw on dc<%> (define (do-continue)
(define (draw-bar orig-dc qi) (:= @state 'waiting)
;; (println ray-traced) (shift-queue-maybe))
(define-values (width height) (send orig-dc get-size))
(send orig-dc draw-bitmap (ray-trace width height (qi^-stage qi) (qi^-progress qi) (qi^-max-progress qi)) 0 0))
(define ((make-progress-updater @qi) a b c) (define (display-basename basename)
;; (printf "~a: ~a/~a ~a~n" (qi^-wikiname (obs-peek @qi)) a b c) (define limit 40)
(update-qi @qi [progress a] [max-progress b])) (cond [(string? basename)
(define query (basename->name-for-query basename))
(define (do-add-to-queue) (define segments (string-split query "/"))
(define wikiname (string-trim (obs-peek @input))) (when (and ((string-length query) . > . limit) ((length segments) . >= . 2))
(when ((string-length wikiname) . > . 0) (set! query (string-append ".../" (last segments))))
(add-wikiname-to-queue wikiname 'queued 0)) ;; TODO: automatically start? (when ((string-length query) . > . limit)
(:= @input "")) (set! query (string-append (substring query 0 (- limit 3)) "...")))
query]
(define-syntax-rule (update-qi @qi args ...) [#t "?"]))
(let ([wikiname (qi^-wikiname (obs-peek @qi))])
(@queue . <~ . (λ (queue)
(for/list ([qi queue])
(if (equal? (qi^-wikiname qi) wikiname)
(struct-copy qi^ qi args ...)
qi))))))
(define (do-start-qi @qi)
(define th
(thread (λ ()
(with-handlers ([exn? (handle-graphical-exn @qi)])
(define last-stage
(for/last ([stage all-stages]
[i (in-naturals)])
(update-qi @qi [stage i])
(stage (qi^-wikiname (obs-peek @qi)) (make-progress-updater @qi))
i))
(update-qi @qi [st 'complete] [stage (add1 last-stage)])
(do-try-unpause-next-entry)))))
(update-qi @qi [st 'running] [th th]))
(define (do-stop-qi @qi)
(define th (qi^-th (obs-peek @qi)))
(when th (kill-thread th))
(update-qi @qi [th #f] [st 'paused]))
(define (do-try-unpause-next-entry)
(define queue (obs-peek @queue))
(define next-qi (for/first ([qi queue]
#:when (memq (qi^-st qi) '(paused queued error)))
qi))
(when next-qi
(define @qi (@queue . ~> . (λ (queue) (findf (λ (qi) (equal? (qi^-wikiname qi) (qi^-wikiname next-qi))) queue))))
(do-start-qi @qi)))
(define main-window (define main-window
(render (render
(window (window #:title @title
#:title "Fandom Archiver" #:size '(360 200)
#:size '(400 300) #:mixin (λ (%) (class %
#:mixin (λ (%) (class % (super-new)
(super-new) (define/augment (on-close)
(define/augment (on-close) (for ([th active-threads]) (kill-thread th))
(send stick-timer stop) (disconnect slc))))
(for ([qi (obs-peek @queue)]) ;; input box at the top
(when (qi^-th qi) (hpanel (text "https://")
(kill-thread (qi^-th qi)))) (input @wikiname
#;(disconnect*)))) (λ (event data) (cond
(vpanel [(eq? event 'input) (:= @wikiname data)]
#:spacing 10 [(eq? event 'return) (do-start-or-queue)])))
#:margin '(5 5) (text ".fandom.com"))
(hpanel (button (@queue . ~> . (λ (q) (if (null? q) "Start" "Queue"))) (λ () (do-start-or-queue)))
#:stretch '(#t #f) (text (@queue . ~> . (λ (q) (if (null? q) "" (string-join #:before-first "Queue: " q ", ")))))
#:spacing 10 ;; show status based on overall application state
(hpanel (case-view
(text "https://") @state
(input @input ;; waiting for wikiname entry
(λ (event data) (cond ((waiting) (vpanel
[(eq? event 'input) (:= @input data)] (text "Fill in the wikiname and click start.")))
[(eq? event 'return) (do-add-to-queue)]))) ((stage-0) (vpanel
(text ".fandom.com")) (text "Checking data...")))
(button "Download Wiki" do-add-to-queue)) ((stage-1) (vpanel
(list-view (text "Gathering list of pages...")
#:style '(vertical) (text (@just-done . ~> . display-basename))
@queue (text (@done-pages . ~> . (λ (x) (if (eq? x 0)
#:key qi^-wikiname "0/?"
(λ (k @qi) (format "~a/~a" x (obs-peek @num-pages))))))))
(define @status-icons ;; downloading contents
(@> (case (qi^-st @qi) ((stage-2) (vpanel
[(running) @stick] (text "Downloading page text...")
[else (hash-ref status-icons (qi^-st @qi))]))) (progress @done-pages #:range @num-pages)
(define @is-running? (text (@done-pages . ~> . (λ (x) (format "~a/~a" x (obs-peek @num-pages)))))
(@> (memq (qi^-st @qi) '(running)))) (text (@just-done . ~> . display-basename))))
;; state icon at the left side ((done) (vpanel
(hpanel #:stretch '(#t #f) (text "All wikis downloaded!")))
#:alignment '(left center) ((err) (vpanel
#:spacing 8 (text "Error. Check the popup window.")))
(bitmap-view @status-icons status-icon-min-width) (else (text (@state . ~> . (λ (state) (format "invalid state: ~a" state))))))
(vpanel (checkbox #:label "Auto-retry on error? (Dangerous)"
;; name and buttons (top half) #:checked? @auto-retry
(hpanel #:alignment '(left bottom) (λ:= @auto-retry)))))
(text (@> (qi^-wikiname @qi)))
(spacer)
(hpanel
#:stretch '(#f #f)
(if-view @is-running?
(button (hash-ref action-icons 'pause)
(λ () (do-stop-qi @qi)))
(button (hash-ref action-icons 'resume)
(λ () (do-start-qi @qi))))))
;; progress bar (bottom half)
(hpanel
(canvas
@qi
#:style '(transparent)
#:margin '(3 3)
draw-bar)
(hpanel #:min-size '(68 #f)
#:stretch '(#f #f)
#:alignment '(right center)
(text (@> (format "eta ~a" (qi^-eta @qi))))))))))))))

View file

@ -2,35 +2,38 @@
(require racket/file (require racket/file
racket/function racket/function
racket/list racket/list
racket/path racket/runtime-path
racket/sequence
racket/string racket/string
net/url net/url
net/mime net/mime
file/sha1 file/sha1
net/http-easy net/http-easy
db db
"../lib/html-parsing/main.rkt"
json json
"archiver-database.rkt" "archiver-database.rkt"
"../lib/html-parsing/main.rkt"
"../lib/mime-types.rkt" "../lib/mime-types.rkt"
"../lib/syntax.rkt"
"../lib/tree-updater.rkt" "../lib/tree-updater.rkt"
"../lib/url-utils.rkt" "../lib/url-utils.rkt"
"../lib/xexpr-utils.rkt" "../lib/xexpr-utils.rkt"
"../lib/archive-file-mappings.rkt") "../lib/archive-file-mappings.rkt")
(define archive-slc slc)
(provide (provide
if-necessary-download-list-of-pages
download-list-of-pages
save-each-page
basename->name-for-query basename->name-for-query
image-url->values image-url->values
hash->save-dir hash->save-dir
all-stages) archive-slc)
(module+ test (module+ test
(require rackunit)) (require rackunit))
(define archive-root (anytime-path ".." "storage/archive")) (define-runtime-path archive-root "../storage/archive")
(make-directory* archive-root) #;(define archive-root "archive")
(define sources '#hasheq((style . 1) (page . 2))) (define sources '#hasheq((style . 1) (page . 2)))
@ -43,34 +46,127 @@
wikiname wikiname
(params->query '(("action" . "query") (params->query '(("action" . "query")
("meta" . "siteinfo") ("meta" . "siteinfo")
("siprop" . "general|rightsinfo|statistics") ("siprop" . "general|rightsinfo")
("format" . "json") ("format" . "json")
("formatversion" . "2"))))) ("formatversion" . "2")))))
(define data (response-json (get dest-url))) (define data (response-json (get dest-url)))
(define exists? (query-maybe-value* "select progress from wiki where wikiname = ?" wikiname)) (define exists? (query-maybe-value slc "select progress from wiki where wikiname = ?" wikiname))
(if (and exists? (not (sql-null? exists?))) (if exists?
(query-exec* "update wiki set sitename = ?, basepage = ?, license_text = ?, license_url = ? where wikiname = ?" (query-exec slc "update wiki set sitename = ?, basepage = ?, license_text = ?, license_url = ? where wikiname = ?"
(jp "/query/general/sitename" data) (jp "/query/general/sitename" data)
(second (regexp-match #rx"/wiki/(.*)" (jp "/query/general/base" data))) (second (regexp-match #rx"/wiki/(.*)" (jp "/query/general/base" data)))
(jp "/query/rightsinfo/text" data) (jp "/query/rightsinfo/text" data)
(jp "/query/rightsinfo/url" data) (jp "/query/rightsinfo/url" data)
wikiname) wikiname)
(query-exec* "insert into wiki (wikiname, progress, sitename, basepage, license_text, license_url) values (?, 0, ?, ?, ?, ?)" (query-exec slc "insert into wiki (wikiname, progress, sitename, basepage, license_text, license_url) values (?, 1, ?, ?, ?, ?)"
wikiname wikiname
(jp "/query/general/sitename" data) (jp "/query/general/sitename" data)
(second (regexp-match #rx"/wiki/(.*)" (jp "/query/general/base" data))) (second (regexp-match #rx"/wiki/(.*)" (jp "/query/general/base" data)))
(jp "/query/rightsinfo/text" data) (jp "/query/rightsinfo/text" data)
(jp "/query/rightsinfo/url" data))) (jp "/query/rightsinfo/url" data))))
(jp "/query/statistics/articles" data))
;; call 1 if not yet done for that wiki
(define (if-necessary-download-list-of-pages wikiname callback)
(define wiki-progress (query-maybe-value slc "select progress from wiki where wikiname = ?" wikiname))
;; done yet?
(unless (and (real? wiki-progress) (wiki-progress . >= . 1))
;; count total pages
(define dest-url
(format "https://~a.fandom.com/api.php?~a"
wikiname
(params->query `(("action" . "query") ("meta" . "siteinfo") ("siprop" . "statistics") ("format" . "json")))))
(define num-pages (jp "/query/statistics/articles" (response-json (get dest-url))))
(download-list-of-pages wikiname callback 0 num-pages #f)))
;; 1. Download list of wiki pages and store in database
(define (download-list-of-pages wikiname callback total-so-far grand-total path-with-namefrom)
(define url (if path-with-namefrom
(format "https://~a.fandom.com~a" wikiname path-with-namefrom)
(format "https://~a.fandom.com/wiki/Local_Sitemap" wikiname)))
(define r (get url))
(define page (html->xexp (bytes->string/utf-8 (response-body r))))
(define link-namefrom
((query-selector (λ (t a c x) (and (eq? t 'a)
(pair? x)
(string-contains? (car x) "Next page")
(let ([href (get-attribute 'href a)] )
(and href (string-contains? href "/wiki/Local_Sitemap")))))
page #:include-text? #t)))
(define row-values
(for/list ([link (in-producer
(query-selector
(λ (t a c) (eq? t 'a))
((query-selector (λ (t a c) (has-class? "mw-allpages-chunk" a)) page)))
#f)])
(list wikiname (local-encoded-url->basename (get-attribute 'href (bits->attributes link))) 0)))
(define query-template (string-join (make-list (length row-values) "(?, ?, ?)") ", " #:before-first "insert or ignore into page (wikiname, basename, progress) values "))
(apply query-exec slc query-template (flatten row-values))
(define new-total (+ (length row-values) total-so-far))
(callback new-total grand-total (second (last row-values)))
(cond
[link-namefrom ; repeat on the next page
(download-list-of-pages wikiname callback new-total grand-total (get-attribute 'href (bits->attributes link-namefrom)))]
[#t ; all done downloading sitemap
(insert-wiki-entry wikiname)]))
;; 2. Download each page via API and:
;; * Save API response to file
(define max-page-progress 1)
(define (save-each-page wikiname callback)
;; prepare destination folder
(define save-dir (build-path archive-root wikiname))
(make-directory* save-dir)
;; gather list of basenames to download (that aren't yet complete)
(define basenames (query-list slc "select basename from page where wikiname = ? and progress < ?"
wikiname max-page-progress))
;; counter of complete/incomplete basenames
(define already-done-count
(query-value slc "select count(*) from page where wikiname = ? and progress = ?"
wikiname max-page-progress))
(define not-done-count
(query-value slc "select count(*) from page where wikiname = ? and progress < ?"
wikiname max-page-progress))
;; set initial progress
(callback already-done-count (+ already-done-count not-done-count) "")
;; loop through basenames and download
(for ([basename basenames]
[i (in-naturals 1)])
(define name-for-query (basename->name-for-query basename))
(define dest-url
(format "https://~a.fandom.com/api.php?~a"
wikiname
(params->query `(("action" . "parse")
("page" . ,name-for-query)
("prop" . "text|headhtml|langlinks")
("formatversion" . "2")
("format" . "json")))))
(define r (get dest-url))
(define body (response-body r))
(define filename (string-append basename ".json"))
(define save-path
(cond [((string-length basename) . > . 240)
(define key (sha1 (string->bytes/latin-1 basename)))
(query-exec slc "insert into special_page (wikiname, key, basename) values (?, ?, ?)"
wikiname key basename)
(build-path save-dir (string-append key ".json"))]
[#t
(build-path save-dir (string-append basename ".json"))]))
(display-to-file body save-path #:exists 'replace)
(query-exec slc "update page set progress = 1 where wikiname = ? and basename = ?"
wikiname basename)
(callback (+ already-done-count i) (+ already-done-count not-done-count) basename))
;; saved all pages, register that fact in the database
(query-exec slc "update wiki set progress = 2 where wikiname = ?" wikiname))
;; 3. Download CSS and:
;; * Save CSS to file
;; * Record style images to database
(define (check-style-for-images wikiname path) (define (check-style-for-images wikiname path)
(define content (file->string path)) (define content (file->string path))
(define urls (regexp-match* #rx"url\\(\"?'?([^)]*)'?\"?\\)" content #:match-select cadr)) (define urls (regexp-match* #rx"url\\(\"?'?([^)]*)'?\"?\\)" content #:match-select cadr))
(for/list ([url urls] (for/list ([url urls]
#:when (not (or (equal? url "") #:when (not (or (equal? url "")
(equal? url "'") (equal? url "'")
(string-suffix? url "\"")
(string-contains? url "/resources-ucp/") (string-contains? url "/resources-ucp/")
(string-contains? url "/fonts/") (string-contains? url "/fonts/")
(string-contains? url "/drm_fonts/") (string-contains? url "/drm_fonts/")
@ -88,7 +184,7 @@
[(string-prefix? url "/") (format "https://~a.fandom.com~a" wikiname url)] [(string-prefix? url "/") (format "https://~a.fandom.com~a" wikiname url)]
[else (raise-user-error "While calling check-style-for-images, this URL had an unknown format and couldn't be saved:" url path)]))) [else (raise-user-error "While calling check-style-for-images, this URL had an unknown format and couldn't be saved:" url path)])))
(define (download-styles-for-wiki wikiname callback) (define (download-styles-for-wiki wikiname)
(define save-dir (build-path archive-root wikiname "styles")) (define save-dir (build-path archive-root wikiname "styles"))
(make-directory* save-dir) (make-directory* save-dir)
(define theme (λ (theme-name) (define theme (λ (theme-name)
@ -102,137 +198,18 @@
(theme "dark") (theme "dark")
(cons (format "https://~a.fandom.com/load.php?lang=en&modules=skin.fandomdesktop.styles%7Cext.fandom.PortableInfoboxFandomDesktop.css%7Cext.fandom.GlobalComponents.CommunityHeaderBackground.css%7Cext.gadget.site-styles%2Csound-styles%7Csite.styles&only=styles&skin=fandomdesktop" wikiname) (cons (format "https://~a.fandom.com/load.php?lang=en&modules=skin.fandomdesktop.styles%7Cext.fandom.PortableInfoboxFandomDesktop.css%7Cext.fandom.GlobalComponents.CommunityHeaderBackground.css%7Cext.gadget.site-styles%2Csound-styles%7Csite.styles&only=styles&skin=fandomdesktop" wikiname)
(build-path save-dir "site.css")))) (build-path save-dir "site.css"))))
(for ([style styles] (for ([style styles])
[i (in-naturals)])
(callback i (length styles) "styles...")
(define r (get (car style))) (define r (get (car style)))
(define body (response-body r)) (define body (response-body r))
(display-to-file body (cdr style) #:exists 'replace) (display-to-file body (cdr style) #:exists 'replace)
;; XXX: how the HELL do I deal with @import?? would need some kind of recursion here. how will the page server know where to look up the style file to be able to serve them again? do I add another link-stylesheet tag to the main page? what about the remaining stuck @import url? ;; XXX: how the HELL do I deal with @import?? would need some kind of recursion here. how will the page server know where to look up the style file to be able to serve them again? do I add another link-stylesheet tag to the main page? what about the remaining stuck @import url?
) )
(callback (length styles) (length styles) "styles...")
styles) styles)
(define (hash->save-dir wikiname hash) (define (do-step-3 wikiname)
(build-path archive-root wikiname "images" (substring hash 0 1) (substring hash 0 2))) (define wiki-progress (query-maybe-value slc "select progress from wiki where wikiname = ?" wikiname))
(define (image-url->values i)
;; TODO: handle case where there is multiple broken cb parameter on minecraft wiki
;; TODO: ensure it still "works" with broken &amp; on minecraft wiki
(define no-cb (regexp-replace #rx"\\cb=[0-9]+&?" i "")) ; remove cb url parameter which does nothing
(define key (regexp-replace #rx"[&?]$" no-cb "")) ; remove extra separator if necessary
(define hash (sha1 (string->bytes/utf-8 key)))
(cons key hash))
;; 1. Download list of wiki pages and store in database, if not done yet for that wiki
(define (if-necessary-download-list-of-pages wikiname callback)
(define wiki-progress (query-maybe-value* "select progress from wiki where wikiname = ?" wikiname))
;; done yet?
(unless (and (real? wiki-progress) (wiki-progress . >= . 1))
;; Count total pages
(define num-pages (insert-wiki-entry wikiname))
;; Download the entire index of pages
(define basenames
(let loop ([path-with-namefrom "/wiki/Local_Sitemap"]
[basenames-previous-pages null])
;; Download the current index page
(define url (format "https://~a.fandom.com~a" wikiname path-with-namefrom))
(define r (get url))
;; Metadata from this page (the link to the next page)
(define page (html->xexp (bytes->string/utf-8 (response-body r))))
(define link-namefrom
((query-selector (λ (t a c x) (and (eq? t 'a)
(pair? x)
(string-contains? (car x) "Next page")
(let ([href (get-attribute 'href a)] )
(and href (string-contains? href "/wiki/Local_Sitemap")))))
page #:include-text? #t)))
;; Content from this page
(define basenames-this-page
(for/list ([link (in-producer
(query-selector
(λ (t a c) (eq? t 'a))
((query-selector (λ (t a c) (has-class? "mw-allpages-chunk" a)) page)))
#f)])
(local-encoded-url->basename (get-attribute 'href (bits->attributes link)))))
;; Call the progress callback
(define all-basenames (append basenames-previous-pages basenames-this-page))
(callback (length all-basenames) num-pages (last all-basenames))
;; Recurse to download from the next page
(if link-namefrom
(loop (get-attribute 'href (bits->attributes link-namefrom)) all-basenames)
all-basenames)))
;; Save those pages into the database
;; SQLite can have a maximum of 32766 parameters in a single query
(for ([slice (in-slice 32760 basenames)])
(define query-template (string-join (make-list (length slice) "(?1, ?, 0)") ", " #:before-first "insert or ignore into page (wikiname, basename, progress) values "))
(call-with-transaction
(get-slc)
(λ ()
(apply query-exec* query-template wikiname slice)
;; Record that we have the complete list of pages
(query-exec* "update wiki set progress = 1 where wikiname = ?" wikiname))))))
;; 2. Download each page via API and:
;; * Save API response to file
(define max-page-progress 1)
(define (save-each-page wikiname callback)
;; prepare destination folder
(define save-dir (build-path archive-root wikiname))
(make-directory* save-dir)
;; gather list of basenames to download (that aren't yet complete)
(define basenames (query-list* "select basename from page where wikiname = ? and progress < ?"
wikiname max-page-progress))
;; counter of complete/incomplete basenames
(define already-done-count
(query-value* "select count(*) from page where wikiname = ? and progress = ?"
wikiname max-page-progress))
(define not-done-count
(query-value* "select count(*) from page where wikiname = ? and progress < ?"
wikiname max-page-progress))
(define total-count (+ already-done-count not-done-count))
;; set initial progress
(callback already-done-count total-count "")
;; loop through basenames and download
(for ([basename basenames]
[i (in-naturals (add1 already-done-count))])
(define name-for-query (basename->name-for-query basename))
(define dest-url
(format "https://~a.fandom.com/api.php?~a"
wikiname
(params->query `(("action" . "parse")
("page" . ,name-for-query)
("prop" . "text|headhtml|langlinks")
("formatversion" . "2")
("format" . "json")))))
(define r (get dest-url))
(define body (response-body r))
(define filename (string-append basename ".json"))
(define save-path
(cond [((string-length basename) . > . 240)
(define key (sha1 (string->bytes/latin-1 basename)))
(query-exec* "insert into special_page (wikiname, key, basename) values (?, ?, ?)"
wikiname key basename)
(build-path save-dir (string-append key ".json"))]
[#t
(build-path save-dir (string-append basename ".json"))]))
(display-to-file body save-path #:exists 'replace)
(query-exec* "update page set progress = 1 where wikiname = ? and basename = ?"
wikiname basename)
(callback i total-count basename))
;; saved all pages, register that fact in the database
(query-exec* "update wiki set progress = 2 where wikiname = ?" wikiname))
;; 3. Download CSS and:
;; * Save CSS to file
;; * Record style images to database
(define (if-necessary-download-and-check-styles wikiname callback)
(define wiki-progress (query-maybe-value* "select progress from wiki where wikiname = ?" wikiname))
(unless (and (number? wiki-progress) (wiki-progress . >= . 3)) (unless (and (number? wiki-progress) (wiki-progress . >= . 3))
(define styles (download-styles-for-wiki wikiname callback)) (define styles (download-styles-for-wiki wikiname))
(define unique-image-urls (define unique-image-urls
(remove-duplicates (remove-duplicates
(map image-url->values (map image-url->values
@ -240,40 +217,48 @@
(for/list ([style styles]) (for/list ([style styles])
(check-style-for-images wikiname (cdr style))))) (check-style-for-images wikiname (cdr style)))))
#:key cdr)) #:key cdr))
(println unique-image-urls)
(for ([pair unique-image-urls]) (for ([pair unique-image-urls])
(query-exec* "insert or ignore into image (wikiname, url, hash, ext, source, progress) values (?, ?, ?, NULL, 1, 0)" wikiname (car pair) (cdr pair))) (query-exec slc "insert or ignore into image (wikiname, url, hash, ext, source, progress) values (?, ?, ?, NULL, 1, 0)" wikiname (car pair) (cdr pair)))
(query-exec* "update wiki set progress = 3 where wikiname = ?" wikiname))) (query-exec slc "update wiki set progress = 3 where wikiname = ?" wikiname)))
;; 4: From downloaded pages, record URLs of image sources and inline style images to database ;; 4: From downloaded pages, record URLs of image sources and inline style images to database
(define (hash->save-dir wikiname hash)
(build-path archive-root wikiname "images" (substring hash 0 1) (substring hash 0 2)))
(define (image-url->values i)
;; TODO: handle case where there is multiple cb parameter on minecraft wiki
;; TODO: ensure it still "works" with broken &amp; on minecraft wiki
(define no-cb (regexp-replace #rx"\\cb=[0-9]+&?" i "")) ; remove cb url parameter which does nothing
(define key (regexp-replace #rx"[&?]$" no-cb "")) ; remove extra separator if necessary
(define hash (sha1 (string->bytes/utf-8 key)))
(cons key hash))
(define (check-json-for-images wikiname path) (define (check-json-for-images wikiname path)
(define data (with-input-from-file path (λ () (read-json)))) (define data (with-input-from-file path (λ () (read-json))))
(define page (html->xexp (preprocess-html-wiki (jp "/parse/text" data)))) (define page (html->xexp (preprocess-html-wiki (jp "/parse/text" data))))
(define tree (update-tree-wiki page wikiname)) (define tree (update-tree-wiki page wikiname))
null (remove-duplicates
#;(remove-duplicates (for/list ([element (in-producer
(for/list ([element (in-producer (query-selector
(query-selector (λ (t a c)
(λ (t a c) (and (eq? t 'img)
(and (eq? t 'img) (get-attribute 'src a)))
(get-attribute 'src a))) tree)
tree) #f)])
#f)]) (image-url->values (get-attribute 'src (bits->attributes element))))))
(image-url->values (get-attribute 'src (bits->attributes element))))))
;; 5. Download image sources and style images according to database ;; 5. Download image sources and style images according to database
(define (save-each-image wikiname callback) (define (save-each-image wikiname source callback)
(define source (hash-ref sources 'style)) ;; TODO: download entire wiki images instead?
;; gather list of basenames to download (that aren't yet complete) ;; gather list of basenames to download (that aren't yet complete)
(define rows (query-rows* "select url, hash from image where wikiname = ? and source <= ? and progress < 1" (define rows (query-rows slc "select url, hash from image where wikiname = ? and source <= ? and progress < 1"
wikiname source)) wikiname source))
;; counter of complete/incomplete basenames ;; counter of complete/incomplete basenames
(define already-done-count (define already-done-count
(query-value* "select count(*) from image where wikiname = ? and source <= ? and progress = 1" (query-value slc "select count(*) from image where wikiname = ? and source <= ? and progress = 1"
wikiname source)) wikiname source))
(define not-done-count (define not-done-count
(query-value* "select count(*) from image where wikiname = ? and source <= ? and progress < 1" (query-value slc "select count(*) from image where wikiname = ? and source <= ? and progress < 1"
wikiname source)) wikiname source))
;; set initial progress ;; set initial progress
(callback already-done-count (+ already-done-count not-done-count) "") (callback already-done-count (+ already-done-count not-done-count) "")
@ -284,35 +269,26 @@
(define url (vector-ref row 0)) (define url (vector-ref row 0))
(define hash (vector-ref row 1)) (define hash (vector-ref row 1))
;; check ;; check
#; (printf "~a -> ~a~n" url hash) (printf "~a -> ~a~n" url hash)
(define r (get url)) (define r (get url))
(define declared-type (response-headers-ref r 'content-type)) (define declared-type (response-headers-ref r 'content-type))
(define final-type (if (equal? declared-type #"application/octet-stream") (define final-type (if (equal? declared-type #"application/octet-stream")
(let ([sniff-entity (message-entity (mime-analyze (response-body r)))]) (let ([sniff-entity (message-entity (mime-analyze (response-body r)))])
(string->bytes/latin-1 (format "~a/~a" (entity-type sniff-entity) (entity-subtype sniff-entity)))) (string->bytes/latin-1 (format "~a/~a" (entity-type sniff-entity) (entity-subtype sniff-entity))))
declared-type)) declared-type))
(define ext (define ext (bytes->string/latin-1 (mime-type->ext final-type)))
(with-handlers ([exn:fail:contract? (λ _ (error 'save-each-image "no ext found for mime type `~a` in file ~a" final-type url))])
(bytes->string/latin-1 (mime-type->ext final-type))))
;; save ;; save
(define save-dir (hash->save-dir wikiname hash)) (define save-dir (hash->save-dir wikiname hash))
(make-directory* save-dir) (make-directory* save-dir)
(define save-path (build-path save-dir (string-append hash "." ext))) (define save-path (build-path save-dir (string-append hash "." ext)))
(define body (response-body r)) (define body (response-body r))
(display-to-file body save-path #:exists 'replace) (display-to-file body save-path #:exists 'replace)
(query-exec* "update image set progress = 1, ext = ? where wikiname = ? and hash = ?" (query-exec slc "update image set progress = 1, ext = ? where wikiname = ? and hash = ?"
ext wikiname hash) ext wikiname hash)
(callback (+ already-done-count i) (+ already-done-count not-done-count) (string-append (substring hash 0 6) "..." ext))) (callback (+ already-done-count i) (+ already-done-count not-done-count) (string-append hash "." ext)))
;; saved all images, register that fact in the database ;; TODO: saved all images, register that fact in the database
(query-exec* "update wiki set progress = 4 where wikiname = ?" wikiname)) )
(define all-stages
(list
if-necessary-download-list-of-pages
save-each-page
if-necessary-download-and-check-styles
;; check-json-for-images
save-each-image))
(module+ test (module+ test
(check-equal? (html->xexp "<img src=\"https://example.com/images?src=Blah.jpg&amp;width=150\">") (check-equal? (html->xexp "<img src=\"https://example.com/images?src=Blah.jpg&amp;width=150\">")
@ -323,13 +299,11 @@
#;(do-step-3 "gallowmere") #;(do-step-3 "gallowmere")
#;(save-each-image "gallowmere" (hash-ref sources 'style) (λ (a b c) (printf "~a/~a ~a~n" a b c))) #;(save-each-image "gallowmere" (hash-ref sources 'style) (λ (a b c) (printf "~a/~a ~a~n" a b c)))
#;(for ([wikiname (query-list* "select wikiname from wiki")]) #;(for ([wikiname (query-list slc "select wikiname from wiki")])
(println wikiname) (println wikiname)
(insert-wiki-entry wikiname)) (insert-wiki-entry wikiname))
#;(for ([wikiname (query-list* "select wikiname from wiki")]) #;(for ([wikiname (query-list slc "select wikiname from wiki")])
(println wikiname) (println wikiname)
(do-step-3 wikiname) (do-step-3 wikiname)
(save-each-image wikiname (hash-ref sources 'style) (λ (a b c) (printf "~a/~a ~a~n" a b c))))) (save-each-image wikiname (hash-ref sources 'style) (λ (a b c) (printf "~a/~a ~a~n" a b c)))))
; (for ([stage all-stages]) (stage "create" (λ (a b c) (printf "~a/~a ~a~n" a b c))))

View file

@ -30,9 +30,7 @@
(define ch (make-channel)) (define ch (make-channel))
(define (start) (define (start)
(serve/launch/wait (serve/launch/wait
#:listen-ip (if (equal? (config-get 'bind_host) "auto") #:listen-ip (if (config-true? 'debug) "127.0.0.1" #f)
(if (config-true? 'debug) "127.0.0.1" #f)
(config-get 'bind_host))
#:port (string->number (config-get 'port)) #:port (string->number (config-get 'port))
(λ (quit) (λ (quit)
(channel-put ch (lambda () (semaphore-post quit))) (channel-put ch (lambda () (semaphore-post quit)))

View file

@ -20,9 +20,7 @@
(require (only-in "src/page-file.rkt" page-file)) (require (only-in "src/page-file.rkt" page-file))
(serve/launch/wait (serve/launch/wait
#:listen-ip (if (equal? (config-get 'bind_host) "auto") #:listen-ip (if (config-true? 'debug) "127.0.0.1" #f)
(if (config-true? 'debug) "127.0.0.1" #f)
(config-get 'bind_host))
#:port (string->number (config-get 'port)) #:port (string->number (config-get 'port))
(λ (quit) (λ (quit)
(dispatcher-tree (dispatcher-tree

View file

@ -1,6 +1,5 @@
text/html html text/html html
text/css css text/css css
application/xml xml
text/xml xml text/xml xml
image/gif gif image/gif gif
image/jpeg jpeg image/jpeg jpeg
@ -26,7 +25,6 @@ application/font-woff2 woff2
application/acad woff2 application/acad woff2
font/woff2 woff2 font/woff2 woff2
application/font-woff woff application/font-woff woff
font/woff woff
application/x-font-ttf ttf application/x-font-ttf ttf
application/x-font-truetype ttf application/x-font-truetype ttf
application/x-truetype-font ttf application/x-truetype-font ttf

View file

@ -1,5 +1,5 @@
#lang racket/base #lang racket/base
(require (for-syntax racket/base syntax/location)) (require (for-syntax racket/base))
(provide (provide
; help make a nested if. if/in will gain the same false form of its containing if/out. ; help make a nested if. if/in will gain the same false form of its containing if/out.
@ -7,12 +7,7 @@
; cond, but values can be defined between conditions ; cond, but values can be defined between conditions
cond/var cond/var
; wrap sql statements into lambdas so they can be executed during migration ; wrap sql statements into lambdas so they can be executed during migration
wrap-sql wrap-sql)
; get the name of the file that contains the currently evaluating form
this-directory
this-file
; replacement for define-runtime-path
anytime-path)
(module+ test (module+ test
(require rackunit) (require rackunit)
@ -101,16 +96,6 @@
(check-equal? (if/out #t (if/in #f 'yes) 'no) 'no) (check-equal? (if/out #t (if/in #f 'yes) 'no) 'no)
(check-equal? (if/out #f (if/in #f 'yes) 'no) 'no)) (check-equal? (if/out #f (if/in #f 'yes) 'no) 'no))
(define-syntax (this-directory stx)
(datum->syntax stx (syntax-source-directory stx)))
(define-syntax (this-file stx)
(datum->syntax stx (build-path (or (syntax-source-directory stx) 'same) (syntax-source-file-name stx))))
(module+ test
(require racket/path)
(check-equal? (file-name-from-path (this-file)) (build-path "syntax.rkt")))
(define-syntax (cond/var stx) (define-syntax (cond/var stx)
(transform/out-cond/var stx)) (transform/out-cond/var stx))
(module+ test (module+ test
@ -118,28 +103,7 @@
#'(cond #'(cond
[#f 0] [#f 0]
[#t [#t
(let* ([d (* a 2)]) (let ([d (* a 2)])
(cond (cond
[(eq? d 8) d] [(eq? d 8) d]
[#t "not 4"]))]))) [#t "not 4"]))])))
;;; Replacement for define-runtime-path that usually works well and doesn't include the files/folder contents into the distribution.
;;; When running from source, should always work appropriately.
;;; When running from a distribution, (current-directory) is treated as the root.
;;; Usage:
;;; * to-root : Path-String * relative path from the source file to the project root
;;; * to-dest : Path-String * relative path from the root to the desired file/folder
(define-syntax (anytime-path stx)
(define-values (_ to-root to-dest) (apply values (syntax->list stx)))
(define source (syntax-source stx))
(unless (complete-path? source)
(error 'anytime-path "syntax source has no directory: ~v" stx))
(datum->syntax
stx
`(let* ([syntax-to-root (build-path (path-only ,source) ,to-root)]
[root (if (directory-exists? syntax-to-root)
;; running on the same filesystem it was compiled on, i.e. it's running the source code out of a directory, and the complication is the intermediate compilation
syntax-to-root
;; not running on the same filesystem, i.e. it's a distribution. we assume that the current working directory is where the executable is, and treat this as the root.
(current-directory))])
(simple-form-path (build-path root ,to-dest)))))

View file

@ -29,13 +29,12 @@
(define default-config (define default-config
'((application_name . "BreezeWiki") '((application_name . "BreezeWiki")
(bind_host . "auto")
(port . "10416")
(canonical_origin . "") (canonical_origin . "")
(debug . "false") (debug . "false")
(feature_search_suggestions . "true") (feature_search_suggestions . "true")
(instance_is_official . "false") ; please don't turn this on, or you will make me very upset (instance_is_official . "false") ; please don't turn this on, or you will make me very upset
(log_outgoing . "true") (log_outgoing . "true")
(port . "10416")
(strict_proxy . "false") (strict_proxy . "false")
(feature_offline::enabled . "false") (feature_offline::enabled . "false")

View file

@ -8,8 +8,8 @@
db db
memo memo
"static-data.rkt" "static-data.rkt"
"whole-utils.rkt"
"../lib/url-utils.rkt" "../lib/url-utils.rkt"
"whole-utils.rkt"
"../lib/xexpr-utils.rkt" "../lib/xexpr-utils.rkt"
"../archiver/archiver-database.rkt" "../archiver/archiver-database.rkt"
"config.rkt") "config.rkt")
@ -42,8 +42,8 @@
[(config-true? 'feature_offline::only) [(config-true? 'feature_offline::only)
(when (config-true? 'debug) (when (config-true? 'debug)
(printf "using offline mode for siteinfo ~a~n" wikiname)) (printf "using offline mode for siteinfo ~a~n" wikiname))
(define row (query-maybe-row* "select sitename, basepage, license_text, license_url from wiki where wikiname = ?" (define row (query-maybe-row slc "select sitename, basepage, license_text, license_url from wiki where wikiname = ?"
wikiname)) wikiname))
(if row (if row
(siteinfo^ (vector-ref row 0) (siteinfo^ (vector-ref row 0)
(vector-ref row 1) (vector-ref row 1)

View file

@ -2,6 +2,7 @@
(require racket/file (require racket/file
racket/path racket/path
racket/port racket/port
racket/runtime-path
racket/string racket/string
net/url net/url
web-server/http web-server/http
@ -10,7 +11,6 @@
(only-in web-server/dispatchers/dispatch next-dispatcher) (only-in web-server/dispatchers/dispatch next-dispatcher)
"../archiver/archiver.rkt" "../archiver/archiver.rkt"
"../lib/mime-types.rkt" "../lib/mime-types.rkt"
"../lib/syntax.rkt"
"../lib/xexpr-utils.rkt" "../lib/xexpr-utils.rkt"
"config.rkt" "config.rkt"
"log.rkt") "log.rkt")
@ -18,7 +18,7 @@
(provide (provide
page-static-archive) page-static-archive)
(define path-archive (anytime-path ".." "storage/archive")) (define-runtime-path path-archive "../storage/archive")
(define ((replacer wikiname) whole url) (define ((replacer wikiname) whole url)
(format (format

View file

@ -8,7 +8,6 @@
(only-in web-server/dispatchers/dispatch next-dispatcher) (only-in web-server/dispatchers/dispatch next-dispatcher)
(prefix-in files: web-server/dispatchers/dispatch-files) (prefix-in files: web-server/dispatchers/dispatch-files)
"../lib/mime-types.rkt" "../lib/mime-types.rkt"
"../lib/syntax.rkt"
"config.rkt") "config.rkt")
(provide (provide
@ -18,7 +17,7 @@
(require rackunit)) (require rackunit))
(define-runtime-path path-static "../static") (define-runtime-path path-static "../static")
(define path-archive (anytime-path ".." "storage/archive")) (define-runtime-path path-archive "../storage/archive")
(define hash-ext-mime-type (define hash-ext-mime-type
(hash #".css" #"text/css" (hash #".css" #"text/css"

View file

@ -4,7 +4,7 @@
racket/function racket/function
racket/list racket/list
racket/match racket/match
racket/path racket/runtime-path
racket/string racket/string
; libs ; libs
(prefix-in easy: net/http-easy) (prefix-in easy: net/http-easy)
@ -38,7 +38,7 @@
(module+ test (module+ test
(require rackunit)) (require rackunit))
(define path-archive (anytime-path ".." "storage/archive")) (define-runtime-path path-archive "../storage/archive")
(define (page-wiki-offline req) (define (page-wiki-offline req)
(response-handler (response-handler