2025-02-17 16:45:51 +01:00
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
2024-02-28 15:45:30 +01:00
// DEALINGS IN THE SOFTWARE.
2018-11-02 21:34:07 +01:00
using Duplicati.Library.Common.IO ;
2019-02-22 21:58:40 -06:00
using Duplicati.Library.Interface ;
2025-03-20 10:54:38 +01:00
using Duplicati.Library.Utility ;
using Duplicati.Library.Utility.Options ;
2015-12-07 09:57:22 +01:00
using Newtonsoft.Json ;
using System.Net ;
2025-02-17 16:45:51 +01:00
using System.Runtime.CompilerServices ;
2015-12-07 09:57:22 +01:00
namespace Duplicati.Library.Backend.Box
{
2019-04-28 13:34:15 -07:00
// ReSharper disable once ClassNeverInstantiated.Global
// This class is instantiated dynamically in the BackendLoader.
2015-12-07 09:57:22 +01:00
public class BoxBackend : IBackend , IStreamingBackend
{
2024-09-29 22:00:57 +02:00
private static readonly string LOGTAG = Logging . Log . LogTagFromType < BoxBackend >();
2015-12-07 09:57:22 +01:00
private const string REALLY_DELETE_OPTION = "box-delete-from-trash" ;
2025-03-20 10:54:38 +01:00
private const string OAUTH_MODULE = "box.com" ;
private static readonly string OAUTH_LOGIN_URL = OAuthHelper . OAUTH_LOGIN_URL ( OAUTH_MODULE );
2015-12-07 09:57:22 +01:00
private const string BOX_API_URL = "https://api.box.com/2.0" ;
private const string BOX_UPLOAD_URL = "https://upload.box.com/api/2.0/files" ;
private const int PAGE_SIZE = 200 ;
2018-05-23 21:18:01 -07:00
private readonly BoxHelper m_oauth ;
private readonly string m_path ;
private readonly bool m_deleteFromTrash ;
2015-12-07 09:57:22 +01:00
2025-03-20 10:54:38 +01:00
private readonly TimeoutOptionsHelper . Timeouts m_timeouts ;
private string? m_currentfolder ;
2018-06-16 11:02:18 -07:00
private readonly Dictionary < string , string > m_filecache = new Dictionary < string , string >();
2015-12-07 09:57:22 +01:00
private class BoxHelper : OAuthHelper
{
public BoxHelper ( string authid )
2025-03-20 10:54:38 +01:00
: base ( authid , OAUTH_MODULE )
2015-12-07 09:57:22 +01:00
{
AutoAuthHeader = true ;
}
protected override void ParseException ( Exception ex )
{
2025-03-20 10:54:38 +01:00
Exception ? newex = null ;
2015-12-07 09:57:22 +01:00
try
{
2019-09-29 20:16:28 -07:00
if ( ex is WebException exception && exception . Response is HttpWebResponse hs )
2015-12-07 09:57:22 +01:00
{
2025-03-20 10:54:38 +01:00
string? rawdata = null ;
using ( var rs = AsyncHttpRequest . TrySetTimeout ( hs . GetResponseStream ()))
2024-09-29 22:00:57 +02:00
using ( var sr = new System . IO . StreamReader ( rs ))
2015-12-07 09:57:22 +01:00
rawdata = sr . ReadToEnd ();
if ( string . IsNullOrWhiteSpace ( rawdata ))
return ;
2024-09-29 22:00:57 +02:00
2015-12-07 09:57:22 +01:00
newex = new Exception ( "Raw message: " + rawdata );
var msg = JsonConvert . DeserializeObject < ErrorResponse >( rawdata );
2025-03-20 10:54:38 +01:00
newex = new Exception ( string . Format ( "{0} - {1}: {2}" , msg ?. Status , msg ?. Code , msg ?. Message ));
2015-12-07 09:57:22 +01:00
/*if (msg.ContextInfo != null && msg.ContextInfo.Length > 0)
newex = new Exception(string.Format("{0} - {1}: {2}{3}{4}", msg.Status, msg.Code, msg.Message, Environment.NewLine, string.Join("; ", from n in msg.ContextInfo select n.Message)));
*/
}
}
2024-09-29 22:00:57 +02:00
catch ( Exception ex2 )
2015-12-07 09:57:22 +01:00
{
2024-09-29 22:00:57 +02:00
Library . Logging . Log . WriteWarningMessage ( LOGTAG , "BoxErrorParser" , ex2 , "Failed to parse error from Box" );
2015-12-07 09:57:22 +01:00
}
if ( newex != null )
2024-09-29 22:00:57 +02:00
throw newex ;
2015-12-07 09:57:22 +01:00
}
}
2019-04-28 13:35:07 -07:00
// ReSharper disable once UnusedMember.Global
// This constructor is needed by the BackendLoader.
2015-12-07 09:57:22 +01:00
public BoxBackend ()
{
2025-03-20 10:54:38 +01:00
m_oauth = null !;
m_path = null !;
m_timeouts = null !;
2015-12-07 09:57:22 +01:00
}
2019-04-28 13:35:07 -07:00
// ReSharper disable once UnusedMember.Global
// This constructor is needed by the BackendLoader.
2025-03-20 10:54:38 +01:00
public BoxBackend ( string url , Dictionary < string , string? > options )
2015-12-07 09:57:22 +01:00
{
var uri = new Utility . Uri ( url );
2018-10-27 12:17:07 +02:00
m_path = Util . AppendDirSeparator ( uri . HostAndPath , "/" );
2018-09-21 16:26:30 -07:00
2025-03-20 10:54:38 +01:00
var authid = AuthIdOptionsHelper . Parse ( options )
. RequireCredentials ( OAUTH_LOGIN_URL );
2015-12-07 09:57:22 +01:00
m_deleteFromTrash = Library . Utility . Utility . ParseBoolOption ( options , REALLY_DELETE_OPTION );
2025-03-20 10:54:38 +01:00
m_timeouts = TimeoutOptionsHelper . Parse ( options );
2015-12-07 09:57:22 +01:00
2025-03-20 10:54:38 +01:00
m_oauth = new BoxHelper ( authid . AuthId !);
2015-12-07 09:57:22 +01:00
}
2024-10-01 09:30:02 +02:00
private async Task < string > GetCurrentFolderWithCacheAsync ( CancellationToken cancelToken )
2015-12-07 09:57:22 +01:00
{
2024-10-01 09:30:02 +02:00
if ( m_currentfolder == null )
2025-03-20 10:54:38 +01:00
return await GetCurrentFolderAsync ( false , cancelToken ). ConfigureAwait ( false );
2024-09-29 22:00:57 +02:00
2024-10-01 09:30:02 +02:00
return m_currentfolder ;
2015-12-07 09:57:22 +01:00
}
2025-03-20 10:54:38 +01:00
private async Task < string > GetCurrentFolderAsync ( bool create , CancellationToken cancelToken )
2015-12-07 09:57:22 +01:00
{
var parentid = "0" ;
2024-09-29 22:00:57 +02:00
foreach ( var p in m_path . Split ( new string [] { "/" }, StringSplitOptions . RemoveEmptyEntries ))
2015-12-07 09:57:22 +01:00
{
2025-03-20 10:54:38 +01:00
var el = ( MiniFolder ?) await PagedFileListResponse ( parentid , true , cancelToken ). FirstOrDefaultAsync ( x => x . Name == p ). ConfigureAwait ( false );
2015-12-07 09:57:22 +01:00
if ( el == null )
{
if (! create )
throw new FolderMissingException ();
2025-03-20 10:54:38 +01:00
el = await Utility . Utility . WithTimeout ( m_timeouts . ShortTimeout , cancelToken , async tr =>
await m_oauth . PostAndGetJSONDataAsync < ListFolderResponse >(
string . Format ( "{0}/folders" , BOX_API_URL ),
tr ,
new CreateItemRequest () { Name = p , Parent = new IDReference () { ID = parentid } }
). ConfigureAwait ( false )
2024-10-01 09:30:02 +02:00
). ConfigureAwait ( false );
2015-12-07 09:57:22 +01:00
}
2025-03-20 10:54:38 +01:00
if ( string . IsNullOrWhiteSpace ( el . ID ))
throw new Exception ( "Folder ID is empty?" );
2015-12-07 09:57:22 +01:00
parentid = el . ID ;
}
2025-03-20 10:54:38 +01:00
return m_currentfolder = parentid ;
2015-12-07 09:57:22 +01:00
}
2024-10-01 09:30:02 +02:00
private async Task < string > GetFileIDAsync ( string name , CancellationToken cancelToken )
2015-12-07 09:57:22 +01:00
{
2018-06-16 11:02:18 -07:00
if ( m_filecache . ContainsKey ( name ))
2015-12-07 09:57:22 +01:00
return m_filecache [ name ];
2018-06-16 11:02:18 -07:00
// Make sure we enumerate this, otherwise the m_filecache is empty.
2024-10-01 09:30:02 +02:00
var currentFolder = await GetCurrentFolderWithCacheAsync ( cancelToken ). ConfigureAwait ( false );
2025-02-17 16:45:51 +01:00
await PagedFileListResponse ( currentFolder , false , cancelToken ). LastOrDefaultAsync (). ConfigureAwait ( false );
2015-12-07 09:57:22 +01:00
2018-06-16 11:02:18 -07:00
if ( m_filecache . ContainsKey ( name ))
2015-12-07 09:57:22 +01:00
return m_filecache [ name ];
throw new FileMissingException ();
}
2025-02-17 16:45:51 +01:00
private async IAsyncEnumerable < FileEntity > PagedFileListResponse ( string parentid , bool onlyfolders , [ EnumeratorCancellation ] CancellationToken cancelToken )
2015-12-07 09:57:22 +01:00
{
var offset = 0 ;
var done = false ;
if (! onlyfolders )
2018-06-16 11:02:18 -07:00
m_filecache . Clear ();
2024-09-29 22:00:57 +02:00
2015-12-07 09:57:22 +01:00
do
{
2025-03-20 10:54:38 +01:00
var resp = await Utility . Utility . WithTimeout ( m_timeouts . ListTimeout , cancelToken , async ct
=> await m_oauth . GetJSONDataAsync < ShortListResponse >( $"{BOX_API_URL}/folders/{parentid}/items?limit={PAGE_SIZE}&offset={offset}&fields=name,size,modified_at" , ct ). ConfigureAwait ( false )
). ConfigureAwait ( false );
2015-12-07 09:57:22 +01:00
if ( resp . Entries == null || resp . Entries . Length == 0 )
break ;
2024-09-29 22:00:57 +02:00
foreach ( var f in resp . Entries )
2015-12-07 09:57:22 +01:00
{
if ( onlyfolders && f . Type != "folder" )
{
done = true ;
break ;
}
else
{
2025-03-20 10:54:38 +01:00
if ( string . IsNullOrWhiteSpace ( f . ID ) || string . IsNullOrWhiteSpace ( f . Name ))
{
Logging . Log . WriteVerboseMessage ( LOGTAG , "BoxFileList" , null , "Skipping invalid entry" );
continue ;
}
2015-12-07 09:57:22 +01:00
if (! onlyfolders && f . Type == "file" )
2018-06-16 11:02:18 -07:00
m_filecache [ f . Name ] = f . ID ;
2024-09-29 22:00:57 +02:00
2015-12-07 09:57:22 +01:00
yield return f ;
}
}
offset = offset + PAGE_SIZE ;
if ( offset >= resp . TotalCount )
break ;
2024-09-29 22:00:57 +02:00
} while (! done );
2015-12-07 09:57:22 +01:00
}
#region IStreamingBackend implementation
2019-03-17 18:20:14 -05:00
public async Task PutAsync ( string remotename , System . IO . Stream stream , CancellationToken cancelToken )
2015-12-07 09:57:22 +01:00
{
2024-10-01 09:30:02 +02:00
var currentFolder = await GetCurrentFolderWithCacheAsync ( cancelToken ). ConfigureAwait ( false );
2024-09-29 22:00:57 +02:00
var createreq = new CreateItemRequest ()
{
2015-12-07 09:57:22 +01:00
Name = remotename ,
2024-09-29 22:00:57 +02:00
Parent = new IDReference ()
{
2024-10-01 09:30:02 +02:00
ID = currentFolder
2015-12-07 09:57:22 +01:00
}
};
2018-06-16 11:02:18 -07:00
if ( m_filecache . Count == 0 )
2025-02-17 16:45:51 +01:00
await PagedFileListResponse ( currentFolder , false , cancelToken ). LastOrDefaultAsync (). ConfigureAwait ( false );
2015-12-07 09:57:22 +01:00
var existing = m_filecache . ContainsKey ( remotename );
try
{
2019-02-23 09:13:40 -06:00
string url ;
var items = new List < MultipartItem >( 2 );
2015-12-07 09:57:22 +01:00
if ( existing )
2019-02-23 09:13:40 -06:00
url = $"{BOX_UPLOAD_URL}/{m_filecache[remotename]}/content" ;
2015-12-07 09:57:22 +01:00
else
{
2019-02-23 09:13:40 -06:00
url = $"{BOX_UPLOAD_URL}/content" ;
items . Add ( new MultipartItem ( createreq , "attributes" ));
2015-12-07 09:57:22 +01:00
}
2019-02-23 09:13:40 -06:00
items . Add ( new MultipartItem ( stream , "file" , remotename ));
2025-03-20 10:54:38 +01:00
// TODO: Add read-write-timeout when upgraded away from WebRequest
var res = ( await m_oauth . PostMultipartAndGetJSONDataAsync < FileList >( url , null , cancelToken , items . ToArray ())). Entries ?. FirstOrDefault ();
if ( string . IsNullOrWhiteSpace ( res ?. ID ))
throw new Exception ( "Failed to get ID from uploaded file" );
2015-12-07 09:57:22 +01:00
m_filecache [ remotename ] = res . ID ;
}
catch
{
2018-06-16 11:02:18 -07:00
m_filecache . Clear ();
2015-12-07 09:57:22 +01:00
throw ;
}
}
2024-09-29 22:00:57 +02:00
public async Task GetAsync ( string remotename , System . IO . Stream stream , CancellationToken cancelToken )
2015-12-07 09:57:22 +01:00
{
2024-10-01 09:30:02 +02:00
var fileId = await GetFileIDAsync ( remotename , cancelToken ). ConfigureAwait ( false );
2025-03-20 10:54:38 +01:00
using var resp = await Utility . Utility . WithTimeout ( m_timeouts . ShortTimeout , cancelToken , async ct
=> await m_oauth . GetResponseAsync ( string . Format ( "{0}/files/{1}/content" , BOX_API_URL , fileId ), cancelToken ). ConfigureAwait ( false )
). ConfigureAwait ( false );
2024-09-29 22:00:57 +02:00
using ( var rs = Duplicati . Library . Utility . AsyncHttpRequest . TrySetTimeout ( resp . GetResponseStream ()))
2025-03-20 10:54:38 +01:00
using ( var ts = rs . ObserveReadTimeout ( m_timeouts . ReadWriteTimeout ))
await Library . Utility . Utility . CopyStreamAsync ( ts , stream , cancelToken ). ConfigureAwait ( false );
2015-12-07 09:57:22 +01:00
}
#endregion
#region IBackend implementation
2025-02-17 16:45:51 +01:00
public async IAsyncEnumerable < IFileEntry > ListAsync ([ EnumeratorCancellation ] CancellationToken cancelToken )
2015-12-07 09:57:22 +01:00
{
2025-02-17 16:45:51 +01:00
var currentFolder = await GetCurrentFolderWithCacheAsync ( cancelToken ). ConfigureAwait ( false );
await foreach ( var n in PagedFileListResponse ( currentFolder , false , cancelToken ). ConfigureAwait ( false ))
yield return new FileEntry ( n . Name , n . Size , n . ModifiedAt , n . ModifiedAt ) { IsFolder = n . Type == "folder" };
2015-12-07 09:57:22 +01:00
}
2021-06-12 11:30:08 -07:00
public async Task PutAsync ( string remotename , string filename , CancellationToken cancelToken )
2015-12-07 09:57:22 +01:00
{
using ( System . IO . FileStream fs = System . IO . File . OpenRead ( filename ))
2021-06-12 11:30:08 -07:00
await PutAsync ( remotename , fs , cancelToken );
2015-12-07 09:57:22 +01:00
}
2024-09-29 22:00:57 +02:00
public async Task GetAsync ( string remotename , string filename , CancellationToken cancelToken )
2015-12-07 09:57:22 +01:00
{
using ( System . IO . FileStream fs = System . IO . File . Create ( filename ))
2024-09-29 22:00:57 +02:00
await GetAsync ( remotename , fs , cancelToken ). ConfigureAwait ( false );
2015-12-07 09:57:22 +01:00
}
2024-09-29 23:04:54 +02:00
public async Task DeleteAsync ( string remotename , CancellationToken cancelToken )
2015-12-07 09:57:22 +01:00
{
2024-10-01 09:30:02 +02:00
var fileid = await GetFileIDAsync ( remotename , cancelToken ). ConfigureAwait ( false );
2015-12-07 09:57:22 +01:00
try
{
2025-03-20 10:54:38 +01:00
using ( var r = await Utility . Utility . WithTimeout ( m_timeouts . ShortTimeout , cancelToken , async ct =>
await m_oauth . GetResponseAsync ( string . Format ( "{0}/files/{1}" , BOX_API_URL , fileid ), ct , null , "DELETE" ). ConfigureAwait ( false )
). ConfigureAwait ( false ))
2015-12-07 09:57:22 +01:00
{
}
if ( m_deleteFromTrash )
2025-03-20 10:54:38 +01:00
using ( var r = await Utility . Utility . WithTimeout ( m_timeouts . ShortTimeout , cancelToken , async ct
=> await m_oauth . GetResponseAsync ( string . Format ( "{0}/files/{1}/trash" , BOX_API_URL , fileid ), cancelToken , null , "DELETE" ). ConfigureAwait ( false )
). ConfigureAwait ( false ))
2015-12-07 09:57:22 +01:00
{
}
}
catch
{
2018-06-16 11:02:18 -07:00
m_filecache . Clear ();
2015-12-07 09:57:22 +01:00
throw ;
}
}
2024-10-01 09:30:02 +02:00
public Task TestAsync ( CancellationToken cancelToken )
2025-02-17 16:45:51 +01:00
=> this . TestListAsync ( cancelToken );
2015-12-07 09:57:22 +01:00
2024-10-01 09:30:02 +02:00
public Task CreateFolderAsync ( CancellationToken cancellationToken )
2015-12-07 09:57:22 +01:00
{
2024-10-01 09:30:02 +02:00
return GetCurrentFolderAsync ( true , cancellationToken );
2015-12-07 09:57:22 +01:00
}
public string DisplayName
{
get
{
return Strings . Box . DisplayName ;
}
}
public string ProtocolKey
{
get
{
return "box" ;
}
}
public IList < ICommandLineArgument > SupportedCommands
{
2024-09-29 22:00:57 +02:00
get
{
2025-03-20 10:54:38 +01:00
return [
.. AuthIdOptionsHelper . GetOptions ( OAUTH_LOGIN_URL ),
2015-12-07 09:57:22 +01:00
new CommandLineArgument ( REALLY_DELETE_OPTION , CommandLineArgument . ArgumentType . Boolean , Strings . Box . ReallydeleteShort , Strings . Box . ReallydeleteLong ),
2025-03-20 10:54:38 +01:00
.. TimeoutOptionsHelper . GetOptions ()
];
2015-12-07 09:57:22 +01:00
}
}
public string Description
{
get
{
return Strings . Box . Description ;
}
}
2024-10-02 09:37:52 +02:00
public Task < string []> GetDNSNamesAsync ( CancellationToken cancelToken ) => Task . FromResult ( new string [] {
new System . Uri ( BOX_API_URL ). Host ,
new System . Uri ( BOX_UPLOAD_URL ). Host
});
2018-02-18 00:18:44 +01:00
2015-12-07 09:57:22 +01:00
#endregion
#region IDisposable implementation
public void Dispose ()
{
}
#endregion
private class MiniUser : IDReference
{
[JsonProperty("type")]
2025-03-20 10:54:38 +01:00
public string? Type { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("name")]
2025-03-20 10:54:38 +01:00
public string? Name { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("login")]
2025-03-20 10:54:38 +01:00
public string? Login { get ; set ; }
2015-12-07 09:57:22 +01:00
}
private class MiniFolder : IDReference
{
[JsonProperty("type")]
2025-03-20 10:54:38 +01:00
public string? Type { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("name")]
2025-03-20 10:54:38 +01:00
public string? Name { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("etag")]
2025-03-20 10:54:38 +01:00
public string? ETag { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("sequence_id")]
2025-03-20 10:54:38 +01:00
public string? SequenceID { get ; set ; }
2015-12-07 09:57:22 +01:00
}
private class FileEntity : MiniFolder
{
public FileEntity () { Size = - 1 ; }
[JsonProperty("sha1")]
2025-03-20 10:54:38 +01:00
public string? SHA1 { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("size", NullValueHandling = NullValueHandling.Ignore)]
public long Size { get ; set ; }
[JsonProperty("modified_at", NullValueHandling = NullValueHandling.Ignore)]
public DateTime ModifiedAt { get ; set ; }
}
private class FolderList
{
[JsonProperty("total_count")]
public long TotalCount { get ; set ; }
[JsonProperty("entries")]
2025-03-20 10:54:38 +01:00
public MiniFolder []? Entries { get ; set ; }
2015-12-07 09:57:22 +01:00
}
private class FileList
{
[JsonProperty("total_count")]
public long TotalCount { get ; set ; }
[JsonProperty("entries")]
2025-03-20 10:54:38 +01:00
public FileEntity []? Entries { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("offset")]
public long Offset { get ; set ; }
[JsonProperty("limit")]
public long Limit { get ; set ; }
}
private class UploadEmail
{
[JsonProperty("access")]
2025-03-20 10:54:38 +01:00
public string? Access { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("email")]
2025-03-20 10:54:38 +01:00
public string? Email { get ; set ; }
2015-12-07 09:57:22 +01:00
}
private class ListFolderResponse : MiniFolder
{
[JsonProperty("created_at")]
public DateTime CreatedAt { get ; set ; }
[JsonProperty("modified_at")]
public DateTime ModifiedAt { get ; set ; }
[JsonProperty("description")]
2025-03-20 10:54:38 +01:00
public string? Description { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("size")]
public long Size { get ; set ; }
[JsonProperty("path_collection")]
2025-03-20 10:54:38 +01:00
public FolderList ? PathCollection { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("created_by")]
2025-03-20 10:54:38 +01:00
public MiniUser ? CreatedBy { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("modified_by")]
2025-03-20 10:54:38 +01:00
public MiniUser ? ModifiedBy { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("owned_by")]
2025-03-20 10:54:38 +01:00
public MiniUser ? OwnedBy { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("shared_link")]
2025-03-20 10:54:38 +01:00
public MiniUser ? SharedLink { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("folder_upload_email")]
2025-03-20 10:54:38 +01:00
public UploadEmail ? FolderUploadEmail { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("parent")]
2025-03-20 10:54:38 +01:00
public MiniFolder ? Parent { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("item_status")]
2025-03-20 10:54:38 +01:00
public string? ItemStatus { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("item_collection")]
2025-03-20 10:54:38 +01:00
public FileList ? ItemCollection { get ; set ; }
2015-12-07 09:57:22 +01:00
}
private class OrderEntry
{
[JsonProperty("by")]
2025-03-20 10:54:38 +01:00
public string? By { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("direction")]
2025-03-20 10:54:38 +01:00
public string? Direction { get ; set ; }
2015-12-07 09:57:22 +01:00
}
private class ShortListResponse : FileList
{
[JsonProperty("order")]
2025-03-20 10:54:38 +01:00
public OrderEntry []? Order { get ; set ; }
2015-12-07 09:57:22 +01:00
}
private class IDReference
{
[JsonProperty("id")]
2025-03-20 10:54:38 +01:00
public string? ID { get ; set ; }
2015-12-07 09:57:22 +01:00
}
private class CreateItemRequest
{
[JsonProperty("name")]
2025-03-20 10:54:38 +01:00
public string? Name { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("parent")]
2025-03-20 10:54:38 +01:00
public IDReference ? Parent { get ; set ; }
2015-12-07 09:57:22 +01:00
}
private class ErrorResponse
{
[JsonProperty("type")]
2025-03-20 10:54:38 +01:00
public string? Type { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("status")]
public int Status { get ; set ; }
[JsonProperty("code")]
2025-03-20 10:54:38 +01:00
public string? Code { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("help_url")]
2025-03-20 10:54:38 +01:00
public string? HelpUrl { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("message")]
2025-03-20 10:54:38 +01:00
public string? Message { get ; set ; }
2015-12-07 09:57:22 +01:00
[JsonProperty("request_id")]
2025-03-20 10:54:38 +01:00
public string? RequestId { get ; set ; }
2015-12-07 09:57:22 +01:00
}
}
}