My agentic slop goes here. Not intended for anyone else!

more

+9 -13
stack/river/bin/river_cli.ml
···
Ptime.compare b.updated a.updated
) combined
-
let sync_user ~sw ~requests env state ~username =
+
let sync_user client state ~username =
match State.load_user state username with
| None ->
Log.err (fun m -> m "User %s not found" username);
···
| Some user ->
Log.info (fun m -> m "Syncing feeds for user %s..." username);
-
(* Fetch all feeds concurrently using the shared session *)
+
(* Fetch all feeds concurrently using the client *)
let fetched_feeds =
Eio.Fiber.List.filter_map (fun source ->
try
Log.info (fun m -> m " Fetching %s (%s)..." source.River.name source.River.url);
-
Some (River.fetch ~sw ~requests env source)
+
Some (River.fetch client source)
with e ->
Log.err (fun m -> m " Failed to fetch %s: %s"
source.River.name (Printexc.to_string e));
···
0
end
-
let sync_all ~sw ~requests env state =
+
let sync_all client state =
let users = State.list_users state in
if users = [] then begin
Log.info (fun m -> m "No users to sync");
···
let results =
Eio.Fiber.List.map (fun username ->
-
let result = sync_user ~sw ~requests env state ~username in
+
let result = sync_user client state ~username in
Log.debug (fun m -> m "Completed sync for user");
result
) users
···
let state = { xdg } in
State.ensure_directories state;
-
Eio.Switch.run @@ fun sw ->
-
(* Create a single Requests session for all operations *)
-
let requests = Requests.create ~sw env
-
~follow_redirects:true
-
~max_redirects:5 in
-
+
(* Use River.Client.with_client for resource management *)
+
River.Client.with_client env @@ fun client ->
match username_opt with
-
| Some username -> Sync.sync_user ~sw ~requests env state ~username
-
| None -> Sync.sync_all ~sw ~requests env state
+
| Some username -> Sync.sync_user client state ~username
+
| None -> Sync.sync_all client state
) $ username_opt)
(* List command - doesn't need network, just reads local files *)
+1
stack/river/dune-project
···
(eio_main
(>= 1.0))
requests
+
requests_json_api
eiocmd
logs
ptime
+3 -2
stack/river/example/aggregate_feeds.ml
···
]
let main env =
-
Eio.Switch.run @@ fun sw ->
-
let feeds = List.map (River.fetch ~sw env) sources in
+
(* Use River.Client.with_client for proper resource management *)
+
River.Client.with_client env @@ fun client ->
+
let feeds = List.map (River.fetch client) sources in
let posts = River.posts feeds in
let entries = River.create_atom_entries posts in
let feed =
+1 -1
stack/river/lib/dune
···
(library
(name river)
(public_name river)
-
(libraries eio eio_main requests logs str syndic lambdasoup uri ptime))
+
(libraries eio eio_main requests requests_json_api logs str syndic lambdasoup uri ptime))
+11 -5
stack/river/lib/feed.ml
···
Log.err (fun m -> m "Backtrace:\n%s" (Printexc.get_backtrace ()));
raise e
-
let fetch ~sw ?requests env (source : source) =
+
let fetch client (source : source) =
Log.info (fun m -> m "Fetching feed '%s' from %s" source.name source.url);
let xmlbase = Uri.of_string @@ source.url in
+
+
(* Use Requests_json_api.get_result for clean Result-based error handling *)
+
let session = Client.session client in
let response =
-
try Http.get ~sw ?requests env source.url
-
with e ->
-
Log.err (fun m -> m "Failed to fetch feed '%s': %s" source.name (Printexc.to_string e));
-
raise e
+
match Requests_json_api.get_result session source.url with
+
| Ok body ->
+
Log.info (fun m -> m "Successfully fetched %s (%d bytes)" source.url (String.length body));
+
body
+
| Error (status, msg) ->
+
Log.err (fun m -> m "Failed to fetch feed '%s': HTTP %d - %s" source.name status msg);
+
failwith (Printf.sprintf "HTTP %d: %s" status msg)
in
let content = classify_feed ~xmlbase response in
-81
stack/river/lib/http.ml
···
-
(*
-
* Copyright (c) 2014, OCaml.org project
-
* Copyright (c) 2015 KC Sivaramakrishnan <sk826@cl.cam.ac.uk>
-
*
-
* Permission to use, copy, modify, and distribute this software for any
-
* purpose with or without fee is hereby granted, provided that the above
-
* copyright notice and this permission notice appear in all copies.
-
*
-
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
*)
-
-
(* Download urls — using Requests library with Eio *)
-
-
let src = Logs.Src.create "river.http" ~doc:"River HTTP client"
-
module Log = (val Logs.src_log src : Logs.LOG)
-
-
exception Status_unhandled of string
-
exception Timeout
-
-
let get ~sw ?requests env url =
-
Log.info (fun m -> m "Fetching URL: %s" url);
-
-
try
-
(* Create or use existing Requests session with automatic redirect following *)
-
let req = match requests with
-
| Some r -> r
-
| None ->
-
Requests.create ~sw env
-
~follow_redirects:true
-
~max_redirects:5
-
in
-
-
Log.debug (fun m -> m "Using Requests session with max_redirects=5");
-
-
(* Make the GET request with timeout *)
-
let response =
-
try
-
Log.debug (fun m -> m "Making GET request with 3s timeout");
-
Requests.get req
-
~timeout:(Requests.Timeout.create ~total:3.0 ())
-
url
-
with
-
| Requests.Error.ConnectionError msg ->
-
Log.err (fun m -> m "Connection error for %s: %s" url msg);
-
raise (Status_unhandled (Printf.sprintf "Connection error: %s" msg))
-
| Requests.Error.Timeout ->
-
Log.err (fun m -> m "Request timeout for %s after 3s" url);
-
raise Timeout
-
| Requests.Error.TooManyRedirects { url; count; max } ->
-
Log.err (fun m -> m "Too many redirects (%d/%d) for %s" count max url);
-
raise (Status_unhandled (Printf.sprintf "Too many redirects (%d/%d) for %s" count max url))
-
in
-
-
(* Check the status code *)
-
let status = Requests.Response.status_code response in
-
Log.debug (fun m -> m "Received response with status %d" status);
-
-
if status >= 200 && status < 300 then begin
-
(* Success - read the body *)
-
let body = Requests.Response.body response |> Eio.Flow.read_all in
-
let body_size = String.length body in
-
Log.info (fun m -> m "Successfully fetched %s (%d bytes)" url body_size);
-
body
-
end else begin
-
Log.err (fun m -> m "HTTP error %d for %s" status url);
-
raise (Status_unhandled (Printf.sprintf "HTTP status %d" status))
-
end
-
-
with
-
| (Status_unhandled _ | Timeout) as e ->
-
(* Already logged *)
-
raise e
-
| e ->
-
Log.err (fun m -> m "Unexpected error fetching %s: %s" url (Printexc.to_string e));
-
raise e
+4 -2
stack/river/lib/river.ml
···
let src = Logs.Src.create "river" ~doc:"River RSS/Atom aggregator"
module Log = (val Logs.src_log src : Logs.LOG)
+
module Client = Client
+
type source = Feed.source = { name : string; url : string }
type feed = Feed.t
type post = Post.t
-
let fetch ~sw ?requests env source =
+
let fetch client source =
Log.info (fun m -> m "Fetching feed: %s" source.name);
-
Feed.fetch ~sw ?requests env source
+
Feed.fetch client source
let name feed = feed.Feed.name
let url feed = feed.Feed.url
+6 -12
stack/river/lib/river.mli
···
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)
+
(** River HTTP client *)
+
module Client = Client
+
type source = { name : string; url : string }
(** The source of a feed. *)
type feed
type post
-
val fetch :
-
sw:Eio.Switch.t ->
-
?requests:(([> float Eio.Time.clock_ty ] as 'a) Eio.Resource.t, ([> [> `Generic ] Eio.Net.ty ] as 'b) Eio.Resource.t) Requests.t ->
-
< clock : 'a Eio.Resource.t;
-
fs : Eio.Fs.dir_ty Eio.Path.t;
-
net : 'b Eio.Resource.t; .. > ->
-
source ->
-
feed
-
(** [fetch ~sw ?requests env source] returns an Atom or RSS feed from a source.
+
val fetch : Client.t -> source -> feed
+
(** [fetch client source] returns an Atom or RSS feed from a source.
-
@param sw The switch to use for resource cleanup
-
@param requests Optional Requests session to reuse. If not provided, a new session will be created
-
@param env The Eio environment
+
@param client The River HTTP client
@param source The feed source to fetch *)
val name : feed -> string
+1
stack/river/river.opam
···
"eio" {>= "1.0"}
"eio_main" {>= "1.0"}
"requests"
+
"requests_json_api"
"eiocmd"
"logs"
"ptime"
+5 -7
stack/river/test/test_eio_river.ml
···
let main env =
Printf.printf "Testing River library with Eio and Requests...\n";
-
Eio.Switch.run @@ fun sw ->
+
(* Use River.Client.with_client for proper resource management *)
+
River.Client.with_client env @@ fun client ->
(* Test fetching feeds *)
let feeds =
try
-
List.map (River.fetch ~sw env) test_sources
+
List.map (River.fetch client) test_sources
with
-
| River__Http.Status_unhandled msg ->
-
Printf.printf "HTTP error: %s\n" msg;
-
[]
-
| River__Http.Timeout ->
-
Printf.printf "Request timed out\n";
+
| Failure msg ->
+
Printf.printf "Error: %s\n" msg;
[]
| e ->
Printf.printf "Error: %s\n" (Printexc.to_string e);
+4 -6
stack/river/test/test_logging.ml
···
(* Test with logging *)
Printf.printf "Testing River library with logging...\n\n";
-
Eio.Switch.run @@ fun sw ->
+
(* Use River.Client.with_client for proper resource management *)
+
River.Client.with_client env @@ fun client ->
(* Demonstrate fetching with logging *)
let feeds =
try
-
List.map (River.fetch ~sw env) test_sources
+
List.map (River.fetch client) test_sources
with
-
| River__Http.Status_unhandled msg ->
+
| Failure msg ->
Printf.printf "Expected error (for demo): %s\n" msg;
-
[]
-
| River__Http.Timeout ->
-
Printf.printf "Timeout (expected for non-existent feed)\n";
[]
| e ->
Printf.printf "Error: %s\n" (Printexc.to_string e);