Beispiel #1
0
        /// <summary>
        /// Handles deleting a specific version of an image file according to file spec.
        /// </summary>
        private static async Task DeleteImageFileAsync(Image image, ImageFileSpec imageFileSpec)
        {
            if (image == null)
            {
                throw new ArgumentNullException(nameof(image));
            }

            var storageId = imageFileSpec.FileSpec switch
            {
                FileSpec.SpecOriginal => image.Files.OriginalId,
                FileSpec.Spec3840 => image.Files.Spec3840Id,
                FileSpec.Spec2560 => image.Files.Spec2560Id,
                FileSpec.Spec1920 => image.Files.Spec1920Id,
                FileSpec.Spec800 => image.Files.Spec800Id,
                FileSpec.SpecLowRes => image.Files.SpecLowResId,
                _ => string.Empty
            };

            if (!string.IsNullOrEmpty(storageId))
            {
                var container = Server.Instance.BlobServiceClient.GetBlobContainerClient(imageFileSpec.ContainerName);
                var response  = await container.DeleteBlobIfExistsAsync(storageId, DeleteSnapshotsOption.IncludeSnapshots);

                Log.Debug("ImageServer.DeleteImageFileAsync: response status: " + response.Value);
                return;
            }

            Log.Debug("ImageServer.DeleteImageFileAsync: storage id is null. FileSpec: " + imageFileSpec.FileSpec);
        }
Beispiel #2
0
        /// <summary>
        /// Causes the metadata on an image to be re-inspected and then any relevant properties on the Image updated.
        /// </summary>
        public async Task ReprocessImageMetadataAsync(Image image)
        {
            // create the message and send to the Azure Storage queue
            // message format: {operation}:{image_id}:{gallery_id}:{gallery_category_id}:{overwrite_image_properties}
            var ids = $"{WorkerOperation.ReprocessMetadata}:{image.Id}:{image.GalleryId}:{image.GalleryCategoryId}:true";

            Log.Debug($"ReprocessImageMetadataAsync() - Sending message (pre-base64 encoding): {ids}");

            var messageText = Utilities.Base64Encode(ids);
            await Server.Instance.ImageProcessingQueueClient.SendMessageAsync(messageText);
        }
Beispiel #3
0
        /// <summary>
        /// Attempts to return the image after to a given image in terms of the order they are shown in their gallery. May return null.
        /// </summary>
        public async Task <Image> GetNextImageInGalleryAsync(Image currentImage)
        {
            // choose the right query - not all galleries will have had their images ordered. fall back to image creation date for unordered galleries
            var query = currentImage.Position.HasValue
                ? new QueryDefinition("SELECT TOP 1 VALUE c.id FROM c WHERE c.GalleryId = @galleryId AND c.Position > @position ORDER BY c.Position")
                        .WithParameter("@galleryId", currentImage.GalleryId)
                        .WithParameter("@position", currentImage.Position.Value)
                : new QueryDefinition("SELECT TOP 1 VALUE c.id FROM c WHERE c.GalleryId = @galleryId AND c.Created > @created ORDER BY c.Created")
                        .WithParameter("@galleryId", currentImage.GalleryId)
                        .WithParameter("@created", currentImage.Created);

            var id = await GetImageIdByQueryAsync(query);

            return(await GetImageAsync(currentImage.GalleryId, id));
        }
Beispiel #4
0
        private async Task HandleDeletePreGenImagesAsync(Image image)
        {
            await DeleteImageFileAsync(image, ImageFileSpecs.GetImageFileSpec(FileSpec.Spec3840));
            await DeleteImageFileAsync(image, ImageFileSpecs.GetImageFileSpec(FileSpec.Spec2560));
            await DeleteImageFileAsync(image, ImageFileSpecs.GetImageFileSpec(FileSpec.Spec1920));
            await DeleteImageFileAsync(image, ImageFileSpecs.GetImageFileSpec(FileSpec.Spec800));
            await DeleteImageFileAsync(image, ImageFileSpecs.GetImageFileSpec(FileSpec.SpecLowRes));

            image.Files.Spec3840Id   = null;
            image.Files.Spec2560Id   = null;
            image.Files.Spec1920Id   = null;
            image.Files.Spec800Id    = null;
            image.Files.SpecLowResId = null;

            await UpdateImageAsync(image);
        }
Beispiel #5
0
        public async Task DeleteCommentAsync(Gallery gallery, Image image, Comment comment)
        {
            var removed = image.Comments.Remove(comment);

            if (removed)
            {
                image.CommentCount--;
                await Server.Instance.Images.UpdateImageAsync(image);

                gallery.CommentCount--;
                await Server.Instance.Galleries.UpdateGalleryAsync(gallery);
            }
            else
            {
                Log.Information($"GalleryServer.DeleteCommentAsync(): No comment removed. imageId={image.Id}, commentCreatedTicks={comment.Created.Ticks}, commentCreatedByUserId={comment.CreatedByUserId}");
            }
        }
Beispiel #6
0
        /// <summary>
        /// Updates an Image in the database. Must be provided with a complete and recently queried from the database image to avoid losing other recent updates.
        /// </summary>
        public async Task UpdateImageAsync(Image image)
        {
            if (image == null)
            {
                throw new ArgumentNullException(nameof(image));
            }

            if (!image.IsValid())
            {
                throw new InvalidOperationException("Image is invalid. Please check all required properties are set.");
            }

            var container = Server.Instance.Database.GetContainer(Constants.ImagesContainerName);
            var response  = await container.ReplaceItemAsync(image, image.Id, new PartitionKey(image.GalleryId));

            Log.Debug($"ImageServer.UpdateImageAsync: Request charge: {response.RequestCharge}. Elapsed time: {response.Diagnostics.GetClientElapsedTime().TotalMilliseconds} ms");
        }
Beispiel #7
0
        /// <summary>
        /// Deletes the original image and any generated images for an Image.
        /// </summary>
        private static async Task DeleteImageFilesAsync(Image image, bool clearImageFileReferences = false)
        {
            // delete all image files
            var deleteTasks = new List <Task>
            {
                DeleteImageFileAsync(image, ImageFileSpecs.GetImageFileSpec(FileSpec.SpecOriginal)),
                DeleteImageFileAsync(image, ImageFileSpecs.GetImageFileSpec(FileSpec.Spec3840)),
                DeleteImageFileAsync(image, ImageFileSpecs.GetImageFileSpec(FileSpec.Spec2560)),
                DeleteImageFileAsync(image, ImageFileSpecs.GetImageFileSpec(FileSpec.Spec1920)),
                DeleteImageFileAsync(image, ImageFileSpecs.GetImageFileSpec(FileSpec.Spec800)),
                DeleteImageFileAsync(image, ImageFileSpecs.GetImageFileSpec(FileSpec.SpecLowRes))
            };
            await Task.WhenAll(deleteTasks);

            if (clearImageFileReferences)
            {
                image.Files.OriginalId   = null;
                image.Files.Spec3840Id   = null;
                image.Files.Spec2560Id   = null;
                image.Files.Spec1920Id   = null;
                image.Files.Spec800Id    = null;
                image.Files.SpecLowResId = null;
            }
        }
Beispiel #8
0
        /// <summary>
        /// Causes an Image to be permanently deleted from storage and database.
        /// Will result in some images being re-ordered to avoid holes in positions.
        /// </summary>
        /// <param name="image">The Image to be deleted.</param>
        /// <param name="isGalleryBeingDeleted">Will disable all re-ordering and gallery thumbnail tasks if the gallery is being deleted.</param>
        public async Task DeleteImageAsync(Image image, bool isGalleryBeingDeleted = false)
        {
            await DeleteImageFilesAsync(image);

            // make note of the image position and gallery id as we might have to re-order photos
            var position  = image.Position;
            var galleryId = image.GalleryId;

            // finally, delete the database record
            var imagesContainer = Server.Instance.Database.GetContainer(Constants.ImagesContainerName);
            var deleteResponse  = await imagesContainer.DeleteItemAsync <Image>(image.Id, new PartitionKey(image.GalleryId));

            Log.Debug($"ImageServer:DeleteImageAsync: Request charge: {deleteResponse.RequestCharge}. Elapsed time: {deleteResponse.Diagnostics.GetClientElapsedTime().TotalMilliseconds} ms");

            // if necessary, re-order photos down-position from where the deleted photo used to be
            if (!isGalleryBeingDeleted)
            {
                if (position.HasValue)
                {
                    Log.Debug("ImageServer:DeleteImageAsync: Image had an order, re-ordering subsequent images...");

                    // get the ids of images that have a position down from where our deleted image used to be
                    var queryDefinition = new QueryDefinition("SELECT c.id AS Id, c.GalleryId AS PartitionKey FROM c WHERE c.GalleryId = @galleryId AND c.Position > @position ORDER BY c.Position")
                                          .WithParameter("@galleryId", galleryId)
                                          .WithParameter("@position", position.Value);

                    var ids = await Server.GetIdsByQueryAsync(Constants.GalleriesContainerName, queryDefinition);

                    foreach (var databaseId in ids)
                    {
                        var affectedImage = await GetImageAsync(galleryId, databaseId.Id);

                        // this check shouldn't be required as if one image has a position then all should
                        // but life experience suggests it's best to be sure.
                        if (affectedImage.Position == null)
                        {
                            continue;
                        }

                        affectedImage.Position = position;
                        await UpdateImageAsync(affectedImage);

                        position += 1;
                    }
                }

                // do we need to update the gallery thumbnail after we delete this image?
                var gallery = await Server.Instance.Galleries.GetGalleryAsync(image.GalleryCategoryId, image.GalleryId);

                var newThumbnailNeeded = gallery.ThumbnailFiles == null || gallery.ThumbnailFiles.OriginalId == image.Files.OriginalId;
                if (newThumbnailNeeded)
                {
                    var images = await GetGalleryImagesAsync(gallery.Id);

                    if (images.Count > 0)
                    {
                        var orderedImages = Utilities.OrderImages(images);
                        gallery.ThumbnailFiles = orderedImages.First().Files;
                        Log.Debug($"ImageServer.DeleteImageAsync: New gallery thumbnail was needed. Set to {gallery.ThumbnailFiles.Spec800Id}");
                    }
                    else
                    {
                        gallery.ThumbnailFiles = null;
                        Log.Debug("ImageServer.DeleteImageAsync: New gallery thumbnail was needed but no images to choose from.");
                    }

                    await Server.Instance.Galleries.UpdateGalleryAsync(gallery);
                }
            }
        }
Beispiel #9
0
        /// <summary>
        /// Stores an uploaded file in the storage system and adds a supporting Image object to the database.
        /// </summary>
        /// <param name="galleryCategoryId">The id for the category the gallery resides in, which the image resides in.</param>
        /// <param name="galleryId">The gallery this image is going to be contained within.</param>
        /// <param name="imageStream">The stream for the uploaded image file.</param>
        /// <param name="filename">The original filename provided by the client.</param>
        /// <param name="image">Optionally supply a pre-populated Image object.</param>
        /// <param name="performImageDimensionsCheck">Ordinarily images must be bigger than 800x800 in size but for migration purposes we might want to override this.</param>
        public async Task CreateImageAsync(string galleryCategoryId, string galleryId, Stream imageStream, string filename, Image image = null, bool performImageDimensionsCheck = true)
        {
            try
            {
                if (string.IsNullOrEmpty(galleryId))
                {
                    throw new ArgumentNullException(nameof(galleryId));
                }

                if (imageStream == null)
                {
                    throw new ArgumentNullException(nameof(imageStream));
                }

                if (string.IsNullOrEmpty(filename))
                {
                    throw new ArgumentNullException(nameof(filename));
                }

                await CheckImageDimensionsAsync(imageStream);

                if (image == null)
                {
                    // create the Image object anew
                    var id = Utilities.GenerateId();
                    image = new Image
                    {
                        Id = id,
                        GalleryCategoryId = galleryCategoryId,
                        GalleryId         = galleryId,
                        Files             = { OriginalId = id + Path.GetExtension(filename).ToLower() }
                    };
                }
                else
                {
                    // the Image already exists but may not be sufficiently populated...
                    if (!image.Id.HasValue())
                    {
                        image.Id = Utilities.GenerateId();
                    }
                    if (!image.GalleryCategoryId.HasValue())
                    {
                        image.GalleryCategoryId = galleryCategoryId;
                    }
                    if (!image.GalleryId.HasValue())
                    {
                        image.GalleryId = galleryId;
                    }
                    if (!image.Files.OriginalId.HasValue())
                    {
                        image.Files.OriginalId = image.Id + Path.GetExtension(filename).ToLower();
                    }
                }

                // this should be done in the worker when parsing the metadata.
                // we're just not doing it there as that means some work to sanitise and serialise the filename for insertion into the worker message.
                // lazy, I know. I'll come back to this.
                image.Metadata.OriginalFilename = filename;

                if (!image.Name.HasValue())
                {
                    image.Name = Utilities.TidyImageName(Path.GetFileNameWithoutExtension(filename));
                }

                if (!image.IsValid())
                {
                    throw new InvalidOperationException("Image would be invalid. Please check all required properties are set.");
                }

                // upload the original file to storage
                var originalContainerClient = Server.Instance.BlobServiceClient.GetBlobContainerClient(Constants.StorageOriginalContainerName);
                await originalContainerClient.UploadBlobAsync(image.Files.OriginalId, imageStream);

                // create the database record
                var container = Server.Instance.Database.GetContainer(Constants.ImagesContainerName);
                var response  = await container.CreateItemAsync(image, new PartitionKey(image.GalleryId));

                Log.Debug($"ImageServer.CreateImageAsync: Request charge: {response.RequestCharge}. Elapsed time: {response.Diagnostics.GetClientElapsedTime().TotalMilliseconds} ms");

                // have the pre-gen images created by a background process
                await PostProcessImagesAsync(image);
            }
            finally
            {
                // make sure we release valuable server resources in the event of a problem creating the image
                imageStream?.Close();
            }
        }