Files
FileFlowsPlugins/Nextcloud/Helpers/NextcloudUploader.cs
2024-12-20 17:19:05 +13:00

195 lines
7.3 KiB
C#

using System.Net;
using HttpMethod = System.Net.Http.HttpMethod;
namespace FileFlows.Nextcloud.Helpers;
/// <summary>
/// Nextcloud uploader
/// </summary>
public interface INextcloudUploader
{
/// <summary>
/// Uploads a file to Nextcloud
/// </summary>
/// <param name="localFilePath">The full path to the file on disk to be uploaded.</param>
/// <param name="remoteFilePath">The path in Nextcloud where the file should be uploaded.</param>
/// <returns>True if the file was uploaded successfully; otherwise, false.</returns>
Result<bool> UploadFile(string localFilePath, string remoteFilePath);
}
/// <summary>
/// Helper class for uploading files to Nextcloud
/// </summary>
/// <param name="logger">the Logger to use</param>
/// <param name="nextcloudUrl">The URL of the Nextcloud instance.</param>
/// <param name="username">The username for authentication.</param>
/// <param name="password">The password for authentication.</param>
public class NextcloudUploader(ILogger logger, string nextcloudUrl, string username, string password) : INextcloudUploader
{
private static HttpClient client;
static NextcloudUploader()
{
var handler = new HttpClientHandler
{
// Customize the handler as needed
ServerCertificateCustomValidationCallback =
(sender, cert, chain, sslPolicyErrors) => true // Ignore certificate errors
};
client = new HttpClient(handler);
}
/// <inheritdoc />
public Result<bool> UploadFile(string localFilePath, string remoteFilePath)
{
logger?.ILog("Uploading file: " + localFilePath);
try
{
string remoteFolder = remoteFilePath.Replace("\\", "/");
remoteFolder = string.Join("/", remoteFolder.Split("/")[..^1]);
logger?.ILog("Remote Folder: " + remoteFolder);
var fileStream = File.OpenRead(localFilePath);
int chunkIndex = 1;
int chunkSize = 80_000_000; // Split into chunks of approximately 80 MB
// Calculate the number of chunks based on the size of the compressed stream
long fileSize = fileStream.Length;
int numberOfChunks = (int)Math.Ceiling((double)fileSize / chunkSize);
fileStream.Position = 0; // Reset stream position
// Upload each chunk
while (fileStream.Position < fileSize)
{
int bytesToRead = (int)Math.Min(chunkSize, fileSize - fileStream.Position);
byte[] buffer = new byte[bytesToRead];
_ = fileStream.Read(buffer, 0, bytesToRead);
var chunkStream = new MemoryStream(buffer);
if (chunkIndex == 1)
CreateFolder(remoteFolder).Wait();
if (numberOfChunks == 1)
{
// Upload the chunk here using UploadFilePart method
UploadFilePart(chunkStream, remoteFilePath).Wait();
}
else
{
UploadFilePart(chunkStream, remoteFilePath).Wait();
}
chunkStream.Dispose();
chunkIndex++;
}
logger?.ILog("Upload completed with chunks: " + numberOfChunks);
return true; // All files uploaded successfully
}
catch (Exception ex)
{
return Result<bool>.Fail($"Error uploading file: {ex.Message}");
}
}
/// <summary>
/// Uploads a file to Nextcloud
/// </summary>
/// <param name="compressedStream">The stream to upload.</param>
/// <param name="remoteFilePath">The path in Nextcloud where the file should be uploaded.</param>
/// <returns>True if the file was uploaded successfully; otherwise, false.</returns>
private async Task<bool> UploadFilePart(MemoryStream compressedStream, string remoteFilePath)
{
logger?.ILog("Uploading file: " + remoteFilePath);
try
{
// Create the WebDAV request URL
string url = $"{nextcloudUrl.TrimEnd('/')}/remote.php/dav/files/{username}/{remoteFilePath.TrimStart('/')}";
// Set the credentials
var byteArray = new UTF8Encoding().GetBytes($"{username}:{password}");
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
// Set the content
compressedStream.Position = 0;
using (StreamContent content = new StreamContent(compressedStream))
{
content.Headers.ContentLength = compressedStream.Length;
// Send the PUT request
HttpResponseMessage response = await client.PutAsync(url, content);
// Check for success
if (response.StatusCode == HttpStatusCode.Created)
return true;
}
}
catch (Exception ex)
{
// Handle any errors
logger?.ELog($"Error uploading file: {ex.Message}");
}
return false;
}
/// <summary>
/// Creates a folder on the WebDAV server at the specified path, ensuring all parent directories are created.
/// </summary>
/// <param name="remoteFolderPath">The path of the folder to be created on the server.</param>
/// <returns>True if the folder creation is successful, otherwise false.</returns>
private async Task<bool> CreateFolder(string remoteFolderPath)
{
logger?.ILog("Creating folder: " + remoteFolderPath);
try
{
// Create the WebDAV request URL
string baseUrl = $"{nextcloudUrl.TrimEnd('/')}/remote.php/dav/files/{username}";
// Split the remoteFolderPath into parts and create each folder sequentially
string[] folders = remoteFolderPath.Trim('/').Split('/');
string currentPath = string.Empty;
foreach (var folder in folders)
{
currentPath = string.IsNullOrEmpty(currentPath) ? folder : $"{currentPath}/{folder}";
string url = $"{baseUrl}/{currentPath}";
// Set the credentials
var byteArray = new UTF8Encoding().GetBytes($"{username}:{password}");
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
// Create the MKCOL request
HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("MKCOL"), url);
// Send the request
HttpResponseMessage response = await client.SendAsync(request);
// Check for success, ignore if it already exists
if (response.StatusCode != HttpStatusCode.Created &&
response.StatusCode != HttpStatusCode.MethodNotAllowed)
{
logger?.ELog($"Failed to create folder: {currentPath}, Status: {response.StatusCode}");
return false;
}
}
return true;
}
catch (Exception ex)
{
// Handle any errors
logger?.ELog($"Error creating folder: {ex.Message}");
return false;
}
}
}