1+ using System . Collections . Generic ;
2+ using System . Linq ;
3+ using CommunityToolkit . Diagnostics ;
4+ using Ipfs ;
5+ using Ipfs . CoreApi ;
6+ using OwlCore . ComponentModel ;
7+ using OwlCore . Kubo ;
8+ using OwlCore . Nomad ;
9+ using OwlCore . Nomad . Kubo ;
10+ using OwlCore . Storage ;
11+ using WindowsAppCommunity . Sdk . Models ;
12+
13+ namespace WindowsAppCommunity . Sdk . Nomad ;
14+
15+ /// <inheritdoc cref="IModifiableImagesCollection" />
16+ public class ModifiableImagesCollection : NomadKuboEventStreamHandler < ValueUpdateEvent > , IModifiableImagesCollection , IDelegable < ReadOnlyImagesCollection >
17+ {
18+ /// <inheritdoc />
19+ public required ReadOnlyImagesCollection Inner { get ; init ; }
20+
21+ /// <summary>
22+ /// A unique identifier for this instance, persistent across machines and reruns.
23+ /// </summary>
24+ public required string Id { get ; init ; }
25+
26+ /// <inheritdoc />
27+ public async Task AddImageAsync ( IFile imageFile , CancellationToken cancellationToken )
28+ {
29+ var imageCid = await imageFile . GetCidAsync ( Inner . Client , new AddFileOptions { Pin = KuboOptions . ShouldPin , } , cancellationToken ) ;
30+
31+ var newImage = new Image
32+ {
33+ Id = imageFile . Id ,
34+ Name = imageFile . Name ,
35+ Cid = ( DagCid ) imageCid ,
36+ } ;
37+
38+ var keyCid = await Client . Dag . PutAsync ( newImage . Id , pin : KuboOptions . ShouldPin , cancel : cancellationToken ) ;
39+ var valueCid = await Client . Dag . PutAsync ( newImage , pin : KuboOptions . ShouldPin , cancel : cancellationToken ) ;
40+
41+ var updateEvent = new ValueUpdateEvent ( Id , nameof ( AddImageAsync ) , ( DagCid ) keyCid , ( DagCid ) valueCid , false ) ;
42+
43+ await ApplyEntryUpdateAsync ( updateEvent , newImage , cancellationToken ) ;
44+ var appendedEntry = await AppendNewEntryAsync ( updateEvent , cancellationToken ) ;
45+
46+ EventStreamPosition = appendedEntry ;
47+
48+ // Append entry to event stream
49+ // TODO
50+ }
51+
52+ /// <inheritdoc />
53+ public Task RemoveImageAsync ( IFile imageFile , CancellationToken cancellationToken )
54+ {
55+ throw new NotImplementedException ( ) ;
56+ }
57+
58+ /// <inheritdoc />
59+ public IAsyncEnumerable < IFile > GetImageFilesAsync ( CancellationToken cancellationToken ) => Inner . GetImageFilesAsync ( cancellationToken ) ;
60+
61+ /// <inheritdoc />
62+ public event EventHandler < IFile [ ] > ? ImagesAdded ;
63+
64+ /// <inheritdoc />
65+ public event EventHandler < IFile [ ] > ? ImagesRemoved ;
66+
67+ /// <summary>
68+ /// Applies an event stream update event and raises the relevant events.
69+ /// </summary>
70+ /// <remarks>
71+ /// This method will call <see cref="ReadOnlyImagesCollection.GetAsync(string, CancellationToken)"/> and create a new instance to pass to the event handlers.
72+ /// <para/>
73+ /// If already have a resolved instance of <see cref="Image"/>, you should call <see cref="ApplyEntryUpdateAsync(ValueUpdateEvent, Image, CancellationToken)"/> instead.
74+ /// </remarks>
75+ /// <param name="updateEvent">The update event to apply.</param>
76+ /// <param name="cancellationToken">A token that can be used to cancel the ongoing operation.</param>
77+ public override async Task ApplyEntryUpdateAsync ( ValueUpdateEvent updateEvent , CancellationToken cancellationToken )
78+ {
79+ cancellationToken . ThrowIfCancellationRequested ( ) ;
80+
81+ if ( updateEvent . TargetId != Id )
82+ return ;
83+
84+ Guard . IsNotNull ( updateEvent . Value ) ;
85+ var ( image , _) = await Client . ResolveDagCidAsync < Image > ( updateEvent . Value . Value , nocache : ! KuboOptions . UseCache , cancellationToken ) ;
86+
87+ Guard . IsNotNull ( image ) ;
88+ await ApplyEntryUpdateAsync ( updateEvent , image , cancellationToken ) ;
89+ }
90+
91+ /// <summary>
92+ /// Applies an event stream update event and raises the relevant events.
93+ /// </summary>
94+ /// <param name="updateEvent">The update event to apply.</param>
95+ /// <param name="image">The resolved image data for this event.</param>
96+ /// <param name="cancellationToken">A token that can be used to cancel the ongoing operation.</param>
97+ public async Task ApplyEntryUpdateAsync ( ValueUpdateEvent updateEvent , Image image , CancellationToken cancellationToken )
98+ {
99+ cancellationToken . ThrowIfCancellationRequested ( ) ;
100+
101+ switch ( updateEvent . EventId )
102+ {
103+ case nameof ( AddImageAsync ) :
104+ {
105+ var imageFile = await Inner . GetAsync ( image . Id , cancellationToken ) ;
106+ Inner . Inner . Images = [ ..Inner . Inner . Images , image ] ;
107+ ImagesAdded ? . Invoke ( this , [ imageFile ] ) ;
108+ break ;
109+ }
110+ case nameof ( RemoveImageAsync ) :
111+ {
112+ var imageFile = await Inner . GetAsync ( image . Id , cancellationToken ) ;
113+ Inner . Inner . Images = [ .. Inner . Inner . Images . Except ( [ image ] ) ] ;
114+ ImagesRemoved ? . Invoke ( this , [ imageFile ] ) ;
115+ break ;
116+ }
117+ }
118+ }
119+
120+ /// <inheritdoc cref="INomadKuboEventStreamHandler{TEventEntryContent}.AppendNewEntryAsync" />
121+ public override async Task < EventStreamEntry < Cid > > AppendNewEntryAsync ( ValueUpdateEvent updateEvent , CancellationToken cancellationToken = default )
122+ {
123+ // Use extension method for code deduplication (can't use inheritance).
124+ var localUpdateEventCid = await Client . Dag . PutAsync ( updateEvent , pin : KuboOptions . ShouldPin , cancel : cancellationToken ) ;
125+ var newEntry = await this . AppendEventStreamEntryAsync ( localUpdateEventCid , updateEvent . EventId , updateEvent . TargetId , cancellationToken ) ;
126+ return newEntry ;
127+ }
128+
129+ /// <inheritdoc />
130+ public override Task ResetEventStreamPositionAsync ( CancellationToken cancellationToken )
131+ {
132+ EventStreamPosition = null ;
133+ Inner . Inner . Images = [ ] ;
134+
135+ return Task . CompletedTask ;
136+ }
137+ }
0 commit comments