Projekt

Obecné

Profil

Stáhnout (33 KB) Statistiky
| Větev: | Tag: | Revize:
1
using Core.Contexts;
2
using Core.Entities;
3
using Models.Annotations;
4
using Models.Enums;
5
using Serilog;
6
using System;
7
using System.Collections.Generic;
8
using System.Linq;
9
using System.Text;
10
using System.Threading.Tasks;
11
using Microsoft.EntityFrameworkCore;
12
using AutoMapper;
13
using Models.Tags;
14
using Ganss.XSS;
15
using HtmlAgilityPack;
16
using System.Text.RegularExpressions;
17
using Newtonsoft.Json;
18
using Core.GraphUtils;
19
using Models.Documents;
20

    
21
namespace Core.Services.AnnotationService
22
{
23
    public class AnnotationServiceEF : IAnnotationService
24
    {
25
        private readonly DatabaseContext context;
26
        private readonly ILogger logger;
27
        private readonly IMapper mapper;
28
        private readonly IHTMLService htmlService;
29

    
30

    
31
        public AnnotationServiceEF(DatabaseContext context, ILogger logger, IMapper mapper, IHTMLService htmlService)
32
        {
33
            this.context = context;
34
            this.logger = logger;
35
            this.mapper = mapper;
36
            this.htmlService = htmlService;
37
        }
38

    
39
        public void CreateDocumentAnnotations(AnnotationsAddRequest request, Guid clientUserId)
40
        {
41
            User addingUser = context.Users.Single(u => u.Id == clientUserId);
42

    
43
            // Check the documents exist
44
            var documents = context.Documents.Where(d => request.DocumentIdList.Contains(d.Id)).ToList();
45
            if (documents.Count() != request.DocumentIdList.Count)
46
            {
47
                logger.Information($"Received a non-existent Document ID when assigning documents to users");
48
                throw new InvalidOperationException($"{request.DocumentIdList.Count - documents.Count()} of the received documents do not exist");
49
            }
50

    
51
            var users = context.Users.Where(u => request.UserIdList.Contains(u.Id)).ToList();
52
            foreach (var user in users)
53
            {
54
                var userAnnotatedDocuments = context.Annotations
55
                    .Where(a => !(a is FinalAnnotation))
56
                    .Where(a => a.User == user)
57
                    .Select(a => a.Document)
58
                    .ToList();
59
                foreach (var doc in documents)
60
                {
61
                    if (userAnnotatedDocuments.Contains(doc))
62
                    {
63
                        logger.Information($"User {user.Username} has already been assigned the document {doc.Id}, ignoring");
64
                        continue;
65
                    }
66

    
67
                    context.Annotations.Add(new Annotation()
68
                    {
69
                        User = user,
70
                        UserAssigned = addingUser,
71
                        DateAssigned = DateTime.Now,
72
                        DateLastChanged = DateTime.Now,
73
                        Document = doc,
74
                        State = EState.NEW,
75
                        Note = ""
76
                    });
77
                }
78
            }
79

    
80
            context.SaveChanges();
81
        }
82

    
83
        public AnnotationListResponse GetUserAnnotations(Guid userId)
84
        {
85
            var annotations = context.Annotations
86
                .Where(a => !(a is FinalAnnotation))
87
                .Where(a => a.User.Id == userId)
88
                .Include(a => a.Document)
89
                .ToList();
90

    
91
            var infos = new List<AnnotationListInfo>();
92
            foreach (var annotation in annotations)
93
            {
94
                infos.Add(new AnnotationListInfo()
95
                {
96
                    AnnotationId = annotation.Id,
97
                    DocumentName = annotation.Document.Name,
98
                    State = annotation.State
99
                });
100
            }
101

    
102
            return new AnnotationListResponse()
103
            {
104
                Annotations = infos
105
            };
106
        }
107

    
108
        public void AddNoteToAnnotation(Guid annotationId, Guid userId, ERole userRole, AddNoteToAnnotationRequest request, bool isFinal)
109
        {
110
            Annotation annotation = null;
111
            try
112
            {
113
                if (!isFinal)
114
                {
115
                    annotation = context.Annotations.Include(a => a.User).First(a => a.Id == annotationId);
116
                }
117
                else
118
                {
119
                    annotation = context.FinalAnnotations.Include(a => a.User).First(a => a.Id == annotationId);
120
                }
121
            }
122
            catch (Exception)
123
            {
124
                throw new InvalidOperationException("Annotation not found");
125
            }
126

    
127
            if (userRole < ERole.ADMINISTRATOR && annotation.User.Id != userId)
128
            {
129
                throw new UnauthorizedAccessException("User does not have access to this annotation");
130
            }
131

    
132
            annotation.Note = request.Note;
133
            context.SaveChanges();
134
        }
135

    
136

    
137
        public AnnotationInfo GetAnnotation(Guid annotationId, Guid userId, ERole userRole, bool isFinal)
138
        {
139
            Annotation annotation = new();
140
            if (!isFinal)
141
            {
142
                annotation = context.Annotations
143
                .Where(a => !(a is FinalAnnotation))
144
                .Where(a => a.Id == annotationId)
145
                .Include(a => a.User)
146
                .Include(a => a.Document).ThenInclude(d => d.Content)
147
                .First();
148
            }
149
            else
150
            {
151
                annotation = context.FinalAnnotations
152
                .Where(a => a.Id == annotationId)
153
                .Include(a => a.User)
154
                .Include(a => a.Document).ThenInclude(d => d.Content)
155
                .First();
156
            }
157

    
158

    
159
            if (userRole < ERole.ADMINISTRATOR)
160
            {
161
                if (annotation.User.Id != userId)
162
                {
163
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
164
                }
165
            }
166

    
167
            var documentContent = context.Documents.Where(d => d.Id == annotation.Document.Id).Select(d => d.Content).First();
168

    
169
            List<AnnotationTagGeneric> tags;
170
            if (!isFinal)
171
            {
172
                tags = context.AnnotationTags
173
                    .Where(at => at.Annotation.Id == annotationId)
174
                    .Include(at => at.Tag)
175
                    .ThenInclude(t => t.Category)
176
                    .Include(at => at.SubTag)
177
                    .OrderBy(at => at.Position)
178
                    .Select(at => at as AnnotationTagGeneric)
179
                    .ToList();
180
            }
181
            else
182
            {
183
                tags = context.FinalAnnotationTags
184
                    .Where(at => at.Annotation.Id == annotationId)
185
                    .Include(at => at.Users)
186
                    .Include(at => at.Tag)
187
                    .ThenInclude(t => t.Category)
188
                    .Include(at => at.SubTag)
189
                    .OrderBy(at => at.Position)
190
                    .Select(at => at as AnnotationTagGeneric)
191
                    .ToList();
192
            }
193

    
194

    
195
            List<TagInstanceInfo> tagInstanceInfos = new();
196
            foreach (var tag in tags)
197
            {
198
                if (!isFinal)
199
                {
200
                    var tagInstance = mapper.Map<TagInstanceInfo>(tag);
201
                    tagInstanceInfos.Add(tagInstance);
202
                }
203
                else
204
                {
205
                    var tagInstance = mapper.Map<TagInstanceInfo>(tag as FinalAnnotationTag);
206
                    tagInstanceInfos.Add(tagInstance);
207
                }
208

    
209
            }
210

    
211
            var docToRender = "";
212
            HTMLService.CachedInfo cachedInfoToReturn = new();
213
            if (annotation.CachedDocumentHTML == "")
214
            {
215
                var result = htmlService.FullPreprocessHTML(documentContent.Content, tags);
216
                cachedInfoToReturn = result.Item2;
217
                docToRender = result.Item1;
218

    
219
                annotation.CachedStartPositions = JsonConvert.SerializeObject(cachedInfoToReturn.TagStartPositions);
220
                annotation.CachedLengths = JsonConvert.SerializeObject(cachedInfoToReturn.TagStartLengths);
221
                annotation.CachedClosingPositions = JsonConvert.SerializeObject(cachedInfoToReturn.TagClosingPositions);
222
                annotation.CachedClosingLengths = JsonConvert.SerializeObject(cachedInfoToReturn.TagClosingLengths);
223
                annotation.CachedCSS = JsonConvert.SerializeObject(cachedInfoToReturn.TagInstanceCSS);
224
                annotation.ModifiedType = EModified.NONE;
225
                annotation.CachedDocumentHTML = docToRender;
226
                context.SaveChanges();
227
            }
228
            else
229
            {
230
                docToRender = annotation.CachedDocumentHTML;
231
                cachedInfoToReturn = new()
232
                {
233
                    TagStartPositions = JsonConvert.DeserializeObject<List<int>>(annotation.CachedStartPositions),
234
                    TagStartLengths = JsonConvert.DeserializeObject<List<int>>(annotation.CachedLengths),
235
                    TagClosingPositions = JsonConvert.DeserializeObject<List<int>>(annotation.CachedClosingPositions),
236
                    TagClosingLengths = JsonConvert.DeserializeObject<List<int>>(annotation.CachedClosingLengths),
237
                    TagInstanceCSS = JsonConvert.DeserializeObject<List<TagInstanceCSSInfo>>(annotation.CachedCSS)
238
                };
239

    
240
                // The annotation has been modified and we need to either add the new tag or remove the tag
241
                if (annotation.ModifiedType != EModified.NONE)
242
                {
243
                    if (annotation.ModifiedType == EModified.ADDED)
244
                    {
245

    
246
                        AnnotationTagGeneric lastModifiedTag = isFinal ? context.FinalAnnotationTags.Where(at => at.Id == annotation.LastModifiedTagId).First() :
247
                            context.AnnotationTags.Where(at => at.Id == annotation.LastModifiedTagId).First();
248

    
249
                        var result = htmlService.PartialPreprocessHTMLAddTag(docToRender, documentContent.Content, lastModifiedTag, tags, cachedInfoToReturn);
250
                        docToRender = result.Item1;
251
                        cachedInfoToReturn = result.Item2;
252
                    }
253
                    else if (annotation.ModifiedType == EModified.REMOVED)
254
                    {
255
                        var result = htmlService.PartialPreprocessHTMLRemoveTag(docToRender, documentContent.Content, new AnnotationTag() { Id = annotation.LastModifiedTagId.Value }, tags, cachedInfoToReturn);
256
                        docToRender = result.Item1;
257
                        cachedInfoToReturn = result.Item2;
258
                    }
259

    
260
                    annotation.ModifiedType = EModified.NONE;
261
                    annotation.CachedStartPositions = JsonConvert.SerializeObject(cachedInfoToReturn.TagStartPositions);
262
                    annotation.CachedLengths = JsonConvert.SerializeObject(cachedInfoToReturn.TagStartLengths);
263
                    annotation.CachedClosingPositions = JsonConvert.SerializeObject(cachedInfoToReturn.TagClosingPositions);
264
                    annotation.CachedClosingLengths = JsonConvert.SerializeObject(cachedInfoToReturn.TagClosingLengths);
265
                    annotation.CachedDocumentHTML = docToRender;
266
                    annotation.CachedCSS = JsonConvert.SerializeObject(cachedInfoToReturn.TagInstanceCSS);
267
                    context.SaveChanges();
268
                }
269
            }
270

    
271
            // We probably cannot use AutoMapper since we are dealing with too many different entities
272
            AnnotationInfo annotationInfo = new()
273
            {
274
                SourceDocumentContent = documentContent.Content,
275
                DocumentToRender = docToRender,
276
                TagStartPositions = cachedInfoToReturn.TagStartPositions.ToArray(),
277
                TagLengths = cachedInfoToReturn.TagStartLengths.ToArray(),
278
                Note = annotation.Note,
279
                State = annotation.State,
280
                Type = IsHtml(documentContent.Content) ? EDocumentType.HTML : EDocumentType.TEXT,
281
                TagInstances = tagInstanceInfos,
282
                CSSInfo = cachedInfoToReturn.TagInstanceCSS
283
            };
284

    
285
            return annotationInfo;
286
        }
287

    
288
        // TODO temporary
289
        private bool IsHtml(string text)
290
        {
291
            return text.Contains("<html>");
292
        }
293

    
294
        public void AddAnnotationInstance(Guid annotationId, Guid userId, ERole userRole, AnnotationInstanceAddRequest request, bool isFinal)
295
        {
296
            Annotation annotation = new();
297
            if (!isFinal)
298
            {
299
                annotation = context.Annotations
300
                    .Where(a => !(a is FinalAnnotation))
301
                    .Where(a => a.Id == annotationId)
302
                    .Include(a => a.User)
303
                    .Include(a => a.Document).ThenInclude(d => d.Content)
304
                    .First();
305
            }
306
            else
307
            {
308
                annotation = context.FinalAnnotations
309
                    .Where(a => a.Id == annotationId)
310
                    .Include(a => a.User)
311
                    .Include(a => a.Document).ThenInclude(d => d.Content)
312
                    .First();
313
            }
314

    
315
            if (userRole < ERole.ADMINISTRATOR)
316
            {
317
                if (annotation.User.Id != userId)
318
                {
319
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
320
                }
321
            }
322

    
323
            if (annotation.State == EState.NEW)
324
            {
325
                annotation.State = EState.IN_PROGRESS;
326
            }
327

    
328
            AnnotationTagGeneric annotationTag = new();
329
            if (isFinal)
330
            {
331
                annotationTag = new FinalAnnotationTag()
332
                {
333
                    Id = Guid.NewGuid(),
334
                    Annotation = annotation as FinalAnnotation,
335
                    Instance = request.InstanceId == null ? Guid.NewGuid() : request.InstanceId.Value,
336
                    Length = request.Length,
337
                    Position = request.Position,
338
                    SelectedText = request.SelectedText,
339
                    Note = "",
340
                    IsFinal = true
341
                };
342
            }
343
            else
344
            {
345
                annotationTag = new AnnotationTag()
346
                {
347
                    Id = Guid.NewGuid(),
348
                    Annotation = annotation as Annotation,
349
                    Instance = request.InstanceId == null ? Guid.NewGuid() : request.InstanceId.Value,
350
                    Length = request.Length,
351
                    Position = request.Position,
352
                    SelectedText = request.SelectedText,
353
                    Note = ""
354
                };
355
            }
356

    
357
            if (request.Type == ETagType.TAG)
358
            {
359
                annotationTag.Tag = context.Tags.Where(t => t.Id == request.Id).Single();
360
                annotationTag.SubTag = null;
361

    
362
                if (annotationTag.Tag.SentimentEnabled)
363
                {
364
                    annotationTag.Sentiment = ETagSentiment.NEUTRAL;
365
                }
366

    
367
                // If for the same annotation exists a tag with same position and length and of the same type, ignore
368
                if (context.AnnotationTags.Any(at =>
369
                at.Position == annotationTag.Position &&
370
                at.Length == annotationTag.Length &&
371
                at.Annotation == annotation &&
372
                at.Tag == annotationTag.Tag))
373
                {
374
                    throw new InvalidOperationException("Duplicate tag");
375
                }
376

    
377
            }
378
            else if (request.Type == ETagType.SUBTAG)
379
            {
380
                var subTag = context.SubTags.Where(st => st.Id == request.Id).Include(st => st.Tag).Single();
381
                annotationTag.SubTag = subTag;
382
                annotationTag.Tag = subTag.Tag;
383

    
384
                if (annotationTag.SubTag.SentimentEnabled)
385
                {
386
                    annotationTag.Sentiment = ETagSentiment.NEUTRAL;
387
                }
388

    
389
                if (context.AnnotationTags.Any(at =>
390
                at.Position == annotationTag.Position &&
391
                at.Length == annotationTag.Length &&
392
                at.Annotation == annotation &&
393
                at.Tag == annotationTag.Tag &&
394
                at.SubTag == annotationTag.SubTag))
395
                {
396
                    throw new InvalidOperationException("Duplicate tag");
397
                }
398
            }
399
            else
400
            {
401
                throw new ArgumentException($"Unknown tag type {request.Type}");
402
            }
403

    
404
            if (annotation.State == EState.NEW)
405
            {
406
                annotation.State = EState.IN_PROGRESS;
407
            }
408

    
409
            annotation.LastModifiedTagId = annotationTag.Id;
410
            annotation.ModifiedType = EModified.ADDED;
411

    
412
            if (isFinal)
413
            {
414
                context.FinalAnnotationTags.Add(annotationTag as FinalAnnotationTag);
415
            }
416
            else
417
            {
418
                context.AnnotationTags.Add(annotationTag as AnnotationTag);
419
            }
420

    
421
            context.SaveChanges();
422
        }
423

    
424
        public void DeleteAnnotationInstance(Guid annotationId, Guid tagInstanceId, Guid loggedUserId, ERole userRole, bool isFinal)
425
        {
426
            Annotation annotation = null;
427
            try
428
            {
429
                if (!isFinal)
430
                {
431
                    annotation = context.Annotations
432
                    .Where(a => !(a is FinalAnnotation))
433
                    .Where(a => a.Id == annotationId)
434
                    .Include(a => a.User)
435
                    .Include(a => a.Document).ThenInclude(d => d.Content)
436
                    .First();
437
                }
438
                else
439
                {
440
                    annotation = context.FinalAnnotations
441
                    .Where(a => a.Id == annotationId)
442
                    .Include(a => a.User)
443
                    .Include(a => a.Document).ThenInclude(d => d.Content)
444
                    .First();
445
                }
446

    
447

    
448
            }
449
            catch (Exception ex)
450
            {
451
                throw new InvalidOperationException("Could not find annotation");
452
            }
453

    
454

    
455
            if (userRole < ERole.ADMINISTRATOR)
456
            {
457
                if (annotation.User.Id != loggedUserId)
458
                {
459
                    throw new UnauthorizedAccessException($"User {loggedUserId} does not have assigned annotation {annotationId}");
460
                }
461
            }
462

    
463
            AnnotationTagGeneric annotationTag = new();
464

    
465
            if (isFinal)
466
            {
467
                if (!context.FinalAnnotationTags.Any(at => at.Id == tagInstanceId))
468
                {
469
                    throw new InvalidOperationException("Could not find tag instance");
470
                }
471
                annotationTag = context.FinalAnnotationTags.First(at => at.Id == tagInstanceId);
472

    
473
            }
474
            else
475
            {
476
                if (!context.AnnotationTags.Any(at => at.Id == tagInstanceId))
477
                {
478
                    throw new InvalidOperationException("Could not find tag instance");
479
                }
480
                annotationTag = context.AnnotationTags.First(at => at.Id == tagInstanceId);
481
            }
482

    
483
            annotation.LastModifiedTagId = annotationTag.Id;
484
            annotation.ModifiedType = EModified.REMOVED;
485

    
486
            if (isFinal)
487
            {
488
                context.FinalAnnotationTags.Remove(annotationTag as FinalAnnotationTag);
489
            }
490
            else
491
            {
492
                context.AnnotationTags.Remove(annotationTag as AnnotationTag);
493
            }
494

    
495
            context.SaveChanges();
496
        }
497

    
498
        public void SetTagInstanceSentiment(Guid annotationId, Guid instanceId, Guid userId, ERole userRole, ETagSentiment sentiment, bool isFinal)
499
        {
500
            Annotation annotation = null;
501
            try
502
            {
503
                if (!isFinal)
504
                {
505
                    annotation = context.Annotations
506
                    .Where(a => !(a is FinalAnnotation))
507
                    .Where(a => a.Id == annotationId)
508
                    .Include(a => a.User)
509
                    .Include(a => a.Document).ThenInclude(d => d.Content)
510
                    .First();
511
                }
512
                else
513
                {
514
                    annotation = context.FinalAnnotations
515
                    .Where(a => a.Id == annotationId)
516
                    .Include(a => a.User)
517
                    .Include(a => a.Document).ThenInclude(d => d.Content)
518
                    .First();
519
                }
520
            }
521
            catch (Exception ex)
522
            {
523
                throw new InvalidOperationException("Could not find annotation");
524
            }
525

    
526

    
527
            if (userRole < ERole.ADMINISTRATOR)
528
            {
529
                if (annotation.User.Id != userId)
530
                {
531
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
532
                }
533
            }
534

    
535

    
536
            IEnumerable<AnnotationTagGeneric> tagInstances = null;
537

    
538
            if (isFinal)
539
            {
540
                tagInstances = context.FinalAnnotationTags.Where(at => at.Instance == instanceId).ToList();
541
            }
542
            else
543
            {
544
                tagInstances = context.AnnotationTags.Where(at => at.Instance == instanceId).ToList();
545
            }
546

    
547
            if (tagInstances.Count() == 0)
548
            {
549
                throw new InvalidOperationException("No such instance found");
550
            }
551

    
552
            foreach (var tagInstance in tagInstances)
553
            {
554
                tagInstance.Sentiment = sentiment;
555
            }
556

    
557
            context.SaveChanges();
558
        }
559

    
560
        public void MarkAnnotationAsDone(Guid annotationId, Guid userId, ERole userRole, bool done, bool isFinal)
561
        {
562

    
563
            Annotation annotation = null;
564
            try
565
            {
566
                if (!isFinal)
567
                {
568
                    annotation = context.Annotations
569
                    .Where(a => !(a is FinalAnnotation))
570
                   .Where(a => a.Id == annotationId)
571
                   .Include(a => a.User)
572
                   .Include(a => a.Document).ThenInclude(d => d.Content)
573
                   .First();
574
                }
575
                else
576
                {
577
                    annotation = context.FinalAnnotations
578
                   .Where(a => a.Id == annotationId)
579
                   .Include(a => a.User)
580
                   .Include(a => a.Document).ThenInclude(d => d.Content)
581
                   .First();
582
                }
583
            }
584
            catch (Exception ex)
585
            {
586
                throw new InvalidOperationException("Could not find annotation");
587
            }
588

    
589
            if (userRole < ERole.ADMINISTRATOR)
590
            {
591
                if (annotation.User.Id != userId)
592
                {
593
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
594
                }
595
            }
596

    
597
            annotation.State = done ? EState.DONE : EState.IN_PROGRESS;
598
            context.SaveChanges();
599
        }
600

    
601
        public Guid CreateFinalAnnotation(Guid documentId, Guid userId)
602
        {
603
            var document = context.Documents.Single(d => d.Id == documentId);
604
            var user = context.Users.Single(u => u.Id == userId);
605

    
606
            // Remove existing 
607
            if (context.FinalAnnotations.Any(fa => fa.Document == document))
608
            {
609
                var finalAnnotationOld = context.FinalAnnotations.Single(fa => fa.Document == document);
610
                context.FinalAnnotations.Remove(finalAnnotationOld);
611
            }
612

    
613
            var annotations = context.Annotations
614
                .Where(a => !(a is FinalAnnotation))
615
                .Include(a => a.Document)
616
                .Where(a => a.Document == document && a.State == EState.DONE)
617
                .ToList();
618

    
619
            var finalAnnotation = new FinalAnnotation()
620
            {
621
                Id = Guid.NewGuid(),
622
                DateAssigned = DateTime.Now,
623
                DateLastChanged = DateTime.Now,
624
                Document = document,
625
                User = user,
626
                UserAssigned = user,
627
                Annotations = annotations,
628
                State = EState.NEW
629
            };
630

    
631
            List<AnnotationTag> annotationTagsAll = new();
632
            foreach (var annotation in annotations)
633
            {
634
                annotationTagsAll.AddRange(context.AnnotationTags
635
                    .Where(at => at.Annotation == annotation)
636
                    .Include(at => at.Tag)
637
                    .Include(at => at.SubTag)
638
                    .Include(at => at.Annotation)
639
                    .ThenInclude(a => a.User)
640
                    .ToList());
641
            }
642
            annotationTagsAll = annotationTagsAll.OrderBy(at => at.Position).ToList();
643

    
644
            Dictionary<Guid, List<AnnotationTag>> occurenceDict = new();
645
            foreach (var annotationTag in annotationTagsAll)
646
            {
647
                if (occurenceDict.ContainsKey(annotationTag.Instance))
648
                {
649
                    occurenceDict[annotationTag.Instance].Add(annotationTag);
650
                }
651
                else
652
                {
653
                    occurenceDict[annotationTag.Instance] = new();
654
                    occurenceDict[annotationTag.Instance].Add(annotationTag);
655
                }
656
            }
657

    
658
            List<List<AnnotationTag>> occurenceLists = new();
659
            foreach (var key in occurenceDict.Keys)
660
            {
661
                occurenceLists.Add(occurenceDict[key]);
662
            }
663

    
664
            List<List<AnnotationTag>> annotationTagsProcessed = new();
665
            List<FinalAnnotationTag> finalAnnotationTags = new();
666
            for (int i = 0; i < occurenceLists.Count; i++)
667
            {
668
                var occurrenceList1 = occurenceLists[i];
669
                List<List<AnnotationTag>> sameLists = new();
670

    
671
                for (int j = 0; j < occurenceLists.Count; j++)
672
                {
673
                    var occurrenceList2 = occurenceLists[j];
674

    
675
                    if (annotationTagsProcessed.Contains(occurrenceList2))
676
                    {
677
                        continue;
678
                    }
679

    
680
                    if (SelectionsAreSame(occurrenceList1, occurrenceList2))
681
                    {
682
                        sameLists.Add(occurrenceList2);
683
                    }
684
                }
685

    
686
                // This means that this occurrence ahs already been processed as matching a previous tag
687
                if (sameLists.Count() == 0)
688
                {
689
                    continue;
690
                }
691

    
692
                List<User> relatedUsers = new();
693
                foreach (var list in sameLists)
694
                {
695
                    relatedUsers.Add(list[0].Annotation.User);
696
                }
697

    
698
                foreach (var tag in sameLists[0])
699
                {
700
                    finalAnnotationTags.Add(new()
701
                    {
702
                        Id = Guid.NewGuid(),
703
                        Tag = tag.Tag,
704
                        SubTag = tag.SubTag,
705
                        Annotation = finalAnnotation,
706
                        SelectedText = tag.SelectedText,
707
                        Sentiment = tag.Sentiment,
708
                        Instance = tag.Instance,
709
                        IsFinal = sameLists.Count == annotations.Count,
710
                        Length = tag.Length,
711
                        Position = tag.Position,
712
                        Note = "",
713
                        Users = relatedUsers
714
                    });
715
                }
716

    
717
                annotationTagsProcessed.AddRange(sameLists);
718
            }
719

    
720
            context.FinalAnnotations.Add(finalAnnotation);
721
            context.SaveChanges();
722
            context.FinalAnnotationTags.AddRange(finalAnnotationTags);
723

    
724
            context.SaveChanges();
725
            return finalAnnotation.Id;
726
        }
727

    
728
        private bool SelectionsAreSame(List<AnnotationTag> list1, List<AnnotationTag> list2)
729
        {
730
            if (list1.Count != list2.Count)
731
            {
732
                return false;
733
            }
734

    
735
            bool sentimentEnabled = list1[0].Tag.SentimentEnabled;
736

    
737
            if (sentimentEnabled)
738
            {
739
                for (int i = 0; i < list1.Count; i++)
740
                {
741
                    var tag1 = list1[i];
742
                    var tag2 = list2[i];
743
                    if (tag1.Position == tag2.Position &&
744
                        tag1.Length == tag2.Length &&
745
                        tag1.Sentiment == tag2.Sentiment &&
746
                        tag1.Tag == tag2.Tag &&
747
                        tag1.SubTag == tag2.SubTag)
748
                    {
749
                        continue;
750
                    }
751
                    else
752
                    {
753
                        return false;
754
                    }
755
                }
756
            }
757
            else
758
            {
759
                for (int i = 0; i < list1.Count; i++)
760
                {
761
                    var tag1 = list1[i];
762
                    var tag2 = list2[i];
763
                    if (tag1.Position == tag2.Position &&
764
                        tag1.Length == tag2.Length &&
765
                        tag1.Tag == tag2.Tag &&
766
                        tag1.SubTag == tag2.SubTag)
767
                    {
768
                        continue;
769
                    }
770
                    else
771
                    {
772
                        return false;
773
                    }
774
                }
775
            }
776

    
777
            return true;
778
        }
779
        
780
        public void SetTagIsFinal(Guid annotationId, Guid occurenceId, bool isFinal)
781
        {
782
            Annotation annotation;
783
            try
784
            {
785
                annotation = context.FinalAnnotations.Single(fa => fa.Id == annotationId);
786
            }
787
            catch (Exception)
788
            {
789
                throw new InvalidOperationException("Annotation not found");
790
            }
791

    
792
            FinalAnnotationTag finalTagInstance;
793
            try
794
            {
795
                finalTagInstance = context.FinalAnnotationTags.Single(fat => fat.Id == occurenceId);
796
            }
797
            catch (Exception)
798
            {
799
                throw new InvalidOperationException("Tag instance not found");
800
            }
801

    
802
            finalTagInstance.IsFinal = isFinal;
803

    
804
            context.SaveChanges();
805
        }
806

    
807
        public MemoryStream Export(ExportRequest request)
808
        {
809
            if (request.ExportAllDone && request.DocumentIds == null)
810
            {
811
                throw new InvalidOperationException("No documents specified");
812
            }
813

    
814
            // Get documents
815
            IEnumerable<Document> documentsToExport = null;
816
            if (request.ExportAllDone)
817
            {
818
                documentsToExport = context.Documents
819
                    .Include(d => d.Content);
820
            }
821
            else
822
            {
823
                documentsToExport = context.Documents
824
                    .Include(d => d.Content)
825
                    .Where(d => request.DocumentIds.Contains(d.Id));
826
            }
827

    
828
            // Get annotations
829
            Dictionary<Document, List<Annotation>> annotationsToExport = new();
830
            foreach (var document in documentsToExport)
831
            {
832
                annotationsToExport[document] = context.Annotations
833
                    .Where(a => a.Document == document && a.State == EState.DONE)
834
                    .Include(a => a.Document)
835
                    .Include(a => a.User)
836
                    .ToList();
837
            }
838

    
839
            // Get final annotations
840
            Dictionary<Document, FinalAnnotation> finalAnnotationsToExport = new();
841
            foreach (var document in documentsToExport)
842
            {
843
                if (!context.FinalAnnotations.Any(fa => fa.Document == document))
844
                {
845
                    finalAnnotationsToExport[document] = null;
846
                }
847
                else
848
                {
849
                    finalAnnotationsToExport[document] = context.FinalAnnotations
850
                        .Single(fa => fa.Document == document);
851
                }
852
            }
853

    
854
            // Get tags for each annotation
855
            Dictionary<Annotation, List<AnnotationTag>> tagsToExport = new();
856
            foreach (var document in annotationsToExport.Keys)
857
            {
858
                foreach (var annotation in annotationsToExport[document])
859
                {
860
                    tagsToExport[annotation] = context.AnnotationTags
861
                        .Include(at => at.Annotation)
862
                        .Include(at => at.Tag).ThenInclude(t => t.Category)
863
                        .Include(at => at.SubTag)
864
                        .Where(at => at.Annotation == annotation)
865
                        .ToList();
866
                }
867
            }
868

    
869
            // Get tags for each final annotation
870
            Dictionary<FinalAnnotation, List<FinalAnnotationTag>> finalTagsToExport = new();
871
            foreach (var document in finalAnnotationsToExport.Keys)
872
            {
873
                var finalAnnotation = finalAnnotationsToExport[document];
874
                finalTagsToExport[finalAnnotation] = context.FinalAnnotationTags
875
                    .Include(at => at.Annotation)
876
                    .Include(at => at.Tag).ThenInclude(t => t.Category)
877
                    .Include(at => at.SubTag)
878
                    .Where(at => at.Annotation == finalAnnotation)
879
                    .ToList();
880
            }
881

    
882
            MemoryStream output = ZipUtils.Export.FullExport(documentsToExport.ToList(), annotationsToExport, finalAnnotationsToExport, tagsToExport, finalTagsToExport);
883
            return output;
884
        }
885
    }
886
}
(1-1/2)