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 <functional>
#include <soci/boost-optional.h>
#include <ctre.hpp>
#include <fmt/chrono.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/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) {});
@@ -205,6 +209,70 @@ void Api::file(const httplib::Request& rq, httplib::Response& rs)
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)
{
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 file(httplib::Request const& rq, httplib::Response& rs);
void status(httplib::Request const& rq, 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)
{
return os << fmt::format("Task [{}][{}] url: {} ",
file.id,
return os << fmt::format("Task [{}] url: {} Audio: {} Format: {}",
magic_enum::enum_name(file.status),
file.url);
file.url,
file.audio_only,
file.format.value_or("Auto"));
}
namespace soci

View File

@@ -2,9 +2,13 @@ POST 127.0.0.1:80/api/add
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": {
"direct": {
"NoRedInk/elm-json-decode-pipeline": "1.0.0",
"andrewMacmurray/elm-delay": "4.0.0",
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/file": "1.0.5",
"elm/html": "1.0.0",
"elm/http": "2.0.0",
"elm/json": "1.1.3",
@@ -17,7 +19,6 @@
},
"indirect": {
"elm/bytes": "1.0.8",
"elm/file": "1.0.5",
"elm/time": "1.0.0",
"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 Element exposing (..)
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Element.Input as Input
import Element.Region as Region
import Globals exposing (apiFiles)
import Http
import Json.Decode exposing (Decoder, int, list, string, succeed)
import Json.Decode.Pipeline exposing (required)
import String exposing (left)
type alias File =
@@ -100,7 +98,7 @@ update msg model =
queryFiles : Cmd Msg
queryFiles =
Http.get
{ url = "http://127.0.0.1/api/files"
{ url = apiFiles
, 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 Html exposing (Html)
import MainPage
import Route exposing (Route)
import Route exposing (Route(..))
import Status
import Url exposing (Url)
@@ -23,12 +24,7 @@ type Page
= NotFound
| Home MainPage.Model
| FilesPage Files.Model
--initModel : Model
--initModel =
-- PageMain MainPage.initModel
| StatusPage Status.Model
init : () -> Url -> Nav.Key -> ( Model, Cmd Msg )
@@ -64,6 +60,13 @@ initCurrentPage ( model, existingCmds ) =
Files.init
in
( FilesPage pageModel, Cmd.map FilesPageMsg pageCmd )
Route.Status file ->
let
( pageModel, pageCmd ) =
Status.init file
in
( StatusPage pageModel, Cmd.map StatusPageMsg pageCmd )
in
( { model | page = currentPage }
, Cmd.batch [ existingCmds, mappedPageCmds ]
@@ -79,6 +82,7 @@ type Msg
| UrlChanged Url
| HomePageMsg MainPage.Msg
| FilesPageMsg Files.Msg
| StatusPageMsg Status.Msg
@@ -91,7 +95,7 @@ update msg model =
( HomePageMsg subMsg, Home pageModel ) ->
let
( updatePageModel, updateCmd ) =
MainPage.update subMsg pageModel
MainPage.update model.navKey subMsg pageModel
in
( { model | page = Home updatePageModel }
, Cmd.map HomePageMsg updateCmd
@@ -106,6 +110,15 @@ update msg model =
, 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, _ ) ->
case urlRequest of
Browser.Internal url ->
@@ -141,6 +154,9 @@ view model =
FilesPage pageModel ->
Files.view pageModel |> Element.map FilesPageMsg
StatusPage pageModel ->
Status.view pageModel |> Element.map StatusPageMsg
_ ->
el
[ Region.heading 1

View File

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

View File

@@ -8,6 +8,7 @@ type Route
= NotFound
| Home
| Files
| Status String
parseUrl : Url -> Route
@@ -25,4 +26,5 @@ matchRoute =
oneOf
[ map Home top
, 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"
]