Skip to content

Commit

Permalink
Merge pull request #5 from skylab-tech/version-0.0.7
Browse files Browse the repository at this point in the history
Add ability to download photo with output path + photo name, migrate …
  • Loading branch information
kev-le authored Feb 29, 2024
2 parents c0cbfc5 + d6c2938 commit c18438e
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 63 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ Erorred photos: [4.jpg]
OR

```dotnet
api.DownloadPhoto(photoId, "/output/folder/path");
api.DownloadPhoto(photoId, "/path/to/photo.jpg"); # accepts full path OR
api.DownloadPhoto(photoId, "/path/to/output/folder"); # accepts directory
```

#### Delete photo
Expand Down
12 changes: 0 additions & 12 deletions StudioClient.Tests/Job.Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,4 @@ public async Task Call_CancelJob_ReturnsJob()

Assert.NotNull(cancelledJob);
}

[Fact]
public async Task Call_JobsInFront_ReturnsJob()
{
Guid randomUuid = Guid.NewGuid();
var jobName = $"test-job-{randomUuid}";
dynamic job = await apiClient.CreateJob(new { name = jobName, profile_id = profile?.id.Value });

dynamic jobsInFrontMessage = await apiClient.JobsInFront(job.id.Value);

Assert.NotNull(jobsInFrontMessage.message);
}
}
28 changes: 16 additions & 12 deletions StudioClient/DownloadHelper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Newtonsoft.Json.Linq;
using NetVips;
using RestSharp;


namespace SkylabStudio
Expand Down Expand Up @@ -31,8 +32,6 @@ public partial class StudioClient
{
private async Task<List<Image>?> DownloadBgImages(dynamic profile)
{
var httpClient = new HttpClient();

List<Image> tempBgs = new List<Image>();
List<JToken> bgPhotos = ((JArray) profile!.photos).Where(photo => photo["jobId"] != null).ToList();

Expand All @@ -54,10 +53,13 @@ public partial class StudioClient

try
{
using (HttpClient httpClient = new HttpClient())
using (RestClient httpClient = new RestClient())
{
RestRequest request = new RestRequest(imageUrl, Method.Get);
// Download the image into a byte array
byte[] imageBuffer = await httpClient.GetByteArrayAsync(imageUrl);
RestResponse response = await httpClient.ExecuteAsync(request);

byte[] imageBuffer = response.RawBytes ?? Array.Empty<byte>();

return imageBuffer;
}
Expand Down Expand Up @@ -118,8 +120,6 @@ public async Task<DownloadAllPhotosResult> DownloadAllPhotos(JArray photosList,
bgs = await DownloadBgImages(profile);
}

var httpClient = new HttpClient();

List<string> photoIds = photosList.Select(photo => photo?["id"]?.ToString() ?? "").ToList() ?? new List<string>();

// Use a semaphore to control access to the download operation
Expand Down Expand Up @@ -165,18 +165,22 @@ public async Task<DownloadAllPhotosResult> DownloadAllPhotos(JArray photosList,
}
public async Task<Tuple<string,bool>> DownloadPhoto(long photoId, string outputPath, dynamic? profile = null, PhotoOptions? options = null, SemaphoreSlim? semaphore = null)
{
string fileName = "";

if (!Directory.Exists(outputPath))
{
throw new Exception("Invalid output path");
// Must be a file path - separate outputPath and fileName
fileName = Path.GetFileName(outputPath);
outputPath = Path.GetDirectoryName(outputPath) ?? "";
}

dynamic photo = await GetPhoto(photoId);
long profileId = photo.job.profileId;

string fileName = photo.name.Value;
if (semaphore != null) await semaphore.WaitAsync(); // Wait until a slot is available

try {
if (semaphore != null) await semaphore.WaitAsync(); // Wait until a slot is available
var photo = await GetPhoto(photoId);
long profileId = photo.job.profileId;

if (fileName.Length <= 0) fileName = photo.name.Value;

if (profile == null) {
profile = await GetProfile(profileId);
Expand Down
18 changes: 9 additions & 9 deletions StudioClient/Job.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,47 @@ public partial class StudioClient
{
public async Task<dynamic> ListJobs()
{
return await Request("jobs", HttpMethod.Get);
return await Request("jobs", RestSharp.Method.Get);
}

public async Task<dynamic> CreateJob(object payload)
{
return await Request("jobs", HttpMethod.Post, payload);
return await Request("jobs", RestSharp.Method.Post, payload);
}

public async Task<dynamic> GetJob(long jobId)
{
return await Request($"jobs/{jobId}", HttpMethod.Get);
return await Request($"jobs/{jobId}", RestSharp.Method.Get);
}

public async Task<dynamic> GetJobByName(string jobName)
{
return await Request($"jobs/find_by_name/?name={jobName}", HttpMethod.Get);
return await Request($"jobs/find_by_name/?name={jobName}", RestSharp.Method.Get);
}

public async Task<dynamic> UpdateJob(long jobId, object payload)
{
return await Request($"jobs/{jobId}", HttpMethod.Put, payload);
return await Request($"jobs/{jobId}", RestSharp.Method.Put, payload);
}

public async Task<dynamic> QueueJob(long jobId, object payload)
{
return await Request($"jobs/{jobId}/queue", HttpMethod.Post, payload);
return await Request($"jobs/{jobId}/queue", RestSharp.Method.Post, payload);
}

public async Task<dynamic> CancelJob(long jobId)
{
return await Request($"jobs/{jobId}/cancel", HttpMethod.Post);
return await Request($"jobs/{jobId}/cancel", RestSharp.Method.Post);
}

public async Task<dynamic> DeleteJob(long jobId)
{
return await Request($"jobs/{jobId}", HttpMethod.Delete);
return await Request($"jobs/{jobId}", RestSharp.Method.Delete);
}

public async Task<dynamic> JobsInFront(long jobId)
{
return await Request($"jobs/{jobId}/jobs_in_front", HttpMethod.Get);
return await Request($"jobs/{jobId}/jobs_in_front", RestSharp.Method.Get);
}
}
}
24 changes: 13 additions & 11 deletions StudioClient/Photo.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Security.Cryptography;
using Newtonsoft.Json.Linq;
using RestSharp;


namespace SkylabStudio
Expand All @@ -11,24 +12,24 @@ public partial class StudioClient

public async Task<dynamic> CreatePhoto(object payload)
{
return await Request("photos", HttpMethod.Post, payload);
return await Request("photos", Method.Post, payload);
}

public async Task<dynamic> GetPhoto(long photoId)
{
return await Request($"photos/{photoId}", HttpMethod.Get);
return await Request($"photos/{photoId}", Method.Get);
}

public async Task<dynamic> DeletePhoto(long photoId)
{
return await Request($"photos/{photoId}", HttpMethod.Delete);
return await Request($"photos/{photoId}", Method.Delete);
}

public async Task<dynamic> GetUploadUrl(long photoId, string md5 = "", bool useCacheUpload = false)
{
string queryParams = $"use_cache_upload={useCacheUpload.ToString().ToLower()}&photo_id={photoId}&content_md5={md5}";

return await Request($"photos/upload_url?{queryParams}", HttpMethod.Get);
return await Request($"photos/upload_url?{queryParams}", Method.Get);
}

public async Task<dynamic> UploadJobPhoto(string photoPath, long jobId)
Expand Down Expand Up @@ -74,16 +75,17 @@ private async Task<dynamic> UploadPhoto(string photoPath, string modelName, long
dynamic uploadObj = await GetUploadUrl(photo.id.Value, md5Base64);
string presignedUrl = uploadObj.url.Value;

using (var client = new HttpClient())
using (RestClient httpClient = new RestClient())
{
var content = new ByteArrayContent(photoData);
var request = new HttpRequestMessage(HttpMethod.Put, presignedUrl);
request.Content = content;
request.Content.Headers.ContentMD5 = MD5.Create().ComputeHash(photoData);
if (modelName == "job") request.Headers.Add("X-Amz-Tagging", "job=photo&api=true");
byte[] fileBytes = File.ReadAllBytes(photoPath);
RestRequest request = new RestRequest(presignedUrl, Method.Put);

request.AddParameter("application/octet-stream", fileBytes, ParameterType.RequestBody);
request.AddHeader("Content-MD5", Convert.ToBase64String(MD5.Create().ComputeHash(fileBytes)));
if (modelName == "job") request.AddHeader("X-Amz-Tagging", "job=photo&api=true");

// Upload image via PUT request to presigned url
HttpResponseMessage response = await client.SendAsync(request);
RestResponse response = await httpClient.ExecuteAsync(request);

if (!response.IsSuccessStatusCode)
{
Expand Down
10 changes: 5 additions & 5 deletions StudioClient/Profile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,27 @@ public partial class StudioClient
{
public async Task<dynamic> CreateProfile(object payload)
{
return await Request("profiles", HttpMethod.Post, payload);
return await Request("profiles", RestSharp.Method.Post, payload);
}

public async Task<dynamic> ListProfiles()
{
return await Request("profiles", HttpMethod.Get);
return await Request("profiles", RestSharp.Method.Get);
}

public async Task<dynamic> GetProfile(long profileId)
{
return await Request($"profiles/{profileId}", HttpMethod.Get);
return await Request($"profiles/{profileId}", RestSharp.Method.Get);
}

public async Task<dynamic> UpdateProfile(long profileId, object payload)
{
return await Request($"profiles/{profileId}", HttpMethod.Put, payload);
return await Request($"profiles/{profileId}", RestSharp.Method.Put, payload);
}

public async Task<dynamic> GetProfileBgs(long profileId)
{
return await Request($"profiles/{profileId}/bg_photos", HttpMethod.Get);
return await Request($"profiles/{profileId}/bg_photos", RestSharp.Method.Get);
}
}
}
26 changes: 14 additions & 12 deletions StudioClient/StudioClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
using RestSharp;


namespace SkylabStudio {
Expand All @@ -11,7 +12,7 @@ public class StudioOptions

public partial class StudioClient
{
private readonly HttpClient _httpClient;
private readonly RestClient _httpClient;
private readonly string _apiKey;
private readonly int _maxConcurrentDownloads = 5;

Expand All @@ -20,39 +21,40 @@ public StudioClient(string? apiKey = null, StudioOptions? options = null)
if (apiKey == null) throw new Exception("No API key provided");

string baseUrl = Environment.GetEnvironmentVariable("SKYLAB_API_URL") ?? "https://studio.skylabtech.ai";
_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri(baseUrl);
_httpClient = new RestClient(baseUrl);
_apiKey = apiKey;
_maxConcurrentDownloads = options?.MaxConcurrentDownloads ?? 5;
}

private async Task<dynamic> Request(string endpoint, HttpMethod httpMethod, object? payload = null)
private async Task<dynamic> Request(string endpoint, Method httpMethod, object? payload = null)
{
var apiEndpoint = $"api/public/v1/{endpoint}";
var request = new HttpRequestMessage(httpMethod, apiEndpoint);
RestRequest request = new RestRequest(apiEndpoint, httpMethod);
var headers = BuildRequestHeaders();

foreach (var header in headers)
{
request.Headers.Add(header.Key, header.Value);
request.AddHeader(header.Key, header.Value);
}

if (payload != null)
{
var jsonObj = JsonConvert.SerializeObject(payload);
request.Content = new StringContent(jsonObj, Encoding.UTF8, "application/json");
request.AddJsonBody(jsonObj);
}

try
{
// Send the request and get the response
HttpResponseMessage response = await _httpClient.SendAsync(request);
RestResponse response = await _httpClient.ExecuteAsync(request);

string responseContent = await response.Content.ReadAsStringAsync();
dynamic? jsonData = JsonConvert.DeserializeObject<dynamic>(responseContent);
if (jsonData != null) return jsonData;
string responseContent = response?.Content ?? "";
if (response?.IsSuccessStatusCode ?? false) {
dynamic? jsonData = JsonConvert.DeserializeObject<dynamic>(responseContent);
if (jsonData != null) return jsonData;
}

throw new Exception("Failed to get response from server.");
throw new Exception($"Failed to get successful response: {response?.Content}");
}
catch (Exception ex)
{
Expand Down
3 changes: 2 additions & 1 deletion StudioClient/StudioClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<PackageProjectUrl>https://github.com/skylab-tech/studio_client_dotnet</PackageProjectUrl>
<PackageIconUrl>https://avatars.githubusercontent.com/u/45469060</PackageIconUrl>
<PackageTags>studio skylab skylabtech api library .net nuget</PackageTags>
<TargetFrameworks>net5.0;net6.0;net7.0;netstandard2.1;netcoreapp3.0;netcoreapp3.1</TargetFrameworks>
<TargetFrameworks>net47;net471;net472;net48;net481;net5.0;net6.0;net7.0;netstandard2.1;netcoreapp3.1</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageReadmeFile>README.md</PackageReadmeFile>
Expand All @@ -24,6 +24,7 @@
<PackageReference Include="NetVips.Native" Version="8.15.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<None Include="..\README.md" Pack="true" PackagePath="\" />
<PackageReference Include="RestSharp" Version="110.2.0" />
</ItemGroup>

</Project>

0 comments on commit c18438e

Please sign in to comment.