Skip to content

Commit

Permalink
Add zip functionality (#700)
Browse files Browse the repository at this point in the history
  • Loading branch information
sujaygarlanka authored Oct 16, 2020
1 parent cca504c commit b1f88f3
Show file tree
Hide file tree
Showing 16 changed files with 443 additions and 3 deletions.
84 changes: 84 additions & 0 deletions Box.V2.Test/BoxFilesManagerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
using Newtonsoft.Json;
using System.Text;
using System.Globalization;
using Box.V2.Models.Request;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;

namespace Box.V2.Test
{
Expand Down Expand Up @@ -1108,5 +1111,86 @@ public async Task PromoteVersion_ValidResponse()
Assert.AreEqual(DateTime.Parse("2013-11-20T13:20:50-08:00"), result.CreatedAt);
Assert.AreEqual(DateTime.Parse("2013-11-20T13:26:48-08:00"), result.ModifiedAt);
}

[TestMethod]
[TestCategory("CI-UNIT-TEST")]
public async Task DownloadZip_ValidResponse()
{
using (FileStream exampleFile = new FileStream(string.Format("../.././TestData/smalltest.pdf"), FileMode.OpenOrCreate))
{
/*** Arrange ***/
string responseStringCreateZip = "{\"download_url\": \"https://api.box.com/zip_downloads/124hfiowk3fa8kmrwh/content\",\"status_url\": \"https://api.box.com/zip_downloads/124hfiowk3fa8kmrwh/status\",\"expires_at\": \"2018-04-25T11:00:18-07:00\", \"name_conflicts\":[[{\"id\":\"100\",\"type\":\"file\",\"original_name\":\"salary.pdf\",\"download_name\":\"aqc823.pdf\"},{\"id\":\"200\",\"type\": \"file\",\"original_name\":\"salary.pdf\",\"download_name\": \"aci23s.pdf\"}],[{\"id\":\"1000\",\"type\": \"folder\",\"original_name\":\"employees\",\"download_name\":\"3d366a_employees\"},{\"id\":\"2000\",\"type\": \"folder\",\"original_name\":\"employees\",\"download_name\": \"3aa6a7_employees\"}]]}";
string responseStringDownloadStatus = "{\"total_file_count\": 20, \"downloaded_file_count\": 10, \"skipped_file_count\": 10, \"skipped_folder_count\": 10, \"state\": \"succeeded\"}";
IBoxRequest boxRequest = null;
IBoxRequest boxRequest2 = null;
IBoxRequest boxRequest3 = null;
Handler.Setup(h => h.ExecuteAsync<BoxZip>(It.IsAny<IBoxRequest>()))
.Returns(Task.FromResult<IBoxResponse<BoxZip>>(new BoxResponse<BoxZip>()
{
Status = ResponseStatus.Success,
ContentString = responseStringCreateZip
}))
.Callback<IBoxRequest>(r => boxRequest = r);

Handler.Setup(h => h.ExecuteAsync<Stream>(It.IsAny<IBoxRequest>()))
.Returns(Task.FromResult<IBoxResponse<Stream>>(new BoxResponse<Stream>()
{
Status = ResponseStatus.Success,
ResponseObject = exampleFile

}))
.Callback<IBoxRequest>(r => boxRequest2 = r);

Handler.Setup(h => h.ExecuteAsync<BoxZipDownloadStatus>(It.IsAny<IBoxRequest>()))
.Returns(Task.FromResult<IBoxResponse<BoxZipDownloadStatus>>(new BoxResponse<BoxZipDownloadStatus>()
{
Status = ResponseStatus.Success,
ContentString = responseStringDownloadStatus
}))
.Callback<IBoxRequest>(r => boxRequest3 = r);



/*** Act ***/
BoxZipRequest request = new BoxZipRequest();
request.Name = "test";
request.Items = new List<BoxZipRequestItem>();

var file = new BoxZipRequestItem()
{
Id = "466239504569",
Type = BoxZipItemType.file
};
var folder = new BoxZipRequestItem()
{
Id = "466239504580",
Type = BoxZipItemType.folder
};
request.Items.Add(file);
request.Items.Add(folder);
Stream fs = new MemoryStream(100);

BoxZipDownloadStatus status = await _filesManager.DownloadZip(request, fs);
/*** Assert ***/

// Request check
Assert.IsNotNull(boxRequest);
Assert.AreEqual(RequestMethod.Post, boxRequest.Method);
JObject payload = JObject.Parse(boxRequest.Payload);
Assert.AreEqual("test", payload["name"]);
JArray items = JArray.Parse(payload["items"].ToString());
Assert.AreEqual("466239504569", items[0]["id"]);
Assert.AreEqual("file", items[0]["type"]);

// Reponse Check
Assert.AreEqual(status.TotalFileCount, 20);
Assert.AreEqual(status.State, BoxZipDownloadState.succeeded);
Assert.AreEqual(status.NameConflicts[0].items[0].OriginalName, "salary.pdf");
Assert.AreEqual(status.NameConflicts[0].items[1].OriginalName, "salary.pdf");
Assert.AreEqual(status.NameConflicts[1].items[0].OriginalName, "employees");
Assert.AreEqual(status.NameConflicts[1].items[1].OriginalName, "employees");
Assert.AreNotEqual(fs.Length, 0);
}
}
}
}
Binary file added Box.V2.Test/TestData/smalltest.pdf
Binary file not shown.
7 changes: 7 additions & 0 deletions Box.V2/Box.V2.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
<Compile Include="Auth\Token\ActorTokenBuilder.cs" />
<Compile Include="Auth\Token\TokenExchange.cs" />
<Compile Include="BoxClient.cs" />
<Compile Include="Converter\BoxZipConflictConverter.cs" />
<Compile Include="JWTAuth\BoxJWTAuth.cs" />
<Compile Include="Converter\BoxItemConverter.cs" />
<Compile Include="Exceptions\BoxAuthenticationFailedException.cs" />
Expand Down Expand Up @@ -144,13 +145,18 @@
<Compile Include="Models\BoxWebLink.cs" />
<Compile Include="Models\BoxCollaborationWhitelistEntry.cs" />
<Compile Include="Models\BoxCollaborationWhitelistTargetEntry.cs" />
<Compile Include="Models\BoxZip.cs" />
<Compile Include="Models\BoxZipConflict.cs" />
<Compile Include="Models\BoxZipConflictItem.cs" />
<Compile Include="Models\BoxZipDownloadStatus.cs" />
<Compile Include="Models\Permissions\BoxFilePermission.cs" />
<Compile Include="Models\BoxFileVersion.cs" />
<Compile Include="Models\BoxGroup.cs" />
<Compile Include="Models\BoxGroupMembership.cs" />
<Compile Include="Models\Request\BoxActionableByRequest.cs" />
<Compile Include="Models\BoxSessionParts.cs" />
<Compile Include="Models\Request\BoxFileUploadSessionRequest.cs" />
<Compile Include="Models\Request\BoxZipRequest.cs" />
<Compile Include="Models\Request\BoxLegalHoldPolicyAssignmentRequest.cs" />
<Compile Include="Models\Request\BoxLegalHoldPolicyRequest.cs" />
<Compile Include="Models\Request\BoxMetadataQueryOrderBy.cs" />
Expand Down Expand Up @@ -184,6 +190,7 @@
<Compile Include="Models\Request\BoxUserInviteRequest.cs" />
<Compile Include="Models\Request\BoxWatermarkRequest.cs" />
<Compile Include="Models\Request\BoxWebhookRequest.cs" />
<Compile Include="Models\Request\BoxZipRequestItem.cs" />
<Compile Include="Plugin\BoxResourcePlugins.cs" />
<Compile Include="Plugin\IBoxMetadataManager.cs" />
<Compile Include="Plugin\IResourcePlugin.cs" />
Expand Down
4 changes: 4 additions & 0 deletions Box.V2/Config/BoxConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ public static IBoxConfig CreateFromJsonString(string jsonString)
/// </summary>
public virtual Uri FileVersionLegalHoldsEndpointUri { get { return new Uri(BoxApiUri, Constants.FileVersionLegalHoldsString); } }
/// <summary>
/// Gets the zip downloads endpoint URI.
/// </summary>
public virtual Uri ZipDownloadsEndpointUri { get { return new Uri(BoxApiUri, Constants.ZipDownloadsString); } }
/// <summary>
/// The web proxy for HttpRequestHandler
/// </summary>
public IWebProxy WebProxy { get; set; }
Expand Down
1 change: 1 addition & 0 deletions Box.V2/Config/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public static class Constants
public const string StoragePoliciesString = @"storage_policies/";
public const string StoragePolicyAssignmentsString = @"storage_policy_assignments/";
public const string StoragePolicyAssignmentsForTargetString = @"storage_policy_assignments";
public const string ZipDownloadsString = @"zip_downloads";

/// <summary>
/// The shared items constant
Expand Down
4 changes: 4 additions & 0 deletions Box.V2/Config/IBoxConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ public interface IBoxConfig
/// Gets the file viersion legal holds endpoint URI.
/// </summary>
Uri FileVersionLegalHoldsEndpointUri { get; }
/// <summary>
/// Gets the zip downloads endpoint URI.
/// </summary>
Uri ZipDownloadsEndpointUri { get; }

/// <summary>
/// The web proxy for HttpRequestHandler
Expand Down
40 changes: 40 additions & 0 deletions Box.V2/Converter/BoxZipConflictConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using Box.V2.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Box.V2.Converter
{
internal class BoxZipConflictConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
List<BoxZipConflict> conflicts = new List<BoxZipConflict>();

// Load JObject from stream
JArray conflictsArray = JArray.Load(reader);
foreach (JArray conflict in conflictsArray)
{
BoxZipConflict zipConflict = new BoxZipConflict();
JObject conflictObject = new JObject();
conflictObject.Add("items", conflict);
// Populate the object properties
serializer.Populate(conflictObject.CreateReader(), zipConflict);
conflicts.Add(zipConflict);
}

return conflicts;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
46 changes: 46 additions & 0 deletions Box.V2/Managers/BoxFilesManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using Newtonsoft.Json.Linq;
using Box.V2.Models.Request;

namespace Box.V2.Managers
{
Expand Down Expand Up @@ -1326,6 +1327,41 @@ public async Task<BoxRepresentationCollection<BoxRepresentation>> GetRepresentat
return response.ResponseObject.Representations;
}

/// <summary>
/// Creates a zip and downloads it to a given Stream.
/// </summary>
/// <param name="zipRequest">Object of type BoxZipRequest that contains name and items.</param>
/// <param name="output">The stream to where the zip file will be written.</param>
/// <returns>The status of the download.</returns>
/// </summary>
public async Task<BoxZipDownloadStatus> DownloadZip(BoxZipRequest zipRequest, Stream output)
{
BoxZip createdZip = await CreateZip(zipRequest);
IBoxRequest downloadRequest = new BoxRequest(createdZip.DownloadUrl);
IBoxResponse<Stream> streamResponse = await ToResponseAsync<Stream>(downloadRequest).ConfigureAwait(false);
Stream fileStream = streamResponse.ResponseObject;

// Default the buffer size to 4K.
const int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesRead = 0;
do
{
bytesRead = fileStream.Read(buffer, 0, bufferSize);
if (bytesRead > 0)
{
output.Write(buffer, 0, bytesRead);
}
} while (bytesRead > 0);

BoxRequest downloadStatusRequest = new BoxRequest(createdZip.StatusUrl)
.Method(RequestMethod.Get);
IBoxResponse<BoxZipDownloadStatus> response = await ToResponseAsync<BoxZipDownloadStatus>(downloadStatusRequest).ConfigureAwait(false);
BoxZipDownloadStatus finalResponse = response.ResponseObject;
finalResponse.NameConflicts = createdZip.NameConflicts;
return finalResponse;
}

/// <summary>
/// Representations are digital assets stored in Box. We can request the following representations: PDF, Extracted Text, Thumbnail,
/// and Single Page depending on whether the file type is supported by passing in the corresponding x-rep-hints header. This will generate a
Expand Down Expand Up @@ -1367,6 +1403,16 @@ public async Task<Stream> GetRepresentationContentAsync(BoxRepresentationRequest

}

private async Task<BoxZip> CreateZip(BoxZipRequest zipRequest)
{
BoxRequest request = new BoxRequest(_config.ZipDownloadsEndpointUri)
.Method(RequestMethod.Post)
.Payload(_converter.Serialize(zipRequest));

IBoxResponse<BoxZip> response = await ToResponseAsync<BoxZip>(request).ConfigureAwait(false);
return response.ResponseObject;
}

private async Task<string> PollRepresentationInfo(string infoUrl)
{
var infoRequest = new BoxRequest(new Uri(infoUrl));
Expand Down
44 changes: 44 additions & 0 deletions Box.V2/Models/BoxZip.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Box.V2.Converter;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;

namespace Box.V2.Models
{
/// <summary>
/// Box representation of a created zip
/// </summary>
public class BoxZip
{
public const string FieldDownloadUrl = "download_url";
public const string FieldStatusUrl = "status_url";
public const string FieldExpiresAt = "expires_at";
public const string FieldNameConflicts = "name_conflicts";

/// <summary>
/// A URL to download the zip from
/// </summary>
[JsonProperty(PropertyName = FieldDownloadUrl)]
public Uri DownloadUrl { get; private set; }

/// <summary>
/// A URL to get the download status of a zip
/// </summary>
[JsonProperty(PropertyName = FieldStatusUrl)]
public Uri StatusUrl { get; private set; }

/// <summary>
/// The date after which the zip can no longer be downloaded
/// </summary>
[JsonProperty(PropertyName = FieldExpiresAt)]
public DateTime ExpiresAt { get; private set; }

/// <summary>
/// A list of naming conflicts among the files and folders in the zip
/// </summary>
[JsonProperty(PropertyName = FieldNameConflicts)]
[JsonConverter(typeof(BoxZipConflictConverter))]
public List<BoxZipConflict> NameConflicts { get; private set; }
}
}

19 changes: 19 additions & 0 deletions Box.V2/Models/BoxZipConflict.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;

namespace Box.V2.Models
{
/// <summary>
/// Represents a conflict that occurs between items that have the same name.
/// </summary>
public class BoxZipConflict
{
/// <summary>
/// The items that have a conflict
/// </summary>
[JsonProperty(PropertyName = "items")]
public List<BoxZipConflictItem> items { get; private set; }
}
}

39 changes: 39 additions & 0 deletions Box.V2/Models/BoxZipConflictItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Newtonsoft.Json;

namespace Box.V2.Models
{
/// <summary>
/// Box representation of a naming conflict creating a zip file for an item
/// </summary>
public class BoxZipConflictItem
{
public const string FieldId = "id";
public const string FieldType = "type";
public const string FieldOriginalName = "original_name";
public const string FieldDownloadName = "download_name";

/// <summary>
/// The Id of the item
/// </summary>
[JsonProperty(PropertyName = FieldId)]
public string Id { get; private set; }

/// <summary>
/// The type of the item
/// </summary>
[JsonProperty(PropertyName = FieldType)]
public string Type { get; private set; }

/// <summary>
/// The original name of the item
/// </summary>
[JsonProperty(PropertyName = FieldOriginalName)]
public string OriginalName { get; private set; }

/// <summary>
/// the new name of the item when it downloads that resolves the conflict
/// </summary>
[JsonProperty(PropertyName = FieldDownloadName)]
public string DownloadName { get; private set; }
}
}
Loading

0 comments on commit b1f88f3

Please sign in to comment.