Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
PhotosLoader.cs
4using SkiaSharp;
5using System.Collections.ObjectModel;
6using System.Security.Cryptography;
7using Waher.Content;
10using 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 (this.loadPhotosTimestamp > Now)
115 {
116 WhenDoneAction?.Invoke();
117
118 foreach (Photo Photo in this.photos)
119 return Photo;
120
121 return null;
122 }
123
124 try
125 {
126 (byte[]? Bin, string ContentType, int Rotation) = await this.GetPhoto(attachment, SignWith, Now);
127
128 if (Bin is null)
129 continue;
130
131 Photo Photo = new(Bin, Rotation);
132 First ??= Photo;
133
134 if (Bin is not null)
135 {
136 TaskCompletionSource<bool> PhotoAddedTaskSource = new();
137
138 MainThread.BeginInvokeOnMainThread(() =>
139 {
140 this.photos.Add(Photo);
141 PhotoAddedTaskSource.TrySetResult(true);
142 });
143
144 await PhotoAddedTaskSource.Task;
145 }
146 }
147 catch (Exception ex)
148 {
149 ServiceRef.LogService.LogException(ex);
150 }
151 }
152
153 WhenDoneAction?.Invoke();
154
155 return First;
156 }
157
158 private async Task<(byte[]?, string, int)> GetPhoto(Attachment Attachment, SignWith SignWith, DateTime Now)
159 {
160 if (Attachment is null)
161 return (null, string.Empty, 0);
162
163 (byte[]? Bin, string ContentType) = await ServiceRef.AttachmentCacheService.TryGet(Attachment.Url);
164
165 if (Bin is not null)
166 return (Bin, ContentType, GetImageRotation(Bin));
167
168 if (!ServiceRef.NetworkService.IsOnline || !ServiceRef.XmppService.IsOnline)
169 return (null, string.Empty, 0);
170
171 KeyValuePair<string, TemporaryFile> pair = await ServiceRef.XmppService.GetAttachment(Attachment.Url, SignWith, Constants.Timeouts.DownloadFile);
172
173 using TemporaryFile file = pair.Value;
174
175 if (this.loadPhotosTimestamp > Now) // If download has been cancelled any time _during_ download, stop here.
176 return (null, string.Empty, 0);
177
178 if (pair.Value.Length > int.MaxValue) // Too large
179 return (null, string.Empty, 0);
180
181 file.Reset();
182
183 ContentType = pair.Key;
184 Bin = new byte[file.Length];
185
186 if (file.Length != file.Read(Bin, 0, (int)file.Length))
187 return (null, string.Empty, 0);
188
189 bool IsContact = await ServiceRef.XmppService.IsContact(Attachment.LegalId);
190
191 await ServiceRef.AttachmentCacheService.Add(Attachment.Url, Attachment.LegalId, IsContact, Bin, ContentType);
192
193 return (Bin, ContentType, GetImageRotation(Bin));
194 }
195
201 public static int GetImageRotation(byte[] JpegImage)
202 {
204 if (DeviceInfo.Platform == DevicePlatform.iOS)
205 return 0;
206
207 if (JpegImage is null)
208 return 0;
209
210 if (!EXIF.TryExtractFromJPeg(JpegImage, out ExifTag[] Tags))
211 return 0;
212
213 return GetImageRotation(Tags);
214 }
215
221 public static int GetImageRotation(ExifTag[] Tags)
222 {
223 foreach (ExifTag Tag in Tags)
224 {
225 if (Tag.Name == ExifTagName.Orientation)
226 {
227 if (Tag.Value is ushort Orientation)
228 {
229 return Orientation switch
230 {
231 1 => 0,// Top left. Default orientation.
232 2 => 0,// Top right. Horizontally reversed.
233 3 => 180,// Bottom right. Rotated by 180 degrees.
234 4 => 180,// Bottom left. Rotated by 180 degrees and then horizontally reversed.
235 5 => -90,// Left top. Rotated by 90 degrees counterclockwise and then horizontally reversed.
236 6 => 90,// Right top. Rotated by 90 degrees clockwise.
237 7 => 90,// Right bottom. Rotated by 90 degrees clockwise and then horizontally reversed.
238 8 => -90,// Left bottom. Rotated by 90 degrees counterclockwise.
239 _ => 0,
240 };
241 }
242 }
243 }
244
245 return 0;
246 }
247
253 public static async Task<(byte[]?, string, int)> LoadPhoto(Attachment Attachment)
254 {
255 PhotosLoader Loader = new();
256
257 (byte[]?, string, int) Image = await Loader.LoadOnePhoto(Attachment, SignWith.LatestApprovedIdOrCurrentKeys);
258
259 return Image;
260 }
261
269 public static Task<(string?, int, int)> LoadPhotoAsTemporaryFile(Attachment[] Attachments, int MaxWith, int MaxHeight)
270 {
271 Attachment? Photo = null;
272
273 foreach (Attachment Attachment in Attachments.GetImageAttachments())
274 {
276 {
278 break;
279 }
280 else
281 Photo ??= Attachment;
282 }
283
284 if (Photo is null)
285 return Task.FromResult<(string?, int, int)>((null, 0, 0));
286 else
287 return LoadPhotoAsTemporaryFile(Photo, MaxWith, MaxHeight);
288 }
289
297 public static async Task<(string?, int, int)> LoadPhotoAsTemporaryFile(Attachment Attachment, int MaxWith, int MaxHeight)
298 {
299 (byte[]? Data, string _, int _) = await LoadPhoto(Attachment);
300
301 if (Data is not null)
302 {
303 string FileName = await GetTemporaryFile(Data);
304 int Width;
305 int Height;
306
307 using (SKBitmap Bitmap = SKBitmap.Decode(Data))
308 {
309 Width = Bitmap.Width;
310 Height = Bitmap.Height;
311 }
312
313 double ScaleWidth = ((double)MaxWith) / Width;
314 double ScaleHeight = ((double)MaxHeight) / Height;
315 double Scale = Math.Min(ScaleWidth, ScaleHeight);
316
317 if (Scale < 1)
318 {
319 Width = (int)(Width * Scale + 0.5);
320 Height = (int)(Height * Scale + 0.5);
321 }
322
323 return (FileName, Width, Height);
324 }
325 else
326 return (null, 0, 0);
327 }
328
329 #region From Waher.Content.Markdown.Model.Multimedia.ImageContent, with permission
330
336 public static Task<string> GetTemporaryFile(byte[] BinaryImage)
337 {
338 return GetTemporaryFile(BinaryImage, "tmp");
339 }
340
347 public static async Task<string> GetTemporaryFile(byte[] BinaryImage, string FileExtension)
348 {
349 byte[] Digest = SHA256.HashData(BinaryImage);
350 string FileName = Path.Combine(Path.GetTempPath(), "tmp" + Base64Url.Encode(Digest) + "." + FileExtension);
351
352 if (!File.Exists(FileName))
353 {
354 await Waher.Content.Resources.WriteAllBytesAsync(FileName, BinaryImage);
355
356 lock (synchObject)
357 {
358 if (temporaryFiles is null)
359 {
360 temporaryFiles = [];
361 Log.Terminating += CurrentDomain_ProcessExit;
362 }
363
364 temporaryFiles[FileName] = true;
365 }
366 }
367
368 return FileName;
369 }
370
371 private static Dictionary<string, bool>? temporaryFiles = null;
372 private static readonly object synchObject = new();
373
374 private static void CurrentDomain_ProcessExit(object? sender, EventArgs e)
375 {
376 lock (synchObject)
377 {
378 if (temporaryFiles is not null)
379 {
380 foreach (string FileName in temporaryFiles.Keys)
381 {
382 try
383 {
384 File.Delete(FileName);
385 }
386 catch (Exception)
387 {
388 // Ignore
389 }
390 }
391
392 temporaryFiles.Clear();
393 }
394 }
395 }
396
397 #endregion
398 }
399}
const string Png
The PNG MIME type.
Definition: Constants.cs:242
static readonly TimeSpan DownloadFile
Download file timeout
Definition: Constants.cs:586
A set of never changing property constants and helpful values.
Definition: Constants.cs:7
Base class that references services in the app.
Definition: ServiceRef.cs:31
static ILogService LogService
Log service.
Definition: ServiceRef.cs:91
static INetworkService NetworkService
Network service.
Definition: ServiceRef.cs:103
static IAttachmentCacheService AttachmentCacheService
AttachmentCache service.
Definition: ServiceRef.cs:151
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:67
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:16
static bool TryExtractFromJPeg(string FileName, out ExifTag[] Tags)
Tries to extract EXIF meta-data from a JPEG image.
Definition: EXIF.cs:404
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
Static class managing loading of resources stored as embedded resources or in content files.
Definition: Resources.cs:15
static Task WriteAllBytesAsync(string FileName, byte[] Data)
Creates a binary file asynchronously.
Definition: Resources.cs:216
Contains a reference to an attachment assigned to a legal object.
Definition: Attachment.cs:9
string LegalId
Legal ID of uploader of attachment
Definition: Attachment.cs:38
string ContentType
Internet Content Type of binary attachment.
Definition: Attachment.cs:47
string Url
URL to retrieve attachment, if provided.
Definition: Attachment.cs:65
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 an image to the cache.
Task<(byte[]?, string)> TryGet(string Url)
Tries to get a cached image given the specified url.
class Photo(byte[] Binary, int Rotation)
Class containing information about a photo.
Definition: Photo.cs:8
ExifTagName
Defined EXIF Tag names
Definition: ExifTagName.cs:14
SignWith
Options on what keys to use when signing data.
Definition: Enumerations.cs:84
ContentType
DTLS Record content type.
Definition: Enumerations.cs:11
Definition: App.xaml.cs:4