From c4e943b9ddfebccb250f74c46a6840859f1104b5 Mon Sep 17 00:00:00 2001 From: mwoda <84077698+mwwoda@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:42:31 +0100 Subject: [PATCH 1/2] feat: add marker-based pagination version of `GetFolderItems` --- .../BoxFolderManagerIntegrationTest.cs | 39 ++++ Box.V2.Test/Box.V2.Test.csproj | 3 + Box.V2.Test/BoxFoldersManagerTest.cs | 22 ++ .../GetFolderItemsMarkerBased200.json | 198 ++++++++++++++++++ Box.V2/Managers/BoxFoldersManager.cs | 53 ++++- Box.V2/Managers/IBoxFoldersManager.cs | 23 ++ 6 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 Box.V2.Test/Fixtures/BoxFolders/GetFolderItemsMarkerBased200.json diff --git a/Box.V2.Test.Integration/BoxFolderManagerIntegrationTest.cs b/Box.V2.Test.Integration/BoxFolderManagerIntegrationTest.cs index 3b3451b20..df1dce7c9 100644 --- a/Box.V2.Test.Integration/BoxFolderManagerIntegrationTest.cs +++ b/Box.V2.Test.Integration/BoxFolderManagerIntegrationTest.cs @@ -242,5 +242,44 @@ public async Task GetFolderItemsAsync_ForFolderWithSharedLink_ShouldReturnAllFol Assert.AreEqual(items.TotalCount, 1); Assert.AreEqual(items.Entries[0].Id, file.Id); } + + [TestMethod] + public async Task GetFolderItemsAsync_WithOffsetPagination_ShouldReturnCorrectNumberOfFolderItems() + { + var folder = await CreateFolder(); + await CreateSmallFile(folder.Id); + await CreateSmallFile(folder.Id); + + var items = await UserClient.FoldersManager.GetFolderItemsAsync(folder.Id, 1, 0); + + Assert.AreEqual(items.Entries.Count, 1); + Assert.AreEqual(items.TotalCount, 2); + + items = await UserClient.FoldersManager.GetFolderItemsAsync(folder.Id, 1, 1); + + Assert.AreEqual(items.Entries.Count, 1); + + items = await UserClient.FoldersManager.GetFolderItemsAsync(folder.Id, 1, 2); + Assert.AreEqual(items.Entries.Count, 0); + } + + [TestMethod] + public async Task GetFolderItemsMarkerBasedAsync_WithMarkerPagination_ShouldReturnCorrectNumberOfFolderItems() + { + var folder = await CreateFolder(); + await CreateSmallFile(folder.Id); + await CreateSmallFile(folder.Id); + + var items = await UserClient.FoldersManager.GetFolderItemsMarkerBasedAsync(folder.Id, 1); + + Assert.AreEqual(items.Entries.Count, 1); + Assert.IsNotNull(items.NextMarker); + + var nextMarker = items.NextMarker; + + items = await UserClient.FoldersManager.GetFolderItemsMarkerBasedAsync(folder.Id, 1, marker: nextMarker); + Assert.AreEqual(items.Entries.Count, 1); + Assert.IsNull(items.NextMarker); + } } } diff --git a/Box.V2.Test/Box.V2.Test.csproj b/Box.V2.Test/Box.V2.Test.csproj index a3c2d717c..d86972715 100644 --- a/Box.V2.Test/Box.V2.Test.csproj +++ b/Box.V2.Test/Box.V2.Test.csproj @@ -51,6 +51,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Box.V2.Test/BoxFoldersManagerTest.cs b/Box.V2.Test/BoxFoldersManagerTest.cs index 9515b115e..41e93217d 100644 --- a/Box.V2.Test/BoxFoldersManagerTest.cs +++ b/Box.V2.Test/BoxFoldersManagerTest.cs @@ -1063,5 +1063,27 @@ public async Task DeleteFolderLock_ValidResponse() //Response check Assert.AreEqual(true, result); } + + [TestMethod] + public async Task GetFolderItemsMarkerBased_ValidResponse_ValidFolder() + { + Handler.Setup(h => h.ExecuteAsync>(It.IsAny())) + .Returns(() => Task.FromResult>>(new BoxResponse>() + { + Status = ResponseStatus.Success, + ContentString = LoadFixtureFromJson("Fixtures/BoxFolders/GetFolderItemsMarkerBased200.json") + })); + + BoxCollectionMarkerBased items = await _foldersManager.GetFolderItemsMarkerBasedAsync("0", 1000); + + Assert.AreEqual(items.Entries.Count, 1); + Assert.AreEqual(items.Entries[0].Type, "file"); + Assert.AreEqual(items.Entries[0].Id, "12345"); + Assert.AreEqual(items.Entries[0].SequenceId, "3"); + Assert.AreEqual(items.Entries[0].ETag, "1"); + Assert.AreEqual(items.Entries[0].Name, "Contract.pdf"); + Assert.AreEqual(items.NextMarker, "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVii"); + Assert.AreEqual(items.Limit, 1000); + } } } diff --git a/Box.V2.Test/Fixtures/BoxFolders/GetFolderItemsMarkerBased200.json b/Box.V2.Test/Fixtures/BoxFolders/GetFolderItemsMarkerBased200.json new file mode 100644 index 000000000..6669dec91 --- /dev/null +++ b/Box.V2.Test/Fixtures/BoxFolders/GetFolderItemsMarkerBased200.json @@ -0,0 +1,198 @@ +{ + "entries": [ + { + "id": "12345", + "etag": "1", + "type": "file", + "sequence_id": "3", + "name": "Contract.pdf", + "sha1": "85136C79CBF9FE36BB9D05D0639C70C265C18D37", + "file_version": { + "id": "12345", + "type": "file_version", + "sha1": "134b65991ed521fcfe4724b7d814ab8ded5185dc" + }, + "description": "Contract for Q1 renewal", + "size": 629644, + "path_collection": { + "total_count": 1, + "entries": [ + { + "id": "12345", + "etag": "1", + "type": "folder", + "sequence_id": "3", + "name": "Contracts" + } + ] + }, + "created_at": "2012-12-12T10:53:43-08:00", + "modified_at": "2012-12-12T10:53:43-08:00", + "trashed_at": "2012-12-12T10:53:43-08:00", + "purged_at": "2012-12-12T10:53:43-08:00", + "content_created_at": "2012-12-12T10:53:43-08:00", + "content_modified_at": "2012-12-12T10:53:43-08:00", + "created_by": { + "id": "11446498", + "type": "user", + "name": "Aaron Levie", + "login": "ceo@example.com" + }, + "modified_by": { + "id": "11446498", + "type": "user", + "name": "Aaron Levie", + "login": "ceo@example.com" + }, + "owned_by": { + "id": "11446498", + "type": "user", + "name": "Aaron Levie", + "login": "ceo@example.com" + }, + "shared_link": { + "url": "https://www.box.com/s/vspke7y05sb214wjokpk", + "download_url": "https://www.box.com/shared/static/rh935iit6ewrmw0unyul.jpeg", + "vanity_url": "https://acme.app.box.com/v/my_url/", + "vanity_name": "my_url", + "access": "open", + "effective_access": "company", + "effective_permission": "can_download", + "unshared_at": "2018-04-13T13:53:23-07:00", + "is_password_enabled": true, + "permissions": { + "can_download": true, + "can_preview": true, + "can_edit": false + }, + "download_count": 3, + "preview_count": 3 + }, + "parent": { + "id": "12345", + "etag": "1", + "type": "folder", + "sequence_id": "3", + "name": "Contracts" + }, + "item_status": "active", + "version_number": "1", + "comment_count": 10, + "permissions": { + "can_delete": true, + "can_download": true, + "can_invite_collaborator": true, + "can_rename": true, + "can_set_share_access": true, + "can_share": true, + "can_annotate": true, + "can_comment": true, + "can_preview": true, + "can_upload": true, + "can_view_annotations_all": true, + "can_view_annotations_self": true + }, + "tags": [ + "approved" + ], + "lock": { + "id": "11446498", + "type": "lock", + "created_by": { + "id": "11446498", + "type": "user", + "name": "Aaron Levie", + "login": "ceo@example.com" + }, + "created_at": "2012-12-12T10:53:43-08:00", + "expired_at": "2012-12-12T10:53:43-08:00", + "is_download_prevented": true, + "app_type": "office_wopiplus" + }, + "extension": "pdf", + "is_package": true, + "expiring_embed_link": { + "access_token": "c3FIOG9vSGV4VHo4QzAyg5T1JvNnJoZ3ExaVNyQWw6WjRsanRKZG5lQk9qUE1BVQ", + "expires_in": 3600, + "token_type": "bearer", + "restricted_to": [ + { + "scope": "item_download", + "object": { + "id": "12345", + "etag": "1", + "type": "folder", + "sequence_id": "3", + "name": "Contracts" + } + } + ], + "url": "https://cloud.app.box.com/preview/expiring_embed/..." + }, + "watermark_info": { + "is_watermarked": true + }, + "is_accessible_via_shared_link": true, + "allowed_invitee_roles": [ + "editor" + ], + "is_externally_owned": true, + "has_collaborations": true, + "metadata": { + "enterprise_27335": { + "marketingCollateral": { + "$canEdit": true, + "$id": "01234500-12f1-1234-aa12-b1d234cb567e", + "$parent": "folder_59449484661", + "$scope": "enterprise_27335", + "$template": "marketingCollateral", + "$type": "properties-6bcba49f-ca6d-4d2a-a758-57fe6edf44d0", + "$typeVersion": 2, + "$version": 1 + } + } + }, + "expires_at": "2012-12-12T10:53:43-08:00", + "representations": { + "entries": [ + { + "content": { + "url_template": "https://dl.boxcloud.com/api/2.0/internal_files/123/versions/345/representations/png_paged_2048x2048/content/{+asset_path}?watermark_content=4567" + }, + "info": { + "url": "https://api.box.com/2.0/internal_files/123/versions/345/representations/png_paged_2048x2048" + }, + "properties": { + "dimensions": "2048x2048", + "paged": true, + "thumb": true + }, + "representation": "png", + "status": { + "state": "success" + } + } + ] + }, + "classification": { + "name": "Top Secret", + "definition": "Content that should not be shared outside the company.", + "color": "#FF0000" + }, + "uploader_display_name": "Ellis Wiggins", + "disposition_at": "2012-12-12T10:53:43-08:00", + "shared_link_permission_options": [ + "can_preview" + ] + } + ], + "limit": 1000, + "next_marker": "JV9IRGZmieiBasejOG9yDCRNgd2ymoZIbjsxbJMjIs3kioVii", + "order": [ + { + "by": "type", + "direction": "ASC" + } + ], + "total_count": 5000 +} diff --git a/Box.V2/Managers/BoxFoldersManager.cs b/Box.V2/Managers/BoxFoldersManager.cs index 49d17e77c..97cd9b2a0 100644 --- a/Box.V2/Managers/BoxFoldersManager.cs +++ b/Box.V2/Managers/BoxFoldersManager.cs @@ -18,7 +18,8 @@ public BoxFoldersManager(IBoxConfig config, IBoxService service, IBoxConverter c : base(config, service, converter, auth, asUser, suppressNotifications) { } /// - /// Retrieves the files and/or folders contained within this folder without any other metadata about the folder. + /// Retrieves the files and/or folders contained within this folder without any other metadata about the folder. + /// Uses offset-based pagination. /// Any attribute in the full files or folders objects can be passed in with the fields parameter to get specific attributes, /// and only those specific attributes back; otherwise, the mini format is returned for each item by default. /// Multiple attributes can be passed in using the fields parameter. Paginated results can be @@ -67,6 +68,56 @@ public async Task> GetFolderItemsAsync(string id, int lim } } + /// + /// Retrieves the files and/or folders contained within this folder without any other metadata about the folder. + /// Uses marker-based pagination. + /// Any attribute in the full files or folders objects can be passed in with the fields parameter to get specific attributes, + /// and only those specific attributes back; otherwise, the mini format is returned for each item by default. + /// Multiple attributes can be passed in using the fields parameter. Paginated results can be + /// retrieved using the limit and marker parameters. + /// + /// + /// The maximum number of items to return in a page. The default is 100 and the max is 1000. + /// Position to return results from.. + /// Attribute(s) to include in the response + /// Whether or not to auto-paginate to fetch all items; defaults to false. + /// The field to sort items on + /// The direction to sort results in: ascending or descending + /// The shared link for this folder + /// The password for the shared link (if required) + /// A collection of items contained in the folder is returned. An error is thrown if the folder does not exist, + /// or if any of the parameters are invalid. The total_count returned may not match the number of entries when using enterprise scope, + /// because external folders are hidden the list of entries. + public async Task> GetFolderItemsMarkerBasedAsync(string id, int limit, string marker = null, IEnumerable fields = null, bool autoPaginate = false, string sort = null, BoxSortDirection? direction = null, + string sharedLink = null, string sharedLinkPassword = null) + { + id.ThrowIfNullOrWhiteSpace("id"); + + BoxRequest request = new BoxRequest(_config.FoldersEndpointUri, string.Format(Constants.ItemsPathString, id)) + .Param("limit", limit.ToString()) + .Param("marker", marker) + .Param("usemarker", "true") + .Param("sort", sort) + .Param("direction", direction.ToString()) + .Param(ParamFields, fields); + + if (!string.IsNullOrEmpty(sharedLink)) + { + var sharedLinkHeader = SharedLinkUtils.GetSharedLinkHeader(sharedLink, sharedLinkPassword); + request.Header(sharedLinkHeader.Item1, sharedLinkHeader.Item2); + } + + if (autoPaginate) + { + return await AutoPaginateMarker(request, limit).ConfigureAwait(false); + } + else + { + IBoxResponse> response = await ToResponseAsync>(request).ConfigureAwait(false); + return response.ResponseObject; + } + } + /// /// Used to create a new empty folder. The new folder will be created inside of the specified parent folder. /// diff --git a/Box.V2/Managers/IBoxFoldersManager.cs b/Box.V2/Managers/IBoxFoldersManager.cs index 6f6bb2277..622bdb7ab 100644 --- a/Box.V2/Managers/IBoxFoldersManager.cs +++ b/Box.V2/Managers/IBoxFoldersManager.cs @@ -31,6 +31,29 @@ public interface IBoxFoldersManager Task> GetFolderItemsAsync(string id, int limit, int offset = 0, IEnumerable fields = null, bool autoPaginate = false, string sort = null, BoxSortDirection? direction = null, string sharedLink = null, string sharedLinkPassword = null); + /// + /// Retrieves the files and/or folders contained within this folder without any other metadata about the folder. + /// Uses marker-based pagination. + /// Any attribute in the full files or folders objects can be passed in with the fields parameter to get specific attributes, + /// and only those specific attributes back; otherwise, the mini format is returned for each item by default. + /// Multiple attributes can be passed in using the fields parameter. Paginated results can be + /// retrieved using the limit and marker parameters. + /// + /// + /// The maximum number of items to return in a page. The default is 100 and the max is 1000. + /// Position to return results from.. + /// Attribute(s) to include in the response + /// Whether or not to auto-paginate to fetch all items; defaults to false. + /// The field to sort items on + /// The direction to sort results in: ascending or descending + /// The shared link for this folder + /// The password for the shared link (if required) + /// A collection of items contained in the folder is returned. An error is thrown if the folder does not exist, + /// or if any of the parameters are invalid. The total_count returned may not match the number of entries when using enterprise scope, + /// because external folders are hidden the list of entries. + Task> GetFolderItemsMarkerBasedAsync(string id, int limit, string marker = null, IEnumerable fields = null, bool autoPaginate = false, string sort = null, BoxSortDirection? direction = null, + string sharedLink = null, string sharedLinkPassword = null); + /// /// Used to create a new empty folder. The new folder will be created inside of the specified parent folder. /// From 86accd458969051a77760dff7cfd1e4e08b6f913 Mon Sep 17 00:00:00 2001 From: mwoda <84077698+mwwoda@users.noreply.github.com> Date: Wed, 24 Jan 2024 16:42:10 +0100 Subject: [PATCH 2/2] remove sample access token from json --- .../Fixtures/BoxFolders/GetFolderItemsMarkerBased200.json | 1 - 1 file changed, 1 deletion(-) diff --git a/Box.V2.Test/Fixtures/BoxFolders/GetFolderItemsMarkerBased200.json b/Box.V2.Test/Fixtures/BoxFolders/GetFolderItemsMarkerBased200.json index 6669dec91..5aa445531 100644 --- a/Box.V2.Test/Fixtures/BoxFolders/GetFolderItemsMarkerBased200.json +++ b/Box.V2.Test/Fixtures/BoxFolders/GetFolderItemsMarkerBased200.json @@ -112,7 +112,6 @@ "extension": "pdf", "is_package": true, "expiring_embed_link": { - "access_token": "c3FIOG9vSGV4VHo4QzAyg5T1JvNnJoZ3ExaVNyQWw6WjRsanRKZG5lQk9qUE1BVQ", "expires_in": 3600, "token_type": "bearer", "restricted_to": [