#lang scheme/gui
(require net/url
"webdav.ss"
"async.ss")
(define (find-muvee-reveal-common-files-folder default-path)
(let ([result (box default-path)])
(if (get-resource "HKEY_LOCAL_MACHINE"
"SOFTWARE\\Classes\\CLSID\\{30A8E5D8-A4BD-4038-981E-AD2B7AD781EA}\\InprocServer32\\"
result)
(path-only (unbox result))
(path->directory-path (string->path (unbox result))))))
(define *install-dir*
(match (system-type 'os)
['windows (find-muvee-reveal-common-files-folder "C:\\Program Files\\Common Files\\muvee Technologies\\071203\\")]
[_ (string->path "/tmp/installed-styles/")]))
(when (not (directory-exists? *install-dir*))
(message-box "Error" "Please install muvee Reveal first.\nhttp://www.muvee.com" #f '(ok))
(exit))
(define *warehouse-dir* (let* ([pref (find-system-path 'pref-dir)]
[msdir (path->directory-path (build-path pref "muveeStyles"))]
[uidir (path->directory-path (build-path msdir "warehouse"))])
(make-directory* uidir)
uidir))
(define *uninstall-info-file* "uninstall-path.scm")
(provide/contract [my-styles-folder path?])
(define my-styles-folder (path->directory-path (build-path (find-system-path 'pref-dir) "muveeStyles" "collection")))
(make-directory* my-styles-folder)
(define-struct style (id url path icon strings))
(provide/contract [struct style ((id string?)
(url url?)
(path path?)
(icon (is-a?/c bitmap%))
(strings (and/c hash? immutable?)))])
(provide/contract [style-string (style? symbol? symbol? . -> . string?)])
(define (style-string style key lang)
(hash-ref (hash-ref (style-strings style) lang) key))
(provide/contract [style-url? ((or/c string? url?) . -> . boolean?)])
(define (style-url? url)
(if (string? url)
(style-url? (string->url url))
(if (string=? (url-scheme url) "file")
(style-path? (url->path url))
(and (webdav-directory-exists? url)
(regexp-match #px"/(S[[:digit:]]{5}_[[:alnum:]_]+)/$" (url->string url))
(let ([contents (webdav-directory-list url)])
(and (memf (lambda (s) (string=? s "data.scm")) contents)
(memf (lambda (s) (string=? s "strings.txt")) contents)
(memf (lambda (s) (string=? s "icon.png")) contents)
#t))))))
(define (style-path? path)
(and (directory-exists? path)
(regexp-match #px"^S[[:digit:]]{5}_[[:alnum:]_]+$"
(let-values ([(base name must-be-dir?) (split-path path)])
(path->string name)))
(let ((contents (map path->string (directory-list path))))
(and (memf (lambda (s) (string-ci=? s "data.scm")) contents)
(memf (lambda (s) (string-ci=? s "strings.txt")) contents)
(memf (lambda (s) (string-ci=? s "icon.png")) contents)
#t))))
(provide/contract [fetch-styles ((or/c string? url?) . -> . (listof style?))])
(define (fetch-styles url)
(with-handlers [(exn:fail? (lambda (e) '()))]
(if (string? url)
(fetch-styles (string->url url))
(if (string=? (url-scheme url) "file")
(fetch-style-list-from-folder (url->path url))
(fetch-style-list-from-webdav-folder url)))))
(provide/contract [fetch-styles/async ((or/c string? url?)
(list? list? . -> . any)
(list? . -> . any)
(list? . -> . any)
. -> . any)])
(define (fetch-styles/async url progress-proc result-proc error-proc)
(if (string? url)
(fetch-styles/async (string->url url) progress-proc result-proc error-proc)
(if (string=? (url-scheme url) "file")
(fetch-style-list-from-folder/async (url->path url) progress-proc result-proc error-proc)
(fetch-style-list-from-webdav-folder/async url progress-proc result-proc error-proc))))
(define (fetch-style-list-from-folder path)
(if (directory-exists? path)
(let* ([files-and-dirs (directory-list path)]
[full-paths (map (lambda (f) (path->complete-path f path))
files-and-dirs)]
[only-styles (filter style-path? full-paths)])
(map path->style only-styles))
(raise 'exn:path-not-a-valid-directory)
))
(define (fetch-style-list-from-folder/async path progress-proc result-proc error-proc)
(if (directory-exists? path)
(let* ([files-and-dirs (directory-list path)]
[full-paths (map (lambda (f) (path->complete-path f path))
files-and-dirs)]
[only-styles (filter style-path? full-paths)])
(map/async path->style only-styles progress-proc result-proc error-proc))
(raise 'exn:path-not-a-valid-directory)
))
(define (fetch-style-list-from-webdav-folder url)
(map url->style (fetch-style-urls url)))
(define (fetch-style-list-from-webdav-folder/async url progress-proc result-proc error-proc)
(map/async url->style (fetch-style-urls url) progress-proc result-proc error-proc))
(provide/contract [fetch-style-urls ((or/c string? url?) . -> . (listof url?))])
(define (fetch-style-urls url)
(if (string? url)
(fetch-style-urls (if (directory-exists? (string->path url))
(path->url (string->path url))
(string->url url)))
(if (string=? (url-scheme url) "file")
(let* ([path (url->path url)]
[files-and-dirs (directory-list path)]
[full-paths (map (lambda (f) (path->complete-path f path))
files-and-dirs)]
[only-styles (filter style-path? full-paths)])
(map path->url only-styles))
(let* ([files-and-dirs (webdav-directory-list url)]
[full-urls (map (lambda (u) (combine-url/relative url u)) files-and-dirs)]
[only-styles (filter style-url? full-urls)])
only-styles))))
(provide/contract [style-warehouse-path (case-> (style? . -> . path?)
(string? string? . -> . path?))])
(define style-warehouse-path
(case-lambda
[(s) (style-warehouse-path (url->string (style-url s)) (style-id s))]
[(url-str id) (path->directory-path (build-path *warehouse-dir* (number->string (equal-hash-code url-str)) id))]))
(define (real-uninstall-path installed-path)
(let ([uipathfile (build-path installed-path *uninstall-info-file*)])
(if (file-exists? uipathfile)
(string->path (call-with-input-file uipathfile read))
#f)))
(provide/contract [style-installed? (style? . -> . boolean?)])
(define (style-installed? s)
(and (directory-exists? (style-path s))
(equal? (real-uninstall-path (style-path s))
(style-warehouse-path s))))
(define (load-style-icon path max-width max-height)
(let* ([bmp (make-object bitmap% path)]
[w (send bmp get-width)]
[h (send bmp get-height)]
[wf (if (> w max-width) (/ max-width w) 1)]
[hf (if (> h max-height) (/ max-height h) 1)]
[f (if (> wf hf) hf wf)]
[wfinal (round (* w f))]
[hfinal (round (* h f))]
[destbmp (make-object bitmap% wfinal hfinal)]
[destbmpdc (new bitmap-dc% [bitmap destbmp])])
(send destbmpdc draw-bitmap-section-smooth bmp 0 0 wfinal hfinal 0 0 w h #f)
(send destbmpdc set-bitmap #f)
destbmp))
(define path->style
(case-lambda
[(path) (path->style (path->url path) path)]
[(url path) (let*-values ([(base style-id must-be-dir?) (split-path path)]
[(style-id-str) (path->string style-id)]
[(style-install-dir) (build-path *install-dir* style-id-str)]
[(all-strings) (load-string-table-from-file (build-path path "strings.txt"))])
(make-style style-id-str
url
style-install-dir
(load-style-icon (build-path path "icon.png") 64 64)
all-strings))]))
(provide/contract [url->style ((or/c string? url?) . -> . style?)])
(define (url->style url)
(if (string? url)
(url->style (if (directory-exists? (string->path url))
(path->url (string->path url))
(string->url url)))
(if (string=? (url-scheme url) "file")
(path->style (url->path url))
(let* ([url-str (url->string url)]
[style-id-str (second (regexp-match #px"/(S[[:digit:]]{5}_[[:alnum:]_]+)/?$" url-str))]
[style-install-dir (build-path *install-dir* style-id-str)]
[style-prefs-dir (build-path (find-system-path 'pref-dir) "muveeStyles")]
[whp (style-warehouse-path url-str style-id-str)])
(make-directory* style-prefs-dir)
(if (directory-exists? whp)
(path->style url whp)
(let ([all-strings (load-string-table-from-file
(webdav-download-file (combine-url/relative url "strings.txt")
(build-path style-prefs-dir "strings.txt")
#:use-cache #t))]
[icon (load-style-icon (webdav-download-file (combine-url/relative url "icon.png")
(build-path style-prefs-dir "icon.png")
#:use-cache #t)
64 64)])
(make-style style-id-str url style-install-dir icon all-strings)))))))
(define (load-string-table-from-port port)
(let* ([lines (port->lines port)]
[parsed (map (lambda (str)
(let ([result (regexp-match #px"([[:alnum:]]+)\t([a-zA-Z-]+)\t(.+)" str)])
(if result (rest result) #f)))
lines)]
[only-useful-lines (filter (lambda (x) x) parsed)]
[langcodes (make-hasheq)])
(for-each (lambda (entry)
(let* ([langcode (string->symbol (second entry))]
[langstrs (hash-ref langcodes langcode (make-hasheq))])
(hash-set! langstrs (string->symbol (first entry)) (third entry))
(hash-set! langcodes langcode langstrs)))
only-useful-lines)
langcodes))
(define (load-string-table-from-file path)
(if path
(call-with-input-file path load-string-table-from-port)
(make-hasheq)))
(provide/contract [install-style (style? . -> . any)])
(define (install-style s)
(with-handlers [(exn:fail? (lambda (e)
(display e)
(message-box "Error"
(format "Failed to install style [~a]!" (style-id s))
#f
'(ok caution))
#f))]
(cond
[(style-installed? s) #t]
[(directory-exists? (style-warehouse-path s))
(when (directory-exists? (style-path s))
(uninstall-unknown-style (style-path s)))
(rename-file-or-directory (style-warehouse-path s) (style-path s))
#t]
[#t (webdav-download-folder (style-url s) (style-warehouse-path s))
(install-style s)])))
(provide/contract [uninstall-style (style? . -> . any)])
(define (uninstall-style s)
(if (style-installed? s)
(begin (rename-file-or-directory (style-path s) (style-warehouse-path s))
#t)
#f))
(define (uninstall-unknown-style path-to-installed-style)
(if (string? path-to-installed-style)
(uninstall-unknown-style (path->directory-path (string->path path-to-installed-style)))
(let ([uipathfile (build-path path-to-installed-style *uninstall-info-file*)])
(if (file-exists? uipathfile)
(let ([uipath (string->path (call-with-input-file uipathfile read))])
(rename-file-or-directory path-to-installed-style uipath))
(begin
(when (eq? 'yes
(message-box "Warning"
(format "Delete style directory [~a]?" (path->string path-to-installed-style))
#f
'(yes-no caution)))
(delete-directory/files path-to-installed-style)))))))
(define (dir-modify-seconds dir)
(if (directory-exists? dir)
(file-or-directory-modify-seconds dir)
0))
(provide/contract [update-style (style? . -> . any)])
(define (update-style s)
(with-handlers [(exn:fail? (lambda (e) (display e) s))]
(if (not (style-installed? s))
(let ([whp (style-warehouse-path s)])
(if (string=? (url-scheme (style-url s)) "file")
(if (> (recursive-path-modify-seconds (url->path (style-url s)))
(dir-modify-seconds whp))
(begin (when (directory-exists? whp) (delete-directory/files whp))
(make-directory* whp)
(delete-directory whp)
(copy-directory/files (url->path (style-url s)) whp)
(call-with-output-file (build-path whp *uninstall-info-file*)
(lambda (p) (write (path->string whp) p))
#:exists 'replace)
(url->style (style-url s)))
s)
(if (> (webdav-resource-modify-seconds (style-url s))
(dir-modify-seconds whp))
(begin (webdav-download-folder (style-url s) whp)
(call-with-output-file (build-path whp *uninstall-info-file*)
(lambda (p) (write (path->string whp) p))
#:exists 'replace)
(url->style (style-url s)))
s)))
(begin (uninstall-style s)
(let ([uds (update-style s)])
(install-style uds)
uds)))))
(provide/contract [copy-style (style? string? . -> . any)])
(define (copy-style s new-id)
(if (style-installed? s)
(dynamic-wind (lambda () (uninstall-style s))
(lambda () (copy-style s new-id))
(lambda () (install-style s)))
(let ([dest-path (path->directory-path (build-path my-styles-folder new-id))])
(update-style s)
(if (directory-exists? dest-path)
#f
(begin (copy-directory/files (style-warehouse-path s) dest-path)
(let ([uiinfo (build-path dest-path *uninstall-info-file*)])
(when (file-exists? uiinfo)
(delete-file uiinfo)))
(path->style dest-path))))))