···7474 let content = really_input_string ic (in_channel_length ic) in
7575 of_string ?progress content
76767777-let load_from_url ?progress:_ _url =
7878- (* For now, this is a placeholder. In a full implementation, this would
7979- use an HTTP client to fetch the URL content and parse it *)
8080- failwith "Registry.load_from_url not yet implemented - requires HTTP client"
7777+let load_from_url ?progress url =
7878+ (* Use wget/curl via system command to fetch URL content *)
7979+ let temp_file = Filename.temp_file "toru_registry" ".txt" in
8080+ let finally () =
8181+ try Sys.remove temp_file with _ -> ()
8282+ in
8383+ Fun.protect ~finally @@ fun () ->
8484+ let wget_cmd = Printf.sprintf "timeout 60 wget -q -O '%s' '%s'" temp_file url in
8585+ let curl_cmd = Printf.sprintf "timeout 60 curl -s -o '%s' '%s'" temp_file url in
8686+8787+ (* Try wget first, then curl as fallback *)
8888+ let result =
8989+ if Sys.command wget_cmd = 0 then Ok ()
9090+ else if Sys.command curl_cmd = 0 then Ok ()
9191+ else Error "Failed to download registry (neither wget nor curl worked)"
9292+ in
9393+9494+ match result with
9595+ | Error msg -> failwith msg
9696+ | Ok () ->
9797+ let ic = open_in temp_file in
9898+ let finally2 () = close_in ic in
9999+ Fun.protect ~finally:finally2 @@ fun () ->
100100+ let content = really_input_string ic (in_channel_length ic) in
101101+ of_string ?progress content
8110282103let save path registry =
83104 let file_path = Eio.Path.native_exn path in
+127-12
toru/lib/toru/toru.ml
···44 registry : Registry.t;
55 downloader : (module Downloader.DOWNLOADER);
66 sw : Eio.Switch.t;
77+ env : Eio_unix.Stdenv.base;
78}
89910let create ~sw ~env ~base_url ~cache_path ?version ?registry_file ?registry_url ?downloader () =
1010- let cache = Cache.create ~sw ~env ?version cache_path in
1111+ let cache = Cache.create ~sw ~fs:env#fs ?version cache_path in
1112 let registry = match registry_file with
1213 | Some file -> Registry.load (Eio.Path.(env#fs / file))
1314 | None ->
···2021 | None ->
2122 Downloader.Downloaders.create_default ~env
2223 in
2323- { base_url; cache; registry; downloader; sw }
2424+ { base_url; cache; registry; downloader; sw; env }
24252526let base_url t = t.base_url
2627let cache t = t.cache
2728let registry t = t.registry
28292929-let fetch _t ~filename:_ ?processor:_ () =
3030- (* TODO: Implement file fetching *)
3131- Error "Toru.fetch not yet implemented"
3030+let rec fetch t ~filename ?processor () =
3131+ (* 1. Check if file exists in registry *)
3232+ match Registry.find filename t.registry with
3333+ | None -> Error ("File not found in registry: " ^ filename)
3434+ | Some entry ->
3535+ (* 2. Check if file is already cached *)
3636+ let cache_path = Cache.file_path t.cache filename in
3737+ let cached_file_exists = Cache.exists t.cache filename in
3838+3939+ (* 3. If cached, verify hash *)
4040+ if cached_file_exists then (
4141+ let entry_hash = Registry.hash entry in
4242+ if Hash.verify cache_path entry_hash then (
4343+ (* File is cached and valid - apply processor if provided *)
4444+ match processor with
4545+ | None -> Ok cache_path
4646+ | Some proc -> Ok (proc cache_path)
4747+ ) else (
4848+ (* Cached file is corrupt - remove it and re-download *)
4949+ (try Eio.Path.unlink cache_path with _ -> ());
5050+ (* Fall through to download *)
5151+ fetch_file_to_cache t entry filename processor
5252+ )
5353+ ) else (
5454+ (* File not cached - download it *)
5555+ fetch_file_to_cache t entry filename processor
5656+ )
32573333-let fetch_all _t ?concurrency:_ () =
3434- (* TODO: Implement batch fetching *)
3535- Error "Toru.fetch_all not yet implemented"
5858+and fetch_file_to_cache t entry filename processor =
5959+ (* Ensure cache directory exists *)
6060+ Cache.ensure_dir t.cache;
6161+6262+ (* Get the download URL *)
6363+ let download_url = match Registry.custom_url entry with
6464+ | Some custom -> custom
6565+ | None -> t.base_url ^ filename
6666+ in
6767+6868+ (* Download the file *)
6969+ let (module D : Downloader.DOWNLOADER) = t.downloader in
7070+ let downloader_instance = D.create ~sw:t.sw ~env:t.env () in
7171+ let cache_path = Cache.file_path t.cache filename in
7272+ let entry_hash = Registry.hash entry in
7373+7474+ match D.download downloader_instance ~url:download_url ~dest:cache_path ~hash:entry_hash () with
7575+ | Error msg -> Error ("Download failed: " ^ msg)
7676+ | Ok () ->
7777+ (* Apply processor if provided *)
7878+ match processor with
7979+ | None -> Ok cache_path
8080+ | Some proc -> Ok (proc cache_path)
8181+8282+let fetch_all t ?concurrency:_ () =
8383+ let all_entries = Registry.entries t.registry in
8484+ let total = List.length all_entries in
8585+8686+ if total = 0 then
8787+ Ok ()
8888+ else (
8989+ (* Create a semaphore to limit concurrency *)
9090+ let results = ref [] in
9191+ let errors = ref [] in
9292+ let completed = ref 0 in
9393+9494+ (* Process entries in batches *)
9595+ let rec process_batch entries =
9696+ match entries with
9797+ | [] ->
9898+ if !completed = total then
9999+ if List.length !errors > 0 then
100100+ Error ("Multiple failures: " ^ String.concat "; " !errors)
101101+ else
102102+ Ok ()
103103+ else
104104+ Error "Internal error: not all files processed"
105105+ | entry :: rest ->
106106+ let filename = Registry.filename entry in
107107+ (match fetch t ~filename () with
108108+ | Ok path ->
109109+ results := path :: !results;
110110+ incr completed
111111+ | Error msg ->
112112+ errors := (filename ^ ": " ^ msg) :: !errors;
113113+ incr completed);
114114+ process_batch rest
115115+ in
116116+117117+ (* For now, implement simple sequential processing *)
118118+ (* TODO: Add actual concurrent processing with Eio fibers *)
119119+ process_batch all_entries
120120+ )
3612137122let load_registry t source =
38123 let new_registry =
···51136let update_base_url t new_url =
52137 { t with base_url = new_url }
531385454-let retrieve ~sw:_ ~fs:_ ~url:_ ?hash:_ ?cache_path:_ ?downloader:_ () =
5555- (* TODO: Implement one-off file retrieval *)
5656- Error "Toru.retrieve not yet implemented"
139139+let retrieve ~sw ~env ~url ?hash ?cache_path ?downloader () =
140140+ (* Get cache path *)
141141+ let cache_dir = match cache_path with
142142+ | Some path -> path
143143+ | None -> Cache.default_cache_path ~app_name:"toru-temp" ()
144144+ in
145145+146146+ (* Create a temporary cache *)
147147+ let cache = Cache.create ~sw ~fs:env#fs cache_dir in
148148+ Cache.ensure_dir cache;
149149+150150+ (* Extract filename from URL *)
151151+ let filename =
152152+ match String.rindex_opt url '/' with
153153+ | Some idx -> String.sub url (idx + 1) (String.length url - idx - 1)
154154+ | None -> "downloaded_file"
155155+ in
156156+157157+ (* Get downloader *)
158158+ let downloader_module = match downloader with
159159+ | Some d -> d
160160+ | None -> Downloader.Downloaders.create_default ~env
161161+ in
162162+163163+ (* Download the file *)
164164+ let (module D : Downloader.DOWNLOADER) = downloader_module in
165165+ let downloader_instance = D.create ~sw ~env () in
166166+ let dest_path = Cache.file_path cache filename in
167167+168168+ match D.download downloader_instance ~url ~dest:dest_path ?hash () with
169169+ | Error msg -> Error ("Download failed: " ^ msg)
170170+ | Ok () -> Ok dest_path
5717158172let default_cache_path = Cache.default_cache_path
59173···68182module Cache = Cache
69183module Downloader = Downloader
70184module Processors = Processors
7171-module Make_registry = Make_registry185185+module Make_registry = Make_registry
186186+module Logging = Logging
···2626 Printf.printf "=== Testing basic Cache functionality ===\n";
27272828 (* Create a test cache *)
2929- let cache = Cache.create ~sw ~env "test_cache" in
2929+ let cache = Cache.create ~sw ~fs:env#fs "test_cache" in
3030 Printf.printf "Created cache at: %s\n"
3131 (Eio.Path.native_exn (Cache.base_path cache));
3232···3535 Printf.printf "Default cache path: %s\n" default_path;
36363737 (* Test XDG cache path detection *)
3838- let cache_with_version = Cache.create ~sw ~env ~version:"v1.0" "test_cache_versioned" in
3838+ let cache_with_version = Cache.create ~sw ~fs:env#fs ~version:"v1.0" "test_cache_versioned" in
3939 Printf.printf "Cache with version at: %s\n"
4040 (Eio.Path.native_exn (Cache.base_path cache_with_version));
4141
+3-3
toru/test/test_cache_xdg.ml
···7373 Eio_main.run @@ fun env ->
7474 Eio.Switch.run @@ fun sw ->
7575 (* Test cache without version *)
7676- let cache_no_version = Cache.create ~sw ~env "test_cache_no_version" in
7676+ let cache_no_version = Cache.create ~sw ~fs:env#fs "test_cache_no_version" in
7777 let base_path = Cache.base_path cache_no_version in
7878 Printf.printf "Cache without version: %s\n" (Eio.Path.native_exn base_path);
79798080 (* Test cache with version *)
8181- let cache_with_version = Cache.create ~sw ~env ~version:"v2.1" "test_cache_with_version" in
8181+ let cache_with_version = Cache.create ~sw ~fs:env#fs ~version:"v2.1" "test_cache_with_version" in
8282 let versioned_path = Cache.base_path cache_with_version in
8383 Printf.printf "Cache with version: %s\n" (Eio.Path.native_exn versioned_path);
8484···102102103103 Eio_main.run @@ fun env ->
104104 Eio.Switch.run @@ fun sw ->
105105- let cache = Cache.create ~sw ~env "test_cache_mgmt" in
105105+ let cache = Cache.create ~sw ~fs:env#fs "test_cache_mgmt" in
106106 Cache.ensure_dir cache;
107107108108 (* Create test files with different content sizes *)
+2-1
toru/test/test_xdg_integration.ml
···6969 Eio_main.run @@ fun env ->
7070 Eio.Switch.run @@ fun sw ->
7171 (* Create cache using default XDG paths *)
7272- let cache = Toru.Cache.default ~sw ~env ~app_name:"xdg-test" () in
7272+ let base_path = Toru.Cache.default_cache_path ~app_name:"xdg-test" () in
7373+ let cache = Toru.Cache.create ~sw ~fs:env#fs base_path in
7374 let base_path = Toru.Cache.base_path cache in
7475 let path_str = Eio.Path.native_exn base_path in
7576