Skip to content

Commit

Permalink
(#102) ContentProxy: add file names and MIME types
Browse files Browse the repository at this point in the history
  • Loading branch information
ForNeVeR committed Aug 28, 2022
1 parent b02512c commit 5d954d6
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 25 deletions.
8 changes: 7 additions & 1 deletion Emulsion.ContentProxy/ContentStorage.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ type MessageContentIdentity = {
ChatUserName: string
MessageId: int64
FileId: string
FileName: string option
MimeType: string option
}

let getOrCreateMessageRecord (context: EmulsionDbContext) (id: MessageContentIdentity): Async<TelegramContent> = async {
Expand All @@ -17,7 +19,9 @@ let getOrCreateMessageRecord (context: EmulsionDbContext) (id: MessageContentIde
for content in context.TelegramContents do
where (content.ChatUserName = id.ChatUserName
&& content.MessageId = id.MessageId
&& content.FileId = id.FileId)
&& content.FileId = id.FileId
&& content.FileName = id.FileName
&& content.MimeType = id.MimeType)
} |> tryExactlyOneAsync
match existingItem with
| None ->
Expand All @@ -26,6 +30,8 @@ let getOrCreateMessageRecord (context: EmulsionDbContext) (id: MessageContentIde
ChatUserName = id.ChatUserName
MessageId = id.MessageId
FileId = id.FileId
FileName = id.FileName
MimeType = id.MimeType
}
do! addAsync context.TelegramContents newItem
return newItem
Expand Down
4 changes: 4 additions & 0 deletions Emulsion.Database/EmulsionDbContext.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Emulsion.Database

open EntityFrameworkCore.FSharp.Extensions
open Microsoft.EntityFrameworkCore
open Microsoft.EntityFrameworkCore.Design

Expand All @@ -8,6 +9,9 @@ open Emulsion.Database.Entities
type EmulsionDbContext(options: DbContextOptions) =
inherit DbContext(options)

override _.OnModelCreating builder =
builder.RegisterOptionTypes()

[<DefaultValue>] val mutable private telegramContents: DbSet<TelegramContent>
member this.TelegramContents with get() = this.telegramContents and set v = this.telegramContents <- v

Expand Down
2 changes: 2 additions & 0 deletions Emulsion.Database/Entities.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ type TelegramContent = {
ChatUserName: string
MessageId: int64
FileId: string
FileName: string option
MimeType: string option
}
77 changes: 77 additions & 0 deletions Emulsion.Database/Migrations/20220828121717_FileNameAndMimeType.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// <auto-generated />
namespace Emulsion.Database.Migrations

open System
open Emulsion.Database
open Microsoft.EntityFrameworkCore
open Microsoft.EntityFrameworkCore.Infrastructure
open Microsoft.EntityFrameworkCore.Metadata
open Microsoft.EntityFrameworkCore.Migrations
open Microsoft.EntityFrameworkCore.Storage.ValueConversion

[<DbContext(typeof<EmulsionDbContext>)>]
[<Migration("20220828121717_FileNameAndMimeType")>]
type FileNameAndMimeType() =
inherit Migration()

override this.Up(migrationBuilder:MigrationBuilder) =
migrationBuilder.AddColumn<string>(
name = "FileName"
,table = "TelegramContents"
,``type`` = "TEXT"
,nullable = true
) |> ignore

migrationBuilder.AddColumn<string>(
name = "MimeType"
,table = "TelegramContents"
,``type`` = "TEXT"
,nullable = true
) |> ignore


override this.Down(migrationBuilder:MigrationBuilder) =
migrationBuilder.DropColumn(
name = "FileName"
,table = "TelegramContents"
) |> ignore

migrationBuilder.DropColumn(
name = "MimeType"
,table = "TelegramContents"
) |> ignore


override this.BuildTargetModel(modelBuilder: ModelBuilder) =
modelBuilder
.HasAnnotation("ProductVersion", "5.0.10")
|> ignore

modelBuilder.Entity("Emulsion.Database.Entities.TelegramContent", (fun b ->

b.Property<Int64>("Id")
.IsRequired(true)
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER") |> ignore
b.Property<string>("ChatUserName")
.IsRequired(false)
.HasColumnType("TEXT") |> ignore
b.Property<string>("FileId")
.IsRequired(false)
.HasColumnType("TEXT") |> ignore
b.Property<string option>("FileName")
.IsRequired(false)
.HasColumnType("TEXT") |> ignore
b.Property<Int64>("MessageId")
.IsRequired(true)
.HasColumnType("INTEGER") |> ignore
b.Property<string option>("MimeType")
.IsRequired(false)
.HasColumnType("TEXT") |> ignore

b.HasKey("Id") |> ignore

b.ToTable("TelegramContents") |> ignore

)) |> ignore

Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ open System
open Emulsion.Database
open Microsoft.EntityFrameworkCore
open Microsoft.EntityFrameworkCore.Infrastructure
open Microsoft.EntityFrameworkCore.Metadata
open Microsoft.EntityFrameworkCore.Migrations
open Microsoft.EntityFrameworkCore.Storage.ValueConversion

[<DbContext(typeof<EmulsionDbContext>)>]
type EmulsionDbContextModelSnapshot() =
Expand All @@ -30,9 +27,15 @@ type EmulsionDbContextModelSnapshot() =
b.Property<string>("FileId")
.IsRequired(false)
.HasColumnType("TEXT") |> ignore
b.Property<string option>("FileName")
.IsRequired(false)
.HasColumnType("TEXT") |> ignore
b.Property<Int64>("MessageId")
.IsRequired(true)
.HasColumnType("INTEGER") |> ignore
b.Property<string option>("MimeType")
.IsRequired(false)
.HasColumnType("TEXT") |> ignore

b.HasKey("Id") |> ignore

Expand Down
69 changes: 51 additions & 18 deletions Emulsion.Telegram/LinkGenerator.fs
Original file line number Diff line number Diff line change
Expand Up @@ -30,41 +30,74 @@ let private gatherMessageLink(message: FunogramMessage) =
| { Text = Some _} | { Poll = Some _ } -> None
| _ -> getMessageLink message

let private getFileIds(message: FunogramMessage): string seq =
let allFileIds = ResizeArray()
let inline extractFileId(o: ^a option) =
Option.iter(fun o -> allFileIds.Add((^a) : (member FileId: string) o)) o
type private FileInfo = {
FileId: string
FileName: string option
MimeType: string option
}

let private getFileInfos(message: FunogramMessage): FileInfo seq =
let allFileInfos = ResizeArray()
let inline extractFileInfo(o: ^a option) =
o |> Option.iter(fun o ->
allFileInfos.Add({
FileId = ((^a) : (member FileId: string) o)
FileName = ((^a) : (member FileName: string option) o)
MimeType = ((^a) : (member MimeType: string option) o)
}))

let inline extractFileInfoWithName (fileName: string) (o: ^a option) =
o |> Option.iter(fun o ->
allFileInfos.Add({
FileId = ((^a) : (member FileId: string) o)
FileName = Some fileName
MimeType = ((^a) : (member MimeType: string option) o)
}))

let extractPhotoFileId: PhotoSize[] option -> unit =
let inline extractFileInfoWithNameAndMimeType (fileName: string) (mimeType: string) (o: ^a option) =
o |> Option.iter(fun o ->
allFileInfos.Add({
FileId = ((^a) : (member FileId: string) o)
FileName = Some fileName
MimeType = Some mimeType
}))

let extractPhotoFileInfo: PhotoSize[] option -> unit =
Option.iter(
// Telegram may send several differently-sized thumbnails in one message. Pick the biggest one of them.
Seq.sortByDescending(fun size -> size.Height * size.Width)
>> Seq.map(fun photoSize -> photoSize.FileId)
>> Seq.tryHead
>> Option.iter(allFileIds.Add)
>> Option.iter(fun fileId -> allFileInfos.Add {
FileId = fileId
FileName = Some "photo.jpg"
MimeType = Some "image/jpeg"
})
)

extractFileId message.Document
extractFileId message.Audio
extractFileId message.Animation
extractPhotoFileId message.Photo
extractFileId message.Sticker
extractFileId message.Video
extractFileId message.Voice
extractFileId message.VideoNote
extractFileInfo message.Document
extractFileInfo message.Audio
extractFileInfo message.Animation
extractPhotoFileInfo message.Photo
extractFileInfoWithNameAndMimeType "sticker.jpg" "image/jpeg" message.Sticker
extractFileInfo message.Video
extractFileInfoWithName "voice.ogg" message.Voice
extractFileInfoWithNameAndMimeType "video.mp4" "video/mp4" message.VideoNote

allFileIds
allFileInfos

let private getContentIdentities(message: FunogramMessage): ContentStorage.MessageContentIdentity seq =
match message.Chat with
| { Type = SuperGroup
Username = Some chatName } ->
getFileIds message
|> Seq.map (fun fileId ->
getFileInfos message
|> Seq.map (fun fileInfo ->
{
ChatUserName = chatName
MessageId = message.MessageId
FileId = fileId
FileId = fileInfo.FileId
FileName = fileInfo.FileName
MimeType = fileInfo.MimeType
}
)
| _ -> Seq.empty
Expand Down
2 changes: 2 additions & 0 deletions Emulsion.Tests/ContentProxy/ContentStorageTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ let private testIdentity = {
ChatUserName = "test"
MessageId = 123L
FileId = "this_is_file"
FileName = None
MimeType = None
}

let private executeQuery settings =
Expand Down
2 changes: 2 additions & 0 deletions Emulsion.Tests/Database/DatabaseStructureTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ let ``Unique constraint should hold``(): unit =
ChatUserName = "testChat"
MessageId = 666L
FileId = "foobar"
FileName = None
MimeType = None
}
async {
do! DataStorage.addAsync ctx.TelegramContents newContent
Expand Down
9 changes: 8 additions & 1 deletion Emulsion.Tests/Web/ContentControllerTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ type ContentControllerTests(output: ITestOutputHelper) =
ChatUserName = chatUserName
MessageId = messageId
FileId = "foobar"
FileName = None
MimeType = None
}

performTestWithContent None content (fun controller -> async {
Expand All @@ -104,6 +106,8 @@ type ContentControllerTests(output: ITestOutputHelper) =
ChatUserName = chatUserName
MessageId = messageId
FileId = fileId
FileName = None
MimeType = None
}

telegramClient.SetResponse(fileId, None)
Expand All @@ -127,9 +131,10 @@ type ContentControllerTests(output: ITestOutputHelper) =
ChatUserName = chatUserName
MessageId = messageId
FileId = fileId
FileName = None
MimeType = None
}


use fileCache = setUpFileCache()
use fileStorage = new WebFileStorage(Map.empty)
telegramClient.SetResponse(fileId, Some {
Expand All @@ -155,6 +160,8 @@ type ContentControllerTests(output: ITestOutputHelper) =
ChatUserName = chatUserName
MessageId = messageId
FileId = fileId
FileName = None
MimeType = None
}

let onServerFileId = "fileIdOnServer"
Expand Down
4 changes: 3 additions & 1 deletion Emulsion.Web/ContentController.fs
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,7 @@ type ContentController(logger: ILogger<ContentController>,
logger.LogWarning $"Link \"{fileInfo}\" could not be downloaded."
return this.NotFound() :> IActionResult
| Some stream ->
return FileStreamResult(stream, "application/octet-stream")
let contentType = Option.defaultValue "application/octet-stream" content.MimeType
let fileName = Option.defaultValue "" content.FileName
return FileStreamResult(stream, contentType, FileDownloadName = fileName)
}
2 changes: 1 addition & 1 deletion docs/create-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

This article explains how to create a database migration using [EFCore.FSharp][efcore.fsharp].

1. Change the entity type (see `Emulsion.Database/Models.fs`), update the `EmulsionDbContext` if required.
1. Change the entity type (see `Emulsion.Database/Entities.fs`), update the `EmulsionDbContext` if required.
2. Run the following shell commands:

```console
Expand Down

0 comments on commit 5d954d6

Please sign in to comment.