Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
PhotosLoader.cs
3using SkiaSharp;
4using System.Collections.ObjectModel;
6using Waher.Content;
9using Waher.Events;
13
15{
22 public class PhotosLoader(ObservableCollection<Photo> Photos) : BaseViewModel
23 {
24 private readonly ObservableCollection<Photo> photos = Photos;
25 private readonly List<string> attachmentIds = [];
26 private DateTime loadPhotosTimestamp;
27
32 public PhotosLoader() : this([])
33 {
34 }
35
43 public Task<Photo?> LoadPhotos(Attachment[] Attachments, SignWith SignWith, Action? WhenDoneAction = null)
44 {
45 return this.LoadPhotos(Attachments, SignWith, DateTime.UtcNow, WhenDoneAction);
46 }
47
52 public void CancelLoadPhotos()
53 {
54 try
55 {
56 this.loadPhotosTimestamp = DateTime.UtcNow;
57 this.attachmentIds.Clear();
58 this.photos.Clear();
59 }
60 catch (Exception ex)
61 {
62 ServiceRef.LogService.LogException(ex);
63 }
64 }
65
72 public async Task<(byte[]?, string, int)> LoadOnePhoto(Attachment Attachment, SignWith SignWith)
73 {
74 try
75 {
76 return await this.GetPhoto(Attachment, SignWith, DateTime.UtcNow);
77 }
78 catch (Exception ex)
79 {
80 ServiceRef.LogService.LogException(ex);
81 }
82
83 return (null, string.Empty, 0);
84 }
85
86 private async Task<Photo?> LoadPhotos(Attachment[] Attachments, SignWith SignWith, DateTime Now, Action? WhenDoneAction)
87 {
88 if (Attachments is null || Attachments.Length <= 0)
89 {
90 WhenDoneAction?.Invoke();
91 return null;
92 }
93
94 List<Attachment> AttachmentsList = Attachments.GetImageAttachments().ToList();
95 List<string> NewAttachmentIds = AttachmentsList.Select(x => x.Id).ToList();
96
97 if (this.attachmentIds.HasSameContentAs(NewAttachmentIds))
98 {
99 WhenDoneAction?.Invoke();
100
101 foreach (Photo Photo in this.photos)
102 return Photo;
103
104 return null;
105 }
106
107 this.attachmentIds.Clear();
108 this.attachmentIds.AddRange(NewAttachmentIds);
109
110 Photo? First = null;
111
112 foreach (Attachment Attachment in AttachmentsList)
113 {
114 if (Array.IndexOf(ImageCodec.ImageContentTypes, Attachment.ContentType) < 0)
115 continue;
116
117 if (this.loadPhotosTimestamp > Now)
118 {
119 WhenDoneAction?.Invoke();
120
121 foreach (Photo Photo in this.photos)
122 return Photo;
123
124 return null;
125 }
126
127 try
128 {
129 (byte[]? Bin, string ContentType, int Rotation) = await this.GetPhoto(Attachment, SignWith, Now);
130
131 if (Bin is null)
132 continue;
133
134 Photo Photo = new(Bin, Rotation, Attachment);
135 First ??= Photo;
136
137 if (Bin is not null)
138 {
139 TaskCompletionSource<bool> PhotoAddedTaskSource = new();
140
141 MainThread.BeginInvokeOnMainThread(() =>
142 {
143 this.photos.Add(Photo);
144 PhotoAddedTaskSource.TrySetResult(true);
145 });
146
147 await PhotoAddedTaskSource.Task;
148 }
149 }
150 catch (Exception ex)
151 {
152 ServiceRef.LogService.LogException(ex);
153 }
154 }
155
156 WhenDoneAction?.Invoke();
157
158 return First;
159 }
160
161 private async Task<(byte[]?, string, int)> GetPhoto(Attachment Attachment, SignWith SignWith, DateTime Now)
162 {
163 if (Attachment is null)
164 return (null, string.Empty, 0);
165
166 (byte[]? Bin, string ContentType) = await ServiceRef.AttachmentCacheService.TryGet(Attachment.Url);
167
168 if (Bin is not null)
169 return (Bin, ContentType, GetImageRotation(Bin));
170
171 if (!ServiceRef.NetworkService.IsOnline || !ServiceRef.XmppService.IsOnline)
172 return (null, string.Empty, 0);
173
174 KeyValuePair<string, TemporaryFile> pair = await ServiceRef.XmppService.GetAttachment(Attachment.Url, SignWith, Constants.Timeouts.DownloadFile);
175
176 using TemporaryFile file = pair.Value;
177
178 if (this.loadPhotosTimestamp > Now) // If download has been cancelled any time _during_ download, stop here.
179 return (null, string.Empty, 0);
180
181 if (pair.Value.Length > int.MaxValue) // Too large
182 return (null, string.Empty, 0);
183
184 file.Reset();
185
186 ContentType = pair.Key;
187 Bin = new byte[file.Length];
188
189 if (file.Length != file.Read(Bin, 0, (int)file.Length))
190 return (null, string.Empty, 0);
191
192 bool IsContact = await ServiceRef.XmppService.IsContact(Attachment.LegalId);
193
194 await ServiceRef.AttachmentCacheService.Add(Attachment.Url, Attachment.LegalId, IsContact, Bin, ContentType);
195
196 return (Bin, ContentType, GetImageRotation(Bin));
197 }
198
204 public static int GetImageRotation(byte[] JpegImage)
205 {
207 if (DeviceInfo.Platform == DevicePlatform.iOS)
208 return 0;
209
210 if (JpegImage is null)
211 return 0;
212
213 if (!EXIF.TryExtractFromJPeg(JpegImage, out ExifTag[] Tags))
214 return 0;
215
216 return GetImageRotation(Tags);
217 }
218
224 public static int GetImageRotation(ExifTag[] Tags)
225 {
226 foreach (ExifTag Tag in Tags)
227 {
228 if (Tag.Name == ExifTagName.Orientation)
229 {
230 if (Tag.Value is ushort Orientation)
231 {
232 return Orientation switch
233 {
234 1 => 0,// Top left. Default orientation.
235 2 => 0,// Top right. Horizontally reversed.
236 3 => 180,// Bottom right. Rotated by 180 degrees.
237 4 => 180,// Bottom left. Rotated by 180 degrees and then horizontally reversed.
238 5 => -90,// Left top. Rotated by 90 degrees counterclockwise and then horizontally reversed.
239 6 => 90,// Right top. Rotated by 90 degrees clockwise.
240 7 => 90,// Right bottom. Rotated by 90 degrees clockwise and then horizontally reversed.
241 8 => -90,// Left bottom. Rotated by 90 degrees counterclockwise.
242 _ => 0,
243 };
244 }
245 }
246 }
247
248 return 0;
249 }
250
256 public static async Task<(byte[]?, string, int)> LoadPhoto(Attachment Attachment)
257 {
258 PhotosLoader Loader = new();
259
260 (byte[]?, string, int) Image = await Loader.LoadOnePhoto(Attachment, SignWith.LatestApprovedIdOrCurrentKeys);
261
262 return Image;
263 }
264
272 public static Task<(string?, int, int)> LoadPhotoAsTemporaryFile(Attachment[] Attachments, int MaxWith, int MaxHeight)
273 {
274 Attachment? Photo = null;
275
276 foreach (Attachment Attachment in Attachments.GetImageAttachments())
277 {
279 {
281 break;
282 }
283 else
284 Photo ??= Attachment;
285 }
286
287 if (Photo is null)
288 return Task.FromResult<(string?, int, int)>((null, 0, 0));
289 else
290 return LoadPhotoAsTemporaryFile(Photo, MaxWith, MaxHeight);
291 }
292
300 public static async Task<(string?, int, int)> LoadPhotoAsTemporaryFile(Attachment Attachment, int MaxWith, int MaxHeight)
301 {
302 (byte[]? Data, string _, int _) = await LoadPhoto(Attachment);
303
304 if (Data is not null)
305 {
306 string FileName = await GetTemporaryFile(Data);
307 int Width;
308 int Height;
309
310 using (SKBitmap Bitmap = SKBitmap.Decode(Data))
311 {
312 Width = Bitmap.Width;
313 Height = Bitmap.Height;
314 }
315
316 double ScaleWidth = ((double)MaxWith) / Width;
317 double ScaleHeight = ((double)MaxHeight) / Height;
318 double Scale = Math.Min(ScaleWidth, ScaleHeight);
319
320 if (Scale < 1)
321 {
322 Width = (int)(Width * Scale + 0.5);
323 Height = (int)(Height * Scale + 0.5);
324 }
325
326 return (FileName, Width, Height);
327 }
328 else
329 return (null, 0, 0);
330 }
331
332 #region From Waher.Content.Markdown.Model.Multimedia.ImageContent, with permission
333
339 public static Task<string> GetTemporaryFile(byte[] BinaryImage)
340 {
341 return GetTemporaryFile(BinaryImage, "tmp");
342 }
343
350 public static async Task<string> GetTemporaryFile(byte[] BinaryImage, string FileExtension)
351 {
352 byte[] Digest = SHA256.HashData(BinaryImage);
353 string FileName = Path.Combine(Path.GetTempPath(), "tmp" + Base64Url.Encode(Digest) + "." + FileExtension);
354
355 if (!File.Exists(FileName))
356 {
357 await Waher.Runtime.IO.Files.WriteAllBytesAsync(FileName, BinaryImage);
358
359 lock (synchObject)
360 {
361 if (temporaryFiles is null)
362 {
363 temporaryFiles = [];
364 Log.Terminating += CurrentDomain_ProcessExit;
365 }
366
367 temporaryFiles[FileName] = true;
368 }
369 }
370
371 return FileName;
372 }
373
374 private static Dictionary<string, bool>? temporaryFiles = null;
375 private static readonly object synchObject = new();
376
377 private static Task CurrentDomain_ProcessExit(object? sender, EventArgs e)
378 {
379 lock (synchObject)
380 {
381 if (temporaryFiles is not null)
382 {
383 foreach (string FileName in temporaryFiles.Keys)
384 {
385 try
386 {
387 File.Delete(FileName);
388 }
389 catch (Exception)
390 {
391 // Ignore
392 }
393 }
394
395 temporaryFiles.Clear();
396 }
397 }
398
399 return Task.CompletedTask;
400 }
401
402 #endregion
403 }
404}
const string Png
The PNG MIME type.
Definition: Constants.cs:296
static readonly TimeSpan DownloadFile
Download file timeout
Definition: Constants.cs:717
A set of never changing property constants and helpful values.
Definition: Constants.cs:24
Base class that references services in the app.
Definition: ServiceRef.cs:43
static ILogService LogService
Log service.
Definition: ServiceRef.cs:214
static INetworkService NetworkService
Network service.
Definition: ServiceRef.cs:226
static IAttachmentCacheService AttachmentCacheService
AttachmentCache service.
Definition: ServiceRef.cs:274
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:190
A base class for all view models, inheriting from the BindableObject. NOTE: using this class requir...
Static class that does BASE64URL encoding (using URL and filename safe alphabet), as defined in RFC46...
Definition: Base64Url.cs:11
static string Encode(byte[] Data)
Converts a binary block of data to a Base64URL-encoded string.
Definition: Base64Url.cs:48
Extracts EXIF meta-data from images.
Definition: EXIF.cs:17
static bool TryExtractFromJPeg(string FileName, out ExifTag[] Tags)
Tries to extract EXIF meta-data from a JPEG image.
Definition: EXIF.cs:405
Abstract base class for EXIF meta-data tags.
Definition: ExifTag.cs:10
ExifTagName Name
EXIF Tag Name
Definition: ExifTag.cs:33
abstract object Value
EXIF Tag Value
Definition: ExifTag.cs:38
Image encoder/decoder.
Definition: ImageCodec.cs:14
static readonly string[] ImageContentTypes
Image content types.
Definition: ImageCodec.cs:126
Contains a reference to an attachment assigned to a legal object.
Definition: Attachment.cs:10
string LegalId
Legal ID of uploader of the attachment
Definition: Attachment.cs:39
string ContentType
Internet Content Type of binary attachment.
Definition: Attachment.cs:48
string Url
URL to retrieve attachment, if provided.
Definition: Attachment.cs:66
Contains static methods
Definition: Files.cs:14
static Task WriteAllBytesAsync(string FileName, byte[] Data)
Creates a binary file asynchronously.
Definition: Files.cs:33
Class managing the contents of a temporary file. When the class is disposed, the temporary file is de...
Task Add(string Url, string ParentId, bool Permanent, byte[] Data, string ContentType)
Adds or updates an attachment in the cache.
Task<(byte[]? Data, string ContentType)> TryGet(string Url)
Tries to retrieve cached attachment data for the specified URL.
Definition: ImplTypes.g.cs:58
class Photo(byte[] Binary, int Rotation, Attachment? Attachment)
Class containing information about a photo.
Definition: Photo.cs:10
ExifTagName
Defined EXIF Tag names
Definition: ExifTagName.cs:10
SignWith
Options on what keys to use when signing data.
Definition: Enumerations.cs:82
ContentType
DTLS Record content type.
Definition: Enumerations.cs:11
Definition: App.xaml.cs:4