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

Fixing Emoji.UploadLeaseImage, adding missing async methods #86

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
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
14 changes: 13 additions & 1 deletion src/Reddit.NET/Inputs/Emoji/EmojiAddInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,27 @@ public class EmojiAddInput
/// </summary>
public string s3_key { get; set; }

public bool mod_flair_only { get; set; }

public bool post_flair_allowed { get; set; }

public bool user_flair_allowed { get; set; }

/// <summary>
/// Data for emoji to be uploaded.
/// </summary>
/// <param name="name">Name of the emoji to be created. It can be alphanumeric without any special characters except '-' & '_' and cannot exceed 24 characters</param>
/// <param name="s3Key">S3 key of the uploaded image which can be obtained from the S3 url. This is of the form subreddit/hash_value</param>
public EmojiAddInput(string name, string s3Key)
/// <param name="modFlairOnly">If this emoji is exclusive to mods' flairs (or mod-assigned flairs).</param>
/// <param name="postFlairAllowed">If this emoji should be usable on post flairs.</param>
/// <param name="userFlairAllowed">If this emoji should be usable on user flairs.</param>
public EmojiAddInput(string name, string s3Key, bool modFlairOnly, bool postFlairAllowed, bool userFlairAllowed)
{
this.name = name;
s3_key = s3Key;
mod_flair_only = modFlairOnly;
post_flair_allowed = postFlairAllowed;
user_flair_allowed = userFlairAllowed;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;

namespace Reddit.Inputs
namespace Reddit.Inputs.Emoji
{
[Serializable]
public class ImageUploadInput
Expand All @@ -18,11 +18,11 @@ public class ImageUploadInput
/// <summary>
/// Data for image to be uploaded.
/// </summary>
/// <param name="filePath">name and extension of the image file e.g. image1.png</param>
/// <param name="fileName">name and extension of the image file e.g. image1.png</param>
/// <param name="mimeType">mime type of the image e.g. image/png</param>
public ImageUploadInput(string filePath, string mimeType)
public ImageUploadInput(string fileName, string mimeType)
{
filepath = filePath;
filepath = fileName;
mimetype = mimeType;
}
}
Expand Down
167 changes: 142 additions & 25 deletions src/Reddit.NET/Models/Emoji.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
using Newtonsoft.Json;
using Reddit.Inputs;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Newtonsoft.Json;
using RestSharp;
using Reddit.Inputs.Emoji;
using Reddit.Things;
using RestSharp;
using System;
using System.Linq;
using Reddit.Exceptions;

namespace Reddit.Models
{
public class Emoji : BaseModel
{
// Used for deserializing S3PostResponse from S3 emoji post. --MingweiSamuel
private static readonly XmlSerializer S3PostResponseXmlSerializer = new XmlSerializer(typeof(S3PostResponse));

internal override RestClient RestClient { get; set; }

public Emoji(string appId, string appSecret, string refreshToken, string accessToken, ref RestClient restClient, string deviceId = null)
Expand All @@ -24,11 +29,27 @@ public Emoji(string appId, string appSecret, string refreshToken, string accessT
/// <param name="subreddit">The subreddit with the emojis</param>
/// <param name="emojiAddInput">A valid EmojiAddInput instance</param>
/// <returns>(TODO - Untested)</returns>
// TODO returns {"json": {"errors": []}}
public object Add(string subreddit, EmojiAddInput emojiAddInput)
{
return SendRequest<object>("api/v1/" + subreddit + "/emoji.json", emojiAddInput, Method.POST);
}

// TODO - Needs testing.
/// <summary>
/// Add an emoji to the DB by posting a message on emoji_upload_q.
/// A job processor that listens on a queue uses the s3_key provided in the request to locate the image in S3 Temp Bucket and moves it to the PERM bucket.
/// It also adds it to the DB using name as the column and sr_fullname as the key and sends the status on the websocket URL that is provided as part of this response.
/// </summary>
/// <param name="subreddit">The subreddit with the emojis</param>
/// <param name="emojiAddInput">A valid EmojiAddInput instance</param>
/// <returns>(TODO - Untested)</returns>
// TODO returns {"json": {"errors": []}}
public async Task<object> AddAsync(string subreddit, EmojiAddInput emojiAddInput)
{
return await SendRequestAsync<object>("api/v1/" + subreddit + "/emoji.json", emojiAddInput, Method.POST);
}

// TODO - Needs testing.
/// <summary>
/// Delete a Subreddit emoji. Remove the emoji from Cassandra and purge the assets from S3 and the image resizing provider.
Expand All @@ -40,6 +61,18 @@ public object Delete(string subreddit, string emojiName)
{
return JsonConvert.DeserializeObject(ExecuteRequest("api/v1/" + subreddit + "/emoji/" + emojiName, Method.DELETE));
}

// TODO - Needs testing.
/// <summary>
/// Delete a Subreddit emoji. Remove the emoji from Cassandra and purge the assets from S3 and the image resizing provider.
/// </summary>
/// <param name="subreddit">The subreddit with the emojis</param>
/// <param name="emojiName">The name of the emoji to be deleted</param>
/// <returns>(TODO - Untested)</returns>
public async Task<object> DeleteAsync(string subreddit, string emojiName)
{
return JsonConvert.DeserializeObject(await ExecuteRequestAsync("api/v1/" + subreddit + "/emoji/" + emojiName, Method.DELETE));
}

/// <summary>
/// Acquire and return an upload lease to s3 temp bucket.
Expand All @@ -54,39 +87,113 @@ public S3UploadLeaseContainer AcquireLease(string subreddit, ImageUploadInput im
return SendRequest<S3UploadLeaseContainer>("api/v1/" + subreddit + "/emoji_asset_upload_s3.json", imageUploadInput, Method.POST);
}

// TODO - Can't get this to work. Action URL keeps returning 403. --Kris
// See: https://www.reddit.com/r/redditdev/comments/9s1pio/getting_aws_error_when_trying_to_upload_emoji/
// Update: Switching headers to parameters helped, but now it's complaining about the content-type on the image for some reason. --Kris
/// <summary>
/// Upload an Emoji.
/// Acquire and return an upload lease to s3 temp bucket.
/// The return value of this function is a json object containing credentials for uploading assets to S3 bucket, S3 url for upload request and the key to use for uploading.
/// Using this lease the client will upload the emoji image to S3 temp bucket (included as part of the S3 URL). This lease is used by S3 to verify that the upload is authorized.
/// </summary>
/// <param name="subreddit">The subreddit with the emojis</param>
/// <param name="imageUploadInput">A valid ImageUploadInput instance</param>
/// <returns>An S3 lease.</returns>
public async Task<S3UploadLeaseContainer> AcquireLeaseAsync(string subreddit, ImageUploadInput imageUploadInput)
{
return await SendRequestAsync<S3UploadLeaseContainer>("api/v1/" + subreddit + "/emoji_asset_upload_s3.json", imageUploadInput, Method.POST);
}

#region UploadLeaseImage
// TODO: write tests for these.

/// <summary>
/// Upload an Emoji to S3.
/// </summary>
/// <param name="s3">The data retrieved by AcquireLease.</param>
/// <param name="imageData">Raw image data.</param>
/// <param name="imageUploadInput">File name (with extension) and mime type.</param>
/// <returns>Decoded PostResponse including Key.</returns>
public S3PostResponse UploadLeaseImage(S3UploadLeaseContainer s3, byte[] imageData, ImageUploadInput imageUploadInput)
{
return HelperUploadLeaseImage(s3.S3UploadLease, new RestRequest(Method.POST)
.AddFile("file", imageData, imageUploadInput.filepath, imageUploadInput.mimetype));
}

/// <summary>
/// Upload an Emoji to S3.
/// </summary>
/// <param name="s3">The data retrieved by AcquireLease.</param>
/// <returns>(TODO - Untested)</returns>
public void UploadLeaseImage(byte[] imageData, S3UploadLeaseContainer s3)
/// <param name="imageData"></param>
/// <param name="imageUploadInput">File name (with extension) and mime type.</param>
/// <param name="contentLength">Optional length of imageData. Otherwise imageData.Length will be used.</param>
/// <returns>Decoded PostResponse including Key.</returns>
public S3PostResponse UploadLeaseImage(S3UploadLeaseContainer s3, Stream imageData, ImageUploadInput imageUploadInput, long? contentLength = null)
{
RestClient = new RestClient("https:" + s3.S3UploadLease.Action);
RestRequest restRequest = new RestRequest(Method.POST);
return HelperUploadLeaseImage(s3.S3UploadLease, new RestRequest(Method.POST)
.AddFile("file", imageData.CopyTo, imageUploadInput.filepath, contentLength ?? imageData.Length, imageUploadInput.mimetype));
}

foreach (S3UploadLeaseField s3Field in s3.S3UploadLease.Fields)
// Helper for upload lease image.
private S3PostResponse HelperUploadLeaseImage(S3UploadLease s3Lease, IRestRequest restRequest)
{
RestClient s3RestClient = new RestClient("https:" + s3Lease.Action);
foreach (S3UploadLeaseField s3Field in s3Lease.Fields)
{
if (!s3Field.Name.Equals("content-type", StringComparison.OrdinalIgnoreCase))
{
restRequest.AddParameter(s3Field.Name, s3Field.Value);
}
restRequest.AddParameter(s3Field.Name, s3Field.Value);
}

//restRequest.AddBody(JsonConvert.SerializeObject(s3.S3UploadLease.Fields));
IRestResponse response = s3RestClient.Execute(restRequest);
return HandleUploadLeaseImageResponse(response);
}

/// <summary>
/// Upload an Emoji to S3.
/// </summary>
/// <param name="s3">The data retrieved by AcquireLease.</param>
/// <param name="imageData">Raw image data.</param>
/// <param name="imageUploadInput">File name (with extension) and mime type.</param>
/// <returns>Task which may contain exceptions.</returns>
/// <returns>Decoded PostResponse including Key.</returns>
public async Task<S3PostResponse> UploadLeaseImageAsync(
S3UploadLeaseContainer s3, byte[] imageData, ImageUploadInput imageUploadInput)
{
return await HelperUploadLeaseImageAsync(s3.S3UploadLease, new RestRequest(Method.POST)
.AddFile("file", imageData, imageUploadInput.filepath, imageUploadInput.mimetype));
}

/// <summary>
/// Upload an Emoji to S3.
/// </summary>
/// <param name="s3">The data retrieved by AcquireLease.</param>
/// <param name="imageData"></param>
/// <param name="imageUploadInput">File name (with extension) and mime type.</param>
/// <param name="contentLength">Optional length of imageData. Otherwise imageData.Length will be used.</param>
/// <returns>Decoded PostResponse including Key.</returns>
public async Task<S3PostResponse> UploadLeaseImageAsync(
S3UploadLeaseContainer s3, Stream imageData, ImageUploadInput imageUploadInput, long? contentLength = null)
{
return await HelperUploadLeaseImageAsync(s3.S3UploadLease, new RestRequest(Method.POST)
.AddFile("file", imageData.CopyTo, imageUploadInput.filepath, contentLength ?? imageData.Length, imageUploadInput.mimetype));
}

// Helper for upload lease image (async).
private async Task<S3PostResponse> HelperUploadLeaseImageAsync(S3UploadLease s3Lease, IRestRequest restRequest)
{
RestClient s3RestClient = new RestClient("https:" + s3Lease.Action);
foreach (S3UploadLeaseField s3Field in s3Lease.Fields)
{
restRequest.AddParameter(s3Field.Name, s3Field.Value);
}

restRequest.AddHeader("content-type", "multipart/form-data");
//restRequest.AddHeader("key", s3.S3UploadLease.Fields.First(item => item.Name.Equals("key", StringComparison.OrdinalIgnoreCase)).Value);
//restRequest.AddParameter("key", s3.S3UploadLease.Fields.First(item => item.Name.Equals("key", StringComparison.OrdinalIgnoreCase)).Value);
IRestResponse response = await s3RestClient.ExecuteTaskAsync<S3PostResponse>(restRequest);
return HandleUploadLeaseImageResponse(response);
}

restRequest.AddFileBytes("file", imageData, "birdie.jpg", s3.S3UploadLease.Fields.First(
item => item.Name.Equals("content-type", StringComparison.OrdinalIgnoreCase)).Value);
private S3PostResponse HandleUploadLeaseImageResponse(IRestResponse s3Response)
{
if (null != s3Response.ErrorException || HttpStatusCode.Created != s3Response.StatusCode)
throw new RedditException("Failed to upload image to s3.", s3Response.ErrorException);

ExecuteRequest(restRequest);
return (S3PostResponse) S3PostResponseXmlSerializer.Deserialize(new StringReader(s3Response.Content));
}
#endregion

// TODO - Needs testing.
/// <summary>
Expand Down Expand Up @@ -115,5 +222,15 @@ public SnoomojiContainer All(string subreddit)
{
return JsonConvert.DeserializeObject<SnoomojiContainer>(ExecuteRequest("api/v1/" + subreddit + "/emojis/all"));
}

/// <summary>
/// Get all emojis for a SR. The response includes reddit emojis as well as emojis for the SR specified in the request.
/// </summary>
/// <param name="subreddit">The subreddit with the emojis</param>
/// <returns>Emojis.</returns>
public async Task<SnoomojiContainer> AllAsync(string subreddit)
{
return JsonConvert.DeserializeObject<SnoomojiContainer>(await ExecuteRequestAsync("api/v1/" + subreddit + "/emojis/all"));
}
}
}
22 changes: 22 additions & 0 deletions src/Reddit.NET/Things/Emoji/S3PostResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Xml.Serialization;

namespace Reddit.Things
{
[Serializable]
[XmlRoot("PostResponse", IsNullable = false)]
public class S3PostResponse
{
[XmlElement("Location")]
public string Location { get; set; }

[XmlElement("Bucket")]
public string Bucket { get; set; }

[XmlElement("Key")]
public string Key { get; set; }

[XmlElement("ETag")]
public string ETag { get; set; }
}
}