Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add custom uploader #99

Merged
merged 6 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ Obj/
.config/
/wwwroot/dist/

# JetBrains
.idea/

# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
Expand Down
128 changes: 128 additions & 0 deletions Classes/Uploaders/CustomUploader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using Json.Path;
using RePlays.Services;
using RePlays.Utils;

namespace RePlays.Uploaders
{
public class CustomUploader : BaseUploader
{
public override async Task<string> Upload(string id, string title, string file, string game)
{
using (var httpClient = new HttpClient())
{
httpClient.Timeout = Timeout.InfiniteTimeSpan; // sometimes, uploading can take long
var customSettings = SettingsService.Settings.uploadSettings.customUploaderSettings;

// add the url params to the url
var url = customSettings.urlparams.Aggregate(customSettings.url,
(current, param) =>
{
if (param.Key == "") return current;
return current + ((current.Contains("?") ? "&" : "?") + param.Key + "=" + param.Value);
});


// add the custom http headers
foreach (var header in customSettings.headers)
{
if(header.Key == "") continue;
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}

switch (customSettings.responseType)
{
case "JSON":
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
break;
// case "XML":
// httpClient.DefaultRequestHeaders.Accept.Add(
// new MediaTypeWithQualityHeaderValue("application/xml"));
// break;
case "TEXT":
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
break;
default:
throw new ArgumentOutOfRangeException();
}

using (var formDataContent = new MultipartFormDataContent())
{
var titleContent = new StringContent(title);
var fileContent = new ProgressableStreamContent(new StreamContent(File.OpenRead(file)), 4096,
(sent, total) =>
{
WebMessage.DisplayToast(id, title, "Upload", "none", (long)((float)sent / total * 100),
100);
}
);
fileContent.Headers.ContentType = new MediaTypeHeaderValue("video/mp4");
formDataContent.Add(fileContent, "file", "video.mp4");
formDataContent.Add(titleContent, "title");

// send request using the correct method
HttpResponseMessage response;
switch (customSettings.method)
{
case "POST":
response = await httpClient.PostAsync(url, formDataContent);
break;
case "PUT":
response = await httpClient.PutAsync(url, formDataContent);
break;
case "PATCH":
response = await httpClient.PatchAsync(url, formDataContent);
break;
default:
throw new ArgumentOutOfRangeException();
}

// parse the response according the the response type and the responsepath setting using JSONpath or XMLPath
var content = response.Content.ReadAsStringAsync().Result;
Logger.WriteLine(response.StatusCode.ToString() + " " + content);

string result;
switch (customSettings.responseType)
{
case "JSON":
result = CustomUploaderJsonPath.Get(content, customSettings.responsePath);
break;
// case "XML":
// result = XmlPath.Get(content, customSettings.responsePath);
// break;
case "TEXT":
result = content;
break;
default:
throw new ArgumentOutOfRangeException();
}

return result;
}
}
}

private static class CustomUploaderJsonPath
{
public static string Get(string json, string jsonPath)
{
PathResult results = null;
if (JsonPath.TryParse(jsonPath, out var path))
{
results = path.Evaluate(JsonNode.Parse(json));
}

var jsonNode = results?.Matches?[0].Value;
return jsonNode != null ? jsonNode?.ToString() : string.Empty;
}
}
}
}
28 changes: 28 additions & 0 deletions Classes/Utils/JSONObjects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,34 @@ public class LocalFolderSettings {

private LocalFolderSettings _localFolderSettings = new();
public LocalFolderSettings localFolderSettings { get { return _localFolderSettings; } set { _localFolderSettings = value; } }

public class CustomUploaderSettings
{

private string _method = "POST";
public string method { get { return _method; } set { _method = value; } }

private string _url = "";
public string url { get { return _url; } set { _url = value; } }

private KeyValuePair<string, string>[] _headers = Array.Empty<KeyValuePair<string, string>>();
public KeyValuePair<string, string>[] headers { get { return _headers; } set { _headers = value; } }

private KeyValuePair<string, string>[] _urlparams = Array.Empty<KeyValuePair<string, string>>();
public KeyValuePair<string, string>[] urlparams { get { return _urlparams; } set { _urlparams = value; } }

private string _responseType = "TEXT";
public string responseType { get { return _responseType; } set { _responseType = value; } }

private string _responsePath = "";
public string responsePath { get { return _responsePath; } set { _responsePath = value; } }


}
private CustomUploaderSettings _customUploaderSettings = new();
public CustomUploaderSettings customUploaderSettings { get { return _customUploaderSettings; } set { _customUploaderSettings = value; } }


}

public class DetectionSettings {
Expand Down
17 changes: 10 additions & 7 deletions ClientApp/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ClientApp/src/components/UploadModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const UploadModal: React.FC<Props> = ({video, game, thumb}) => {
<option value="Streamable">Streamable</option>
{/* <option value="Youtube">Youtube</option> */}
<option value="LocalFolder">Local Folder</option>
<option value="Custom">Custom</option>
</select>
</div>
</div>
Expand Down
9 changes: 9 additions & 0 deletions ClientApp/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,15 @@ declare global {
localFolderSettings: {
dir: string,
},
customUploaderSettings: {
url: string,
method: string,
headers: {Key: string, Value: string}[],
urlparams: {Key: string, Value: string}[],
responseType: string,
responsePath: string,
}

}
interface StorageSettings {
videoSaveDir: string,
Expand Down
126 changes: 124 additions & 2 deletions ClientApp/src/pages/Settings/Upload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ export const Upload: React.FC<Props> = ({settings, updateSettings}) => {
<div onClick={() => setSubPage("LocalFolder")} className="cursor-pointer flex items-center block py-2 px-4 rounded transition duration-100 hover:bg-blue-700 hover:text-white text-base font-medium">
Local Folder
</div>
<div onClick={() => setSubPage("Custom")} className="cursor-pointer flex items-center block py-2 px-4 rounded transition duration-100 hover:bg-blue-700 hover:text-white text-base font-medium">
Custom
</div>
</div>
<div className="flex-auto overflow-auto h-full w-full p-7 pt-0">
{(subPage === 'RePlays' ? <RePlays settings={settings} updateSettings={updateSettings} /> : "")}
{(subPage === 'Streamable' ? <Streamable settings={settings} updateSettings={updateSettings}/> : "")}
{(subPage === 'LocalFolder' ? <LocalFolder settings={settings} updateSettings={updateSettings}/> : "")}
{(subPage === 'Custom' ? <Custom settings={settings} updateSettings={updateSettings}/> : "")}
</div>
</div>
</div>
Expand Down Expand Up @@ -101,8 +105,6 @@ const Streamable: React.FC<Props> = ({settings, updateSettings}) => {
)
}



const Youtube: React.FC<Props> = ({settings, updateSettings}) => {
return (
<div className="flex flex-col gap-2 font-medium text-base pb-7">
Expand All @@ -126,4 +128,124 @@ const LocalFolder: React.FC<Props> = ({settings, updateSettings}) => {
)
}

const Custom: React.FC<Props> = ({settings, updateSettings}) => {
return (
<div className="flex flex-col gap-2 font-medium text-base pb-7">
<div className="font-semibold">Custom</div>
<div className="flex flex-col">
URL
<input className={`inline-flex align-middle justify-center w-64 h-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-700 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800`}
type="email" defaultValue={settings === undefined ? "" : settings.customUploaderSettings.url} onBlur={(e) => {
if(settings !== undefined)
settings.customUploaderSettings.url = e.target.value;
updateSettings();
}}/>
</div>
<div className="flex flex-col">
Method
<select className={`inline-flex align-middle justify-center w-64 h-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-700 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800`}
defaultValue={settings === undefined ? "" : settings.customUploaderSettings.method} onBlur={(e) => {
if(settings !== undefined)
settings.customUploaderSettings.method = e.target.value;
updateSettings();
}}>
<option value="POST">POST</option>
<option value="GET">GET</option>
<option value="PUT">PUT</option>
<option value="PATCH">PATCH</option>
</select>
</div>
<div className="flex flex-col">
Headers
{settings === undefined ? <></> : settings.customUploaderSettings.headers.map((header, index) => {
return (
<div className="flex flex-row gap-2">
<input className={`inline-flex align-middle justify-center w-64 h-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-700 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800`}
type="text" defaultValue={header.Key} onBlur={(e) => {
if(settings !== undefined)
settings.customUploaderSettings.headers[index].Key = e.target.value;
updateSettings();
}}/>
<input className={`inline-flex align-middle justify-center w-64 h-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-700 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800`}
type="text" defaultValue={header.Value} onBlur={(e) => {
if(settings !== undefined)
settings.customUploaderSettings.headers[index].Value = e.target.value;
updateSettings();
}}/>
<Button text="Remove" width={"auto"} onClick={(e) => {
if(settings !== undefined)
settings.customUploaderSettings.headers.splice(index, 1);
updateSettings();
}}/>
</div>
)
})
}
<Button text="Add Header" width={"auto"} onClick={(e) => {
if(settings !== undefined)
settings.customUploaderSettings.headers.push({Key: "", Value: ""});
updateSettings();
}}/>
</div>
<div className="flex flex-col">
URL Parameters
{settings === undefined ? <></> : settings.customUploaderSettings.urlparams.map((params, index) => {
return (
<div className="flex flex-row gap-2">
<input className={`inline-flex align-middle justify-center w-64 h-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-700 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800`}
type="text" defaultValue={params.Key} onBlur={(e) => {
if(settings !== undefined)
settings.customUploaderSettings.urlparams[index].Key = e.target.value;
updateSettings();
}}/>
<input className={`inline-flex align-middle justify-center w-64 h-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-700 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800`}
type="text" defaultValue={params.Value} onBlur={(e) => {
if(settings !== undefined)
settings.customUploaderSettings.urlparams[index].Value = e.target.value;
updateSettings();
}}/>
<Button text="Remove" width={"auto"} onClick={(e) => {
if(settings !== undefined)
settings.customUploaderSettings.urlparams.splice(index, 1);
updateSettings();
}}/>
</div>
)
})
}
<Button text="Add Header" width={"auto"} onClick={(e) => {
if(settings !== undefined)
settings.customUploaderSettings.urlparams.push({Key: "", Value: ""});
updateSettings();
}}/>
</div>
<div className="flex flex-col">
Response Type
<select className={`inline-flex align-middle justify-center w-64 h-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-700 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800`}
defaultValue={settings === undefined ? "" : settings.customUploaderSettings.responseType} onBlur={(e) => {
if(settings !== undefined)
settings.customUploaderSettings.responseType = e.target.value;
updateSettings();
}}>
<option value="JSON">JSON</option>
<option value="TEXT">Text</option>
{/*<option value="XML">XML</option>*/}
</select>
</div>
<div className="flex flex-col">
Response Path
<input className={`inline-flex align-middle justify-center w-64 h-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-700 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800`}
type="text" defaultValue={settings === undefined ? "" : settings.customUploaderSettings.responsePath} onBlur={(e) => {
if(settings !== undefined)
settings.customUploaderSettings.responsePath = e.target.value;
updateSettings();
}}/>
<p className="text-xs text-gray-500">The url to the video using <a href="https://jsonpath.com/" target="_blank" rel="noreferrer">JSONPath</a>
{/*or <a href="http://xpather.com/" target="_blank" rel="noreferrer">XPath</a>*/}
</p>
</div>
</div>
)
}

export default Upload;
1 change: 1 addition & 0 deletions RePlays.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

<ItemGroup>
<PackageReference Include="Clowd.Squirrel" Version="2.8.40" />
<PackageReference Include="JsonPath.Net" Version="0.5.2" />
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.0.1" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1054.31" />
<PackageReference Include="NuGet.CommandLine" Version="6.0.0">
Expand Down