Permalink
Browse files

added support for anchors, fixed the handling of wrong paths on serve…

…r-side
  • Loading branch information...
1 parent 72d6589 commit 1a690ab2d668d98e84e144e861ed132879ee17f5 Mathieu Baudet committed May 31, 2012
Showing with 158 additions and 42 deletions.
  1. +11 −6 Makefile
  2. +11 −0 plugins/encode.js
  3. +57 −0 plugins/encode.ml
  4. +4 −0 src/data.opa
  5. +30 −20 src/server.opa
  6. +45 −16 src/view.opa
View
17 Makefile
@@ -7,8 +7,10 @@ RESOURCES=resources/*
LIBDIR=src/stdlib
LIBS=custom.stdlib.apis.common.opx custom.stdlib.apis.oauth.opx custom.stdlib.apis.dropbox.opx
-statbox: $(LIBS) $(SOURCES) $(RESOURCES)
- opa $(SOURCES) -o statbox --slicer-dump
+PLUGINS=encode.opp
+
+statbox: $(LIBS) $(SOURCES) $(RESOURCES) $(PLUGINS)
+ opa $(PLUGINS) $(SOURCES) -o statbox --slicer-dump
custom.stdlib.apis.common.opx: $(LIBDIR)/api_libs.opa
opa -c --parser classic $^
@@ -19,18 +21,21 @@ custom.stdlib.apis.oauth.opx: $(LIBDIR)/oauth.opa
custom.stdlib.apis.dropbox.opx: $(LIBDIR)/dropbox.opa
opa -c --parser classic $^
+%.opp: plugins/%.js plugins/%.ml
+ opa-plugin-builder --js-validator-off $^ -o $@
+
clean::
- rm -rf _build _tracks .opx/* $(LIBS) $(LIBS:.opx=.opx.broken) statbox
+ rm -rf _build _tracks $(PLUGINS) .opx/* $(LIBS) $(LIBS:.opx=.opx.broken) statbox
## hackish deployment scripts: use with care
DBPATH=$(HOME)/var/mongodb
BINPATH=$(HOME)/bin
run:: statbox
killall mongod || true
+ killall statbox || true
mkdir -p $(DBPATH)
$(BINPATH)/mongod --dbpath $(DBPATH) &
- killall statbox || true
authbind ./statbox -p 80
clean-all:: clean
@@ -39,8 +44,8 @@ clean-all:: clean
deploy::
git push -f origin master:deploy && \
- ssh "$(USER)@$(HOST)" 'cd git/statbox && git fetch && git checkout deploy && git pull origin deploy && make run'
+ ssh "$(USER)@$(HOST)" 'cd git/statbox && git fetch && git checkout deploy && git reset --hard origin/deploy && make run'
clean-deploy::
git push -f origin master:deploy && \
- ssh "$(USER)@$(HOST)" 'cd git/statbox && make clean && git fetch && git checkout deploy && make clean-all && git pull origin deploy && make run'
+ ssh "$(USER)@$(HOST)" 'cd git/statbox && make clean && git fetch && git checkout deploy && make clean-all && git reset --hard origin/deploy && make run'
View
11 plugins/encode.js
@@ -0,0 +1,11 @@
+##register encode_string : string -> string
+ ##args(str)
+ {
+ return encodeURI(str);
+ }
+
+##register decode_string : string -> string
+ ##args(str)
+ {
+ return decodeURI(str);
+ }
View
57 plugins/encode.ml
@@ -0,0 +1,57 @@
+open Base
+
+(* these functions originate from Opa's libbase *)
+
+let chhxmp = Array.init 256 (fun i -> Printf.sprintf "%02X" i)
+let pc_encode ch = "%"^(chhxmp.(Char.code ch))
+
+(* https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI *)
+let is_url = function
+ | 'a'..'z' -> true | 'A'..'Z' -> true | '0'..'9' -> true
+ | '-' | '_' | '.' | '!' | '~' | '*' | '\'' |'(' | ')' -> true
+ | ';' | ',' | '/' | '?' | ':' | '@' | '&' | '=' | '+' | '$' | '#' -> true
+ | _ -> false
+
+let encode_chars_filter ?(hint=(fun l -> (l + (l asr 4)))) is_char encode_char s =
+ let l = String.length s in
+ let b = Buffer.create (hint l) in
+ let rec aux i j =
+ if i < l
+ then
+ if is_char s.[i]
+ then (Buffer.add_char b s.[i]; aux (i+1) (j+1))
+ else
+ let code = encode_char s.[i] in
+ let clen = String.length code in
+ Buffer.add_string b code; aux (i+1) (j+clen)
+ else j
+ in
+ Buffer.sub b 0 (aux 0 0)
+
+let http_unencode s =
+ let l = String.length s in
+ let r = String.copy s in
+ let rec aux i j =
+ if i < l
+ then
+ (match s.[i] with
+ | '%' ->
+ let k = i + 1 in
+ if k + 1 < l
+ then
+ (match s.[k],s.[k+1] with
+ | (ch1,ch2) when (Charf.is_hexf ch1 && Charf.is_hexf ch2) ->
+ (r.[j] <- Char.chr (Charf.c2h s.[k] s.[k+1]); aux (i+3) (j+1))
+ | _ -> (r.[j] <- '%'; aux (i+1) (j+1)))
+ else (r.[j] <- '%'; aux (i+1) (j+1))
+ | ch -> (r.[j] <- ch; aux (i+1) (j+1)))
+ else j
+ in
+ String.unsafe_sub r 0 (aux 0 0)
+
+##register encode_string : string -> string
+let encode_string s = encode_chars_filter is_url pc_encode s
+
+##register decode_string : string -> string
+let decode_string = http_unencode
+
View
4 src/data.opa
@@ -83,6 +83,10 @@ function is_folder(Dropbox.element element) {
}
}
+ function is_valid(string path, int uid) {
+ (path == root_path) || (?/entries/all[{~path, ~uid}] != {none})
+}
+
function process_delta_entry(int uid, Dropbox.delta_entry de) {
// Log.info("Data.process_delta_entry", "Processing delta entry: {de.path}");
path = de.path
View
50 src/server.opa
@@ -2,6 +2,8 @@ import stdlib.web.client
/* functions exposed to clients */
+// FIXME: make each of these function thread-safe by taking a 'mutex' on the uid
+
module ServerLib {
/* authentication */
@@ -43,11 +45,13 @@ module ServerLib {
function read_content() {
match (DropboxSession.get()) {
- case {~current_path, ~uid, credentials:_}:
- path =
- if (?/entries/all[{path:current_path, ~uid}] != {none}) current_path
- else "/"
- {folder: path, user_info:Data.get_user_info(uid).quota_info}
+ case {~current_path, ~uid, ~credentials}:
+ if (Data.is_valid(current_path, uid)) {
+ {folder: current_path, user_info:Data.get_user_info(uid).quota_info}
+ } else {
+ DropboxSession.set({current_path:Data.root_path, ~uid, ~credentials});
+ {folder: Data.root_path, user_info:Data.get_user_info(uid).quota_info};
+ }
case {pending_request:_}: {error}
case {disconnected}: {welcome}
}
@@ -69,14 +73,18 @@ module ServerLib {
function read_data(path) {
match (DropboxSession.get_uid()) {
case {some:uid}:
- ViewLib.folder_info info =
- {counter: Analytics.count_folder_entries(uid, path),
- total_size: Analytics.get_folder_total_size(uid, path),
- dotslash_size: Analytics.get_folder_dotslash_size(uid, path),
- full_path: Analytics.get_folder_full_path(uid, path),
- subdirs: Analytics.list_folder_subdirs(uid, path),
- }
- {some:info}
+ if (Data.is_valid(path, uid)) {
+ ViewLib.folder_info info =
+ {counter: Analytics.count_folder_entries(uid, path),
+ total_size: Analytics.get_folder_total_size(uid, path),
+ dotslash_size: Analytics.get_folder_dotslash_size(uid, path),
+ full_path: Analytics.get_folder_full_path(uid, path),
+ subdirs: Analytics.list_folder_subdirs(uid, path),
+ }
+ {some:info}
+ } else {
+ {none}
+ }
default: {none}
}
}
@@ -90,15 +98,17 @@ module ServerLib {
@async exposed function move_to_path(string path) {
match (DropboxSession.get()) {
- case {~uid, ~credentials, current_path:_}: DropboxSession.set({~uid, ~credentials, current_path: path})
+ case {~uid, ~credentials, current_path:_}:
+ if (Data.is_valid(path, uid)) {
+ DropboxSession.set({~uid, ~credentials, current_path: path});
+ match(read_data(path)) {
+ case {some:data}: ViewLib.set_data(path, data)
+ case {none}: void
+ } // N.B. we don't use the async function push_data to ensure that get_data is computed before the last call
+ }
+ push_content() // in any case
default: void
}
- match(read_data(path)) {
- case {some:data}: ViewLib.set_data(path, data)
- case {none}: void
- }
- // N.B. we don't use the async function push_data to ensure that get_data is computed before the last call
- push_content()
}
}
View
61 src/view.opa
@@ -41,19 +41,6 @@ type ViewLib.folder_info = {
module ViewLib {
-
- function initial_setup(login, content, current_folder_data) {
- // update client's state right away
- ClientReference.set(viewlib_login, login);
- ClientReference.set(viewlib_content, content);
- match ((content, current_folder_data)) {
- case ({folder:path ...}, {some:value}):
- Client.Anchor.set_anchor(path);
- ClientReference.set(viewlib_data, Map.add(path, value, ClientReference.get(viewlib_data)));
- default: void
- }
- render_contentframe();
- }
// server -> client synchro
@async client function set_login(value) {
@@ -63,6 +50,7 @@ module ViewLib {
@async client function set_content(value) {
ClientReference.set(viewlib_content, value)
+ set_anchor(value);
render_contentframe();
render_footer();
}
@@ -131,6 +119,47 @@ module ViewLib {
}
}
+
+ function set_anchor(ViewLib.content content) {
+ match (content) {
+ case ({folder:path ...}): Client.Anchor.set_anchor(path)
+ default: Client.Anchor.set_anchor("#")
+ }
+ }
+
+ // Ocaml and javascript binding
+ encode_string = %% encode.encode_string %%
+ decode_string = %% encode.decode_string %%
+
+ function make_link(xhtml label, string path_key) {
+ path = encode_string(path_key);
+ Xhtml.add_attribute_unsafe("href", "#{path}", <a>{label}</a>)
+ }
+
+ client function process_anchor(string anchor) {
+ path = decode_string(anchor);
+ Log.info("process_anchor", path);
+ match (ClientReference.get(viewlib_content)) {
+ case {~folder, ~user_info}: if (path != folder) set_content({folder: path, ~user_info});
+ default: void // not connected => we ignore anchors
+ }
+ void
+ }
+
+ client function initial_setup(ViewLib.login login, ViewLib.content content, option(ViewLib.folder_info) current_folder_data) {
+ // update client's state right away
+ ClientReference.set(viewlib_login, login);
+ ClientReference.set(viewlib_content, content);
+ set_anchor(content);
+ match ((content, current_folder_data)) {
+ case ({folder:path ...}, {some:info}):
+ ClientReference.set(viewlib_data, Map.add(path, info, ClientReference.get(viewlib_data)));
+ render_charts(info);
+ default: void
+ };
+ ignore(Client.Anchor.add_handler(process_anchor))
+ }
+
}
// pure functions to construct Html
@@ -169,8 +198,7 @@ module ViewMake {
function subdir_html({~label, ~path_key, ~total_size}) {
<tr>
- <td><a href="#" onclick={function(_){ServerLib.move_to_path(path_key)}}>
- {string_limit(label, 45)}</a></td>
+ <td>{ViewLib.make_link(<span>{string_limit(label, 45)}</span>, path_key)}</td>
<td class="pull-right">{size_opt_html(total_size)}</td>
</tr>
}
@@ -216,13 +244,14 @@ module ViewMake {
m = if (n == 0) 50 else 80 / n;
function label_html(~{label, path_key}) {
+ Log.info("debug", "Making element {label} {path_key} in breadcrumb.");
hlabel =
if (label == "") {
<img src="resources/dropbox_logo.png" alt="Root" height="32" width="32" />
} else {
<span>{string_limit(label, m)}</span>
}
- ha = <a href="#" onclick={function(_){ServerLib.move_to_path(path_key)}}>{hlabel}</a>
+ ha = ViewLib.make_link(hlabel, path_key)
<+> <span class="divider">/</span>
if (path_key == path) { // last one?
<li class="active">{ha}</li>

0 comments on commit 1a690ab

Please sign in to comment.