123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328openCoreopenBonsai.Let_syntaxmoduleProgress=structtypet={loaded:int;total:int}[@@derivingcompare,equal,sexp]letto_percentage{loaded;total}=Percent.of_mult(floatloaded/.floattotal)endmodule_=structtypet=|Contentsofstring|LoadingofProgress.toption|ErrorofError.t[@@derivingcompare,equal,sexp]endmoduleRead_error=structtypet=|Aborted|ErrorofError.t[@@derivingcompare,equal,sexp]endmoduleFile_read=structtypet={result:(string,Read_error.t)Result.tUi_effect.t;abort:unitUi_effect.t}[@@derivingfields]endtypet={read:(Progress.t->unitUi_effect.t)->File_read.tUi_effect.t;filename:string}[@@derivingfields]letsexp_of_tt=Sexp.Atom[%string"<file %{filename t#String}>"]letread?(on_progress=fun_progress->Ui_effect.Ignore)t=t.readon_progressletcontentst=letopenUi_effect.Let_syntaxinmatch%mapreadt>>=File_read.resultwith|Okcontents->Okcontents|ErrorAborted->assertfalse|Error(Errore)->Errore;;moduleExpert=structtypefile_read=File_read.t={result:(string,Read_error.t)Result.tUi_effect.t;abort:unitUi_effect.t}letcreate=Fields.createendmoduleFor_testing=structmoduleTest_data=structtypedata=|ClosedofstringOr_error.t|Openof{chunks:stringQueue.t;total_bytes:int}typeread_callbacks={on_progress:Progress.t->unit;on_finished:stringOr_error.t->unit}moduleRead_state=structtypet=|Not_reading|Aborted|Readingofread_callbacksletitert~f=matchtwith|Not_reading|Aborted->()|Readingcallbacks->fcallbacks;;endtypet={filename:string;mutabledata:data;mutableread_state:Read_state.t}letcreate_stream~filename~total_bytes={filename;data=Open{chunks=Queue.create();total_bytes};read_state=Not_reading};;letcreate_static~filename~contents={filename;data=Closed(Okcontents);read_state=Not_reading};;letread_statust=matcht.read_statewith|Not_reading->`Not_reading|Aborted->`Aborted|Reading_->`Reading;;letreadtread=(matcht.datawith|Open{chunks;total_bytes}->read.on_progress{Progress.loaded=Queue.sum(moduleInt)chunks~f:String.length;total=total_bytes}|Closedresult->read.on_finishedresult);t.read_state<-Readingread;;letabort_readt=t.read_state<-Abortedletfeed_exntchunk=matcht.datawith|Open{chunks;total_bytes}->Queue.enqueuechunkschunk;letprogress={Progress.loaded=Queue.sum(moduleInt)chunks~f:String.length;total=total_bytes}inRead_state.itert.read_state~f:(funread->read.on_progressprogress)|Closed_->raise_s[%message"Bonsai_web_ui_file.Test_data.feed: already closed"];;letcloset=matcht.datawith|Closed_->()|Open{chunks;total_bytes=_}->letresult=Ok(Queue.to_listchunks|>String.concat)int.data<-Closedresult;Read_state.itert.read_state~f:(funread->read.on_finishedresult);;letclose_errorterror=matcht.datawith|Closed_->()|Open_->letresult=Errorerrorint.data<-Closedresult;Read_state.itert.read_state~f:(funread->read.on_finishedresult);;endletcreatetest_data=letmoduleSvar=Ui_effect.For_testing.Svarinletreadon_progress=let(result_var:(string,Read_error.t)Result.tSvar.t)=Svar.create()inletresult=Ui_effect.For_testing.of_svar_fun(fun()->Test_data.readtest_data{on_progress=(funprogress->on_progressprogress|>Ui_effect.Expert.handle);on_finished=(funresult->Svar.fill_if_emptyresult_var(Result.map_errorresult~f:(fune->Read_error.Errore)))};result_var)()inletabort=Ui_effect.of_sync_fun(fun()->Test_data.abort_readtest_data;Svar.fill_if_emptyresult_var(ErrorAborted))()in{File_read.result;abort}in{read=Ui_effect.of_sync_funread;filename=test_data.filename};;endmoduleRead_on_change=structmoduleFile=structtypenonrect=(t[@sexp.opaque])[@@derivingsexp]letequal=phys_equalendmoduleFile_read'=structtypet=(File_read.t[@sexp.opaque])[@@derivingsexp]letequal=phys_equalendmoduleStatus=structtypet=|Starting|In_progressofProgress.t|CompleteofstringOr_error.t[@@derivingcompare,equal,sexp]endmoduleFile_state=structtypet=|Before_first_read|Readingof{file_read:File_read'.t;status:Status.t}[@@derivingequal,sexp]letto_status=function|Before_first_read->Status.Starting|Reading{status;_}->status;;moduleAction=structtypet=|Start_readofFile_read'.t|Set_statusofStatus.t[@@derivingequal,sexp]endletapply_action~inject:_~schedule_eventt(action:Action.t)=matchactionwith|Start_readfile_read->(matchtwith|Before_first_read->()|Reading{file_read=old_file_read;status=_}->schedule_event(File_read.abortold_file_read));Reading{file_read;status=Starting}|Set_statusstatus->(matchtwith|Before_first_read->t|Reading{file_read;status=_}->Reading{file_read;status});;letabort_read_if_applicablet=match%subtwith|Before_first_read->Bonsai.constUi_effect.Ignore|Reading{file_read;status=_}->Bonsai.read(file_read>>|File_read.abort);;endletcreate_helperfile=let%substate,inject=Bonsai.state_machine0[%here](moduleFile_state)(moduleFile_state.Action)~default_model:Before_first_read~apply_action:File_state.apply_actioninlet%sub()=let%subabort=File_state.abort_read_if_applicablestateinBonsai.Edge.lifecycle~on_deactivate:abort()inlet%sub()=Bonsai.Edge.on_change[%here](moduleFile)file~callback:(let%mapinject=injectinfunfile->letopenUi_effect.Let_syntaxinlet%bindfile_read=readfile~on_progress:(funprogress->inject(Set_status(In_progressprogress)))inlet%bind()=inject(Start_readfile_read)inmatch%bindFile_read.resultfile_readwith|ErrorAborted->(* Let the next read take over *)return()|Error(Errore)->inject(Set_status(Complete(Errore)))|Okcontents->inject(Set_status(Complete(Okcontents))))inBonsai.readstate;;letcreate_multiplefiles=let%subfile_states=(* In reality, I suspect that whenever the user changes their selection in a file
picker widget, the browser generates an entirely new set of File objects for us.
So I suspect it's not possible for [files] to change in such a way that some, but
not all, of the keys change. However, it's easy enough to support that, so we do.
The one thing we don't support is if a file disappears from the map and then
comes back. In that case, we've already told the file reader to abort the read
when it disappeared, so there is no way for us to recover. *)Bonsai.assoc(moduleFilename)files~f:(fun_filenamefile->let%subreading=create_helperfileinBonsai.read(match%mapreadingwith|File_state.Before_first_read->None|Reading{status;file_read=_}->Somestatus))inBonsai.Incr.computefile_states~f:(Ui_incr.Map.filter_map~f:Fn.id);;letcreate_singlefile=let%substate=create_helperfileinBonsai.read(let%mapfile=fileandstate=stateinfile.filename,File_state.to_statusstate);;letcreate_single_optfile=match%subfilewith|None->Bonsai.constNone|Somefile->Bonsai.Computation.map(create_singlefile)~f:Option.some;;end