add: Task Status

This commit is contained in:
Simon Hardt
2021-05-31 23:19:02 +02:00
parent 98fb1349a1
commit e12fbfa2aa
12 changed files with 1222 additions and 147 deletions

View File

@@ -8,6 +8,8 @@
#include <fstream> #include <fstream>
#include <functional> #include <functional>
#include <soci/boost-optional.h>
#include <ctre.hpp> #include <ctre.hpp>
#include <fmt/chrono.h> #include <fmt/chrono.h>
#include <fmt/ostream.h> #include <fmt/ostream.h>
@@ -30,7 +32,9 @@ void Api::RegisterServerHandles(httplib::Server& server)
server.Get("/api/files", std::bind_front(&Api::files, this)); server.Get("/api/files", std::bind_front(&Api::files, this));
server.Get("/api/file/([A-E0-9]+)", std::bind_front(&Api::file, this)); server.Get("/api/file/([A-F0-9]+)", std::bind_front(&Api::file, this));
server.Get("/api/status/([A-F0-9]+)", std::bind_front(&Api::status, this));
server.Options("/(.*)", [this](httplib::Request const& rq, httplib::Response& rp) {}); server.Options("/(.*)", [this](httplib::Request const& rq, httplib::Response& rp) {});
@@ -205,6 +209,70 @@ void Api::file(const httplib::Request& rq, httplib::Response& rs)
rs.set_content("File Not Found", "text/plain"); rs.set_content("File Not Found", "text/plain");
} }
} }
void Api::status(httplib::Request const& rq, httplib::Response& rs)
{
if (!rq.matches[1].matched)
{
rs.status = 404;
rs.set_content("Parameter error", "text/plain");
return;
}
std::string hash = rq.matches[1];
boost::optional<Task> data;
sql << "SELECT * FROM Tasks WHERE hash=:hash", soci::use(hash, "hash"), soci::into(data);
/*soci::rowset<Task> data = (sql.prepare << "SELECT * "
"FROM Tasks "
"WHERE hash=:hash ",
soci::use(hash, "hash"));*/
json response;
auto& status = response["status"];
if (data)
{
auto const& el = data.value();
status["name"] = fmt::format("Task {}", el.hash);
status["source"] = el.url;
status["status"] = magic_enum::enum_name(el.status);
status["status_id"] = static_cast<int>(el.status);
status["timestamp"] = el.timestamp;
status["task_file_type"] =
fmt::format("{}: {}", el.audio_only ? "Audio" : "Video", el.format.value_or("Auto"));
status["id"] = el.hash;
if (el.status == FileStatus::COMPLETE)
{
boost::optional<File> file_data;
sql << "SELECT * FROM Files WHERE hash=:hash", soci::use(hash, "hash"),
soci::into(file_data);
if (file_data)
{
auto const& file = file_data.value();
auto& file_status = status["file"];
file_status["timestamp"] = file.timestamp;
file_status["name"] = file.filename;
file_status["format"] =
fmt::format("{}: {}", el.audio_only ? "Audio" : "Video", file.format);
}
}
} else
{
rs.status = 404;
response["error"] = fmt::format("File '{}' not found!", hash);
}
rs.set_content(response.dump(), "application/json");
}
void Api::set_cross_headers(httplib::Response& rs) void Api::set_cross_headers(httplib::Response& rs)
{ {
rs.set_header("Access-Control-Allow-Origin", "*"); rs.set_header("Access-Control-Allow-Origin", "*");

View File

@@ -18,6 +18,8 @@ public: // Endpoints
void files(httplib::Request const& rq, httplib::Response& rs); void files(httplib::Request const& rq, httplib::Response& rs);
void file(httplib::Request const& rq, httplib::Response& rs); void file(httplib::Request const& rq, httplib::Response& rs);
void status(httplib::Request const& rq, httplib::Response& rs);
void set_cross_headers(httplib::Response& rs); void set_cross_headers(httplib::Response& rs);
}; };

View File

@@ -97,10 +97,11 @@ struct Task
inline std::ostream& operator<<(std::ostream& os, Task const& file) inline std::ostream& operator<<(std::ostream& os, Task const& file)
{ {
return os << fmt::format("Task [{}][{}] url: {} ", return os << fmt::format("Task [{}] url: {} Audio: {} Format: {}",
file.id,
magic_enum::enum_name(file.status), magic_enum::enum_name(file.status),
file.url); file.url,
file.audio_only,
file.format.value_or("Auto"));
} }
namespace soci namespace soci

View File

@@ -2,9 +2,13 @@ POST 127.0.0.1:80/api/add
Content-Type: text/json Content-Type: text/json
{ {
"url": "https://www.youtube.com/watch?v=ZWIwLMpgcbI" "url": "https://www.youtube.com/watch?v=izc1XLbU5Vk"
} }
### ###
GET 127.0.0.1:80/api/queue GET 127.0.0.1:80/api/files
###
GET 127.0.0.1:80/api/status/C3F953D7

View File

@@ -7,8 +7,10 @@
"dependencies": { "dependencies": {
"direct": { "direct": {
"NoRedInk/elm-json-decode-pipeline": "1.0.0", "NoRedInk/elm-json-decode-pipeline": "1.0.0",
"andrewMacmurray/elm-delay": "4.0.0",
"elm/browser": "1.0.2", "elm/browser": "1.0.2",
"elm/core": "1.0.5", "elm/core": "1.0.5",
"elm/file": "1.0.5",
"elm/html": "1.0.0", "elm/html": "1.0.0",
"elm/http": "2.0.0", "elm/http": "2.0.0",
"elm/json": "1.1.3", "elm/json": "1.1.3",
@@ -17,7 +19,6 @@
}, },
"indirect": { "indirect": {
"elm/bytes": "1.0.8", "elm/bytes": "1.0.8",
"elm/file": "1.0.5",
"elm/time": "1.0.0", "elm/time": "1.0.0",
"elm/virtual-dom": "1.0.2" "elm/virtual-dom": "1.0.2"
} }

File diff suppressed because it is too large Load Diff

View File

@@ -2,15 +2,13 @@ module Files exposing (..)
import Debug exposing (toString) import Debug exposing (toString)
import Element exposing (..) import Element exposing (..)
import Element.Background as Background
import Element.Border as Border import Element.Border as Border
import Element.Font as Font import Element.Font as Font
import Element.Input as Input
import Element.Region as Region import Element.Region as Region
import Globals exposing (apiFiles)
import Http import Http
import Json.Decode exposing (Decoder, int, list, string, succeed) import Json.Decode exposing (Decoder, int, list, string, succeed)
import Json.Decode.Pipeline exposing (required) import Json.Decode.Pipeline exposing (required)
import String exposing (left)
type alias File = type alias File =
@@ -100,7 +98,7 @@ update msg model =
queryFiles : Cmd Msg queryFiles : Cmd Msg
queryFiles = queryFiles =
Http.get Http.get
{ url = "http://127.0.0.1/api/files" { url = apiFiles
, expect = Http.expectJson QueryRequestResult queueDecoder , expect = Http.expectJson QueryRequestResult queueDecoder
} }

View File

@@ -0,0 +1,21 @@
module Globals exposing (..)
api : String
api =
"http://127.0.0.1/api/"
apiEndpoint : String -> String
apiEndpoint path =
String.concat [ api, path ]
apiStatus : String -> String
apiStatus id = String.concat [apiEndpoint "status/", id]
apiDownloadFile : String -> String
apiDownloadFile id = String.concat [apiEndpoint "file/", id]
apiFiles : String
apiFiles = apiEndpoint "files"

View File

@@ -8,7 +8,8 @@ import Element.Region as Region
import Files import Files
import Html exposing (Html) import Html exposing (Html)
import MainPage import MainPage
import Route exposing (Route) import Route exposing (Route(..))
import Status
import Url exposing (Url) import Url exposing (Url)
@@ -23,12 +24,7 @@ type Page
= NotFound = NotFound
| Home MainPage.Model | Home MainPage.Model
| FilesPage Files.Model | FilesPage Files.Model
| StatusPage Status.Model
--initModel : Model
--initModel =
-- PageMain MainPage.initModel
init : () -> Url -> Nav.Key -> ( Model, Cmd Msg ) init : () -> Url -> Nav.Key -> ( Model, Cmd Msg )
@@ -64,6 +60,13 @@ initCurrentPage ( model, existingCmds ) =
Files.init Files.init
in in
( FilesPage pageModel, Cmd.map FilesPageMsg pageCmd ) ( FilesPage pageModel, Cmd.map FilesPageMsg pageCmd )
Route.Status file ->
let
( pageModel, pageCmd ) =
Status.init file
in
( StatusPage pageModel, Cmd.map StatusPageMsg pageCmd )
in in
( { model | page = currentPage } ( { model | page = currentPage }
, Cmd.batch [ existingCmds, mappedPageCmds ] , Cmd.batch [ existingCmds, mappedPageCmds ]
@@ -79,6 +82,7 @@ type Msg
| UrlChanged Url | UrlChanged Url
| HomePageMsg MainPage.Msg | HomePageMsg MainPage.Msg
| FilesPageMsg Files.Msg | FilesPageMsg Files.Msg
| StatusPageMsg Status.Msg
@@ -91,7 +95,7 @@ update msg model =
( HomePageMsg subMsg, Home pageModel ) -> ( HomePageMsg subMsg, Home pageModel ) ->
let let
( updatePageModel, updateCmd ) = ( updatePageModel, updateCmd ) =
MainPage.update subMsg pageModel MainPage.update model.navKey subMsg pageModel
in in
( { model | page = Home updatePageModel } ( { model | page = Home updatePageModel }
, Cmd.map HomePageMsg updateCmd , Cmd.map HomePageMsg updateCmd
@@ -106,6 +110,15 @@ update msg model =
, Cmd.map FilesPageMsg updateCmd , Cmd.map FilesPageMsg updateCmd
) )
( StatusPageMsg subMsg, StatusPage pageModel ) ->
let
( updatePageModel, updateCmd ) =
Status.update subMsg pageModel
in
( { model | page = StatusPage updatePageModel }
, Cmd.map StatusPageMsg updateCmd
)
( LinkClicked urlRequest, _ ) -> ( LinkClicked urlRequest, _ ) ->
case urlRequest of case urlRequest of
Browser.Internal url -> Browser.Internal url ->
@@ -141,6 +154,9 @@ view model =
FilesPage pageModel -> FilesPage pageModel ->
Files.view pageModel |> Element.map FilesPageMsg Files.view pageModel |> Element.map FilesPageMsg
StatusPage pageModel ->
Status.view pageModel |> Element.map StatusPageMsg
_ -> _ ->
el el
[ Region.heading 1 [ Region.heading 1

View File

@@ -1,5 +1,6 @@
module MainPage exposing (Model, Msg, initModel, update, view) module MainPage exposing (Model, Msg, initModel, update, view)
import Browser.Navigation as Nav
import Color exposing (..) import Color exposing (..)
import Element exposing (..) import Element exposing (..)
import Element.Background as Background import Element.Background as Background
@@ -7,13 +8,11 @@ import Element.Border as Border
import Element.Font as Font import Element.Font as Font
import Element.Input as Input import Element.Input as Input
import Element.Region as Region import Element.Region as Region
import Html exposing (form)
import Http import Http
import Json.Decode exposing (Decoder, int, string, succeed) import Json.Decode exposing (Decoder, int, string, succeed)
import Json.Decode.Pipeline exposing (required) import Json.Decode.Pipeline exposing (required)
import Json.Encode as Encode import Json.Encode as Encode
import List exposing (map3)
import Tuple exposing (mapBoth)
import Html exposing (form)
type alias Form = type alias Form =
@@ -53,13 +52,21 @@ type AudioFormats
| AAC | AAC
| WAV | WAV
audioFormatName : AudioFormats -> String audioFormatName : AudioFormats -> String
audioFormatName format = audioFormatName format =
case format of case format of
MP3 -> "mp3" MP3 ->
OPUS -> "opus" "mp3"
AAC -> "aac"
WAV -> "wav" OPUS ->
"opus"
AAC ->
"aac"
WAV ->
"wav"
type VideoFormats type VideoFormats
@@ -68,38 +75,54 @@ type VideoFormats
| OGG | OGG
| WEBM | WEBM
videoFormatName : VideoFormats -> String videoFormatName : VideoFormats -> String
videoFormatName format = videoFormatName format =
case format of case format of
FLV -> "flv" FLV ->
MP4 -> "mp4" "flv"
OGG -> "ogg"
WEBM -> "wav" MP4 ->
"mp4"
OGG ->
"ogg"
WEBM ->
"webm"
type Format type Format
= Auto = Auto
| AudioFormat AudioFormats | AudioFormat AudioFormats
| VideoFormat VideoFormats | VideoFormat VideoFormats
formatName : Format -> String formatName : Format -> String
formatName format = formatName format =
case format of case format of
Auto -> "Auto" Auto ->
AudioFormat audio -> audioFormatName audio "Auto"
VideoFormat video -> videoFormatName video
AudioFormat audio ->
audioFormatName audio
VideoFormat video ->
videoFormatName video
-- -- Request -- -- -- -- Request -- --
type alias PostFormResponse = type alias PostFormResponse =
{ id : Int, status : String } { id : String, status : String }
formDecoder : Decoder PostFormResponse formDecoder : Decoder PostFormResponse
formDecoder = formDecoder =
succeed PostFormResponse succeed PostFormResponse
|> required "id" int |> required "file_id" string
|> required "status" string |> required "status" string
@@ -115,8 +138,9 @@ formPostEncoder form =
[] []
, if form.format == Auto then , if form.format == Auto then
[] []
else
[( "format", Encode.string <| formatName form.format )] else
[ ( "format", Encode.string <| formatName form.format ) ]
] ]
@@ -124,8 +148,8 @@ formPostEncoder form =
-- -- Update -- -- -- -- Update -- --
update : Msg -> Model -> ( Model, Cmd Msg ) update : Nav.Key -> Msg -> Model -> ( Model, Cmd Msg )
update msg model = update navKey msg model =
case msg of case msg of
UpdateForm new_form -> UpdateForm new_form ->
( { model | form = new_form }, Cmd.none ) ( { model | form = new_form }, Cmd.none )
@@ -151,13 +175,20 @@ update msg model =
( { model | form = new_form }, Cmd.none ) ( { model | form = new_form }, Cmd.none )
PostForm form -> PostForm form ->
( model if not (String.isEmpty form.url) then
, Http.post ( model
{ url = "http://127.0.0.1/api/add" , Http.post
, body = Http.jsonBody (formPostEncoder form) { url = "http://127.0.0.1/api/add"
, expect = Http.expectJson PostResult formDecoder , body = Http.jsonBody (formPostEncoder form)
} , expect = Http.expectJson PostResult formDecoder
) }
)
else
( model, Cmd.none )
PostResult (Ok data) ->
( model, Nav.pushUrl navKey <| String.concat [ "/status/", data.id ] )
_ -> _ ->
( model, Cmd.none ) ( model, Cmd.none )
@@ -254,6 +285,7 @@ formatButton position label state =
, Border.roundEach corners , Border.roundEach corners
, Border.widthEach borders , Border.widthEach borders
, Border.color color.blue , Border.color color.blue
, width (px 80)
, Background.color <| , Background.color <|
if state == Input.Selected then if state == Input.Selected then
color.lightBlue color.lightBlue
@@ -269,6 +301,7 @@ formatButton position label state =
formatOption format position = formatOption format position =
Input.optionWith format <| formatButton position <| formatName format Input.optionWith format <| formatButton position <| formatName format
view : Model -> Element Msg view : Model -> Element Msg
view model = view model =
Element.column Element.column
@@ -287,15 +320,6 @@ view model =
(text "localTube") (text "localTube")
, Input.text , Input.text
[ spacing 12 [ spacing 12
, below
(el
[ Font.color color.red
, Font.size 14
, alignRight
, moveDown 6
]
(text "This one is wrong")
)
] ]
{ text = model.form.url { text = model.form.url
, placeholder = Just (Input.placeholder [] (text "http://youtube.com")) , placeholder = Just (Input.placeholder [] (text "http://youtube.com"))
@@ -335,7 +359,7 @@ view model =
[ formatOption (VideoFormat FLV) Mid [ formatOption (VideoFormat FLV) Mid
, formatOption (VideoFormat MP4) Mid , formatOption (VideoFormat MP4) Mid
, formatOption (VideoFormat OGG) Mid , formatOption (VideoFormat OGG) Mid
, formatOption (VideoFormat WEBM)Last , formatOption (VideoFormat WEBM) Last
] ]
] ]
} }

View File

@@ -8,6 +8,7 @@ type Route
= NotFound = NotFound
| Home | Home
| Files | Files
| Status String
parseUrl : Url -> Route parseUrl : Url -> Route
@@ -25,4 +26,5 @@ matchRoute =
oneOf oneOf
[ map Home top [ map Home top
, map Files (s "files") , map Files (s "files")
, map Status (s "status" </> string)
] ]

View File

@@ -0,0 +1,232 @@
module Status exposing (Model, Msg, init, queryStatus, update, view)
import Color exposing (color)
import Debug exposing (toString)
import Delay
import Element exposing (..)
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Element.Input as Input
import File.Download exposing (url)
import Files exposing (Msg(..))
import Globals exposing (apiDownloadFile, apiStatus)
import Http
import Json.Decode exposing (Decoder, int, list, string, succeed)
import Json.Decode.Pipeline exposing (optional, required)
import Route exposing (Route(..))
import String exposing (right)
import Task
-- Messages
type Msg
= Reload
| StatusRequestResult (Result Http.Error StatusResponse)
| Download String
-- Model
type alias Model =
{ file_id : String
, status : Maybe Status
}
init : String -> ( Model, Cmd Msg )
init file_id =
( { file_id = file_id, status = Nothing }, send Reload )
-- Request
type alias File =
{ format : String
, filename : String
, timestamp : Int
}
type alias Status =
{ file : Maybe File
, name : String
, source : String
, status : String
, status_id : Int
, format : String
, timestamp : Int
, id : String
}
type alias StatusResponse =
{ status : Status }
fileDecoder : Decoder File
fileDecoder =
succeed File
|> required "format" string
|> required "name" string
|> required "timestamp" int
statusDecoder : Decoder Status
statusDecoder =
succeed Status
|> optional "file" (Json.Decode.map Just fileDecoder) Nothing
|> required "name" string
|> required "source" string
|> required "status" string
|> required "status_id" int
|> required "task_file_type" string
|> required "timestamp" int
|> required "id" string
statusResponseDecoder : Decoder StatusResponse
statusResponseDecoder =
succeed StatusResponse
|> required "status" statusDecoder
queryStatus : String -> Cmd Msg
queryStatus id =
Http.get
{ url = apiStatus id
, expect = Http.expectJson StatusRequestResult statusResponseDecoder
}
-- Update
cardShadow : Attr decorative msg
cardShadow =
Border.shadow
{ offset = ( 0, 4 )
, size = 4
, blur = 8
, color = rgba 0 0 0 0.2
}
cardShadowOver : Attr decorative msg
cardShadowOver =
Border.shadow
{ offset = ( 0, 4 )
, size = 8
, blur = 16
, color = rgba 0 0 0 0.2
}
card : List (Attribute msg) -> Element msg -> Element msg
card attr element =
el
(List.concat
[ attr
, [ Border.rounded 15, cardShadow, mouseOver [ cardShadowOver ], padding 20 ]
]
)
element
innerCard : List (Attribute msg) -> Element msg -> Element msg
innerCard attr element =
el
(List.concat
[ attr
, [ Border.roundEach { topLeft = 15, topRight = 15, bottomLeft = 15, bottomRight = 15 }, padding 5 ]
]
)
element
send : msg -> Cmd msg
send msg =
Task.succeed msg
|> Task.perform identity
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Reload ->
( model, queryStatus model.file_id )
StatusRequestResult (Ok res) ->
( { model | status = Just res.status }, Delay.after 1000 Reload )
StatusRequestResult _ ->
( { model | status = Nothing }, Cmd.none )
Download file_id ->
( model, url <| apiDownloadFile file_id )
-- View
--Just (Download.
statusView : Status -> Element Msg
statusView status =
card [ width fill, height shrink ]
(Element.column
[ width fill ]
[ row [ width fill, height <| px 40 ]
[ el [ Font.size 28, alignLeft ] <| text status.name
, el [ Font.size 28, alignRight ] <| text (String.concat [ "[", status.status, "]" ])
]
, link [ Font.bold, Font.underline, Font.color color.blue, Font.size 16, padding 2 ]
{ url = status.source, label = text status.source }
, el [ height <| px 10 ] none
, case status.file of
Just file ->
innerCard [ width fill, height shrink, Background.color color.grey ] <|
row [ width fill, spacing 20 ]
[ Input.button
[ Background.color color.blue
, Font.color color.white
, Border.color color.red
, paddingXY 32 16
, Border.rounded 15
]
{ onPress = Just <| Download status.id
, label = Element.text "Download"
}
, el [ Font.size 16 ] <| text file.filename
]
Nothing ->
Element.none
]
)
view : Model -> Element Msg
view model =
Element.column
[ width (px 800)
, height shrink
, centerX
, centerY
, spacing 36
]
[ case model.status of
Just status ->
statusView status
Nothing ->
Element.text "Test"
]