From 5b2464a8113fa4a68c8749b3553a5924d2131d9f Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Mon, 19 Aug 2019 15:43:47 +0800 Subject: Add avatar 304. --- Timeline/Controllers/UserAvatarController.cs | 16 +++++- Timeline/Entities/DatabaseContext.cs | 2 +- Timeline/Entities/UserAvatar.cs | 24 +++++++-- Timeline/Services/UserAvatarService.cs | 76 ++++++++++++++++------------ Timeline/Services/UserService.cs | 2 +- 5 files changed, 81 insertions(+), 39 deletions(-) (limited to 'Timeline') diff --git a/Timeline/Controllers/UserAvatarController.cs b/Timeline/Controllers/UserAvatarController.cs index 710ca764..89d2650c 100644 --- a/Timeline/Controllers/UserAvatarController.cs +++ b/Timeline/Controllers/UserAvatarController.cs @@ -57,10 +57,22 @@ namespace Timeline.Controllers [Authorize] public async Task Get(string username) { + const string IfModifiedSinceHeaderKey = "If-Modified-Since"; try { - var avatar = await _service.GetAvatar(username); - return File(avatar.Data, avatar.Type); + var avatarInfo = await _service.GetAvatar(username); + var avatar = avatarInfo.Avatar; + if (Request.Headers.TryGetValue(IfModifiedSinceHeaderKey, out var value)) + { + var t = DateTime.Parse(value); + if (t > avatarInfo.LastModified) + { + Response.Headers.Add(IfModifiedSinceHeaderKey, avatarInfo.LastModified.ToString("r")); + return StatusCode(StatusCodes.Status304NotModified); + } + } + + return File(avatar.Data, avatar.Type, new DateTimeOffset(avatarInfo.LastModified), null); } catch (UserNotExistException e) { diff --git a/Timeline/Entities/DatabaseContext.cs b/Timeline/Entities/DatabaseContext.cs index f32e5992..b12db46e 100644 --- a/Timeline/Entities/DatabaseContext.cs +++ b/Timeline/Entities/DatabaseContext.cs @@ -28,6 +28,7 @@ namespace Timeline.Entities [Column("version"), Required] public long Version { get; set; } + [Required] public UserAvatar Avatar { get; set; } } @@ -42,7 +43,6 @@ namespace Timeline.Entities protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().Property(e => e.Version).HasDefaultValue(0); - } public DbSet Users { get; set; } diff --git a/Timeline/Entities/UserAvatar.cs b/Timeline/Entities/UserAvatar.cs index d7c24403..a2fd6821 100644 --- a/Timeline/Entities/UserAvatar.cs +++ b/Timeline/Entities/UserAvatar.cs @@ -1,5 +1,7 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Timeline.Services; namespace Timeline.Entities { @@ -9,10 +11,26 @@ namespace Timeline.Entities [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public long Id { get; set; } - [Column("data"), Required] + [Column("data")] public byte[] Data { get; set; } - [Column("type"), Required] + [Column("type")] public string Type { get; set; } + + [Column("last_modified"), Required] + public DateTime LastModified { get; set; } + + public long UserId { get; set; } + + public static UserAvatar Create(DateTime lastModified) + { + return new UserAvatar + { + Id = 0, + Data = null, + Type = null, + LastModified = lastModified + }; + } } } diff --git a/Timeline/Services/UserAvatarService.cs b/Timeline/Services/UserAvatarService.cs index dd0e5e7c..a83b8a52 100644 --- a/Timeline/Services/UserAvatarService.cs +++ b/Timeline/Services/UserAvatarService.cs @@ -18,6 +18,12 @@ namespace Timeline.Services public byte[] Data { get; set; } } + public class AvatarInfo + { + public Avatar Avatar { get; set; } + public DateTime LastModified { get; set; } + } + /// /// Thrown when avatar is of bad format. /// @@ -61,7 +67,7 @@ namespace Timeline.Services /// /// Get the default avatar. /// - Task GetDefaultAvatar(); + Task GetDefaultAvatar(); } public interface IUserAvatarValidator @@ -80,10 +86,10 @@ namespace Timeline.Services /// 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. + /// The avatar info. /// Thrown if is null or empty. /// Thrown if the user does not exist. - Task GetAvatar(string username); + Task GetAvatar(string username); /// /// Set avatar for a user. @@ -106,12 +112,17 @@ namespace Timeline.Services _environment = environment; } - public async Task GetDefaultAvatar() + public async Task GetDefaultAvatar() { - return new Avatar + var path = Path.Combine(_environment.ContentRootPath, "default-avatar.png"); + return new AvatarInfo { - Type = "image/png", - Data = await File.ReadAllBytesAsync(Path.Combine(_environment.ContentRootPath, "default-avatar.png")) + Avatar = new Avatar + { + Type = "image/png", + Data = await File.ReadAllBytesAsync(path) + }, + LastModified = File.GetLastWriteTime(path) }; } } @@ -134,7 +145,7 @@ namespace Timeline.Services } catch (UnknownImageFormatException e) { - throw new AvatarDataException(avatar, AvatarDataException.ErrorReason.CantDecode, "Failed to decode image. See inner exception.", e); + throw new AvatarDataException(avatar, AvatarDataException.ErrorReason.CantDecode, "Failed to decode image. See inner exception.", e); } }); } @@ -158,7 +169,7 @@ namespace Timeline.Services _avatarValidator = avatarValidator; } - public async Task GetAvatar(string username) + public async Task GetAvatar(string username) { if (string.IsNullOrEmpty(username)) throw new ArgumentException("Username is null or empty.", nameof(username)); @@ -170,16 +181,26 @@ namespace Timeline.Services await _database.Entry(user).Reference(u => u.Avatar).LoadAsync(); var avatar = user.Avatar; - if (avatar == null) + if ((avatar.Type == null) == (avatar.Data == null)) + _logger.LogCritical("Database corupted! One of type and data of a avatar is null but the other is not."); + // TODO: Throw an exception to indicate this. + + if (avatar.Data == null) { - return await _defaultUserAvatarProvider.GetDefaultAvatar(); + var defaultAvatar = await _defaultUserAvatarProvider.GetDefaultAvatar(); + defaultAvatar.LastModified = defaultAvatar.LastModified > avatar.LastModified ? defaultAvatar.LastModified : avatar.LastModified; + return defaultAvatar; } else { - return new Avatar + return new AvatarInfo { - Type = avatar.Type, - Data = avatar.Data + Avatar = new Avatar + { + Type = avatar.Type, + Data = avatar.Data + }, + LastModified = avatar.LastModified }; } } @@ -206,34 +227,25 @@ namespace Timeline.Services if (avatar == null) { - if (avatarEntity == null) + if (avatarEntity.Data == null) return; else { - _database.UserAvatars.Remove(avatarEntity); + avatarEntity.Data = null; + avatarEntity.Type = null; + avatarEntity.LastModified = DateTime.Now; await _database.SaveChangesAsync(); - _logger.LogInformation("Removed an entry in user_avatars."); + _logger.LogInformation("Updated an entry in user_avatars."); } } else { await _avatarValidator.Validate(avatar); - - if (avatarEntity == null) - { - user.Avatar = new UserAvatar - { - Type = avatar.Type, - Data = avatar.Data - }; - } - else - { - avatarEntity.Type = avatar.Type; - avatarEntity.Data = avatar.Data; - } + avatarEntity.Type = avatar.Type; + avatarEntity.Data = avatar.Data; + avatarEntity.LastModified = DateTime.Now; await _database.SaveChangesAsync(); - _logger.LogInformation("Added or modified an entry in user_avatars."); + _logger.LogInformation("Updated an entry in user_avatars."); } } } diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index 96c3e256..347b8cbb 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -376,7 +376,7 @@ namespace Timeline.Services Name = username, EncryptedPassword = _passwordService.HashPassword(password), RoleString = IsAdminToRoleString(administrator), - Version = 0 + Avatar = UserAvatar.Create(DateTime.Now) }; await _databaseContext.AddAsync(newUser); await _databaseContext.SaveChangesAsync(); -- cgit v1.2.3