using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; using System.IO; using System.Linq; using System.Threading.Tasks; using Timeline.Entities; namespace Timeline.Services { public class Avatar { public string Type { get; set; } public byte[] Data { get; set; } } /// /// Thrown when avatar is of bad format. /// [Serializable] public class AvatarDataException : Exception { public AvatarDataException(Avatar avatar, string message) : base(message) { Avatar = avatar; } public AvatarDataException(Avatar avatar, string message, Exception inner) : base(message, inner) { Avatar = avatar; } protected AvatarDataException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } public Avatar Avatar { get; set; } } /// /// Provider for default user avatar. /// /// /// Mainly for unit tests. /// public interface IDefaultUserAvatarProvider { /// /// Get the default avatar. /// Task GetDefaultAvatar(); } public interface IUserAvatarService { /// /// Get avatar of a user. If the user has no avatar, a default one is returned. /// /// The username of the user to get avatar of. /// The avatar. /// Thrown if is null or empty. /// Thrown if the user does not exist. Task GetAvatar(string username); /// /// Set avatar for a user. /// /// The username of the user to set avatar for. /// The avatar. Can be null to delete the saved avatar. /// Throw if is null or empty. /// Or thrown if is not null but is null or empty or is null. /// Thrown if the user does not exist. /// Thrown if avatar is of bad format. Task SetAvatar(string username, Avatar avatar); } public class DefaultUserAvatarProvider : IDefaultUserAvatarProvider { private readonly IHostingEnvironment _environment; public DefaultUserAvatarProvider(IHostingEnvironment environment) { _environment = environment; } public async Task GetDefaultAvatar() { return new Avatar { Type = "image/png", Data = await File.ReadAllBytesAsync(Path.Combine(_environment.ContentRootPath, "default-avatar.png")) }; } } public class UserAvatarService : IUserAvatarService { private readonly ILogger _logger; private readonly DatabaseContext _database; private readonly IDefaultUserAvatarProvider _defaultUserAvatarProvider; public UserAvatarService(ILogger logger, DatabaseContext database, IDefaultUserAvatarProvider defaultUserAvatarProvider) { _logger = logger; _database = database; _defaultUserAvatarProvider = defaultUserAvatarProvider; } public async Task GetAvatar(string username) { if (string.IsNullOrEmpty(username)) throw new ArgumentException("Username is null or empty.", nameof(username)); var user = await _database.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); if (user == null) throw new UserNotExistException(username); await _database.Entry(user).Reference(u => u.Avatar).LoadAsync(); var avatar = user.Avatar; if (avatar == null) { return await _defaultUserAvatarProvider.GetDefaultAvatar(); } else { return new Avatar { Type = avatar.Type, Data = avatar.Data }; } } public async Task SetAvatar(string username, Avatar avatar) { if (string.IsNullOrEmpty(username)) throw new ArgumentException("Username is null or empty.", nameof(username)); if (avatar != null) { if (string.IsNullOrEmpty(avatar.Type)) throw new ArgumentException("Type of avatar is null or empty.", nameof(avatar)); if (avatar.Data == null) throw new ArgumentException("Data of avatar is null.", nameof(avatar)); } var user = await _database.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); if (user == null) throw new UserNotExistException(username); await _database.Entry(user).Reference(u => u.Avatar).LoadAsync(); var avatarEntity = user.Avatar; if (avatar == null) { if (avatarEntity == null) return; else { _database.UserAvatars.Remove(avatarEntity); await _database.SaveChangesAsync(); } } else { // TODO: Use image library to check the format to prohibit bad data. if (avatarEntity == null) { user.Avatar = new UserAvatar { Type = avatar.Type, Data = avatar.Data }; } else { avatarEntity.Type = avatar.Type; avatarEntity.Data = avatar.Data; } await _database.SaveChangesAsync(); } } } }