diff --git a/.github/workflows/External-Storage-Tests.yml b/.github/workflows/External-Storage-Tests.yml
new file mode 100644
index 000000000..fdd2ecc3c
--- /dev/null
+++ b/.github/workflows/External-Storage-Tests.yml
@@ -0,0 +1,100 @@
+name: External Storage Tests
+
+on:
+ pull_request:
+ branches:
+ - 'master'
+ - 'release-*'
+ push:
+ branches:
+ - 'master'
+ - 'beta'
+ - 'release-*'
+
+jobs:
+ build:
+ env:
+ GIT_REF: ${{ github.ref }}
+ GIT_SHA: ${{ github.sha }}
+ Configuration: Release
+ SolutionFile: dotnet\DotNetStandardClasses.sln
+
+ runs-on: windows-latest
+ environment: external-storage-tests
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Install .NET Core 3.1
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: '3.1.x'
+
+ - name: Install .NET 6
+ uses: actions/setup-dotnet@v1.7.2
+ with:
+ dotnet-version: '6.0.100-preview.3.21202.5'
+
+ - uses: actions/setup-dotnet@v1
+ with:
+ source-url: https://nuget.pkg.github.com/genexuslabs/index.json
+ env:
+ NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
+
+ - name: Calculate environment variables
+ run: |
+ $IsPrerelease = !($Env:GIT_REF -match 'release-[0-9]+(?:\.[0-9]+)?$')
+ echo "IsPrerelease=$IsPrerelease" >> $env:GITHUB_ENV
+
+ $COMMIT_NUMBER = @($(git rev-list --count origin/master..), $(git rev-list --count HEAD))[$IsPrerelease]
+
+ echo "COMMIT_NUMBER=$COMMIT_NUMBER" >> $env:GITHUB_ENV
+
+ - name: Calculate package version
+ env:
+ PackageVersionString: ./.github/generatePackageVersion.ps1
+ run: |
+ $NuGetPackageVersion = & "$Env:PackageVersionString"
+
+ Write-Output "Packge version to be used: $NuGetPackageVersion"
+
+ echo "NuGetPackageVersion=$NuGetPackageVersion" >> $env:GITHUB_ENV
+
+ - name: Restore packages
+ run: dotnet restore $Env:SolutionFile
+
+ - name: Build
+ run: dotnet build $Env:SolutionFile --no-restore --configuration $Env:Configuration
+
+ - name: Test External Storage
+ run: |
+ $Env:AWSS3_TEST_ENABLED="true"
+ $Env:STORAGE_AWSS3_ACCESS_KEY="${{ secrets.AWSS3_ACCESS_KEY }}"
+ $Env:STORAGE_AWSS3_SECRET_KEY="${{ secrets.AWSS3_SECRET_KEY }}"
+ $Env:STORAGE_AWSS3_BUCKET_NAME="genexus-s3-test"
+ $Env:STORAGE_AWSS3_FOLDER_NAME="gxclasses"
+ $Env:STORAGE_AWSS3_REGION="us-east-1"
+ $Env:IBMCOS_TEST_ENABLED="true"
+ $Env:STORAGE_IBMCOS_ACCESS_KEY="${{ secrets.IBMCOS_ACCESS_KEY }}"
+ $Env:STORAGE_IBMCOS_SECRET_KEY="${{ secrets.IBMCOS_SECRET_KEY }}"
+ $Env:STORAGE_IBMCOS_BUCKET_NAME="gxclasses-unit-tests"
+ $Env:STORAGE_IBMCOS_FOLDER_NAME="tests"
+ $Env:STORAGE_IBMCOS_REGION="us-south"
+ $Env:AZUREBS_TEST_ENABLED="true"
+ $Env:STORAGE_AZUREBS_ACCESS_KEY="${{ secrets.AZUREBS_ACCESS_KEY }}"
+ $Env:STORAGE_AZUREBS_ACCOUNT_NAME="${{ secrets.AZUREBS_ACCOUNT_NAME }}"
+ $Env:STORAGE_AZUREBS_FOLDER_NAME="tests"
+ $Env:STORAGE_AZUREBS_PUBLIC_CONTAINER_NAME="contluispublic"
+ $Env:STORAGE_AZUREBS_PRIVATE_CONTAINER_NAME="contluisprivate"
+ $Env:GOOGLECS_TEST_ENABLED="true"
+ $Env:STORAGE_GOOGLECS_KEY='${{ secrets.GOOGLECS_KEY }}'
+ $Env:STORAGE_GOOGLECS_PROJECT_ID="gxjavacloudstorageunittests"
+ $Env:STORAGE_GOOGLECS_BUCKET_NAME="javaclasses-unittests"
+ $Env:STORAGE_GOOGLECS_FOLDER_NAME="gxclasses"
+ $Env:STORAGE_GOOGLECS_APPLICATION_NAME="gxjavacloudstorageunittests"
+
+ dotnet test $Env:SolutionFile --no-restore --no-build --configuration $Env:Configuration
+
diff --git a/dotnet/DotNetStandardClasses.sln b/dotnet/DotNetStandardClasses.sln
index b52cfbd94..d754b07fd 100644
--- a/dotnet/DotNetStandardClasses.sln
+++ b/dotnet/DotNetStandardClasses.sln
@@ -65,8 +65,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GXAmazonS3", "src\dotnetfra
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GXAzureStorage", "src\dotnetframework\Providers\Storage\GXAzureStorage\GXAzureStorage.csproj", "{F6372249-AF37-4455-B572-5BDF8DE2ACC8}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GXBluemix", "src\dotnetframework\Providers\Storage\GXBluemix\GXBluemix.csproj", "{FCA5536D-542E-480E-92B0-316E0290553B}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GXGoogleCloud", "src\dotnetframework\Providers\Storage\GXGoogleCloud\GXGoogleCloud.csproj", "{E9072D95-D116-4D4B-B981-46146BCDE052}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GXOpenStack", "src\dotnetframework\Providers\Storage\GXOpenStack\GXOpenStack.csproj", "{64E958D8-CE7B-482D-B339-3B52E26CA4AC}"
@@ -241,10 +239,6 @@ Global
{F6372249-AF37-4455-B572-5BDF8DE2ACC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F6372249-AF37-4455-B572-5BDF8DE2ACC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F6372249-AF37-4455-B572-5BDF8DE2ACC8}.Release|Any CPU.Build.0 = Release|Any CPU
- {FCA5536D-542E-480E-92B0-316E0290553B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {FCA5536D-542E-480E-92B0-316E0290553B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FCA5536D-542E-480E-92B0-316E0290553B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {FCA5536D-542E-480E-92B0-316E0290553B}.Release|Any CPU.Build.0 = Release|Any CPU
{E9072D95-D116-4D4B-B981-46146BCDE052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E9072D95-D116-4D4B-B981-46146BCDE052}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9072D95-D116-4D4B-B981-46146BCDE052}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -378,7 +372,6 @@ Global
{F82842DA-F15E-49C5-993E-4C269818FF1F} = {F900A4AD-7249-41B4-B918-CB9E8C73747C}
{3701565F-1A95-4592-B90D-291CCDE5505D} = {F82842DA-F15E-49C5-993E-4C269818FF1F}
{F6372249-AF37-4455-B572-5BDF8DE2ACC8} = {F82842DA-F15E-49C5-993E-4C269818FF1F}
- {FCA5536D-542E-480E-92B0-316E0290553B} = {F82842DA-F15E-49C5-993E-4C269818FF1F}
{E9072D95-D116-4D4B-B981-46146BCDE052} = {F82842DA-F15E-49C5-993E-4C269818FF1F}
{64E958D8-CE7B-482D-B339-3B52E26CA4AC} = {F82842DA-F15E-49C5-993E-4C269818FF1F}
{A14C2C2C-ACE3-4712-A527-E4E5F02729FA} = {F900A4AD-7249-41B4-B918-CB9E8C73747C}
diff --git a/dotnet/src/dotnetcore/GxClasses/GxClasses.csproj b/dotnet/src/dotnetcore/GxClasses/GxClasses.csproj
index 5287d3e2e..ad96ce5a5 100644
--- a/dotnet/src/dotnetcore/GxClasses/GxClasses.csproj
+++ b/dotnet/src/dotnetcore/GxClasses/GxClasses.csproj
@@ -78,6 +78,7 @@
+
diff --git a/dotnet/src/dotnetframework/GxClasses/Configuration/ExternalStorage.cs b/dotnet/src/dotnetframework/GxClasses/Configuration/ExternalStorage.cs
index 37b103595..d37cfcfbb 100644
--- a/dotnet/src/dotnetframework/GxClasses/Configuration/ExternalStorage.cs
+++ b/dotnet/src/dotnetframework/GxClasses/Configuration/ExternalStorage.cs
@@ -95,7 +95,7 @@ public bool Connect(string profileName, GXProperties properties, ref GxStoragePr
private void preprocess(String name, GXProperties properties)
{
- string className = null;
+ string className;
switch (name)
{
@@ -104,37 +104,27 @@ private void preprocess(String name, GXProperties properties)
className = "GeneXus.Storage.GXAmazonS3.ExternalProviderS3";
SetDefaultProperty(properties, "STORAGE_PROVIDER_REGION", "us-east-1");
SetDefaultProperty(properties, "STORAGE_ENDPOINT", "s3.amazonaws.com");
- SetEncryptProperty(properties, "STORAGE_PROVIDER_ACCESSKEYID");
- SetEncryptProperty(properties, "STORAGE_PROVIDER_SECRETACCESSKEY");
- SetEncryptProperty(properties, "BUCKET_NAME");
+ SetEncryptedProperty(properties, "STORAGE_PROVIDER_ACCESSKEYID");
+ SetEncryptedProperty(properties, "STORAGE_PROVIDER_SECRETACCESSKEY");
+ SetEncryptedProperty(properties, "BUCKET_NAME");
break;
case "AZURESTORAGE":
className = "GeneXus.Storage.GXAzureStorage.AzureStorageExternalProvider";
- SetEncryptProperty(properties, "PUBLIC_CONTAINER_NAME");
- SetEncryptProperty(properties, "PRIVATE_CONTAINER_NAME");
- SetEncryptProperty(properties, "ACCOUNT_NAME");
- SetEncryptProperty(properties, "ACCESS_KEY");
+ SetEncryptedProperty(properties, "PUBLIC_CONTAINER_NAME");
+ SetEncryptedProperty(properties, "PRIVATE_CONTAINER_NAME");
+ SetEncryptedProperty(properties, "ACCOUNT_NAME");
+ SetEncryptedProperty(properties, "ACCESS_KEY");
break;
-
- case "BLUEMIXSTORAGE":
- className = "GeneXus.Storage.GXBluemix.ExternalProviderBluemix";
- SetDefaultProperty(properties, "SERVER_URL", "https://identity.open.softlayer.com");
- SetDefaultProperty(properties, "STORAGE_PROVIDER_REGION", "dallas");
- SetEncryptProperty(properties, "PUBLIC_BUCKET_NAME");
- SetEncryptProperty(properties, "PRIVATE_BUCKET_NAME");
- SetEncryptProperty(properties, "STORAGE_PROVIDER_USER");
- SetEncryptProperty(properties, "STORAGE_PROVIDER_PASSWORD");
- break;
-
+
//case "BOX":
// className = "{class}";
// break;
case "GOOGLE":
className = "GeneXus.Storage.GXGoogleCloud.ExternalProviderGoogle";
- SetEncryptProperty(properties, "KEY");
- SetEncryptProperty(properties, "BUCKET_NAME");
+ SetEncryptedProperty(properties, "KEY");
+ SetEncryptedProperty(properties, "BUCKET_NAME");
break;
//case "IBMCOS":
@@ -146,9 +136,9 @@ private void preprocess(String name, GXProperties properties)
case "OPENSTACKSTORAGE":
className = "GeneXus.Storage.GXOpenStack.ExternalProviderOpenStack";
- SetEncryptProperty(properties, "BUCKET_NAME");
- SetEncryptProperty(properties, "STORAGE_PROVIDER_USER");
- SetEncryptProperty(properties, "STORAGE_PROVIDER_PASSWORD");
+ SetEncryptedProperty(properties, "BUCKET_NAME");
+ SetEncryptedProperty(properties, "STORAGE_PROVIDER_USER");
+ SetEncryptedProperty(properties, "STORAGE_PROVIDER_PASSWORD");
break;
default:
@@ -171,7 +161,7 @@ private void SetDefaultProperty(GXProperties properties, String prop, String val
properties.Set(prop, value);
}
- private void SetEncryptProperty(GXProperties properties, String prop)
+ private void SetEncryptedProperty(GXProperties properties, String prop)
{
String value = properties.Get(prop);
if (string.IsNullOrEmpty(value))
diff --git a/dotnet/src/dotnetframework/GxClasses/Core/GXApplication.cs b/dotnet/src/dotnetframework/GxClasses/Core/GXApplication.cs
index a31c42067..2cd62a6f1 100644
--- a/dotnet/src/dotnetframework/GxClasses/Core/GXApplication.cs
+++ b/dotnet/src/dotnetframework/GxClasses/Core/GXApplication.cs
@@ -679,13 +679,10 @@ public static bool GetHttpRequestPostedFile(IGxContext gxContext, string varName
if (httpContext != null)
{
HttpPostedFile pf = httpContext.Request.GetFile(varName);
- if (pf != null)
+ if (pf != null && pf.ContentLength > 0)
{
-#pragma warning disable SCS0018 // Path traversal: injection possible in {1} argument passed to '{0}'
- FileInfo fi = new FileInfo(pf.FileName);
-#pragma warning restore SCS0018 // Path traversal: injection possible in {1} argument passed to '{0}'
string tempDir = Preferences.getTMP_MEDIA_PATH();
- string ext = fi.Extension;
+ string ext = Path.GetExtension(pf.FileName);
if (ext != null)
ext = ext.TrimStart('.');
string filePath = FileUtil.getTempFileName(tempDir);
@@ -695,7 +692,7 @@ public static bool GetHttpRequestPostedFile(IGxContext gxContext, string varName
string fileGuid = GxUploadHelper.GetUploadFileGuid();
fileToken = GxUploadHelper.GetUploadFileId(fileGuid);
- GxUploadHelper.CacheUploadFile(fileGuid, filePath, Path.GetFileName(pf.FileName), ext, file, gxContext);
+ GxUploadHelper.CacheUploadFile(fileGuid, Path.GetFileName(pf.FileName), ext, file, gxContext);
return true;
}
diff --git a/dotnet/src/dotnetframework/GxClasses/Core/GXUtils.cs b/dotnet/src/dotnetframework/GxClasses/Core/GXUtils.cs
index 20e1ce69d..23d3d8c80 100644
--- a/dotnet/src/dotnetframework/GxClasses/Core/GXUtils.cs
+++ b/dotnet/src/dotnetframework/GxClasses/Core/GXUtils.cs
@@ -1116,12 +1116,13 @@ public bool Upload(string filefullpath, string storageobjectfullname, GxFile upl
try
{
ValidProvider();
+ GxFileType acl = GxFileType.PublicRead;
if (String.IsNullOrEmpty(storageobjectfullname))
{
storageobjectfullname = Path.GetFileName(filefullpath);
}
- string url = provider.Upload(filefullpath, storageobjectfullname, GxFileType.Public);
- uploadedFile.FileInfo = new GxExternalFileInfo(storageobjectfullname, url, provider);
+ string url = provider.Upload(filefullpath, storageobjectfullname, acl);
+ uploadedFile.FileInfo = new GxExternalFileInfo(storageobjectfullname, url, provider, acl);
return true;
}
catch (Exception ex)
@@ -1139,8 +1140,9 @@ public bool UploadPrivate(string filefullpath, string storageobjectfullname, GxF
{
storageobjectfullname=Path.GetFileName(filefullpath);
}
- string url = provider.Upload(filefullpath, storageobjectfullname, GxFileType.Private);
- uploadedFile.FileInfo = new GxExternalFileInfo(storageobjectfullname, url, provider);
+ GxFileType acl = GxFileType.Private;
+ string url = provider.Upload(filefullpath, storageobjectfullname, acl);
+ uploadedFile.FileInfo = new GxExternalFileInfo(storageobjectfullname, url, provider, acl);
return true;
}
catch (Exception ex)
@@ -1160,7 +1162,7 @@ public bool Download(string storageobjectfullname, GxFile localFile, GXBaseColle
destFileName = localFile.GetAbsoluteName();
else
destFileName = Path.Combine(GxContext.StaticPhysicalPath(), localFile.Source);
- provider.Download(storageobjectfullname, destFileName, GxFileType.Public);
+ provider.Download(storageobjectfullname, destFileName, GxFileType.PublicRead);
return true;
}
catch (Exception ex)
@@ -1197,7 +1199,8 @@ public bool Get(string storageobjectfullname, GxFile externalFile, GXBaseCollect
try
{
ValidProvider();
- string url = provider.Get(storageobjectfullname, GxFileType.Public, 0);
+ GxFileType acl = GxFileType.PublicRead;
+ string url = provider.Get(storageobjectfullname, acl, 0);
if (String.IsNullOrEmpty(url))
{
GXUtil.ErrorToMessages("Get Error", "File doesn't exists", messages);
@@ -1205,7 +1208,7 @@ public bool Get(string storageobjectfullname, GxFile externalFile, GXBaseCollect
}
else
{
- externalFile.FileInfo = new GxExternalFileInfo(storageobjectfullname, url, provider);
+ externalFile.FileInfo = new GxExternalFileInfo(storageobjectfullname, url, provider, acl);
return true;
}
}
@@ -1222,7 +1225,8 @@ public bool GetPrivate(string storageobjectfullname, GxFile externalFile, int ex
try
{
ValidProvider();
- string url = provider.Get(storageobjectfullname, GxFileType.Private, expirationMinutes);
+ GxFileType acl = GxFileType.Private;
+ string url = provider.Get(storageobjectfullname, acl, expirationMinutes);
if (String.IsNullOrEmpty(url))
{
GXUtil.ErrorToMessages("Get Error", "File doesn't exists", messages);
@@ -1230,7 +1234,7 @@ public bool GetPrivate(string storageobjectfullname, GxFile externalFile, int ex
}
else
{
- externalFile.FileInfo = new GxExternalFileInfo(storageobjectfullname, url, provider, GxFileType.Private);
+ externalFile.FileInfo = new GxExternalFileInfo(storageobjectfullname, url, provider, acl);
return true;
}
}
diff --git a/dotnet/src/dotnetframework/GxClasses/Core/GXUtilsCommon.cs b/dotnet/src/dotnetframework/GxClasses/Core/GXUtilsCommon.cs
index 49b3376d8..9b8e42068 100644
--- a/dotnet/src/dotnetframework/GxClasses/Core/GXUtilsCommon.cs
+++ b/dotnet/src/dotnetframework/GxClasses/Core/GXUtilsCommon.cs
@@ -37,6 +37,8 @@
using System.Security.Cryptography;
using System.Collections.Concurrent;
using System.Drawing.Drawing2D;
+using GeneXus.Storage;
+using GeneXus.Services;
namespace GeneXus.Utils
{
@@ -3364,7 +3366,7 @@ public static string UriToPath(string uriString)
return uriString;
}
}
- public static string getTempFileName(string baseDir, string name="", string extension="tmp", GxFileType fileType = GxFileType.Public)
+ public static string getTempFileName(string baseDir, string name="", string extension="tmp", GxFileType fileType = GxFileType.Private)
{
name = FixFileName(FileUtil.FileNamePrettify(name), string.Empty);
return tempFileName(baseDir, name, extension);
@@ -3409,7 +3411,7 @@ public static string GetFileType(string FileName)
if (GxUploadHelper.IsUpload(FileName))
{
- return new GxFile(string.Empty, FileName, GxFileType.Private).GetExtension();
+ return new GxFile(string.Empty, FileName, GxFileType.PrivateAttribute).GetExtension();
}
string extension = string.Empty;
@@ -3454,7 +3456,7 @@ public static string GetFileName(string FileName)
if (GxUploadHelper.IsUpload(FileName))
{
- FileName = new GxFile(string.Empty, FileName, GxFileType.Private).GetName();
+ FileName = new GxFile(string.Empty, FileName, GxFileType.PrivateAttribute).GetName();
}
try
{
@@ -5257,7 +5259,14 @@ public static string ResolveUri(string uriString, IGxContext context = null)
public static string ResolveUri(string uriString, bool absUrl, IGxContext context = null)
{
if (String.IsNullOrEmpty(uriString))
- return "";
+ return string.Empty;
+
+ string providerObjectName;
+ if (PathUtil.IsAbsoluteUrl(uriString) && StorageFactory.TryGetProviderObjectName(ServiceFactory.GetExternalProvider(), uriString, out providerObjectName))
+ {
+ return new GxFile(string.Empty, providerObjectName, GxFileType.DefaultAttribute).GetURI();
+ }
+
if (schemeRegex.IsMatch(uriString))
{
string fileName = schemeRegex.Replace(uriString, "");
@@ -5265,7 +5274,7 @@ public static string ResolveUri(string uriString, bool absUrl, IGxContext contex
string basePath = Path.Combine(Path.Combine(Preferences.getBLOB_PATH(), MultimediaDirectory));
try
{
- GxFile file = new GxFile(string.Empty, PathUtil.SafeCombine(basePath, fileName), GxFileType.PublicAttribute);
+ GxFile file = new GxFile(string.Empty, PathUtil.SafeCombine(basePath, fileName), GxFileType.PrivateAttribute);
return PathToUrl(file.GetURI(), absUrl, context);
}
catch (ArgumentException ex)
@@ -5318,7 +5327,7 @@ public static string GetUriFromFile(string name, string type, string path)
{
if (GxUploadHelper.IsUpload(path))
{
- return new GxFile(string.Empty, path, GxFileType.Private).GetName();
+ return new GxFile(string.Empty, path, GxFileType.PrivateAttribute).GetName();
}
string fromPathType = Path.GetExtension(path);
if (!String.IsNullOrEmpty(fromPathType) && fromPathType != "tmp")
diff --git a/dotnet/src/dotnetframework/GxClasses/Data/GXDataCommon.cs b/dotnet/src/dotnetframework/GxClasses/Data/GXDataCommon.cs
index dae28864a..122c48a7b 100644
--- a/dotnet/src/dotnetframework/GxClasses/Data/GXDataCommon.cs
+++ b/dotnet/src/dotnetframework/GxClasses/Data/GXDataCommon.cs
@@ -903,7 +903,7 @@ protected static byte[] GetBinary(string fileNameParm, bool dbBlob)
{
if (ServiceFactory.GetExternalProvider() != null)
{
- GxFile file = new GxFile(string.Empty, fileNameParm, GxFileType.Private);
+ GxFile file = new GxFile(string.Empty, fileNameParm, GxFileType.PrivateAttribute);
if (file.Exists())
{
binary = file.ToByteArray();
diff --git a/dotnet/src/dotnetframework/GxClasses/Data/GXDataNTierADO.cs b/dotnet/src/dotnetframework/GxClasses/Data/GXDataNTierADO.cs
index 0219ffb9d..eab6eebd3 100644
--- a/dotnet/src/dotnetframework/GxClasses/Data/GXDataNTierADO.cs
+++ b/dotnet/src/dotnetframework/GxClasses/Data/GXDataNTierADO.cs
@@ -10,8 +10,7 @@
using System.Collections.Generic;
using GeneXus.Services;
using System.Net;
-using System.Diagnostics;
-using System.Linq;
+using GeneXus.Storage;
namespace GeneXus.Data.NTier.ADO
{
@@ -105,7 +104,7 @@ public string getBLOBFile(int id)
public string getBLOBFile(int id, string extension, string name)
{
- string fileName = FileUtil.getTempFileName(_gxDbCommand.Conn.BlobPath, name, extension, GxFileType.PrivateAttribute);
+ string fileName = FileUtil.getTempFileName(_gxDbCommand.Conn.BlobPath, name, extension, GxFileType.Private);
return getBLOBFile(id, extension, name, fileName, true);
}
@@ -149,7 +148,7 @@ public string getMultimediaFile(int id, string gxdbFileUri)
string filePath = PathUtil.SafeCombine(_gxDbCommand.Conn.MultimediaPath, fileName);
try
{
- GxFile file = new GxFile(string.Empty, filePath, GxFileType.PublicAttribute);
+ GxFile file = new GxFile(string.Empty, filePath, GxFileType.DefaultAttribute);
if (file.Exists())
{
return filePath;
@@ -331,7 +330,7 @@ public string getBLOBFile(int id)
public string getBLOBFile(int id, string extension, string name)
{
- string fileName = FileUtil.getTempFileName(_gxDbCommand.Conn.BlobPath, name, extension, GxFileType.PrivateAttribute);
+ string fileName = FileUtil.getTempFileName(_gxDbCommand.Conn.BlobPath, name, extension, GxFileType.Private);
String value = getBLOBFile(id, extension, name, fileName, true);
TraceRow("getBLOBFile - index : ", id.ToString(), " value:", (value!=null ? value.ToString() : string.Empty));
return value;
@@ -345,7 +344,7 @@ private string getBLOBFile(int id, string extension, string name, string fileNam
int bufferSize = 4096;
byte[] outbyte = new byte[bufferSize];
long retval;
- long startIndex = 0;
+ long startIndex;
bool streamClosed = false;
try
{
@@ -433,7 +432,7 @@ public string getMultimediaFile(int id, string gxdbFileUri)
try
{
- GxFile file = new GxFile(string.Empty, filePath, GxFileType.PublicAttribute);
+ GxFile file = new GxFile(string.Empty, filePath, GxFileType.DefaultAttribute);
if (file.Exists())
{
@@ -441,7 +440,7 @@ public string getMultimediaFile(int id, string gxdbFileUri)
}
else
{
- return getBLOBFile(id, FileUtil.GetFileType(gxdbFileUri), FileUtil.GetFileName(gxdbFileUri), filePath, false, GxFileType.PublicAttribute);
+ return getBLOBFile(id, FileUtil.GetFileType(gxdbFileUri), FileUtil.GetFileName(gxdbFileUri), filePath, false, GxFileType.DefaultAttribute);
}
}
catch (ArgumentException)
@@ -456,7 +455,7 @@ public string getMultimediaFile(int id, string gxdbFileUri)
public string getMultimediaUri(int id)
{
- return GXDbFile.ResolveUri(getVarchar(id), true, _gxDbCommand.Conn.DataStore.Context);
+ return getMultimediaUri(id, true);
}
public string getMultimediaUri(int id, bool absUrl)
@@ -613,11 +612,11 @@ public void SetParameterMultimedia(int id, string image_gxi, string image, strin
string objectName;
//file is already on the cloud p.e. https://s3.amazonaws.com/Test/PublicTempStorage/multimedia/Image_ad013b5b050c4bf199f544b5561d9b92.png
//Must be copied to https://s3.amazonaws.com/Test/TableName/FieldName/Image_ad013b5b050c4bf199f544b5561d9b92.png
- if (ServiceFactory.GetExternalProvider().GetObjectNameFromURL(image_gxi, out objectName))
+ if (ServiceFactory.GetExternalProvider().TryGetObjectNameFromURL(image_gxi, out objectName))
{
try
{
- multimediaUri = ServiceFactory.GetExternalProvider().Copy(image_gxi, GXDbFile.GenerateUri(image_gxi, !GXDbFile.HasToken(image_gxi), false), tableName, fieldName, GxFileType.PublicAttribute);
+ multimediaUri = ServiceFactory.GetExternalProvider().Copy(image_gxi, GXDbFile.GenerateUri(image_gxi, !GXDbFile.HasToken(image_gxi), false), tableName, fieldName, GxFileType.DefaultAttribute);
GXLogging.Debug(log, "Copy file already in ExternalProvider:", multimediaUri);
}
catch (Exception ex)
@@ -635,7 +634,7 @@ public void SetParameterMultimedia(int id, string image_gxi, string image, strin
using (var fileStream = new MemoryStream(new WebClient().DownloadData(image_gxi)))
{
//Cannot pass Http Stream directly, because some Providers (AWS S3) does not support Http Stream.
- multimediaUri = ServiceFactory.GetExternalProvider().Save(fileStream, GXDbFile.GenerateUri(image_gxi, !GXDbFile.HasToken(image_gxi), false), tableName, fieldName, GxFileType.Public);
+ multimediaUri = ServiceFactory.GetExternalProvider().Save(fileStream, GXDbFile.GenerateUri(image_gxi, !GXDbFile.HasToken(image_gxi), false), tableName, fieldName, GxFileType.DefaultAttribute);
GXLogging.Debug(log, "Upload external file to ExternalProvider:", multimediaUri);
}
#pragma warning disable SYSLIB0014 // WebClient
@@ -657,7 +656,7 @@ public void SetParameterMultimedia(int id, string image_gxi, string image, strin
String fileName = PathUtil.GetValidFileName(fileFullName, "_");
using (fileStream)
{
- multimediaUri = ServiceFactory.GetExternalProvider().Save(fileStream, GXDbFile.GenerateUri(fileName, !GXDbFile.HasToken(fileName), false), tableName, fieldName, GxFileType.Public);
+ multimediaUri = ServiceFactory.GetExternalProvider().Save(fileStream, GXDbFile.GenerateUri(fileName, !GXDbFile.HasToken(fileName), false), tableName, fieldName, GxFileType.DefaultAttribute);
GXLogging.Debug(log, "Upload file (_gxi) to ExternalProvider:", multimediaUri);
}
}
@@ -665,7 +664,7 @@ public void SetParameterMultimedia(int id, string image_gxi, string image, strin
{
try
{
- multimediaUri = ServiceFactory.GetExternalProvider().Copy(image, GXDbFile.GenerateUri(image_gxi, !GXDbFile.HasToken(image_gxi), false), tableName, fieldName, GxFileType.Public);
+ multimediaUri = ServiceFactory.GetExternalProvider().Copy(image, GXDbFile.GenerateUri(image_gxi, !GXDbFile.HasToken(image_gxi), false), tableName, fieldName, GxFileType.DefaultAttribute);
GXLogging.Debug(log, "Copy external file in ExternalProvider:", multimediaUri);
}
catch(Exception e)
@@ -726,7 +725,7 @@ private static string PushToExternalProvider(Stream fileStream, string externalF
string multimediaUri;
using (fileStream)
{
- multimediaUri = ServiceFactory.GetExternalProvider().Save(fileStream, externalFileName, tableName, fieldName, GxFileType.PublicAttribute);
+ multimediaUri = ServiceFactory.GetExternalProvider().Save(fileStream, externalFileName, tableName, fieldName, GxFileType.DefaultAttribute);
GXLogging.Debug(log, "Upload file to ExternalProvider:", multimediaUri);
}
diff --git a/dotnet/src/dotnetframework/GxClasses/Domain/GXFileIO.cs b/dotnet/src/dotnetframework/GxClasses/Domain/GXFileIO.cs
index 8fe6a1f61..b7f0c1d79 100644
--- a/dotnet/src/dotnetframework/GxClasses/Domain/GXFileIO.cs
+++ b/dotnet/src/dotnetframework/GxClasses/Domain/GXFileIO.cs
@@ -209,7 +209,7 @@ public IGxFileInfo[] GetFiles(string searchPattern)
searchPattern = "";
List files = _provider.GetFiles(_name, searchPattern);
- GxExternalFileInfo[] externalFiles = files.Select(elem => new GxExternalFileInfo(elem, _provider, GxFileType.Public)).ToArray();
+ GxExternalFileInfo[] externalFiles = files.Select(elem => new GxExternalFileInfo(elem, _provider, GxFileType.Default)).ToArray();
return externalFiles;
}
@@ -412,7 +412,7 @@ public class GxExternalFileInfo : IGxFileInfo
private string _name;
private ExternalProvider _provider;
private string _url;
- private GxFileType _fileTypeAtt = GxFileType.Public;
+ private GxFileType _fileTypeAtt = GxFileType.Private;
public GxExternalFileInfo(ExternalProvider provider)
{
@@ -421,32 +421,32 @@ public GxExternalFileInfo(ExternalProvider provider)
_url = "";
}
- public GxExternalFileInfo(string storageObjectFullname, ExternalProvider provider, GxFileType fileType)
+ public GxExternalFileInfo(string objectPath, ExternalProvider provider, GxFileType fileType)
{
- storageObjectFullname = storageObjectFullname!=null ? storageObjectFullname.Replace('\\', '/') : storageObjectFullname;
- _name = storageObjectFullname;
+ objectPath = !String.IsNullOrEmpty(objectPath) ? objectPath.Replace('\\', '/') : objectPath;
_provider = provider;
+ _fileTypeAtt = fileType;
+ _name = objectPath;
+
Uri result;
- if (Uri.TryCreate(storageObjectFullname, UriKind.Absolute, out result) && result.IsAbsoluteUri)
+ if (Uri.TryCreate(objectPath, UriKind.Absolute, out result) && result.IsAbsoluteUri)
{
- _url = storageObjectFullname;
+ _url = objectPath;
}
else {
- if (fileType.HasFlag(GxFileType.Attribute)) //Attributes multimedia consider Storage Provider Folder
+ string folderName = ((ExternalProviderBase)provider).Folder;
+ if (!string.IsNullOrEmpty(folderName) && fileType.HasFlag(GxFileType.Attribute) && !_name.StartsWith(folderName))
{
- _url = provider.GetBaseURL() + storageObjectFullname;
- _name = _url.Replace(provider.StorageUri, string.Empty);
- if (_name.StartsWith("/"))
- _name = _name.Substring(1, _name.Length - 1);
+ _url = $"{provider.GetBaseURL()}{_name}";
+ _name = $"{folderName}{StorageUtils.DELIMITER}{_name}";
}
- }
- _fileTypeAtt = fileType;
+ }
}
- public GxExternalFileInfo(string storageObjectFullname, string url, ExternalProvider provider, GxFileType fileType = GxFileType.Public)
- {
- _name = storageObjectFullname;
- _provider = provider;
+ public GxExternalFileInfo(string objectPath, string url, ExternalProvider provider, GxFileType fileType = GxFileType.Private)
+ {
+ _name = StorageFactory.GetProviderObjectAbsoluteUriSafe(provider, objectPath);
+ _provider = provider;
_url = url;
_fileTypeAtt = fileType;
}
@@ -576,12 +576,8 @@ public string AbsolutePath
private string URL
{
- get {
- if (string.IsNullOrEmpty(_url))
- {
- _url = _provider.Get(_name, _fileTypeAtt, 0);
- }
- return _url;
+ get {
+ return _provider.GetUrl(_name, _fileTypeAtt, 0);
}
}
@@ -640,11 +636,12 @@ public Stream GetStream()
[Flags]
public enum GxFileType
{
- Public = 0,
- Private = 1,
- Attribute = 2,
- PublicAttribute = Attribute | Public,
- PrivateAttribute = Attribute | Private,
+ Default = 0,
+ PublicRead = 1,
+ Private = 2,
+ Attribute = 8,
+ DefaultAttribute = Attribute | Default,
+ PrivateAttribute = Attribute | Private
}
public class GxFile
@@ -670,13 +667,13 @@ public GxFile(string baseDirectory)
_baseDirectory = _baseDirectory.Substring(0, _baseDirectory.Length - 1);
}
- public GxFile(string baseDirectory, IGxFileInfo file, GxFileType fileType = GxFileType.Public)
+ public GxFile(string baseDirectory, IGxFileInfo file, GxFileType fileType = GxFileType.Private)
: this(baseDirectory)
{
_file = file;
}
- public GxFile(string baseDirectory, string fileName, GxFileType fileType = GxFileType.Public)
+ public GxFile(string baseDirectory, string fileName, GxFileType fileType = GxFileType.Private)
: this(baseDirectory)
{
if (GxUploadHelper.IsUpload(fileName))
@@ -733,22 +730,26 @@ public string Source
}
else
{
+ _file = null;
if (GxUploadHelper.IsUpload(value))
{
_uploadFileId = value;
value = GxUploadHelper.UploadPath(value);
+ ExternalProvider provider = ServiceFactory.GetExternalProvider();
+ _file = (provider != null)? new GxExternalFileInfo(value, String.Empty, provider): _file;
}
-
- if (IsAbsoluteUrl(value))
- {
- string objName = string.Empty;
- ExternalProvider p = StorageFactory.GetExternalProviderFromUrl(value, out objName);
- _file = new GxExternalFileInfo(objName, value, p);
- }
- else
+
+ if (_file == null)
{
- _file = new GxFileInfo(_baseDirectory);
- _file.Source = value;
+ if (IsAbsoluteUrl(value))
+ {
+ _file =new GxExternalFileInfo(value, value, ServiceFactory.GetExternalProvider());
+ }
+ else
+ {
+ _file = new GxFileInfo(_baseDirectory);
+ _file.Source = value;
+ }
}
_lastError = 0;
diff --git a/dotnet/src/dotnetframework/GxClasses/Helpers/GXRestUtils.cs b/dotnet/src/dotnetframework/GxClasses/Helpers/GXRestUtils.cs
index 8bd57d8f9..b8ffa60af 100644
--- a/dotnet/src/dotnetframework/GxClasses/Helpers/GXRestUtils.cs
+++ b/dotnet/src/dotnetframework/GxClasses/Helpers/GXRestUtils.cs
@@ -29,10 +29,10 @@ internal static bool IsUploadURL(HttpContext httpContext)
{
return httpContext.Request.GetRawUrl().EndsWith(HttpHelper.GXOBJECT, StringComparison.OrdinalIgnoreCase);
}
- internal static void CacheUploadFile(string fileGuid, string savedFileName, string localfileName, string ext, GxFile file, IGxContext gxContext)
+ internal static void CacheUploadFile(string fileGuid, string realFileName, string realFileExtension, GxFile temporalFile, IGxContext gxContext)
{
- CacheAPI.FilesCache.Set(fileGuid, JSONHelper.Serialize(new UploadCachedFile() { path = savedFileName, fileExtension = ext, fileName = localfileName }), GxRestPrefix.UPLOAD_TIMEOUT);
- GXFileWatcher.Instance.AddTemporaryFile(file, gxContext);
+ CacheAPI.FilesCache.Set(fileGuid, JSONHelper.Serialize(new UploadCachedFile() { path = temporalFile.GetAbsoluteName(), fileExtension = realFileExtension, fileName = realFileName }), GxRestPrefix.UPLOAD_TIMEOUT);
+ GXFileWatcher.Instance.AddTemporaryFile(temporalFile, gxContext);
}
internal static string GetUploadFileGuid()
{
diff --git a/dotnet/src/dotnetframework/GxClasses/Helpers/HttpHelper.cs b/dotnet/src/dotnetframework/GxClasses/Helpers/HttpHelper.cs
index cb2f23036..9a7948910 100644
--- a/dotnet/src/dotnetframework/GxClasses/Helpers/HttpHelper.cs
+++ b/dotnet/src/dotnetframework/GxClasses/Helpers/HttpHelper.cs
@@ -235,7 +235,7 @@ public static bool GetHttpRequestPostedFile(IGxContext gxContext, string varName
ext = ext.TrimStart('.');
filePath = FileUtil.getTempFileName(tempDir);
GXLogging.Debug(log, "cgiGet(" + varName + "), fileName:" + filePath);
- GxFile file = new GxFile(tempDir, filePath, GxFileType.Private);
+ GxFile file = new GxFile(tempDir, filePath, GxFileType.PrivateAttribute);
#if NETCORE
filePath = file.Create(pf.OpenReadStream());
#else
diff --git a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttpServices.cs b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttpServices.cs
index 21994ac6c..ada323861 100644
--- a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttpServices.cs
+++ b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttpServices.cs
@@ -386,7 +386,7 @@ public override void webExecute()
{
try
{
- string savedFileName, ext, fName;
+ string ext, fName;
if (context.isMultipartRequest())
{
localHttpContext.Response.ContentType = MediaTypesNames.TextPlain;
@@ -406,8 +406,7 @@ public override void webExecute()
ext = FileUtil.GetFileType(fName);
string tempDir = Preferences.getTMP_MEDIA_PATH();
- savedFileName = FileUtil.getTempFileName(tempDir);
- GxFile gxFile = new GxFile(tempDir, savedFileName, GxFileType.Private);
+ GxFile gxFile = new GxFile(tempDir, FileUtil.getTempFileName(tempDir), GxFileType.PrivateAttribute);
gxFile.Create(hpf.InputStream);
string uri = gxFile.GetURI();
@@ -423,7 +422,7 @@ public override void webExecute()
thumbnailUrl = url,
path = fileToken
});
- GxUploadHelper.CacheUploadFile(fileGuid, savedFileName, Path.GetFileName(fName), ext, gxFile, context);
+ GxUploadHelper.CacheUploadFile(fileGuid, Path.GetFileName(fName), ext, gxFile, context);
}
UploadFilesResult result = new UploadFilesResult() { files = r };
var jsonObj = JSONHelper.Serialize(result);
@@ -457,11 +456,10 @@ public override void webExecute()
internal void WcfExecute(Stream istream, string contentType)
{
- string savedFileName, ext, fName;
+ string ext, fName;
ext = context.ExtensionForContentType(contentType);
- string tempDir = Preferences.getTMP_MEDIA_PATH();
- savedFileName = FileUtil.getTempFileName(tempDir);
- GxFile file = new GxFile(tempDir, savedFileName, GxFileType.Private);
+ string tempDir = Preferences.getTMP_MEDIA_PATH();
+ GxFile file = new GxFile(tempDir, FileUtil.getTempFileName(tempDir), GxFileType.PrivateAttribute);
file.Create(istream);
JObject obj = new JObject();
@@ -475,7 +473,7 @@ internal void WcfExecute(Stream istream, string contentType)
HttpHelper.SetResponseStatus(localHttpContext, ((int)HttpStatusCode.Created).ToString(), string.Empty);
localHttpContext.Response.Write(obj.ToString());
- GxUploadHelper.CacheUploadFile(fileGuid, savedFileName, $"{Path.GetFileNameWithoutExtension(fName)}.{ext}", ext, file, context);
+ GxUploadHelper.CacheUploadFile(fileGuid, $"{Path.GetFileNameWithoutExtension(fName)}.{ext}", ext, file, context);
}
protected override bool IntegratedSecurityEnabled
{
diff --git a/dotnet/src/dotnetframework/GxClasses/Services/Storage/ExternalProviderBase.cs b/dotnet/src/dotnetframework/GxClasses/Services/Storage/ExternalProviderBase.cs
new file mode 100644
index 000000000..6db3409fb
--- /dev/null
+++ b/dotnet/src/dotnetframework/GxClasses/Services/Storage/ExternalProviderBase.cs
@@ -0,0 +1,173 @@
+using System;
+using System.IO;
+using GeneXus.Encryption;
+using log4net;
+#if NETCORE
+using GeneXus.Mime;
+#else
+using System.Web;
+#endif
+
+namespace GeneXus.Services
+{
+ public abstract class ExternalProviderBase
+ {
+ static readonly ILog logger = log4net.LogManager.GetLogger(typeof(ExternalProviderBase));
+
+ private GXService service;
+
+ protected static String DEFAULT_ACL = "DEFAULT_ACL";
+ protected static String DEFAULT_EXPIRATION = "DEFAULT_EXPIRATION";
+ protected static String FOLDER = "FOLDER_NAME";
+ protected static String DEFAULT_ACL_DEPRECATED = "STORAGE_PROVIDER_DEFAULT_ACL";
+ protected static String DEFAULT_EXPIRATION_DEPRECATED = "STORAGE_PROVIDER_DEFAULT_EXPIRATION";
+ protected TimeSpan defaultExpiration = new TimeSpan(24, 0, 0);
+ protected static string DEFAULT_TMP_CONTENT_TYPE = "image/jpeg";
+ protected static string DEFAULT_CONTENT_TYPE = "application/octet-stream";
+
+ protected GxFileType defaultAcl = GxFileType.Private;
+ public string Folder { get; set; }
+
+ public ExternalProviderBase()
+ {
+ Initialize();
+ }
+
+ public ExternalProviderBase(GXService s)
+ {
+ if (s == null) {
+ try
+ {
+ s = ServiceFactory.GetGXServices().Get(GXServices.STORAGE_SERVICE);
+ }
+ catch (Exception) {
+ logger.Warn("STORAGE_SERVICE is not activated in CloudServices.config");
+ }
+ }
+
+ this.service = s;
+ Initialize();
+ }
+
+ public abstract String GetName();
+
+ private void Initialize()
+ {
+ String aclS = GetPropertyValue(DEFAULT_ACL, DEFAULT_ACL_DEPRECATED, "");
+ if (!String.IsNullOrEmpty(aclS))
+ {
+ this.defaultAcl = aclS.Equals("Private") ? GxFileType.Private : GxFileType.PublicRead;
+ }
+
+ String expirationS = GetPropertyValue(DEFAULT_EXPIRATION, DEFAULT_EXPIRATION, defaultExpiration.TotalMinutes.ToString());
+ if (!String.IsNullOrEmpty(expirationS))
+ {
+ int minutes;
+ if (Int32.TryParse(expirationS, out minutes) && minutes > 0)
+ {
+ defaultExpiration = new TimeSpan(0, minutes, 0);
+ }
+ }
+ Folder = GetPropertyValue(FOLDER, null, string.Empty);
+ }
+
+ protected TimeSpan ResolveExpiration(int expirationMinutes)
+ {
+ return expirationMinutes > 0 ? new TimeSpan(0, expirationMinutes, 0) : defaultExpiration;
+ }
+
+ protected String GetEncryptedPropertyValue(String propertyName, String alternativePropertyName = null)
+ {
+ String value = GetEncryptedPropertyValue(propertyName, alternativePropertyName, null);
+ if (value == null)
+ {
+ String errorMessage = String.Format($"Service configuration error - Property name {ResolvePropertyName(propertyName)} must be defined");
+ logger.Fatal(errorMessage);
+ throw new Exception(errorMessage);
+ }
+ return value;
+ }
+
+ protected String GetEncryptedPropertyValue(String propertyName, String alternativePropertyName, String defaultValue)
+ {
+ String value = GetPropertyValue(propertyName, alternativePropertyName, defaultValue);
+ if (!String.IsNullOrEmpty(value))
+ {
+ try
+ {
+ value = CryptoImpl.Decrypt(value);
+ }
+ catch (Exception)
+ {
+ logger.Warn($"Could not decrypt property name: {ResolvePropertyName(propertyName)}");
+ }
+ }
+ return value;
+ }
+
+ protected String GetPropertyValue(String propertyName, String alternativePropertyName = null)
+ {
+ String value = GetPropertyValue(propertyName, alternativePropertyName, null);
+ if (value == null)
+ {
+ String errorMessage = String.Format($"Service configuration error - Property name {ResolvePropertyName(propertyName)} must be defined");
+ logger.Fatal(errorMessage);
+ throw new Exception(errorMessage);
+ }
+ return value;
+ }
+
+ protected String GetPropertyValue(String propertyName, String alternativePropertyName, String defaultValue)
+ {
+ String value = null;
+ value = string.IsNullOrEmpty(value) ? GetPropertyValueImpl(ResolvePropertyName(propertyName)) : value;
+ value = string.IsNullOrEmpty(value) ? GetPropertyValueImpl(propertyName) : value;
+ value = string.IsNullOrEmpty(value) ? GetPropertyValueImpl(alternativePropertyName) : value;
+ value = string.IsNullOrEmpty(value) ? defaultValue : value;
+ return value;
+ }
+
+ private string GetPropertyValueImpl(string propertyName)
+ {
+ String value = null;
+ if (!string.IsNullOrEmpty(propertyName))
+ {
+ value = Environment.GetEnvironmentVariable(propertyName);
+ if (this.service != null)
+ {
+ value = this.service.Properties.Get(propertyName);
+ }
+ }
+ return value;
+ }
+
+ protected String ResolvePropertyName(String propertyName)
+ {
+ return $"STORAGE_{GetName()}_{propertyName}";
+ }
+
+ protected bool TryGetContentType(string fileName, out string mimeType, string defaultValue = null)
+ {
+ mimeType = defaultValue;
+ string extension = Path.GetExtension(fileName);
+ if (!string.IsNullOrEmpty(extension))
+ {
+ if (fileName.EndsWith(".tmp"))
+ {
+ mimeType = DEFAULT_TMP_CONTENT_TYPE;
+ }
+ else
+ {
+ try
+ {
+ mimeType = MimeMapping.GetMimeMapping(fileName);
+ }
+ catch (Exception)
+ {
+ }
+ }
+ }
+ return mimeType != null;
+ }
+ }
+}
diff --git a/dotnet/src/dotnetframework/GxClasses/Services/Storage/GXServices.cs b/dotnet/src/dotnetframework/GxClasses/Services/Storage/GXServices.cs
index 399e02a56..525d95e55 100644
--- a/dotnet/src/dotnetframework/GxClasses/Services/Storage/GXServices.cs
+++ b/dotnet/src/dotnetframework/GxClasses/Services/Storage/GXServices.cs
@@ -231,6 +231,7 @@ public interface ExternalProvider
string Upload(string localFile, string objectName, GxFileType fileType);
void Download(string objectName, string localFile, GxFileType fileType);
string Get(string objectName, GxFileType fileType, int urlMinutes);
+ string GetUrl(string objectName, GxFileType fileType, int urlMinutes);
void Delete(string objectName, GxFileType fileType);
bool Exists(string objectName, GxFileType fileType);
string Rename(string objectName, string newName, GxFileType fileType);
@@ -247,9 +248,9 @@ public interface ExternalProvider
List GetFiles(string directoryName, string filter = "");
List GetSubDirectories(string directoryName);
Stream GetStream(string objectName, GxFileType fileType);
- bool GetMessageFromException(Exception ex, SdtMessages_Message msg);
- bool GetObjectNameFromURL(string url, out string objectName);
+ bool GetMessageFromException(Exception ex, SdtMessages_Message msg);
string GetBaseURL();
+ bool TryGetObjectNameFromURL(string objectNameOrUrl, out string providerObjectName);
string StorageUri { get; }
}
}
diff --git a/dotnet/src/dotnetframework/GxClasses/Storage/StorageFactory.cs b/dotnet/src/dotnetframework/GxClasses/Storage/StorageFactory.cs
index 0ef2a2d3f..f4bc8fdda 100644
--- a/dotnet/src/dotnetframework/GxClasses/Storage/StorageFactory.cs
+++ b/dotnet/src/dotnetframework/GxClasses/Storage/StorageFactory.cs
@@ -5,20 +5,29 @@ namespace GeneXus.Storage
public class StorageFactory
{
const char QUESTION_MARK = '?';
- public static ExternalProvider GetExternalProviderFromUrl(string url, out string objectName)
+ public static bool TryGetProviderObjectName(ExternalProvider provider, string objectNameOrUrl, out string providerObjectName)
{
- objectName = null;
- ExternalProvider provider = ServiceFactory.GetExternalProvider();
- if (provider != null)
+ providerObjectName = null;
+ if (provider != null && provider.TryGetObjectNameFromURL(objectNameOrUrl, out providerObjectName))
{
- if (provider.GetObjectNameFromURL(url, out objectName))
+ int idx = providerObjectName.IndexOf(QUESTION_MARK);
+ if (idx > 0)
{
- var questionMarkIndex = objectName.IndexOf(QUESTION_MARK);
- objectName = questionMarkIndex >= 0 ? objectName.Substring(0, questionMarkIndex): objectName.Substring(0);
- return provider;
+ providerObjectName = providerObjectName.Substring(0, idx);
}
+ return true;
}
- return null;
+ return false;
+ }
+
+ public static string GetProviderObjectAbsoluteUriSafe(ExternalProvider provider, string rawObjectUri)
+ {
+ string providerObjectName;
+ if (TryGetProviderObjectName(provider, rawObjectUri, out providerObjectName))
+ {
+ rawObjectUri = providerObjectName;
+ }
+ return rawObjectUri;
}
}
}
diff --git a/dotnet/src/dotnetframework/GxExcel/GxExcelI.cs b/dotnet/src/dotnetframework/GxExcel/GxExcelI.cs
index 8317f9bc5..27d4c25a9 100644
--- a/dotnet/src/dotnetframework/GxExcel/GxExcelI.cs
+++ b/dotnet/src/dotnetframework/GxExcel/GxExcelI.cs
@@ -144,7 +144,7 @@ public String Template
string localTemplate = value;
if (!Path.IsPathRooted(localTemplate))
localTemplate = Path.Combine(GxContext.StaticPhysicalPath(), localTemplate);
- ServiceFactory.GetExternalProvider().Upload(localTemplate, template, GxFileType.Public);
+ ServiceFactory.GetExternalProvider().Upload(localTemplate, template, GxFileType.Private);
}
}
else if (!Path.IsPathRooted(value))
diff --git a/dotnet/src/dotnetframework/Providers/Storage/GXAmazonS3/ExternalProviderS3.cs b/dotnet/src/dotnetframework/Providers/Storage/GXAmazonS3/ExternalProviderS3.cs
index 148ad28bb..c7a26cd14 100644
--- a/dotnet/src/dotnetframework/Providers/Storage/GXAmazonS3/ExternalProviderS3.cs
+++ b/dotnet/src/dotnetframework/Providers/Storage/GXAmazonS3/ExternalProviderS3.cs
@@ -10,28 +10,51 @@
using System.Collections.Generic;
using System.IO;
using System.Threading;
-using System.Threading.Tasks;
+
namespace GeneXus.Storage.GXAmazonS3
{
- public class ExternalProviderS3 : ExternalProvider
+ public class ExternalProviderS3 : ExternalProviderBase, ExternalProvider
{
- const int PRIVATE_URL_MINUTES_EXPIRATION = 24 * 60; // 24 hours
- const string ACCESS_KEY_ID = "STORAGE_PROVIDER_ACCESSKEYID";
- const string SECRET_ACCESS_KEY = "STORAGE_PROVIDER_SECRETACCESSKEY";
- const string REGION = "STORAGE_PROVIDER_REGION";
- const string ENDPOINT = "STORAGE_ENDPOINT";
+ public const string Name = "AWSS3";
+
+ const string ACCESS_KEY = "ACCESS_KEY";
+ const string SECRET_ACCESS_KEY = "SECRET_KEY";
+ const string STORAGE_CUSTOM_ENDPOINT = "CUSTOM_ENDPOINT";
+ const string STORAGE_ENDPOINT = "ENDPOINT";
const string BUCKET = "BUCKET_NAME";
- const string FOLDER = "FOLDER_NAME";
+ const string REGION = "REGION";
+ const string STORAGE_CUSTOM_ENDPOINT_VALUE = "custom";
+
+ const string DEFAULT_ENDPOINT = "s3.amazonaws.com";
+ const string DEFAULT_REGION = "us-east-1";
+
+ [Obsolete("Use Property ACCESS_KEY instead", false)]
+ const string ACCESS_KEY_ID_DEPRECATED = "STORAGE_PROVIDER_ACCESSKEYID";
+ [Obsolete("Use Property SECRET_ACCESS_KEY instead", false)]
+ const string SECRET_ACCESS_KEY_DEPRECATED = "STORAGE_PROVIDER_SECRETACCESSKEY";
+ [Obsolete("Use Property REGION instead", false)]
+ const string REGION_DEPRECATED = "STORAGE_PROVIDER_REGION";
+ [Obsolete("Use Property STORAGE_ENDPOINT instead", false)]
+ const string ENDPOINT_DEPRECATED = "STORAGE_ENDPOINT";
+ [Obsolete("Use Property STORAGE_CUSTOM_ENDPOINT instead", false)]
+ const string STORAGE_CUSTOM_ENDPOINT_DEPRECATED = "STORAGE_CUSTOM_ENDPOINT";
+
+ string _storageUri;
IAmazonS3 Client { get; set; }
string Bucket { get; set; }
- string Folder { get; set; }
string Endpoint { get; set; }
+ string Region { get; set; }
+ bool forcePathStyle = false;
+ bool customEndpoint = false;
+
public string StorageUri
{
- get { return $"https://{Bucket}.{Endpoint}/"; }
+ get {
+ return _storageUri;
+ }
}
public string GetBaseURL()
@@ -39,30 +62,41 @@ public string GetBaseURL()
return StorageUri + Folder + StorageUtils.DELIMITER;
}
- public ExternalProviderS3()
- : this(ServiceFactory.GetGXServices().Get(GXServices.STORAGE_SERVICE))
- {
+ public ExternalProviderS3(): this(null)
+ {
}
- public ExternalProviderS3(GXService providerService)
+ public ExternalProviderS3(GXService providerService): base(providerService)
{
- string keyId = CryptoImpl.Decrypt(providerService.Properties.Get(ACCESS_KEY_ID));
- string keySecret = CryptoImpl.Decrypt(providerService.Properties.Get(SECRET_ACCESS_KEY));
+ Initialize();
+ }
+
+ private void Initialize() {
+ string keyId = GetEncryptedPropertyValue(ACCESS_KEY, ACCESS_KEY_ID_DEPRECATED);
+ string keySecret = GetEncryptedPropertyValue(SECRET_ACCESS_KEY, SECRET_ACCESS_KEY_DEPRECATED);
AWSCredentials credentials = null;
if (!string.IsNullOrEmpty(keyId) && !string.IsNullOrEmpty(keySecret))
{
credentials = new BasicAWSCredentials(keyId, keySecret);
}
- var region = Amazon.RegionEndpoint.GetBySystemName(providerService.Properties.Get(REGION));
- Endpoint = providerService.Properties.Get(ENDPOINT);
+ var region = Amazon.RegionEndpoint.GetBySystemName(GetPropertyValue(REGION, REGION_DEPRECATED, DEFAULT_REGION));
AmazonS3Config config = new AmazonS3Config()
{
- ServiceURL = Endpoint,
RegionEndpoint = region
};
+ Endpoint = GetPropertyValue(STORAGE_ENDPOINT, ENDPOINT_DEPRECATED, DEFAULT_ENDPOINT);
+ if (Endpoint == STORAGE_CUSTOM_ENDPOINT_VALUE)
+ {
+ Endpoint = GetPropertyValue(STORAGE_CUSTOM_ENDPOINT, STORAGE_CUSTOM_ENDPOINT_DEPRECATED);
+ forcePathStyle = true;
+ config.ForcePathStyle = forcePathStyle;
+ config.ServiceURL = Endpoint;
+ customEndpoint = true;
+ }
+
#if NETCORE
if (credentials != null)
{
@@ -83,13 +117,33 @@ public ExternalProviderS3(GXService providerService)
}
#endif
- Bucket = CryptoImpl.Decrypt(providerService.Properties.Get(BUCKET));
- Folder = providerService.Properties.Get(FOLDER);
+ Bucket = GetEncryptedPropertyValue(BUCKET);
+ Region = region.SystemName;
+ SetURI();
CreateBucket();
CreateFolder(Folder);
}
+ private void SetURI()
+ {
+ if (customEndpoint)
+ {
+ _storageUri = !Endpoint.EndsWith("/") ? $"{Endpoint}/{Bucket}/": $"{Endpoint}{Bucket}/";
+ }
+ else
+ {
+ if (Region == DEFAULT_REGION)
+ {
+ _storageUri = (forcePathStyle) ? $"{Endpoint}/" : $"https://{Bucket}.{Endpoint}/";
+ }
+ else
+ {
+ _storageUri = $"https://{Bucket}.{Endpoint.Replace("s3.amazonaws.com", $"s3.{Region.ToLower()}.amazonaws.com")}/";
+ }
+ }
+ }
+
private void AddObjectMetadata(MetadataCollection metadata, string tableName, string fieldName, string key)
{
metadata.Add("Table", tableName);
@@ -160,11 +214,11 @@ private void CreateBucket()
PutBucketRequest request = new PutBucketRequest
{
BucketName = Bucket,
- UseClientRegion = true,
- // Every bucket is public
- CannedACL = S3CannedACL.PublicRead
+ UseClientRegion = true
};
-
+ if (defaultAcl == GxFileType.PublicRead) {
+ request.CannedACL = S3CannedACL.PublicRead;
+ }
PutBucket(request);
}
}
@@ -178,28 +232,38 @@ public string Upload(string localFile, string objectName, GxFileType fileType)
FilePath = localFile,
CannedACL = GetCannedACL(fileType)
};
- PutObjectResponse result = PutObject(objectRequest);
- return Get(objectName, fileType);
+ PutObject(objectRequest);
+ return GetUrlImpl(objectName, fileType);
}
- private static bool IsPrivateUpload(GxFileType fileType)
+ private bool IsPrivateUpload(GxFileType fileType)
{
- return fileType.HasFlag(GxFileType.Private);
+ return GetCannedACL(fileType) != S3CannedACL.PublicRead;
}
- public string Get(string objectName, GxFileType fileType, int urlMinutes = PRIVATE_URL_MINUTES_EXPIRATION)
+ public string Get(string objectName, GxFileType fileType, int urlMinutes = 0)
{
- bool isPrivate = IsPrivateUpload(fileType);
if (Exists(objectName, fileType))
- if (isPrivate)
- return GetPreSignedUrl(objectName, urlMinutes);
- else
- return StorageUri + StorageUtils.EncodeUrl(objectName);
+ {
+ return GetUrlImpl(objectName, fileType, urlMinutes);
+ }
else
return string.Empty;
}
- private string GetPreSignedUrl(string objectName, int urlMinutes)
+ public string GetUrl(string objectName, GxFileType fileType, int urlMinutes = 0)
+ {
+ return GetUrlImpl(objectName, fileType, urlMinutes);
+ }
+
+ private string GetUrlImpl(string objectName, GxFileType fileType, int urlMinutes = 0)
+ {
+ bool isPrivate = IsPrivateUpload(fileType);
+ return (isPrivate)? GetPreSignedUrl(objectName, ResolveExpiration(urlMinutes).TotalMinutes): StorageUri + StorageUtils.EncodeUrl(objectName);
+
+ }
+
+ private string GetPreSignedUrl(string objectName, double urlMinutes)
{
GetPreSignedUrlRequest request = new GetPreSignedUrlRequest
{
@@ -207,6 +271,10 @@ private string GetPreSignedUrl(string objectName, int urlMinutes)
Key = objectName,
Expires = DateTime.Now.AddMinutes(urlMinutes)
};
+ if (customEndpoint && StorageUri.StartsWith("http://"))
+ {
+ request.Protocol = Protocol.HTTP;
+ }
return Client.GetPreSignedURL(request);
}
@@ -234,7 +302,7 @@ public void Delete(string objectName, GxFileType fileType)
//https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Services/S3/Custom/_bcl/IO/S3FileInfo.cs
public bool Exists(string objectName, GxFileType fileType)
{
- bool exists = true;
+ bool exists;
try
{
exists = new S3FileInfo(Client, Bucket, objectName).Exists;
@@ -267,9 +335,24 @@ public string Copy(string objectName, GxFileType sourceFileType, string newName,
return StorageUri + StorageUtils.EncodeUrl(newName);
}
- private static S3CannedACL GetCannedACL(GxFileType destFileType)
+ private S3CannedACL GetCannedACL(GxFileType acl)
{
- return (destFileType.HasFlag(GxFileType.Private)) ? S3CannedACL.Private : S3CannedACL.PublicRead;
+ if (acl.HasFlag(GxFileType.Private))
+ {
+ return S3CannedACL.Private;
+ }
+ else if (acl.HasFlag(GxFileType.PublicRead))
+ {
+ return S3CannedACL.PublicRead;
+ }
+ else if (this.defaultAcl == GxFileType.Private)
+ {
+ return S3CannedACL.Private;
+ }
+ else
+ {
+ return S3CannedACL.PublicRead;
+ }
}
public string Upload(string fileName, Stream stream, GxFileType destFileType)
@@ -281,8 +364,9 @@ public string Upload(string fileName, Stream stream, GxFileType destFileType)
InputStream = stream,
CannedACL = GetCannedACL(destFileType)
};
- if (Path.GetExtension(fileName).Equals(".tmp"))
- objectRequest.ContentType = "image/jpeg";
+ if (TryGetContentType(fileName, out string mimeType)) {
+ objectRequest.ContentType = mimeType;
+ }
PutObjectResponse result = PutObject(objectRequest);
return Get(fileName, destFileType);
}
@@ -301,8 +385,15 @@ public string Copy(string url, string newName, string tableName, string fieldNam
SourceKey = url,
DestinationBucket = Bucket,
DestinationKey = resourceKey,
- CannedACL = GetCannedACL(destFileType)
+ CannedACL = GetCannedACL(destFileType),
+ MetadataDirective = S3MetadataDirective.REPLACE
};
+
+ if (TryGetContentType(newName, out string mimeType, DEFAULT_CONTENT_TYPE))
+ {
+ request.ContentType = mimeType;
+ }
+
AddObjectMetadata(request.Metadata, tableName, fieldName, resourceKey);
CopyObject(request);
@@ -322,10 +413,15 @@ public string Save(Stream fileStream, string fileName, string tableName, string
InputStream = fileStream,
CannedACL = GetCannedACL(destFileType)
};
+ if (TryGetContentType(fileName, out string mimeType))
+ {
+ objectRequest.ContentType = mimeType;
+ }
+
AddObjectMetadata(objectRequest.Metadata, tableName, fieldName, resourceKey);
PutObjectResponse result = PutObject(objectRequest);
- return "https://" + Bucket + ".s3.amazonaws.com/" + resourceKey;
+ return StorageUri + resourceKey;
}
catch (Exception ex)
{
@@ -378,7 +474,7 @@ public void RenameDirectory(string directoryName, string newDirectoryName)
if (file.Type == FileSystemType.Directory)
RenameDirectory(directoryName + "\\" + file.Name, newDirectoryName + "\\" + file.Name);
else
- Rename(directoryName.Replace("\\", StorageUtils.DELIMITER) + StorageUtils.DELIMITER + file.Name, newDirectoryName.Replace("\\", StorageUtils.DELIMITER) + StorageUtils.DELIMITER + file.Name, GxFileType.Public);
+ Rename(directoryName.Replace("\\", StorageUtils.DELIMITER) + StorageUtils.DELIMITER + file.Name, newDirectoryName.Replace("\\", StorageUtils.DELIMITER) + StorageUtils.DELIMITER + file.Name, GxFileType.PublicRead);
}
s3DirectoryInfo.Delete();
}
@@ -446,7 +542,7 @@ public bool GetMessageFromException(Exception ex, SdtMessages_Message msg)
}
}
- public bool GetObjectNameFromURL(string url, out string objectName)
+ public bool TryGetObjectNameFromURL(string url, out string objectName)
{
if (url.StartsWith(StorageUri))
{
@@ -456,5 +552,11 @@ public bool GetObjectNameFromURL(string url, out string objectName)
objectName = null;
return false;
}
+
+ public override string GetName()
+ {
+ return Name;
+ }
+
}
}
diff --git a/dotnet/src/dotnetframework/Providers/Storage/GXAzureStorage/AzureStorageExternalProvider.cs b/dotnet/src/dotnetframework/Providers/Storage/GXAzureStorage/AzureStorageExternalProvider.cs
index 811b079db..036ec89e0 100644
--- a/dotnet/src/dotnetframework/Providers/Storage/GXAzureStorage/AzureStorageExternalProvider.cs
+++ b/dotnet/src/dotnetframework/Providers/Storage/GXAzureStorage/AzureStorageExternalProvider.cs
@@ -14,8 +14,10 @@
namespace GeneXus.Storage.GXAzureStorage
{
- public class AzureStorageExternalProvider : ExternalProvider
+ public class AzureStorageExternalProvider : ExternalProviderBase, ExternalProvider
{
+ public static String Name = "AZUREBS"; //Azure Blob Storage
+
const string ACCOUNT_NAME = "ACCOUNT_NAME";
const string ACCESS_KEY = "ACCESS_KEY";
const string PUBLIC_CONTAINER = "PUBLIC_CONTAINER_NAME";
@@ -26,22 +28,35 @@ public class AzureStorageExternalProvider : ExternalProvider
CloudBlobContainer PublicContainer { get; set; }
CloudBlobContainer PrivateContainer { get; set; }
CloudBlobClient Client { get; set; }
+
public string StorageUri
{
get { return $"https://{Account}.blob.core.windows.net"; }
}
- public AzureStorageExternalProvider()
- : this(ServiceFactory.GetGXServices().Get(GXServices.STORAGE_SERVICE)) { }
+ public override string GetName()
+ {
+ return Name;
+ }
- public AzureStorageExternalProvider(GXService providerService)
+ public AzureStorageExternalProvider() : this(null)
{
- Account = CryptoImpl.Decrypt(providerService.Properties.Get(ACCOUNT_NAME));
- Key = CryptoImpl.Decrypt(providerService.Properties.Get(ACCESS_KEY));
+ }
- string publicContainer = CryptoImpl.Decrypt(providerService.Properties.Get(PUBLIC_CONTAINER));
- string privateContainer = CryptoImpl.Decrypt(providerService.Properties.Get(PRIVATE_CONTAINER));
+ public AzureStorageExternalProvider(GXService providerService) : base(providerService)
+ {
+ Initialize();
+ }
+ private void Initialize()
+ {
+ Account = GetEncryptedPropertyValue(ACCOUNT_NAME);
+ Key = GetEncryptedPropertyValue(ACCESS_KEY);
+
+ string publicContainer = GetEncryptedPropertyValue(PUBLIC_CONTAINER);
+ string privateContainer = GetEncryptedPropertyValue(PRIVATE_CONTAINER);
+
+
StorageCredentials credentials = new StorageCredentials(Account, Key);
CloudStorageAccount storageAccount = new CloudStorageAccount(credentials, true);
@@ -76,7 +91,8 @@ public string Upload(string localFile, string externalFileName, GxFileType fileT
private CloudBlockBlob GetCloudBlockBlob(string externalFileName, GxFileType fileType)
{
CloudBlockBlob blob;
- if (fileType.HasFlag(GxFileType.Private))
+
+ if (IsPrivateFile(fileType))
{
blob = PrivateContainer.GetBlockBlobReference(externalFileName);
}
@@ -87,10 +103,25 @@ private CloudBlockBlob GetCloudBlockBlob(string externalFileName, GxFileType fil
return blob;
}
-
- private bool IsPrivateFile(GxFileType fileType)
+
+ private bool IsPrivateFile(GxFileType acl)
{
- return fileType.HasFlag(GxFileType.Private);
+ if (acl.HasFlag(GxFileType.Private))
+ {
+ return true;
+ }
+ else if (acl.HasFlag(GxFileType.PublicRead))
+ {
+ return false;
+ }
+ else if (this.defaultAcl == GxFileType.Private)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
}
public string Get(string objectName, GxFileType fileType, int urlMinutes)
@@ -103,6 +134,12 @@ public string Get(string objectName, GxFileType fileType, int urlMinutes)
return string.Empty;
}
+ public string GetUrl(string objectName, GxFileType fileType, int urlMinutes = 0)
+ {
+ CloudBlockBlob blob = GetCloudBlockBlob(objectName, fileType);
+ return GetURL(blob, fileType, urlMinutes);
+ }
+
private string GetURL(CloudBlockBlob blob, GxFileType fileType, int urlMinutes = 0)
{
string url = StorageUri + StorageUtils.DELIMITER + blob.Container.Name + StorageUtils.DELIMITER + StorageUtils.EncodeUrl(blob.Name);
@@ -110,7 +147,7 @@ private string GetURL(CloudBlockBlob blob, GxFileType fileType, int urlMinutes =
{
SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy();
sasConstraints.SharedAccessStartTime = DateTime.UtcNow;
- sasConstraints.SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes((urlMinutes > 0) ? urlMinutes : 60 * 24 * 7);
+ sasConstraints.SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(ResolveExpiration(urlMinutes).TotalMinutes);
sasConstraints.Permissions = SharedAccessBlobPermissions.Read;
url += blob.GetSharedAccessSignature(sasConstraints);
}
@@ -146,10 +183,11 @@ public string Copy(string objectName, GxFileType sourceFileType, string newName,
public string Upload(string fileName, Stream stream, GxFileType fileType)
{
CloudBlockBlob blob = GetCloudBlockBlob(fileName, fileType);
- if (Path.GetExtension(fileName).Equals(".tmp"))
- blob.Properties.ContentType = "image/jpeg";
- else
- blob.Properties.ContentType = MimeMapping.GetMimeMapping(fileName);
+
+ if (TryGetContentType(fileName, out string mimeType))
+ {
+ blob.Properties.ContentType = mimeType;
+ }
blob.UploadFromStreamAsync(stream).GetAwaiter().GetResult();
return GetURL(blob, fileType);
@@ -165,8 +203,14 @@ public string Copy(string sourceUrl, string newName, string tableName, string fi
targetBlob.Metadata["Table"] = tableName;
targetBlob.Metadata["Field"] = fieldName;
targetBlob.Metadata["KeyValue"] = StorageUtils.EncodeUrl(newName);
+
+ if (TryGetContentType(newName, out string mimeType, DEFAULT_CONTENT_TYPE))
+ {
+ targetBlob.Properties.ContentType = mimeType;
+ }
targetBlob.StartCopyAsync(sourceBlob).GetAwaiter().GetResult();
+ targetBlob.SetPropertiesAsync().GetAwaiter().GetResult(); //Required to apply new object metadata
return GetURL(targetBlob, fileType);
}
return string.Empty;
@@ -254,15 +298,15 @@ public void RenameDirectory(string directoryName, string newDirectoryName)
{
CloudBlockBlob blob = (CloudBlockBlob)item;
fileName = Path.GetFileName(blob.Name);
- Copy(blob.Name, GxFileType.Public, newDirectoryName + fileName, GxFileType.Public);
- Delete(blob.Name, GxFileType.Public);
+ Copy(blob.Name, GxFileType.PublicRead, newDirectoryName + fileName, GxFileType.PublicRead);
+ Delete(blob.Name, GxFileType.PublicRead);
}
else if (item.GetType() == typeof(CloudPageBlob))
{
CloudPageBlob pageBlob = (CloudPageBlob)item;
fileName = Path.GetFileName(pageBlob.Name);
- Copy(directoryName + fileName, GxFileType.Public, newDirectoryName + fileName, GxFileType.Public);
+ Copy(directoryName + fileName, GxFileType.PublicRead, newDirectoryName + fileName, GxFileType.PublicRead);
}
else if (item.GetType() == typeof(CloudBlobDirectory))
@@ -337,7 +381,7 @@ public string Save(Stream fileStream, string fileName, string tableName, string
return Upload(newName, fileStream, fileType);
}
- public bool GetObjectNameFromURL(string url, out string objectName)
+ public bool TryGetObjectNameFromURL(string url, out string objectName)
{
string baseUrl = StorageUri + StorageUtils.DELIMITER + PublicContainer.Name + StorageUtils.DELIMITER;
if (url.StartsWith(baseUrl))
@@ -345,13 +389,19 @@ public bool GetObjectNameFromURL(string url, out string objectName)
objectName = url.Replace(baseUrl, string.Empty);
return true;
}
+ baseUrl = StorageUri + StorageUtils.DELIMITER + PrivateContainer.Name + StorageUtils.DELIMITER;
+ if (url.StartsWith(baseUrl))
+ {
+ objectName = url.Replace(baseUrl, string.Empty);
+ return true;
+ }
objectName = null;
return false;
}
public string GetBaseURL()
{
- return StorageUri + StorageUtils.DELIMITER + PublicContainer.Name + StorageUtils.DELIMITER;
+ return StorageUri + StorageUtils.DELIMITER + PrivateContainer.Name + StorageUtils.DELIMITER;
}
}
diff --git a/dotnet/src/dotnetframework/Providers/Storage/GXBluemix/ExternalProviderBluemix.cs b/dotnet/src/dotnetframework/Providers/Storage/GXBluemix/ExternalProviderBluemix.cs
deleted file mode 100644
index 058182ba4..000000000
--- a/dotnet/src/dotnetframework/Providers/Storage/GXBluemix/ExternalProviderBluemix.cs
+++ /dev/null
@@ -1,1434 +0,0 @@
-using GeneXus.Configuration;
-using GeneXus.Services;
-using JSIStudios.SimpleRESTServices.Client;
-using log4net;
-using net.openstack.Core;
-using net.openstack.Core.Caching;
-using net.openstack.Core.Domain;
-using net.openstack.Core.Domain.Mapping;
-using net.openstack.Core.Exceptions;
-using net.openstack.Core.Exceptions.Response;
-using net.openstack.Core.Providers;
-using net.openstack.Core.Validators;
-using net.openstack.Providers.Rackspace;
-using net.openstack.Providers.Rackspace.Exceptions;
-using net.openstack.Providers.Rackspace.Objects;
-using net.openstack.Providers.Rackspace.Validators;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using OpenStack.Authentication;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Security.Cryptography;
-using System.Text;
-using Thread = System.Threading.Thread;
-using GeneXus.Utils;
-using GeneXus.Encryption;
-
-namespace GeneXus.Storage.GXBluemix
-{
- public class ExternalProviderBluemix : ExternalProvider
- {
- const string USER = "STORAGE_PROVIDER_USER";
- const string PASSWORD = "STORAGE_PROVIDER_PASSWORD";
- const string SERVER_URL = "SERVER_URL";
- const string PROJECT_ID = "PROJECT_ID";
- const string REGION = "STORAGE_PROVIDER_REGION";
- const string PRIVATE_BUCKET = "PRIVATE_BUCKET_NAME";
- const string PUBLIC_BUCKET = "PUBLIC_BUCKET_NAME";
- const string FOLDER = "FOLDER_NAME";
-
- BluemixFilesProvider Client { get; set; }
- string PublicBucket { get; set; }
- string PrivateBucket { get; set; }
- string Folder { get; set; }
- string AuthToken { get; set; }
- public string StorageUri { get; set; }
- string PrivateTempKeyUrl { get; set; }
-
- public ExternalProviderBluemix()
- : this(ServiceFactory.GetGXServices().Get(GXServices.STORAGE_SERVICE))
- {
- }
-
- public ExternalProviderBluemix(GXService providerService)
- {
- var identityEndpoint = new Uri(providerService.Properties.Get(SERVER_URL));
- CloudIdentityWithProject identity = new CloudIdentityWithProject
- {
- Username = CryptoImpl.Decrypt(providerService.Properties.Get(USER)),
- Password = CryptoImpl.Decrypt(providerService.Properties.Get(PASSWORD)),
- ProjectName = providerService.Properties.Get(PROJECT_ID),
- };
-
- BlueMixIdentityProvider identityProvider = new BlueMixIdentityProvider(identityEndpoint, identity);
-
- GetStorageEndpoint(identityProvider, identity);
-
- Client = new BluemixFilesProvider(null, providerService.Properties.Get(REGION), identityProvider, null);
- PublicBucket = CryptoImpl.Decrypt(providerService.Properties.Get(PUBLIC_BUCKET));
- PrivateBucket = CryptoImpl.Decrypt(providerService.Properties.Get(PRIVATE_BUCKET));
- Folder = providerService.Properties.Get(FOLDER);
- AuthToken = identityProvider.AuthToken;
-
- CreateBuckets();
- CreateFolder(Folder);
- }
-
- public ExternalProviderBluemix(string proj, string url, string user, string pass, string bucket, string region)
- {
- var identityEndpoint = new Uri(url);
- CloudIdentityWithProject identity = new CloudIdentityWithProject
- {
- Username = user,
- Password = pass,
- ProjectName = proj,
- };
-
- BlueMixIdentityProvider identityProvider = new BlueMixIdentityProvider(identityEndpoint, identity);
-
- GetStorageEndpoint(identityProvider, identity);
-
- Client = new BluemixFilesProvider(null, region, identityProvider, null);
- PublicBucket = bucket;
- AuthToken = identityProvider.AuthToken;
-
- CreateBuckets();
- }
-
- private void GetStorageEndpoint(BlueMixIdentityProvider identityProvider, CloudIdentityWithProject identity)
- {
- UserAccessV3 user = identityProvider.GetUserAccess(identity);
- var catalog = user.Catalog;
- Endpoint objectStorageEndpoint = null;
- foreach (Catalog service in catalog)
- if (service.Type == "object-store")
- if (service.Endpoints.Where(e => e.Region_id == "dallas").Any())
- objectStorageEndpoint = service.Endpoints.Where(e => e.Region_id == "dallas").First();
-
- StorageUri = objectStorageEndpoint.Url;
-
- if (String.IsNullOrEmpty(StorageUri))
- throw new Exception("Can't find the object storage endpoint, please check the credentials in the storage configuration.");
- }
-
- private Dictionary CreateObjectMetadata(string tableName, string fieldName, string key)
- {
- Dictionary metadata = new Dictionary();
- metadata.Add("Table", tableName);
- metadata.Add("Field", fieldName);
- metadata.Add("KeyValue", key);
- return metadata;
- }
-
- private void CreateBuckets()
- {
- Dictionary headers = new Dictionary();
- headers.Add("X-Auth-Token", AuthToken);
- PrivateTempKeyUrl = Guid.NewGuid().ToString();
- headers.Add("X-Container-Meta-Temp-URL-Key", PrivateTempKeyUrl);
- Client.CreateContainer(PrivateBucket, headers);
-
- headers = new Dictionary();
- headers.Add("X-Auth-Token", AuthToken);
- headers.Add("X-Container-Read", ".r:*");
- Client.CreateContainer(PublicBucket, headers);
- }
-
- private void CreateFolder(string folder, string table = null, string field = null)
- {
- string name = StorageUtils.NormalizeDirectoryName(folder);
- if (table != null)
- name += table + StorageUtils.DELIMITER;
- if (field != null)
- name += field + StorageUtils.DELIMITER;
- using (var stream = new MemoryStream())
- {
- Client.CreateObject(PublicBucket, stream, name, "application/directory");
- }
- }
-
- public void Download(string externalFileName, string localFile, GxFileType fileType)
- {
- string bucket = GetBucket(fileType);
- string localDirectory = Path.GetDirectoryName(localFile);
- string localFileName = Path.GetFileName(localFile);
- Client.GetObjectSaveToFile(bucket, localDirectory, externalFileName, localFileName);
- }
-
- public string Upload(string localFile, string externalFileName, GxFileType fileType)
- {
- string bucket = GetBucket(fileType);
- Client.CreateObjectFromFile(bucket, localFile, externalFileName);
- return StorageUri + StorageUtils.DELIMITER + bucket + StorageUtils.DELIMITER + StorageUtils.EncodeUrl(externalFileName);
- }
-
- private string GetBucket(GxFileType fileType)
- {
- return (fileType.HasFlag(GxFileType.Private)) ? PrivateBucket : PublicBucket;
- }
-
- public string Get(string externalFileName, GxFileType fileType, int urlMinutes)
- {
- string bucket = GetBucket(fileType);
- if (Exists(externalFileName, fileType))
- {
- if (fileType.HasFlag(GxFileType.Private))
- return Client.CreateTemporaryPublicUri(HttpMethod.GET, bucket, externalFileName, PrivateTempKeyUrl, DateTimeOffset.Now.AddMinutes(urlMinutes)).ToString();
- return StorageUri + StorageUtils.DELIMITER + bucket + StorageUtils.DELIMITER + StorageUtils.EncodeUrl(externalFileName);
- }
- return string.Empty;
- }
-
- public void Delete(string objectName, GxFileType fileType)
- {
- Client.DeleteObject(GetBucket(fileType), objectName);
- }
-
- public bool Exists(string objectName, GxFileType fileType)
- {
- try
- {
- Client.GetObjectMetaData(GetBucket(fileType), objectName);
- return true;
- }
- catch (ItemNotFoundException)
- {
- return false;
- }
- }
-
- public string Rename(string objectName, string newName, GxFileType fileType)
- {
- string bucket = GetBucket(fileType);
- Copy(objectName, fileType, newName, fileType);
- Delete(objectName, fileType);
- return StorageUri + StorageUtils.DELIMITER + bucket + StorageUtils.DELIMITER + StorageUtils.EncodeUrl(newName);
- }
-
- public string Copy(string objectName, GxFileType sourceFileType, string newName, GxFileType destFileType)
- {
- string sourceBucket = GetBucket(sourceFileType);
- string destBucket = GetBucket(destFileType);
- Client.CopyObject(sourceBucket, objectName, destBucket, newName);
- return Get(objectName, destFileType, 0);
- }
-
- public string Upload(string fileName, Stream stream, GxFileType fileType)
- {
- string bucket = GetBucket(fileType);
- if (Path.GetExtension(fileName).Equals(".tmp"))
- Client.CreateObject(bucket, stream, fileName, "image / jpeg");
- else
- Client.CreateObject(bucket, stream, fileName);
-
- return Get(fileName, fileType, 0);
- }
-
- public string Copy(string url, string newName, string tableName, string fieldName, GxFileType fileType)
- {
- string bucket = GetBucket(fileType);
- string resourceKey = Folder + StorageUtils.DELIMITER + tableName + StorageUtils.DELIMITER + fieldName + StorageUtils.DELIMITER + newName;
- CreateFolder(Folder, tableName, fieldName);
- url = StorageUtils.DecodeUrl(url.Replace(StorageUri + StorageUtils.DELIMITER + bucket + StorageUtils.DELIMITER, ""));
-
- Copy(url, fileType, resourceKey, fileType);
- Client.UpdateObjectMetadata(bucket, resourceKey, CreateObjectMetadata(tableName, fieldName, resourceKey));
-
- return Get(resourceKey, fileType, 0);
- }
-
- public Stream GetStream(string objectName, GxFileType fileType)
- {
- MemoryStream stream = new MemoryStream();
- Client.GetObject(GetBucket(fileType), objectName, stream);
- return stream;
- }
-
- public string GetDirectory(string directoryName)
- {
- directoryName = StorageUtils.NormalizeDirectoryName(directoryName);
- if (ExistsDirectory(directoryName))
- return PublicBucket + StorageUtils.DELIMITER + directoryName;
- else
- return "";
- }
-
- public long GetLength(string objectName, GxFileType fileType)
- {
- foreach (ContainerObject obj in Client.ListObjects(GetBucket(fileType)))
- if (obj.Name.Equals(objectName))
- return obj.Bytes;
- return 0;
- }
-
- public DateTime GetLastModified(string objectName, GxFileType fileType)
- {
- foreach (ContainerObject obj in Client.ListObjects(GetBucket(fileType)))
- if (obj.Name.Equals(objectName))
- return obj.LastModified.UtcDateTime;
- return new DateTime();
- }
-
- public void CreateDirectory(string directoryName)
- {
- CreateFolder(directoryName);
- }
-
- public void DeleteDirectory(string directoryName)
- {
- List objs = new List();
- foreach (ContainerObject obj in Client.ListObjects(PublicBucket, prefix: StorageUtils.NormalizeDirectoryName(directoryName)))
- {
- objs.Add(obj.Name);
- }
- objs.Add(directoryName);
- Client.DeleteObjects(PublicBucket, objs);
- }
-
- public bool ExistsDirectory(string directoryName)
- {
- List directories = GetDirectories();
- return directories.Contains(directoryName) || directories.Contains(StorageUtils.NormalizeDirectoryName(directoryName));
- }
-
- public void RenameDirectory(string directoryName, string newDirectoryName)
- {
- directoryName = StorageUtils.NormalizeDirectoryName(directoryName);
- newDirectoryName = StorageUtils.NormalizeDirectoryName(newDirectoryName);
-
- foreach (ContainerObject obj in Client.ListObjects(PublicBucket, prefix: directoryName))
- {
- Client.CopyObject(PublicBucket, obj.Name, PublicBucket, obj.Name.Replace(directoryName, newDirectoryName));
- }
- DeleteDirectory(directoryName);
- }
-
- public List GetSubDirectories(string directoryName)
- {
- return GetDirectories(StorageUtils.NormalizeDirectoryName(directoryName));
- }
-
- private List GetDirectories(string directoryName = null)
- {
- List subdir = new List();
- foreach (ContainerObject obj in Client.ListObjects(PublicBucket, prefix: directoryName))
- {
- if (directoryName == null)
- {
- string dir = "";
- string[] parts = obj.Name.Split(Convert.ToChar(StorageUtils.DELIMITER));
- for (int i = 0; i < parts.Length - 1; i++)
- {
- dir += parts[i] + StorageUtils.DELIMITER;
- if (!subdir.Contains(dir))
- subdir.Add(dir);
- }
- }
- else
- {
- string name = obj.Name.Replace(directoryName, "");
- int i = name.IndexOf(StorageUtils.DELIMITER);
- if (i != -1)
- {
- name = name.Substring(0, i);
- string dir = StorageUtils.NormalizeDirectoryName(directoryName + name);
- if (!subdir.Contains(dir))
- subdir.Add(dir);
- }
- }
-
- }
- if (directoryName != null)
- {
- subdir.Remove(directoryName);
- }
- return subdir;
- }
-
- public List GetFiles(string directoryName, string filter = "")
- {
- directoryName = StorageUtils.NormalizeDirectoryName(directoryName);
- List files = new List();
- foreach (ContainerObject obj in Client.ListObjects(PublicBucket, prefix: directoryName))
- {
- if (IsFile(obj, directoryName) && (String.IsNullOrEmpty(filter) || obj.Name.Contains(filter)))
- files.Add(obj.Name);
- }
- return files;
- }
-
- private bool IsFile(ContainerObject obj, string directory = null)
- {
- char delimiter = Convert.ToChar(StorageUtils.DELIMITER);
- if (directory == null)
- return obj.Name.Split(delimiter).Length == 1;
- else
- return obj.Name.Replace(StorageUtils.NormalizeDirectoryName(directory), "").Split(delimiter).Length == 1 && !String.IsNullOrEmpty(obj.Name.Replace(StorageUtils.NormalizeDirectoryName(directory), "").Split(delimiter)[0]);
- }
-
- public bool GetMessageFromException(Exception ex, SdtMessages_Message msg)
- {
- try
- {
- ResponseException rex = (ResponseException)ex;
- msg.gxTpr_Id = rex.Response.Status;
- return true;
- }
- catch (Exception)
- {
- return false;
- }
- }
-
- public string Save(Stream fileStream, string fileName, string tableName, string fieldName, GxFileType fileType)
- {
- CreateFolder(Folder, tableName, fieldName);
- string resourceKey = Folder + StorageUtils.DELIMITER + tableName + StorageUtils.DELIMITER + fieldName + StorageUtils.DELIMITER + fileName;
- return Upload(resourceKey, fileStream, fileType);
- }
-
- public bool GetObjectNameFromURL(string url, out string objectName)
- {
- string baseUrl = StorageUri + StorageUtils.DELIMITER + PublicBucket + StorageUtils.DELIMITER;
- if (url.StartsWith(baseUrl))
- {
- objectName = url.Replace(baseUrl, string.Empty);
- return true;
- }
- objectName = null;
- return false;
- }
- public string GetBaseURL()
- {
- return StorageUri + StorageUtils.DELIMITER + PublicBucket + StorageUtils.DELIMITER + Folder + StorageUtils.DELIMITER;
- }
-
- #region Extension of openstack.net (issue: https://github.com/openstacknetsdk/openstack.net/issues/503) to enable openstack Identity API v3: http://developer.openstack.org/api-ref-identity-v3.html
-
- public class IdentityTokenV3 : IdentityToken
- {
- public new string Id { get; set; }
- }
-
- public class BlueMixIdentityProvider : CloudIdentityProvider
- {
- public string AuthToken { get; set; }
- new ICache TokenCache { get; }
-
- public BlueMixIdentityProvider(Uri urlBase, CloudIdentity defaultIdentity)
- : this(urlBase, defaultIdentity, null, null)
- {
- AuthToken = null;
- }
-
- public BlueMixIdentityProvider(Uri urlBase, CloudIdentity defaultIdentity, IRestService restService, ICache tokenCache)
- : base(defaultIdentity, restService, tokenCache, urlBase)
- {
- AuthToken = null;
- if (urlBase == null)
- throw new ArgumentNullException(nameof(urlBase));
- }
-
- public new IdentityToken GetToken(CloudIdentity identity, bool forceCacheRefresh = false)
- {
- CheckIdentity(identity);
-
- GetUserAccess(identity, forceCacheRefresh);
-
- IdentityTokenV3 token = new IdentityTokenV3();
- token.Id = AuthToken;
- return token;
- }
- public new UserAccessV3 GetUserAccess(CloudIdentity identity, bool forceCacheRefresh = false)
- {
- identity = identity ?? DefaultIdentity;
-
- CloudIdentityWithProject identityWithProject = identity as CloudIdentityWithProject;
-
- if (string.IsNullOrEmpty(identityWithProject.Password))
- throw new NotSupportedException(string.Format("The {0} identity must specify a password.", typeof(CloudIdentityWithProject)));
- if (!string.IsNullOrEmpty(identityWithProject.APIKey))
- throw new NotSupportedException(string.Format("The {0} identity does not support API key authentication.", typeof(CloudIdentityWithProject)));
-
- var auth = Authorization.CreateCredentials(identityWithProject);
- var projectId = identityWithProject.ProjectId != null ? JToken.FromObject(identityWithProject.ProjectId) : string.Empty;
-
- var response = ExecuteRESTRequest(identity, new Uri(UrlBase, "/v3/auth/tokens"), HttpMethod.POST, auth, isTokenRequest: true);
- if (response == null || response.Data == null)
- return null;
-
- AuthToken = response.Headers[0].Value;
- JToken userAccessObject = response.Data["token"];
- if (userAccessObject == null)
- return null;
-
- UserAccessV3 access = userAccessObject.ToObject();
- if (access == null)
- return null;
-
- string key = string.Format("{0}:{1}/{2}", UrlBase, identityWithProject.ProjectId, identityWithProject.Username);
-
- return access;
- }
-
- protected override string LookupServiceTypeKey(IServiceType serviceType)
- {
- return serviceType.Type;
- }
-
- }
-
- public class Authorization
- {
- //Generates the JSON with the credentials to authenticate to the Identity API v3
- public static JObject CreateCredentials(CloudIdentityWithProject identityWithProject)
- {
- return new JObject(
- new JProperty("auth", new JObject(
- new JProperty("identity", new JObject(
- new JProperty("methods", new JArray("password")),
- new JProperty("password", new JObject(
- new JProperty("user", new JObject(
- new JProperty("id", JValue.CreateString(identityWithProject.Username)),
- new JProperty("password", JValue.CreateString(identityWithProject.Password)))))))),
- new JProperty("scope", new JObject(
- new JProperty("project", new JObject(
- new JProperty("id", JValue.CreateString(identityWithProject.ProjectName)))))))));
- }
- }
-
- internal class EncodeDecodeProviderAux : IEncodeDecodeProvider
- {
-
- private static readonly EncodeDecodeProviderAux _default = new EncodeDecodeProviderAux();
-
- public static EncodeDecodeProviderAux Default
- {
- get
- {
- return _default;
- }
- }
-
- public string UrlEncode(string stringToEncode)
- {
- if (stringToEncode == null)
- return null;
-
- return UriUtility.UriEncode(stringToEncode, UriPart.AnyUrl);
- }
-
- public string UrlDecode(string stringToDecode)
- {
- if (stringToDecode == null)
- return null;
-
- return UriUtility.UriDecode(stringToDecode);
- }
- }
-
- public class BluemixFilesProvider : CloudFilesProvider
- {
- private readonly IObjectStorageValidator _cloudFilesValidator;
- private readonly IEncodeDecodeProvider _encodeDecodeProvider;
- private readonly IObjectStorageMetadataProcessor _cloudFilesMetadataProcessor;
- private readonly IStatusParser _statusParser;
- private readonly IObjectMapper _bulkDeletionResultMapper;
- public BluemixFilesProvider(CloudIdentity defaultIdentity, string defaultRegion, IIdentityProvider identityProvider, IRestService restService)
- : base(defaultIdentity, defaultRegion, identityProvider, restService)
- {
- _cloudFilesValidator = CloudFilesValidator.Default;
- _encodeDecodeProvider = EncodeDecodeProviderAux.Default;
- _cloudFilesMetadataProcessor = CloudFilesMetadataProcessor.Default;
- _statusParser = HttpStatusCodeParser.Default;
- _bulkDeletionResultMapper = new BulkDeletionResultMapper(_statusParser);
- }
-
- public new string GetServiceEndpointCloudFiles(CloudIdentity identity, string region = null, bool useInternalUrl = false)
- {
- string serviceType = "object-store";
-
- if (serviceType == null)
- throw new ArgumentNullException("serviceType");
- if (string.IsNullOrEmpty(serviceType))
- throw new ArgumentException("serviceType cannot be empty");
- CheckIdentity(identity);
-
- identity = GetDefaultIdentity(identity);
-
- var userAccess = ((BlueMixIdentityProvider)IdentityProvider).GetUserAccess(identity);
-
- if (userAccess == null || userAccess.Catalog == null)
- throw new UserAuthenticationException("Unable to authenticate user and retrieve authorized service endpoints.");
-
- IEnumerable services = userAccess.Catalog.Where(sc => string.Equals(sc.Type, serviceType, StringComparison.OrdinalIgnoreCase));
-
- IEnumerable> endpoints =
- services.SelectMany(service => service.Endpoints.Select(endpoint => Tuple.Create(service, endpoint)));
-
- string effectiveRegion = region;
- if (string.IsNullOrEmpty(effectiveRegion))
- {
- if (!string.IsNullOrEmpty(DefaultRegion))
- effectiveRegion = DefaultRegion;
- else if (!string.IsNullOrEmpty(userAccess.User.DefaultRegion))
- effectiveRegion = userAccess.User.DefaultRegion;
- }
-
- IEnumerable> regionEndpoints =
- endpoints.Where(i => string.Equals(i.Item2.Region ?? string.Empty, effectiveRegion ?? string.Empty, StringComparison.OrdinalIgnoreCase));
-
- if (regionEndpoints.Any())
- endpoints = regionEndpoints;
- else
- endpoints = endpoints.Where(i => string.IsNullOrEmpty(i.Item2.Region));
-
- if (effectiveRegion == null && !endpoints.Any())
- throw new NoDefaultRegionSetException("No region was provided, the service does not provide a region-independent endpoint, and there is no default region set for the user's account.");
-
- Tuple serviceEndpoint = endpoints.Where(e => e.Item2.Url.Contains("softlayer")).FirstOrDefault();
- if (serviceEndpoint == null)
- throw new UserAuthorizationException("The user does not have access to the requested service or region.");
-
- return serviceEndpoint.Item2.Url;
- }
- public new ObjectStore CreateContainer(string container, Dictionary headers = null, string region = null, bool useInternalUrl = false, CloudIdentity identity = null)
- {
- if (container == null)
- throw new ArgumentNullException(nameof(container));
- if (string.IsNullOrEmpty(container))
- throw new ArgumentException("container cannot be empty");
- CheckIdentity(identity);
-
- _cloudFilesValidator.ValidateContainerName(container);
- var urlPath = new Uri(string.Format("{0}/{1}", GetServiceEndpointCloudFiles(identity, region, useInternalUrl), _encodeDecodeProvider.UrlEncode(container)));
-
- var response = ExecuteRESTRequest(identity, urlPath, HttpMethod.PUT, headers: headers);
-
- switch (response.StatusCode)
- {
- case HttpStatusCode.Created:
- return ObjectStore.ContainerCreated;
-
- case HttpStatusCode.Accepted:
- return ObjectStore.ContainerExists;
-
- default:
- throw new ResponseException(string.Format("Unexpected status {0} returned by Create Container.", response.StatusCode), response);
- }
- }
-
- public new void CreateObject(string container, Stream stream, string objectName, string contentType = null, int chunkSize = 4096, Dictionary headers = null, string region = null, Action progressUpdated = null, bool useInternalUrl = false, CloudIdentity identity = null)
- {
- if (container == null)
- throw new ArgumentNullException(nameof(container));
- if (stream == null)
- throw new ArgumentNullException(nameof(stream));
- if (objectName == null)
- throw new ArgumentNullException(nameof(objectName));
- if (string.IsNullOrEmpty(container))
- throw new ArgumentException("container cannot be empty");
- if (string.IsNullOrEmpty(objectName))
- throw new ArgumentException("objectName cannot be empty");
- if (chunkSize < 0)
- throw new ArgumentOutOfRangeException(nameof(chunkSize));
- CheckIdentity(identity);
-
- _cloudFilesValidator.ValidateContainerName(container);
- _cloudFilesValidator.ValidateObjectName(objectName);
-
- if (stream.Length > LargeFileBatchThreshold)
- {
- throw new ArgumentException("objectName is too big");
- }
- var urlPath = new Uri(string.Format("{0}/{1}/{2}", GetServiceEndpointCloudFiles(identity, region, useInternalUrl), _encodeDecodeProvider.UrlEncode(container), _encodeDecodeProvider.UrlEncode(objectName)));
-
- RequestSettings settings = BuildDefaultRequestSettings();
- settings.ChunkRequest = true;
- settings.ContentType = contentType;
-
- StreamRESTRequest(identity, urlPath, HttpMethod.PUT, stream, chunkSize, headers: headers, progressUpdated: progressUpdated, requestSettings: settings);
- }
-
- public new void CreateObjectFromFile(string container, string filePath, string objectName = null, string contentType = null, int chunkSize = 4096, Dictionary headers = null, string region = null, Action progressUpdated = null, bool useInternalUrl = false, CloudIdentity identity = null)
- {
- if (container == null)
- throw new ArgumentNullException(nameof(container));
- if (filePath == null)
- throw new ArgumentNullException(nameof(filePath));
- if (string.IsNullOrEmpty(container))
- throw new ArgumentException("container cannot be empty");
- if (string.IsNullOrEmpty(filePath))
- throw new ArgumentException("filePath cannot be empty");
- if (chunkSize < 0)
- throw new ArgumentOutOfRangeException(nameof(chunkSize));
- CheckIdentity(identity);
-
- if (string.IsNullOrEmpty(objectName))
- objectName = Path.GetFileName(filePath);
-
- using (var stream = File.OpenRead(filePath))
- {
- CreateObject(container, stream, objectName, contentType, chunkSize, headers, region, progressUpdated, useInternalUrl, identity);
- }
- }
- public new void UpdateObjectMetadata(string container, string objectName, Dictionary metadata, string region = null, bool useInternalUrl = false, CloudIdentity identity = null)
- {
- if (container == null)
- throw new ArgumentNullException(nameof(container));
- if (objectName == null)
- throw new ArgumentNullException(nameof(objectName));
- if (metadata == null)
- throw new ArgumentNullException(nameof(metadata));
- if (string.IsNullOrEmpty(container))
- throw new ArgumentException("container cannot be empty");
- if (string.IsNullOrEmpty(objectName))
- throw new ArgumentException("objectName cannot be empty");
- CheckIdentity(identity);
-
- _cloudFilesValidator.ValidateContainerName(container);
- _cloudFilesValidator.ValidateObjectName(objectName);
-
- var headers = new Dictionary(StringComparer.OrdinalIgnoreCase);
- foreach (KeyValuePair m in metadata)
- {
- if (string.IsNullOrEmpty(m.Key))
- throw new ArgumentException("metadata cannot contain any empty keys");
-
- headers.Add(ObjectMetaDataPrefix + m.Key, EncodeUnicodeValue(m.Value));
- }
-
- var urlPath = new Uri(string.Format("{0}/{1}/{2}", GetServiceEndpointCloudFiles(identity, region, useInternalUrl), _encodeDecodeProvider.UrlEncode(container), _encodeDecodeProvider.UrlEncode(objectName)));
-
- RequestSettings settings = BuildDefaultRequestSettings();
- // make sure the content type is not changed by the metadata operation
- settings.ContentType = null;
-
- ExecuteRESTRequest(identity, urlPath, HttpMethod.POST, headers: headers, settings: settings);
- }
-
- public new Dictionary GetObjectMetaData(string container, string objectName, string region = null, bool useInternalUrl = false, CloudIdentity identity = null)
- {
- if (container == null)
- throw new ArgumentNullException(nameof(container));
- if (objectName == null)
- throw new ArgumentNullException(nameof(objectName));
- if (string.IsNullOrEmpty(container))
- throw new ArgumentException("container cannot be empty");
- if (string.IsNullOrEmpty(objectName))
- throw new ArgumentException("objectName cannot be empty");
- CheckIdentity(identity);
-
- _cloudFilesValidator.ValidateContainerName(container);
- _cloudFilesValidator.ValidateObjectName(objectName);
- var urlPath = new Uri(string.Format("{0}/{1}/{2}", GetServiceEndpointCloudFiles(identity, region, useInternalUrl), _encodeDecodeProvider.UrlEncode(container), _encodeDecodeProvider.UrlEncode(objectName)));
-
- var response = ExecuteRESTRequest(identity, urlPath, HttpMethod.HEAD);
-
- var processedHeaders = _cloudFilesMetadataProcessor.ProcessMetadata(response.Headers);
-
- return processedHeaders[ProcessedHeadersMetadataKey];
- }
-
- public new void GetObject(string container, string objectName, Stream outputStream, int chunkSize = 4096, Dictionary headers = null, string region = null, bool verifyEtag = false, Action progressUpdated = null, bool useInternalUrl = false, CloudIdentity identity = null)
- {
- if (container == null)
- throw new ArgumentNullException(nameof(container));
- if (objectName == null)
- throw new ArgumentNullException(nameof(objectName));
- if (outputStream == null)
- throw new ArgumentNullException(nameof(outputStream));
- if (string.IsNullOrEmpty(container))
- throw new ArgumentException("container cannot be empty");
- if (string.IsNullOrEmpty(objectName))
- throw new ArgumentException("objectName cannot be empty");
- if (chunkSize < 0)
- throw new ArgumentOutOfRangeException(nameof(chunkSize));
- CheckIdentity(identity);
-
- _cloudFilesValidator.ValidateContainerName(container);
- _cloudFilesValidator.ValidateObjectName(objectName);
-
- var urlPath = new Uri(string.Format("{0}/{1}/{2}", GetServiceEndpointCloudFiles(identity, region, useInternalUrl), _encodeDecodeProvider.UrlEncode(container), _encodeDecodeProvider.UrlEncode(objectName)));
-
- long? initialPosition;
- try
- {
- initialPosition = outputStream.Position;
- }
- catch (NotSupportedException)
- {
- if (verifyEtag)
- throw;
-
- initialPosition = null;
- }
-
- // This flag indicates whether the outputStream needs to be set prior to copying data.
- // See: https://github.com/openstacknetsdk/openstack.net/issues/297
- bool requiresPositionReset = false;
-
- var response = ExecuteRESTRequest(identity, urlPath, HttpMethod.GET, (resp, isError) =>
- {
- if (resp == null)
- return new Response(0, null, null);
-
- string body;
-
- if (!isError)
- {
- using (var respStream = resp.GetResponseStream())
- {
- // The second condition will throw a proper NotSupportedException if the position
- // cannot be checked.
- if (requiresPositionReset && outputStream.Position != initialPosition)
- outputStream.Position = initialPosition.Value;
-
- requiresPositionReset = true;
- CopyStream(respStream, outputStream, chunkSize, progressUpdated);
- }
-
- body = "[Binary]";
- }
- else
- {
- using (StreamReader reader = new StreamReader(resp.GetResponseStream()))
- {
- body = reader.ReadToEnd();
- }
- }
-
- var respHeaders = resp.Headers.AllKeys.Select(key => new HttpHeader(key, resp.GetResponseHeader(key))).ToList();
-
- return new Response(resp.StatusCode, respHeaders, body);
- }, headers: headers);
-
- }
-
- public new void GetObjectSaveToFile(string container, string saveDirectory, string objectName, string fileName = null, int chunkSize = 65536, Dictionary headers = null, string region = null, bool verifyEtag = false, Action progressUpdated = null, bool useInternalUrl = false, CloudIdentity identity = null)
- {
- if (container == null)
- throw new ArgumentNullException(nameof(container));
- if (saveDirectory == null)
- throw new ArgumentNullException(nameof(saveDirectory));
- if (objectName == null)
- throw new ArgumentNullException(nameof(objectName));
- if (string.IsNullOrEmpty(container))
- throw new ArgumentException("container cannot be empty");
- if (string.IsNullOrEmpty(saveDirectory))
- throw new ArgumentException("saveDirectory cannot be empty");
- if (string.IsNullOrEmpty(objectName))
- throw new ArgumentException("objectName cannot be empty");
- if (chunkSize < 0)
- throw new ArgumentOutOfRangeException(nameof(chunkSize));
- CheckIdentity(identity);
-
- if (string.IsNullOrEmpty(fileName))
- fileName = objectName;
-
- var filePath = Path.Combine(saveDirectory, string.IsNullOrEmpty(fileName) ? objectName : fileName);
-
- try
- {
- using (var fileStream = File.Open(filePath, FileMode.Create, FileAccess.ReadWrite))
- {
- GetObject(container, objectName, fileStream, chunkSize, headers, region, verifyEtag, progressUpdated, useInternalUrl, identity);
- }
- }
- catch (InvalidETagException)
- {
- File.Delete(filePath);
- throw;
- }
- }
-
- private static string EncodeUnicodeValue(string value)
- {
- if (value == null)
- return null;
-
- return Encoding.GetEncoding("ISO-8859-1").GetString(Encoding.UTF8.GetBytes(value));
- }
- internal void CheckResponse(Response response)
- {
- if (response == null)
- throw new ArgumentNullException(nameof(response));
-
- ResponseCodeValidator.Validate(response);
- }
- protected new Response ExecuteRESTRequest(CloudIdentity identity, Uri absoluteUri, HttpMethod method, object body = null, Dictionary queryStringParameter = null, Dictionary headers = null, bool isRetry = false, bool isTokenRequest = false, RequestSettings settings = null)
- {
- if (absoluteUri == null)
- throw new ArgumentNullException(nameof(absoluteUri));
- CheckIdentity(identity);
-
- return ExecuteRESTRequest>(identity, absoluteUri, method, body, queryStringParameter, headers, isRetry, isTokenRequest, settings, RestService.Execute);
- }
- protected new Response ExecuteRESTRequest(CloudIdentity identity, Uri absoluteUri, HttpMethod method, object body = null, Dictionary queryStringParameter = null, Dictionary headers = null, bool isRetry = false, bool isTokenRequest = false, RequestSettings settings = null)
- {
- if (absoluteUri == null)
- throw new ArgumentNullException(nameof(absoluteUri));
- CheckIdentity(identity);
-
- return ExecuteRESTRequest(identity, absoluteUri, method, body, queryStringParameter, headers, isRetry, isTokenRequest, settings, RestService.Execute);
- }
-
- protected new Response ExecuteRESTRequest(CloudIdentity identity, Uri absoluteUri, HttpMethod method, Func buildResponseCallback, object body = null, Dictionary queryStringParameter = null, Dictionary headers = null, bool isRetry = false, bool isTokenRequest = false, RequestSettings settings = null)
- {
- if (absoluteUri == null)
- throw new ArgumentNullException(nameof(absoluteUri));
- CheckIdentity(identity);
-
- return ExecuteRESTRequest(identity, absoluteUri, method, body, queryStringParameter, headers, isRetry, isTokenRequest, settings,
- (uri, requestMethod, requestBody, requestHeaders, requestQueryParams, requestSettings) => RestService.Execute(uri, requestMethod, buildResponseCallback, requestBody, requestHeaders, requestQueryParams, requestSettings));
- }
-
- private T ExecuteRESTRequest(CloudIdentity identity, Uri absoluteUri, HttpMethod method, object body, Dictionary queryStringParameter, Dictionary headers, bool isRetry, bool isTokenRequest, RequestSettings requestSettings,
- Func, Dictionary, RequestSettings, T> callback) where T : Response
- {
- if (absoluteUri == null)
- throw new ArgumentNullException(nameof(absoluteUri));
- CheckIdentity(identity);
-
- identity = GetDefaultIdentity(identity);
-
- if (requestSettings == null)
- requestSettings = BuildDefaultRequestSettings();
-
- if (headers == null)
- headers = new Dictionary();
-
- if (!isTokenRequest)
- headers["X-Auth-Token"] = ((IdentityTokenV3)((BlueMixIdentityProvider)IdentityProvider).GetToken(identity, isRetry)).Id;
-
- string bodyStr = null;
- if (body != null)
- {
- if (body is JObject)
- bodyStr = body.ToString();
- else if (body is string)
- bodyStr = body as string;
- else
- bodyStr = JsonConvert.SerializeObject(body, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
- }
-
- if (string.IsNullOrEmpty(requestSettings.UserAgent))
- requestSettings.UserAgent = DefaultUserAgent;
-
- var response = callback(absoluteUri, method, bodyStr, headers, queryStringParameter, requestSettings);
-
- // on errors try again 1 time.
- if (response.StatusCode == HttpStatusCode.Unauthorized && !isRetry && !isTokenRequest)
- {
- return ExecuteRESTRequest(identity, absoluteUri, method, body, queryStringParameter, headers, true, isTokenRequest, requestSettings, callback);
- }
-
- CheckResponse(response);
-
- return response;
- }
-
- protected new Response StreamRESTRequest(CloudIdentity identity, Uri absoluteUri, HttpMethod method, Stream stream, int chunkSize, long maxReadLength = 0, Dictionary queryStringParameter = null, Dictionary headers = null, bool isRetry = false, RequestSettings requestSettings = null, Action progressUpdated = null)
- {
- if (absoluteUri == null)
- throw new ArgumentNullException(nameof(absoluteUri));
- if (stream == null)
- throw new ArgumentNullException(nameof(stream));
- if (chunkSize <= 0)
- throw new ArgumentOutOfRangeException(nameof(chunkSize));
- if (maxReadLength < 0)
- throw new ArgumentOutOfRangeException(nameof(maxReadLength));
- CheckIdentity(identity);
-
- identity = GetDefaultIdentity(identity);
-
- if (requestSettings == null)
- requestSettings = BuildDefaultRequestSettings();
-
- requestSettings.Timeout = TimeSpan.FromMilliseconds(14400000); // Need to pass this in.
-
- if (headers == null)
- headers = new Dictionary();
-
- headers["X-Auth-Token"] = ((IdentityTokenV3)((BlueMixIdentityProvider)IdentityProvider).GetToken(identity, isRetry)).Id; ;
-
- if (string.IsNullOrEmpty(requestSettings.UserAgent))
- requestSettings.UserAgent = DefaultUserAgent;
-
- long? initialPosition;
- try
- {
- initialPosition = stream.Position;
- }
- catch (NotSupportedException)
- {
- initialPosition = null;
- }
-
- Response response;
- try
- {
- response = RestService.Stream(absoluteUri, method, stream, chunkSize, maxReadLength, headers, queryStringParameter, requestSettings, progressUpdated);
- }
- catch (ProtocolViolationException)
- {
- ServicePoint servicePoint = ServicePointManager.FindServicePoint(absoluteUri);
- if (servicePoint.ProtocolVersion < HttpVersion.Version11)
- {
- // this is a workaround for issue #333
- // https://github.com/openstacknetsdk/openstack.net/issues/333
- // http://stackoverflow.com/a/22976809/138304
- int maxIdleTime = servicePoint.MaxIdleTime;
- servicePoint.MaxIdleTime = 0;
- Thread.Sleep(1);
- servicePoint.MaxIdleTime = maxIdleTime;
- }
-
- response = RestService.Stream(absoluteUri, method, stream, chunkSize, maxReadLength, headers, queryStringParameter, requestSettings, progressUpdated);
- }
-
- // on errors try again 1 time.
- if (response.StatusCode == HttpStatusCode.Unauthorized && !isRetry && initialPosition != null)
- {
- bool canRetry;
-
- try
- {
- if (stream.Position != initialPosition.Value)
- stream.Position = initialPosition.Value;
-
- canRetry = true;
- }
- catch (NotSupportedException)
- {
- // unable to retry the operation
- canRetry = false;
- }
-
- if (canRetry)
- {
- return StreamRESTRequest(identity, absoluteUri, method, stream, chunkSize, maxReadLength, queryStringParameter, headers, true, requestSettings, progressUpdated);
- }
- }
-
- CheckResponse(response);
-
- return response;
- }
-
- public new void DeleteObject(string container, string objectName, Dictionary headers = null, bool deleteSegments = true, string region = null, bool useInternalUrl = false, CloudIdentity identity = null)
- {
- if (container == null)
- throw new ArgumentNullException(nameof(container));
- if (objectName == null)
- throw new ArgumentNullException(nameof(objectName));
- if (string.IsNullOrEmpty(container))
- throw new ArgumentException("container cannot be empty");
- if (string.IsNullOrEmpty(objectName))
- throw new ArgumentException("objectName cannot be empty");
- CheckIdentity(identity);
-
- _cloudFilesValidator.ValidateContainerName(container);
- _cloudFilesValidator.ValidateObjectName(objectName);
-
- Dictionary objectHeader = null;
- if (deleteSegments)
- objectHeader = GetObjectHeaders(container, objectName, region, useInternalUrl, identity);
-
- if (deleteSegments && objectHeader != null && objectHeader.Any(h => h.Key.Equals(ObjectManifest, StringComparison.OrdinalIgnoreCase)))
- {
- var objects = ListObjects(container, region: region, useInternalUrl: useInternalUrl,
- identity: identity);
-
- if (objects != null && objects.Any())
- {
- var segments = objects.Where(f => f.Name.StartsWith(string.Format("{0}.seg", objectName)));
- var delObjects = new List { objectName };
- if (segments.Any())
- delObjects.AddRange(segments.Select(s => s.Name));
-
- DeleteObjects(container, delObjects, headers, region, useInternalUrl, identity);
- }
- else
- throw new Exception(string.Format("Object \"{0}\" in container \"{1}\" does not exist.", objectName, container));
- }
- else
- {
- var urlPath = new Uri(string.Format("{0}/{1}/{2}", GetServiceEndpointCloudFiles(identity, region, useInternalUrl), _encodeDecodeProvider.UrlEncode(container), _encodeDecodeProvider.UrlEncode(objectName)));
-
- ExecuteRESTRequest(identity, urlPath, HttpMethod.DELETE, headers: headers);
- }
- }
-
- public new Dictionary GetObjectHeaders(string container, string objectName, string region = null, bool useInternalUrl = false, CloudIdentity identity = null)
- {
- if (container == null)
- throw new ArgumentNullException(nameof(container));
- if (objectName == null)
- throw new ArgumentNullException(nameof(objectName));
- if (string.IsNullOrEmpty(container))
- throw new ArgumentException("container cannot be empty");
- if (string.IsNullOrEmpty(objectName))
- throw new ArgumentException("objectName cannot be empty");
- CheckIdentity(identity);
-
- _cloudFilesValidator.ValidateContainerName(container);
- _cloudFilesValidator.ValidateObjectName(objectName);
- var urlPath = new Uri(string.Format("{0}/{1}/{2}", GetServiceEndpointCloudFiles(identity, region, useInternalUrl), _encodeDecodeProvider.UrlEncode(container), _encodeDecodeProvider.UrlEncode(objectName)));
-
- var response = ExecuteRESTRequest(identity, urlPath, HttpMethod.HEAD);
-
- var processedHeaders = _cloudFilesMetadataProcessor.ProcessMetadata(response.Headers);
-
- return processedHeaders[ProcessedHeadersHeaderKey];
- }
-
- public new IEnumerable ListObjects(string container, int? limit = null, string marker = null, string markerEnd = null, string prefix = null, string region = null, bool useInternalUrl = false, CloudIdentity identity = null)
- {
- if (container == null)
- throw new ArgumentNullException(nameof(container));
- if (string.IsNullOrEmpty(container))
- throw new ArgumentException("container cannot be empty");
- if (limit < 0)
- throw new ArgumentOutOfRangeException(nameof(limit));
- CheckIdentity(identity);
-
- _cloudFilesValidator.ValidateContainerName(container);
- var urlPath = new Uri(string.Format("{0}/{1}", GetServiceEndpointCloudFiles(identity, region, useInternalUrl), _encodeDecodeProvider.UrlEncode(container)));
-
- var queryStringParameter = new Dictionary();
-
- if (limit != null)
- queryStringParameter.Add("limit", limit.ToString());
-
- if (!string.IsNullOrEmpty(marker))
- queryStringParameter.Add("marker", marker);
-
- if (!string.IsNullOrEmpty(markerEnd))
- queryStringParameter.Add("end_marker", markerEnd);
-
- if (!string.IsNullOrEmpty(prefix))
- queryStringParameter.Add("prefix", prefix);
-
- var response = ExecuteRESTRequest(identity, urlPath, HttpMethod.GET, null, queryStringParameter);
-
- if (response == null || response.Data == null)
- return null;
-
- return response.Data;
- }
-
- public new void CopyObject(string sourceContainer, string sourceObjectName, string destinationContainer, string destinationObjectName, string destinationContentType = null, Dictionary headers = null, string region = null, bool useInternalUrl = false, CloudIdentity identity = null)
- {
- if (sourceContainer == null)
- throw new ArgumentNullException(nameof(sourceContainer));
- if (sourceObjectName == null)
- throw new ArgumentNullException(nameof(sourceObjectName));
- if (destinationContainer == null)
- throw new ArgumentNullException(nameof(destinationContainer));
- if (destinationObjectName == null)
- throw new ArgumentNullException(nameof(destinationObjectName));
- if (string.IsNullOrEmpty(sourceContainer))
- throw new ArgumentException("sourceContainer cannot be empty");
- if (string.IsNullOrEmpty(sourceObjectName))
- throw new ArgumentException("sourceObjectName cannot be empty");
- if (string.IsNullOrEmpty(destinationContainer))
- throw new ArgumentException("destinationContainer cannot be empty");
- if (string.IsNullOrEmpty(destinationObjectName))
- throw new ArgumentException("destinationObjectName cannot be empty");
- CheckIdentity(identity);
-
- _cloudFilesValidator.ValidateContainerName(sourceContainer);
- _cloudFilesValidator.ValidateObjectName(sourceObjectName);
-
- _cloudFilesValidator.ValidateContainerName(destinationContainer);
- _cloudFilesValidator.ValidateObjectName(destinationObjectName);
-
- var urlPath = new Uri(string.Format("{0}/{1}/{2}", GetServiceEndpointCloudFiles(identity, region, useInternalUrl), _encodeDecodeProvider.UrlEncode(sourceContainer), _encodeDecodeProvider.UrlEncode(sourceObjectName)));
-
- if (headers == null)
- headers = new Dictionary(StringComparer.OrdinalIgnoreCase);
-
- headers.Add(Destination, string.Format("{0}/{1}", UriUtility.UriEncode(destinationContainer, UriPart.AnyUrl, Encoding.UTF8), UriUtility.UriEncode(destinationObjectName, UriPart.AnyUrl, Encoding.UTF8)));
-
- RequestSettings settings = BuildDefaultRequestSettings();
- if (destinationContentType != null)
- {
- settings.ContentType = destinationContentType;
- }
- else
- {
- // make sure to preserve the content type during the copy operation
- settings.ContentType = null;
- }
-
- ExecuteRESTRequest(identity, urlPath, HttpMethod.COPY, headers: headers, settings: settings);
- }
-
- public new void DeleteObjects(string container, IEnumerable objects, Dictionary headers = null, string region = null, bool useInternalUrl = false, CloudIdentity identity = null)
- {
- if (container == null)
- throw new ArgumentNullException(nameof(container));
- if (objects == null)
- throw new ArgumentNullException(nameof(objects));
- if (string.IsNullOrEmpty(container))
- throw new ArgumentException("container cannot be empty");
-
- BulkDelete(objects.Select(o => new KeyValuePair(container, o)), headers, region, useInternalUrl, identity);
- }
-
- public new void BulkDelete(IEnumerable> items, Dictionary headers = null, string region = null, bool useInternalUrl = false, CloudIdentity identity = null)
- {
- var urlPath = new Uri(string.Format("{0}/?bulk-delete", GetServiceEndpointCloudFiles(identity, region, useInternalUrl)));
-
- var encoded = items.Select(
- pair =>
- {
- if (string.IsNullOrEmpty(pair.Key))
- throw new ArgumentException("items", "items cannot contain any entries with a null or empty key (container name)");
- if (string.IsNullOrEmpty(pair.Value))
- throw new ArgumentException("items", "items cannot contain any entries with a null or empty value (object name)");
- _cloudFilesValidator.ValidateContainerName(pair.Key);
- _cloudFilesValidator.ValidateObjectName(pair.Value);
-
- return string.Format("/{0}/{1}", _encodeDecodeProvider.UrlEncode(pair.Key), _encodeDecodeProvider.UrlEncode(pair.Value));
- });
- var body = string.Join("\n", encoded.ToArray());
-
- var response = ExecuteRESTRequest(identity, urlPath, HttpMethod.POST, body: body, headers: headers, settings: new JSIStudios.SimpleRESTServices.Client.Json.JsonRequestSettings { ContentType = "text/plain" });
-
- Status status;
- if (_statusParser.TryParse(response.Data.Status, out status))
- {
- if (status.Code != 200 && !response.Data.Errors.Any())
- {
- response.Data.AllItems = encoded;
- throw new BulkDeletionException(response.Data.Status, _bulkDeletionResultMapper.Map(response.Data));
- }
- }
- }
-
- public new Uri CreateTemporaryPublicUri(HttpMethod method, string container, string objectName, string key, DateTimeOffset expiration, string region = null, bool useInternalUrl = false, CloudIdentity identity = null)
- {
- if (container == null)
- throw new ArgumentNullException(nameof(container));
- if (objectName == null)
- throw new ArgumentNullException(nameof(objectName));
- if (key == null)
- throw new ArgumentNullException(nameof(key));
- if (string.IsNullOrEmpty(container))
- throw new ArgumentException("container cannot be empty");
- if (string.IsNullOrEmpty(objectName))
- throw new ArgumentException("objectName cannot be empty");
- if (string.IsNullOrEmpty(key))
- throw new ArgumentException("key cannot be empty");
- CheckIdentity(identity);
-
- _cloudFilesValidator.ValidateContainerName(container);
- _cloudFilesValidator.ValidateObjectName(objectName);
-
- Uri baseAddress = new Uri(GetServiceEndpointCloudFiles(identity, region, useInternalUrl), UriKind.Absolute);
-
- StringBuilder body = new StringBuilder();
- body.Append(method.ToString().ToUpperInvariant()).Append('\n');
- body.Append(ToTimestamp(expiration) / 1000).Append('\n');
- body.Append(baseAddress.PathAndQuery).Append('/').Append(container).Append('/').Append(objectName);
-
- using (HMAC hmac = HMAC.Create())
- {
- hmac.HashName = "SHA1";
- hmac.Key = Encoding.UTF8.GetBytes(key);
- byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(body.ToString()));
- string sig = string.Join(string.Empty, Array.ConvertAll(hash, i => i.ToString("x2")));
- string expires = (ToTimestamp(expiration) / 1000).ToString();
-
- return new Uri(String.Format("{0}/{1}/{2}?temp_url_sig={3}&temp_url_expires={4}", baseAddress, container, objectName, sig, expires));
- }
- }
- private long ToTimestamp(DateTimeOffset dateTimeOffset)
- {
- DateTimeOffset Epoch = new DateTimeOffset(new DateTime(1970, 1, 1), TimeSpan.Zero);
- return (long)(dateTimeOffset - Epoch).TotalMilliseconds;
- }
-
- }
- internal class BulkDeletionResultMapper : IObjectMapper
- {
- private readonly IStatusParser _statusParser;
-
- public BulkDeletionResultMapper(IStatusParser statusParser)
- {
- _statusParser = statusParser;
- }
-
- public BulkDeletionResults Map(BulkDeleteResponse from)
- {
- var successfulObjects = from.AllItems.Where(i => !from.IsItemError(i));
- var failedObjects = from.Errors.Select(e =>
- {
- var eParts = e.ToArray();
- Status errorStatus;
- string errorItem;
-
- if (eParts.Length != 2)
- {
- errorStatus = new Status(0, "Unknown");
- errorItem = string.Format("The error array has an unexpected length. Array: {0}", string.Join("||", eParts));
- }
- else
- {
- errorItem = eParts[1];
- if (!_statusParser.TryParse(eParts[0], out errorStatus))
- {
- errorItem = eParts[0];
- if (!_statusParser.TryParse(eParts[1], out errorStatus))
- {
- errorStatus = new Status(0, "Unknown");
- errorItem = string.Format("The error array is in an unknown format. Array: {0}", string.Join("||", eParts));
- }
- }
- }
-
- return new BulkDeletionFailedObject(errorItem, errorStatus);
- });
-
- return new BulkDeletionResults(successfulObjects, failedObjects);
- }
-
- public BulkDeleteResponse Map(BulkDeletionResults to)
- {
- throw new NotImplementedException();
- }
- }
- internal class CloudFilesMetadataProcessor : IObjectStorageMetadataProcessor
- {
-
- private static readonly CloudFilesMetadataProcessor _default = new CloudFilesMetadataProcessor();
-
- public static CloudFilesMetadataProcessor Default
- {
- get
- {
- return _default;
- }
- }
-
- public virtual Dictionary> ProcessMetadata(IList httpHeaders)
- {
- if (httpHeaders == null)
- throw new ArgumentNullException(nameof(httpHeaders));
-
- var pheaders = new Dictionary(StringComparer.OrdinalIgnoreCase);
- var metadata = new Dictionary(StringComparer.OrdinalIgnoreCase);
- foreach (var header in httpHeaders)
- {
- if (header == null)
- throw new ArgumentException("httpHeaders cannot contain any null values");
- if (string.IsNullOrEmpty(header.Key))
- throw new ArgumentException("httpHeaders cannot contain any values with a null or empty key");
-
- if (header.Key.StartsWith(CloudFilesProvider.AccountMetaDataPrefix, StringComparison.OrdinalIgnoreCase))
- {
- metadata.Add(header.Key.Substring(CloudFilesProvider.AccountMetaDataPrefix.Length), DecodeUnicodeValue(header.Value));
- }
- else if (header.Key.StartsWith(CloudFilesProvider.ContainerMetaDataPrefix, StringComparison.OrdinalIgnoreCase))
- {
- metadata.Add(header.Key.Substring(CloudFilesProvider.ContainerMetaDataPrefix.Length), DecodeUnicodeValue(header.Value));
- }
- else if (header.Key.StartsWith(CloudFilesProvider.ObjectMetaDataPrefix, StringComparison.OrdinalIgnoreCase))
- {
- metadata.Add(header.Key.Substring(CloudFilesProvider.ObjectMetaDataPrefix.Length), DecodeUnicodeValue(header.Value));
- }
- else
- {
- pheaders.Add(header.Key, header.Value);
- }
- }
-
- var processedHeaders = new Dictionary>(StringComparer.OrdinalIgnoreCase)
- {
- {CloudFilesProvider.ProcessedHeadersHeaderKey, pheaders},
- {CloudFilesProvider.ProcessedHeadersMetadataKey, metadata}
- };
-
- return processedHeaders;
- }
-
- private string DecodeUnicodeValue(string value)
- {
- return Encoding.UTF8.GetString(Encoding.GetEncoding("ISO-8859-1").GetBytes(value));
- }
- }
-
- [JsonObject(MemberSerialization.OptIn)]
- internal class BulkDeleteResponse
- {
- [JsonProperty("Number Not Found")]
- public int NumberNotFound { get; set; }
-
- [JsonProperty("Response Status")]
- public string Status { get; set; }
-
- [JsonProperty("Errors")]
- public IEnumerable> Errors { get; set; }
-
- [JsonProperty("Number Deleted")]
- public int NumberDeleted { get; set; }
-
- [JsonProperty("Response Body")]
- public string ResponseBody { get; set; }
-
- public IEnumerable AllItems { get; set; }
-
- public bool IsItemError(string s)
- {
- return Errors.Any(e => e.Any(e2 => e2.Equals(s)));
- }
- }
- #region Json Classes
- [JsonObject(MemberSerialization.OptIn)]
- public class Endpoint
- {
- [JsonProperty("region_id")]
- public string Region_id { get; set; }
- [JsonProperty("url")]
- public string Url { get; set; }
- [JsonProperty("region")]
- public string Region { get; set; }
- [JsonProperty("interface")]
- public string @Interface { get; set; }
- [JsonProperty("id")]
- public string Id { get; set; }
- }
- [JsonObject(MemberSerialization.OptIn)]
- public class Catalog
- {
- [JsonProperty("endpoints")]
- public Endpoint[] Endpoints { get; set; }
- [JsonProperty("type")]
- public string Type { get; set; }
- [JsonProperty("id")]
- public string Id { get; set; }
- [JsonProperty("name")]
- public string Name { get; set; }
- }
-
- [JsonObject(MemberSerialization.OptIn)]
- public class UserAccessV3 : UserAccess
- {
- [JsonProperty("catalog")]
- public Catalog[] Catalog { get; set; }
-
- [JsonExtensionData]
- public IDictionary _additionalData;
- }
- #endregion
- #endregion
-
- }
-}
-
diff --git a/dotnet/src/dotnetframework/Providers/Storage/GXBluemix/GXBluemix.csproj b/dotnet/src/dotnetframework/Providers/Storage/GXBluemix/GXBluemix.csproj
deleted file mode 100644
index 37f002ed7..000000000
--- a/dotnet/src/dotnetframework/Providers/Storage/GXBluemix/GXBluemix.csproj
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
- net462
- GXBluemix
- GXBluemix
- CA1501;CA1812
- Bluemix
- GeneXus.Bluemix
-
-
-
-
-
-
-
-
-
- 0.2.5
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/dotnet/src/dotnetframework/Providers/Storage/GXBluemix/GlobalSuppressions.cs b/dotnet/src/dotnetframework/Providers/Storage/GXBluemix/GlobalSuppressions.cs
deleted file mode 100644
index 6ca697c8d..000000000
--- a/dotnet/src/dotnetframework/Providers/Storage/GXBluemix/GlobalSuppressions.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-// This file is used by Code Analysis to maintain SuppressMessage
-// attributes that are applied to this project.
-// Project-level suppressions either have no target or are given
-// a specific target and scoped to a namespace, type, member, etc.
-
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:GeneXus.Storage.GXBluemix.ExternalProviderBluemix.Authorization")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:GeneXus.Storage.GXBluemix.ExternalProviderBluemix.BluemixFilesProvider")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:GeneXus.Storage.GXBluemix.ExternalProviderBluemix.BlueMixIdentityProvider")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:GeneXus.Storage.GXBluemix.ExternalProviderBluemix.Catalog")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:GeneXus.Storage.GXBluemix.ExternalProviderBluemix.Endpoint")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:GeneXus.Storage.GXBluemix.ExternalProviderBluemix.IdentityTokenV3")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:GeneXus.Storage.GXBluemix.ExternalProviderBluemix.UserAccessV3")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "", Scope = "member", Target = "~P:GeneXus.Storage.GXBluemix.ExternalProviderBluemix.Endpoint.Region_id")]
-
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "", Scope = "member", Target = "~P:GeneXus.Storage.GXBluemix.ExternalProviderBluemix.Catalog.Endpoints")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "", Scope = "member", Target = "~P:GeneXus.Storage.GXBluemix.ExternalProviderBluemix.UserAccessV3.Catalog")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "", Scope = "member", Target = "~M:GeneXus.Storage.GXBluemix.ExternalProviderBluemix.BluemixFilesProvider.BulkDelete(System.Collections.Generic.IEnumerable{System.Collections.Generic.KeyValuePair{System.String,System.String}},System.Collections.Generic.Dictionary{System.String,System.String},System.String,System.Boolean,net.openstack.Core.Domain.CloudIdentity)")]
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "", Scope = "member", Target = "~M:GeneXus.Storage.GXBluemix.ExternalProviderBluemix.BluemixFilesProvider.GetServiceEndpointCloudFiles(net.openstack.Core.Domain.CloudIdentity,System.String,System.Boolean)~System.String")]
\ No newline at end of file
diff --git a/dotnet/src/dotnetframework/Providers/Storage/GXBluemix/Properties/AssemblyInfo.cs b/dotnet/src/dotnetframework/Providers/Storage/GXBluemix/Properties/AssemblyInfo.cs
deleted file mode 100644
index bb7c4dab9..000000000
--- a/dotnet/src/dotnetframework/Providers/Storage/GXBluemix/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System.Reflection;
-using System.Resources;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("GXBluemix")]
-
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: NeutralResourcesLanguageAttribute("en")]
diff --git a/dotnet/src/dotnetframework/Providers/Storage/GXBluemix/app.config b/dotnet/src/dotnetframework/Providers/Storage/GXBluemix/app.config
deleted file mode 100644
index 1cd55066a..000000000
--- a/dotnet/src/dotnetframework/Providers/Storage/GXBluemix/app.config
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/dotnet/src/dotnetframework/Providers/Storage/GXGoogleCloud/ExternalProviderGoogle.cs b/dotnet/src/dotnetframework/Providers/Storage/GXGoogleCloud/ExternalProviderGoogle.cs
index 7e72b04b7..e0ab68018 100644
--- a/dotnet/src/dotnetframework/Providers/Storage/GXGoogleCloud/ExternalProviderGoogle.cs
+++ b/dotnet/src/dotnetframework/Providers/Storage/GXGoogleCloud/ExternalProviderGoogle.cs
@@ -15,56 +15,70 @@
namespace GeneXus.Storage.GXGoogleCloud
{
- public class ExternalProviderGoogle : ExternalProvider
+ public class ExternalProviderGoogle : ExternalProviderBase, ExternalProvider
{
- const int BUCKET_EXISTS = 409;
+ public static String Name = "GOOGLECS"; //Google Cloud Storage
+ const int BUCKET_EXISTS = 409;
const int OBJECT_NOT_FOUND = 404;
const string APPLICATION_NAME = "APPLICATION_NAME";
const string PROJECT_ID = "PROJECT_ID";
const string KEY = "KEY";
const string BUCKET = "BUCKET_NAME";
- const string FOLDER = "FOLDER_NAME";
+
StorageClient Client { get; set; }
StorageService Service { get; set; }
String Project { get; set; }
String Bucket { get; set; }
- String Folder { get; set; }
UrlSigner Signer { get; set; }
+
+
public string StorageUri
{
get { return $"https://{Bucket}.storage.googleapis.com/"; }
}
- public ExternalProviderGoogle()
- : this(ServiceFactory.GetGXServices().Get(GXServices.STORAGE_SERVICE))
- {
- }
+ public override string GetName()
+ {
+ return Name;
- public ExternalProviderGoogle(GXService providerService)
- {
+ }
+
+ public ExternalProviderGoogle() : this(null)
+ {
+ }
+
+ public ExternalProviderGoogle(GXService providerService) : base(providerService)
+ {
+ Initialize();
+ }
+
+
+ private void Initialize()
+ {
GoogleCredential credentials;
- using (Stream stream = KeyStream(CryptoImpl.Decrypt(providerService.Properties.Get(KEY))))
- {
- credentials = GoogleCredential.FromStream(stream).CreateScoped(StorageService.Scope.CloudPlatform);
- }
+ string key = GetEncryptedPropertyValue(KEY);
- using (Stream stream = KeyStream(CryptoImpl.Decrypt(providerService.Properties.Get(KEY))))
+ using (Stream stream = KeyStream(key))
{
- Signer = UrlSigner.FromServiceAccountData(stream);
- }
+ credentials = GoogleCredential.FromStream(stream).CreateScoped(StorageService.Scope.CloudPlatform);
+ }
- Client = StorageClient.Create(credentials);
+ using (Stream stream = KeyStream(key))
+ {
+ Signer = UrlSigner.FromServiceAccountData(stream);
+ }
+
+ Client = StorageClient.Create(credentials);
Service = new StorageService(new BaseClientService.Initializer()
{
HttpClientInitializer = credentials,
- ApplicationName = providerService.Properties.Get(APPLICATION_NAME)
+ ApplicationName = GetPropertyValue(APPLICATION_NAME)
});
- Project = providerService.Properties.Get(PROJECT_ID);
- Bucket = CryptoImpl.Decrypt(providerService.Properties.Get(BUCKET));
- Folder = providerService.Properties.Get(FOLDER);
+ Bucket = GetEncryptedPropertyValue(BUCKET);
+ Project = GetPropertyValue(PROJECT_ID);
CreateBucket();
CreateFolder(Folder);
@@ -82,25 +96,33 @@ private Stream KeyStream(string key)
private void CreateBucket()
{
- //objects in bucket are public by default
- ObjectAccessControl defaultAccess = new ObjectAccessControl();
- defaultAccess.Entity = "allUsers";
- defaultAccess.Role = "READER";
-
- Bucket bucket = new Bucket();
- bucket.Name = Bucket;
- bucket.DefaultObjectAcl = new List { defaultAccess };
-
- try
- {
- Client.CreateBucket(Project, bucket);
- }
- catch (GoogleApiException ex)
- {
- if (ex.Error.Code != BUCKET_EXISTS)
- throw ex;
- }
-
+ try
+ {
+ //objects in bucket are public by default
+ ObjectAccessControl defaultAccess = new ObjectAccessControl();
+ defaultAccess.Entity = "allUsers";
+ defaultAccess.Role = "READER";
+
+ Bucket bucket = new Bucket();
+ bucket.Name = Bucket;
+ bucket.DefaultObjectAcl = new List { defaultAccess };
+
+ if (Client.GetBucket(Bucket) == null)
+ {
+ try
+ {
+ Client.CreateBucket(Project, bucket);
+ }
+ catch (GoogleApiException ex)
+ {
+ if (ex.Error.Code != BUCKET_EXISTS)
+ throw ex;
+ }
+ }
+ }
+ catch (Exception) {
+ //We should not fail when Bucket cannot be created. Bucket should be created beforehand as Credentials may not allow Bucket creation.
+ }
}
private void CreateFolder(String name, string table = null, string field = null)
@@ -129,13 +151,13 @@ public string Upload(string fileName, Stream stream, GxFileType fileType)
obj.Name = fileName;
obj.Bucket = Bucket;
- if (Path.GetExtension(fileName).Equals(".tmp"))
- obj.ContentType = "image/jpeg";
- else
- obj.ContentType = MimeMapping.GetMimeMapping(fileName);
-
+ if (TryGetContentType(fileName, out string mimeType, DEFAULT_CONTENT_TYPE))
+ {
+ obj.ContentType = mimeType;
+ }
+
Client.UploadObject(obj, stream, GetUploadOptions(fileType));
- return StorageUri + StorageUtils.EncodeUrl(fileName);
+ return GetURL(fileName, fileType);
}
private Dictionary CreateObjectMetadata(string tableName, string fieldName, string name)
@@ -150,45 +172,55 @@ private Dictionary CreateObjectMetadata(string tableName, string
public string Copy(string objectName, GxFileType sourceFileType, string newName, GxFileType targetFileType)
{
Client.CopyObject(Bucket, objectName, Bucket, newName, GetCopyOptions(targetFileType));
- return GetURL(objectName, targetFileType, 0);
+ return GetURL(objectName, targetFileType);
}
- private static CopyObjectOptions GetCopyOptions(GxFileType fileType)
+
+
+ private PredefinedObjectAcl GetPredefinedAcl(GxFileType acl)
{
- CopyObjectOptions options = new CopyObjectOptions();
- if (fileType.HasFlag(GxFileType.Private))
- options.DestinationPredefinedAcl = PredefinedObjectAcl.ProjectPrivate;
- else
- options.DestinationPredefinedAcl = PredefinedObjectAcl.PublicRead;
- return options;
+ PredefinedObjectAcl objectPredefinedObjectAcl = PredefinedObjectAcl.PublicRead;
+ if (acl.HasFlag(GxFileType.Private))
+ {
+ objectPredefinedObjectAcl = PredefinedObjectAcl.ProjectPrivate;
+ }
+ else if (acl.HasFlag(GxFileType.PublicRead))
+ {
+ objectPredefinedObjectAcl = PredefinedObjectAcl.PublicRead;
+ }
+ else if (this.defaultAcl == GxFileType.Private)
+ {
+ objectPredefinedObjectAcl = PredefinedObjectAcl.Private;
+ }
+ return objectPredefinedObjectAcl;
+ }
+
+ private CopyObjectOptions GetCopyOptions(GxFileType acl)
+ {
+ return new CopyObjectOptions() { DestinationPredefinedAcl = GetPredefinedAcl(acl) };
+ }
+
+ private UploadObjectOptions GetUploadOptions(GxFileType acl)
+ {
+ return new UploadObjectOptions() { PredefinedAcl = GetPredefinedAcl(acl) };
}
public void Delete(string objectName, GxFileType fileType)
{
- Google.Apis.Storage.v1.Data.Object obj = new Google.Apis.Storage.v1.Data.Object();
- obj.Name = objectName;
- obj.Bucket = Bucket;
- Client.DeleteObject(obj);
+ Client.DeleteObject(Bucket, objectName);
}
public string Upload(string localFile, string objectName, GxFileType fileType)
{
using (FileStream stream = new FileStream(localFile, FileMode.Open))
{
- Google.Apis.Storage.v1.Data.Object obj = Client.UploadObject(Bucket, objectName, "application/octet-stream", stream, GetUploadOptions(fileType));
- return obj.MediaLink;
+ TryGetContentType(objectName, out string mimeType, DEFAULT_CONTENT_TYPE);
+ Google.Apis.Storage.v1.Data.Object obj = Client.UploadObject(Bucket, objectName, mimeType, stream, GetUploadOptions(fileType));
+ return GetURL(objectName, fileType);
}
}
- private static UploadObjectOptions GetUploadOptions(GxFileType fileType)
- {
- UploadObjectOptions options = new UploadObjectOptions();
- if (fileType.HasFlag(GxFileType.Private))
- options.PredefinedAcl = PredefinedObjectAcl.ProjectPrivate;
- else
- options.PredefinedAcl = PredefinedObjectAcl.PublicRead;
- return options;
- }
+
public void Download(string objectName, string localFile, GxFileType fileType)
{
@@ -222,6 +254,7 @@ public bool Exists(string objectName, GxFileType fileType)
public string Get(string objectName, GxFileType fileType, int urlMinutes)
{
+ objectName = objectName.Replace("%2F", "/"); //Google Cloud Storage Bug. https://github.com/googleapis/google-cloud-dotnet/pull/3677
if (Exists(objectName, fileType))
{
return GetURL(objectName, fileType, urlMinutes);
@@ -229,10 +262,28 @@ public string Get(string objectName, GxFileType fileType, int urlMinutes)
return string.Empty;
}
- private string GetURL(string objectName, GxFileType fileType, int urlMinutes = 60 * 24 * 7)
+ public string GetUrl(string objectName, GxFileType fileType, int urlMinutes)
+ {
+ return GetURL(objectName, fileType, urlMinutes);
+ }
+
+ private bool IsPrivateResource(GxFileType fileType)
{
if (fileType.HasFlag(GxFileType.Private))
- return Signer.Sign(Bucket, StorageUtils.EncodeUrl(objectName), TimeSpan.FromMinutes(urlMinutes), HttpMethod.Get);
+ {
+ return true;
+ }
+ if (fileType.HasFlag(GxFileType.PublicRead))
+ {
+ return false;
+ }
+ return (this.defaultAcl == GxFileType.PublicRead) ? false : true;
+ }
+
+ private string GetURL(string objectName, GxFileType fileType, int urlMinutes = 0)
+ {
+ if (IsPrivateResource(fileType))
+ return Signer.Sign(Bucket, StorageUtils.EncodeUrl(objectName), ResolveExpiration(urlMinutes), HttpMethod.Get);
else
{
return StorageUri + StorageUtils.EncodeUrl(objectName);
@@ -245,8 +296,13 @@ public string Copy(string url, string newName, string tableName, string fieldNam
url = StorageUtils.DecodeUrl(url.Replace(StorageUri, string.Empty));
Google.Apis.Storage.v1.Data.Object obj = Client.CopyObject(Bucket, url, Bucket, newName, GetCopyOptions(fileType));
+
obj.Metadata = CreateObjectMetadata(tableName, fieldName, newName);
- Client.UpdateObject(obj);
+ if (TryGetContentType(newName, out string mimeType, DEFAULT_CONTENT_TYPE))
+ {
+ obj.ContentType = mimeType;
+ }
+ Client.UpdateObject(obj);
return GetURL(newName, fileType, 0);
}
@@ -264,7 +320,8 @@ public DateTime GetLastModified(string objectName, GxFileType fileType)
public long GetLength(string objectName, GxFileType fileType)
{
- return (long)Client.GetObject(Bucket, objectName).Size.GetValueOrDefault();
+ var obj = Client.GetObject(Bucket, objectName);
+ return (long)obj.Size.GetValueOrDefault();
}
public void CreateDirectory(string directoryName)
@@ -279,12 +336,12 @@ public void DeleteDirectory(string directoryName)
foreach (Google.Apis.Storage.v1.Data.Object item in Client.ListObjects(Bucket, directoryName))
{
if (!item.Name.EndsWith(StorageUtils.DELIMITER))
- Delete(item.Name, GxFileType.Public);
+ Delete(item.Name, GxFileType.PublicRead);
}
foreach (string subdir in GetSubDirectories(directoryName))
DeleteDirectory(subdir);
- if (Exists(directoryName, GxFileType.Public))
- Delete(directoryName, GxFileType.Public);
+ if (Exists(directoryName, GxFileType.PublicRead))
+ Delete(directoryName, GxFileType.PublicRead);
}
public bool ExistsDirectory(string directoryName)
@@ -377,13 +434,13 @@ public void RenameDirectory(string directoryName, string newDirectoryName)
{
if (IsFile(item.Name))
{
- Copy(item.Name, GxFileType.Public, item.Name.Replace(directoryName, newDirectoryName), GxFileType.Public);
- Delete(item.Name, GxFileType.Public);
+ Copy(item.Name, GxFileType.PublicRead, item.Name.Replace(directoryName, newDirectoryName), GxFileType.PublicRead);
+ Delete(item.Name, GxFileType.PublicRead);
}
}
CreateDirectory(newDirectoryName);
- if (Exists(directoryName, GxFileType.Public))
- Delete(directoryName, GxFileType.Public);
+ if (Exists(directoryName, GxFileType.PublicRead))
+ Delete(directoryName, GxFileType.PublicRead);
request.PageToken = response.NextPageToken;
} while (response.NextPageToken != null);
foreach (string subdir in GetSubDirectories(directoryName))
@@ -417,12 +474,20 @@ public string Save(Stream fileStream, string fileName, string tableName, string
fileName = Folder + StorageUtils.DELIMITER + tableName + StorageUtils.DELIMITER + fieldName + StorageUtils.DELIMITER + fileName;
return Upload(fileName, fileStream, fileType);
}
- public bool GetObjectNameFromURL(string url, out string objectName)
+ public bool TryGetObjectNameFromURL(string url, out string objectName)
{
string baseUrl = StorageUri;
if (url.StartsWith(baseUrl))
{
objectName = url.Replace(baseUrl, string.Empty);
+ objectName = objectName.Replace("%2F", "/"); //Google Cloud Storage Bug. https://github.com/googleapis/google-cloud-dotnet/pull/3677
+ return true;
+ }
+ baseUrl = $"https://storage.googleapis.com/{Bucket}/";
+ if (url.StartsWith(baseUrl))
+ {
+ objectName = url.Replace(baseUrl, string.Empty);
+ objectName = objectName.Replace("%2F", "/"); //Google Cloud Storage Bug. https://github.com/googleapis/google-cloud-dotnet/pull/3677
return true;
}
objectName = null;
diff --git a/dotnet/src/dotnetframework/Providers/Storage/GXOpenStack/ExternalProviderOpenStack.cs b/dotnet/src/dotnetframework/Providers/Storage/GXOpenStack/ExternalProviderOpenStack.cs
index a8aaddff1..d85e22127 100644
--- a/dotnet/src/dotnetframework/Providers/Storage/GXOpenStack/ExternalProviderOpenStack.cs
+++ b/dotnet/src/dotnetframework/Providers/Storage/GXOpenStack/ExternalProviderOpenStack.cs
@@ -151,6 +151,11 @@ public string Get(string externalFileName, GxFileType fileType, int urlMinutes)
return GetURL(externalFileName, fileType);
}
+ public string GetUrl(string externalFileName, GxFileType fileType, int urlMinutes)
+ {
+ return GetURL(externalFileName, fileType);
+ }
+
public void Delete(string objectName, GxFileType fileType)
{
openstackFilesProvider.DeleteObject(GetBucket(fileType), objectName);
@@ -362,7 +367,7 @@ public string GetBaseURL()
return storageUrl + StorageUtils.DELIMITER + publicBucketName + StorageUtils.DELIMITER;
}
- public bool GetObjectNameFromURL(string url, out string objectName)
+ public bool TryGetObjectNameFromURL(string url, out string objectName)
{
string baseUrl = storageUrl + StorageUtils.DELIMITER + publicBucketName + StorageUtils.DELIMITER;
if (url.StartsWith(baseUrl))
diff --git a/dotnet/test/DotNetUnitTest/DotNetUnitTest.csproj b/dotnet/test/DotNetUnitTest/DotNetUnitTest.csproj
index c883b01b2..e1e18d01d 100644
--- a/dotnet/test/DotNetUnitTest/DotNetUnitTest.csproj
+++ b/dotnet/test/DotNetUnitTest/DotNetUnitTest.csproj
@@ -30,18 +30,30 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+ Always
+
+
PreserveNewest
+
+ Always
+
diff --git a/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderAzurePrivateTest.cs b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderAzurePrivateTest.cs
new file mode 100644
index 000000000..7738d2b3a
--- /dev/null
+++ b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderAzurePrivateTest.cs
@@ -0,0 +1,14 @@
+using GeneXus.Storage.GXAmazonS3;
+using GeneXus.Storage.GXAzureStorage;
+using UnitTesting;
+
+namespace DotNetUnitTest
+{
+ public class ExternalProviderAzurePrivateTest : ExternalProviderTest
+ {
+ public ExternalProviderAzurePrivateTest(): base(AzureStorageExternalProvider.Name, typeof(AzureStorageExternalProvider), true)
+ {
+ }
+
+ }
+}
diff --git a/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderAzureTest.cs b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderAzureTest.cs
new file mode 100644
index 000000000..c78b22364
--- /dev/null
+++ b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderAzureTest.cs
@@ -0,0 +1,14 @@
+using GeneXus.Storage.GXAmazonS3;
+using GeneXus.Storage.GXAzureStorage;
+using UnitTesting;
+
+namespace DotNetUnitTest
+{
+ public class ExternalProviderAzureTest : ExternalProviderTest
+ {
+ public ExternalProviderAzureTest(): base(AzureStorageExternalProvider.Name, typeof(AzureStorageExternalProvider))
+ {
+ }
+
+ }
+}
diff --git a/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderGooglePrivateTest.cs b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderGooglePrivateTest.cs
new file mode 100644
index 000000000..263869594
--- /dev/null
+++ b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderGooglePrivateTest.cs
@@ -0,0 +1,15 @@
+using GeneXus.Storage.GXAmazonS3;
+using GeneXus.Storage.GXAzureStorage;
+using GeneXus.Storage.GXGoogleCloud;
+using UnitTesting;
+
+namespace DotNetUnitTest
+{
+ public class ExternalProviderGooglePrivateTest : ExternalProviderTest
+ {
+ public ExternalProviderGooglePrivateTest(): base(ExternalProviderGoogle.Name, typeof(ExternalProviderGoogle), true)
+ {
+ }
+
+ }
+}
diff --git a/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderGoogleTest.cs b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderGoogleTest.cs
new file mode 100644
index 000000000..c58dbb0a1
--- /dev/null
+++ b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderGoogleTest.cs
@@ -0,0 +1,15 @@
+using GeneXus.Storage.GXAmazonS3;
+using GeneXus.Storage.GXAzureStorage;
+using GeneXus.Storage.GXGoogleCloud;
+using UnitTesting;
+
+namespace DotNetUnitTest
+{
+ public class ExternalProviderGoogleTest : ExternalProviderTest
+ {
+ public ExternalProviderGoogleTest(): base(ExternalProviderGoogle.Name, typeof(ExternalProviderGoogle))
+ {
+ }
+
+ }
+}
diff --git a/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderMinioTest.cs b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderMinioTest.cs
new file mode 100644
index 000000000..2695bd232
--- /dev/null
+++ b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderMinioTest.cs
@@ -0,0 +1,13 @@
+using GeneXus.Storage.GXAmazonS3;
+using UnitTesting;
+
+namespace DotNetUnitTest
+{
+ public class ExternalProviderMinioTest : ExternalProviderTest
+ {
+ public ExternalProviderMinioTest(): base(ExternalProviderS3.Name, typeof(ExternalProviderS3))
+ {
+ }
+
+ }
+}
diff --git a/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderOracleTest.cs b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderOracleTest.cs
new file mode 100644
index 000000000..41e6ee3f0
--- /dev/null
+++ b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderOracleTest.cs
@@ -0,0 +1,13 @@
+using GeneXus.Storage.GXAmazonS3;
+using UnitTesting;
+
+namespace DotNetUnitTest
+{
+ public class ExternalProviderOracleTest : ExternalProviderTest
+ {
+ public ExternalProviderOracleTest(): base("ORACLE", typeof(ExternalProviderS3))
+ {
+ }
+
+ }
+}
diff --git a/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderS3PrivateTest.cs b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderS3PrivateTest.cs
new file mode 100644
index 000000000..048cf4add
--- /dev/null
+++ b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderS3PrivateTest.cs
@@ -0,0 +1,13 @@
+using GeneXus.Storage.GXAmazonS3;
+using UnitTesting;
+
+namespace DotNetUnitTest
+{
+ public class ExternalProviderS3PrivateTest : ExternalProviderTest
+ {
+ public ExternalProviderS3PrivateTest(): base(ExternalProviderS3.Name, typeof(ExternalProviderS3), true)
+ {
+ }
+
+ }
+}
diff --git a/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderS3Test.cs b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderS3Test.cs
new file mode 100644
index 000000000..e53655069
--- /dev/null
+++ b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderS3Test.cs
@@ -0,0 +1,13 @@
+using GeneXus.Storage.GXAmazonS3;
+using UnitTesting;
+
+namespace DotNetUnitTest
+{
+ public class ExternalProviderS3Test : ExternalProviderTest
+ {
+ public ExternalProviderS3Test(): base(ExternalProviderS3.Name, typeof(ExternalProviderS3))
+ {
+ }
+
+ }
+}
diff --git a/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderTest.cs b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderTest.cs
new file mode 100644
index 000000000..1dd2b991b
--- /dev/null
+++ b/dotnet/test/DotNetUnitTest/ExternalProvider/ExternalProviderTest.cs
@@ -0,0 +1,399 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using DotNetUnitTest;
+using GeneXus.Services;
+using GeneXus.Storage;
+using Xunit;
+
+
+#pragma warning disable CA1031 // Do not catch general exception types
+namespace UnitTesting
+{
+ [Collection("Sequential")]
+ public abstract class ExternalProviderTest
+ {
+
+ private GeneXus.Services.ExternalProvider provider;
+ private String TEST_SAMPLE_FILE_NAME = $"text{new Random().Next(1, 10000)}.txt";
+ private String TEST_SAMPLE_FILE_PATH;
+ private bool defaultAclPrivate;
+
+ public ExternalProviderTest(string providerName, Type externalProviderType, bool isPrivate)
+ {
+ defaultAclPrivate = isPrivate;
+ Environment.SetEnvironmentVariable($"STORAGE_{providerName}_DEFAULT_ACL", defaultAclPrivate ? GxFileType.Private.ToString() : GxFileType.PublicRead.ToString());
+ bool testEnabled = Environment.GetEnvironmentVariable(providerName + "_TEST_ENABLED") == "true";
+
+
+ Skip.IfNot(testEnabled, "Environment variables not set");
+ provider = (GeneXus.Services.ExternalProvider)Activator.CreateInstance(externalProviderType);
+
+ Assert.NotNull(provider);
+
+ TEST_SAMPLE_FILE_PATH = Path.Combine("resources", TEST_SAMPLE_FILE_NAME).ToString(CultureInfo.InvariantCulture);
+ File.WriteAllText(TEST_SAMPLE_FILE_PATH, "This is a Sample Test from External Storage GeneXus .NET Generator Unit Tests");
+ }
+
+ public ExternalProviderTest(string providerName, Type externalProviderType) : this(providerName, externalProviderType, false)
+ {
+
+ }
+
+ [SkippableFact]
+ public void TestUploadPublicMethod()
+ {
+ String upload = provider.Upload(TEST_SAMPLE_FILE_PATH, TEST_SAMPLE_FILE_NAME, GxFileType.PublicRead);
+ EnsureUrl(upload, GxFileType.PublicRead);
+ }
+
+ [SkippableFact]
+ public void TestUploadPrivateSubfolderMethod()
+ {
+ String upload = provider.Upload(TEST_SAMPLE_FILE_PATH, $"folder/folder2/folder3/{TEST_SAMPLE_FILE_NAME}", GxFileType.Private);
+ EnsureUrl(upload, GxFileType.Private);
+ }
+
+ [SkippableFact]
+ public void TestUploadDefaultMethod()
+ {
+ String upload = provider.Upload(TEST_SAMPLE_FILE_PATH, TEST_SAMPLE_FILE_NAME, GxFileType.Default);
+ EnsureUrl(upload, GxFileType.Default);
+ }
+
+ [SkippableFact]
+ public void TestUploadDefaultAttributeMethod()
+ {
+ String upload = provider.Upload(TEST_SAMPLE_FILE_PATH, TEST_SAMPLE_FILE_NAME, GxFileType.DefaultAttribute);
+ EnsureUrl(upload, GxFileType.DefaultAttribute);
+ }
+
+ [SkippableFact]
+ public void TestUploadAndCopyDefault()
+ {
+ TestUploadAndCopyByAcl(GxFileType.Default, GxFileType.Default);
+ }
+
+ [SkippableFact]
+ public void TestUploadAndCopyPrivate()
+ {
+ TestUploadAndCopyByAcl(GxFileType.Private, GxFileType.Private);
+ }
+
+ [SkippableFact]
+ public void TestUploadAndCopyPublic()
+ {
+ TestUploadAndCopyByAcl(GxFileType.PublicRead, GxFileType.PublicRead);
+ }
+
+ [SkippableFact]
+ public void TestUploadAndCopyMixed()
+ {
+ TestUploadAndCopyByAcl(GxFileType.Default, GxFileType.Private);
+ }
+
+ [SkippableFact]
+ public void TestUploadAndCopyPrivateToPublic()
+ {
+ TestUploadAndCopyByAcl(GxFileType.Private, GxFileType.PublicRead);
+ }
+
+ [SkippableFact]
+ public void TestUploadAndCopyPublicToPrivate()
+ {
+ TestUploadAndCopyByAcl(GxFileType.PublicRead, GxFileType.Private);
+ }
+
+ public void TestUploadAndCopyByAcl(GxFileType aclUpload, GxFileType aclCopy)
+ {
+ string copySourceName = $"test-source-upload-and-copy_{new Random().Next()}.txt";
+ String copyTargetName = $"test-upload-and-copy_{new Random().Next()}.txt";
+ DeleteSafe(TEST_SAMPLE_FILE_PATH);
+ DeleteSafe(copyTargetName);
+ String upload = provider.Upload(TEST_SAMPLE_FILE_PATH, copySourceName, aclUpload);
+ EnsureUrl(upload, aclUpload);
+
+ String copyUrl = TryGet(copyTargetName, aclCopy);
+ Assert.False(UrlExists(copyUrl), "URL cannot exist: " + copyUrl);
+
+ provider.Copy(copySourceName, aclUpload, copyTargetName, aclCopy);
+ upload = provider.Get(copyTargetName, aclCopy, 100);
+ EnsureUrl(upload, aclCopy);
+ }
+
+ [SkippableFact]
+ public void TestCopyMethod()
+ {
+ String copyFileName = "copy-text.txt";
+ Copy(copyFileName, GxFileType.PublicRead);
+ }
+
+ [SkippableFact]
+ public void TestCopyPrivateMethod()
+ {
+ String copyFileName = "copy-text-private.txt";
+ Copy(copyFileName, GxFileType.Private);
+ }
+
+ [SkippableFact]
+ public void TestMultimediaUpload()
+ {
+ string sourceFile = $"folder1/folder2/folder3{TEST_SAMPLE_FILE_NAME}";
+ String copyFileName = "copy-text-private.txt";
+ GxFileType acl = GxFileType.Private;
+
+ provider.Upload(TEST_SAMPLE_FILE_PATH, sourceFile, acl);
+ String upload = provider.Get(sourceFile, acl, 100);
+ EnsureUrl(upload, acl);
+
+ DeleteSafe(copyFileName);
+ upload = provider.Copy(sourceFile, copyFileName, "Table", "Field", acl);
+
+ copyFileName = StorageFactory.GetProviderObjectAbsoluteUriSafe(provider, upload);
+ if (!copyFileName.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+ {
+ upload = TryGet(copyFileName, acl);
+ }
+ EnsureUrl(upload, acl);
+ }
+
+ [SkippableFact]
+ public void TestGetMethod()
+ {
+ TestUploadPublicMethod();
+ String url = provider.Get(TEST_SAMPLE_FILE_NAME, GxFileType.PublicRead, 10);
+ EnsureUrl(url, GxFileType.PublicRead);
+ }
+
+ [SkippableFact]
+ public void TestGetObjectName()
+ {
+ TestUploadPublicMethod();
+ string url = provider.Get(TEST_SAMPLE_FILE_NAME, GxFileType.PublicRead, 10);
+ Assert.True(UrlExists(url));
+ string objectName;
+ provider.TryGetObjectNameFromURL(url, out objectName);
+ Assert.Equal(TEST_SAMPLE_FILE_NAME, objectName);
+ }
+
+ [SkippableFact]
+ public void TestDownloadMethod()
+ {
+ TestUploadPublicMethod();
+
+ String downloadPath = Path.Combine("resources", "test", TEST_SAMPLE_FILE_NAME);
+ try
+ {
+ File.Delete(downloadPath);
+ }
+ catch (Exception) { }
+ try
+ {
+ Directory.CreateDirectory(Path.Combine("resources", "test"));
+ }
+ catch (Exception) { }
+ provider.Download(TEST_SAMPLE_FILE_NAME, downloadPath, GxFileType.PublicRead);
+ Assert.True(File.Exists(downloadPath));
+ }
+
+ [SkippableFact]
+ public void TestDeleteFile()
+ {
+ GxFileType acl = GxFileType.PublicRead;
+ TestUploadPublicMethod();
+ String url = TryGet(TEST_SAMPLE_FILE_NAME, acl);
+ EnsureUrl(url, acl);
+ provider.Delete(TEST_SAMPLE_FILE_NAME, acl);
+
+ url = TryGet(TEST_SAMPLE_FILE_NAME, acl);
+ Assert.False(UrlExists(url));
+ }
+
+
+ [SkippableFact]
+ public void TestDeleteFilePrivate()
+ {
+ GxFileType acl = GxFileType.Private;
+ provider.Upload(TEST_SAMPLE_FILE_PATH, TEST_SAMPLE_FILE_NAME, acl);
+ provider.Delete(TEST_SAMPLE_FILE_NAME, acl);
+ String url = TryGet(TEST_SAMPLE_FILE_NAME, acl);
+ Assert.False(UrlExists(url));
+ }
+
+ [SkippableFact]
+ public void TestUploadPrivateMethod()
+ {
+ GxFileType acl = GxFileType.Private;
+ String externalFileName = "text-private-2.txt";
+
+ DeleteSafe(externalFileName);
+ String signedUrl = provider.Upload(TEST_SAMPLE_FILE_PATH, externalFileName, acl);
+ EnsureUrl(signedUrl, acl);
+ signedUrl = provider.Get(externalFileName, acl, 10);
+ EnsureUrl(signedUrl, acl);
+
+ }
+
+
+ [SkippableFact]
+ public void TestEmptyFolder()
+ {
+ GxFileType acl = GxFileType.PublicRead;
+ string folderName = $"folderTemp{new Random().Next(1, 100)}";
+
+ provider.DeleteDirectory(folderName);
+
+ List urls = new List();
+
+ urls.Add(provider.Upload(TEST_SAMPLE_FILE_PATH, $"{folderName}/test1.png", acl));
+ urls.Add(provider.Upload(TEST_SAMPLE_FILE_PATH, $"{folderName}/text2.txt", acl));
+ urls.Add(provider.Upload(TEST_SAMPLE_FILE_PATH, $"{folderName}/text3.txt", acl));
+ urls.Add(provider.Upload(TEST_SAMPLE_FILE_PATH, $"{folderName}/text4.txt", acl));
+ urls.Add(provider.Upload(TEST_SAMPLE_FILE_PATH, $"{folderName}/test1.png", acl));
+
+
+ var files = provider.GetFiles(folderName);
+ Assert.Equal(4, files.Count);
+
+ provider.DeleteDirectory(folderName);
+
+ files = provider.GetFiles(folderName);
+ Assert.Empty(files);
+
+ }
+
+ private void Copy(String copyFileName, GxFileType acl)
+ {
+ provider.Upload(TEST_SAMPLE_FILE_PATH, TEST_SAMPLE_FILE_NAME, acl);
+ String upload = provider.Get(TEST_SAMPLE_FILE_NAME, acl, 100);
+ EnsureUrl(upload, acl);
+
+ DeleteSafe(copyFileName);
+ Wait(500); //Google CDN replication seems to be delayed.
+
+ String urlCopy = TryGet(copyFileName, GxFileType.PublicRead);
+ Assert.False(UrlExists(urlCopy), "URL cannot exist: " + urlCopy);
+
+ provider.Copy(TEST_SAMPLE_FILE_NAME, acl, copyFileName, acl);
+ upload = provider.Get(copyFileName, acl, 100);
+ EnsureUrl(upload, acl);
+ }
+
+
+
+ private String TryGet(String objectName, GxFileType acl)
+ {
+ String getValue = "";
+ try
+ {
+ if (((ExternalProviderBase)provider).GetName() == GeneXus.Storage.GXGoogleCloud.ExternalProviderGoogle.Name)
+ {
+ objectName = objectName.Replace("%2F", "/"); //Google Cloud Storage Bug. https://github.com/googleapis/google-cloud-dotnet/pull/3677
+ }
+ getValue = provider.Get(objectName, acl, 5);
+ }
+ catch (Exception)
+ {
+
+ }
+ return getValue;
+ }
+
+ private String GetSafe(String objectName, GxFileType acl)
+ {
+ try
+ {
+ return provider.Get(objectName, acl, 100);
+ }
+ catch (Exception)
+ {
+
+ }
+ return String.Empty;
+ }
+
+ private bool DeleteSafe(String objectName)
+ {
+ DeleteSafeImpl(objectName, GxFileType.Private);
+ DeleteSafeImpl(objectName, GxFileType.PublicRead);
+ return true;
+ }
+
+ private bool DeleteSafeImpl(String objectName, GxFileType acl)
+ {
+ try
+ {
+ provider.Delete(objectName, acl);
+ }
+ catch (Exception)
+ {
+
+ }
+ return true;
+ }
+
+ private static void Wait(int milliseconds)
+ {
+ System.Threading.Thread.Sleep(milliseconds);
+ }
+
+ private void EnsureUrl(String signedOrUnsignedUrl, GxFileType acl)
+ {
+ Assert.True(UrlExists(signedOrUnsignedUrl), "URL not found: " + signedOrUnsignedUrl);
+ if (IsPrivateFile(acl))
+ {
+ if (!(this is ExternalProviderMinioTest)) //Minio local installation not supported
+ {
+ Skip.If(this is ExternalProviderMinioTest);
+ String noSignedUrl = signedOrUnsignedUrl.Substring(0, signedOrUnsignedUrl.IndexOf('?') + 1);
+ Assert.False(UrlExists(noSignedUrl), "URL must be private: " + noSignedUrl);
+ }
+ }
+ else
+ {
+ Assert.False(signedOrUnsignedUrl.Contains("?"), "URL cannot be signed");
+ }
+ }
+ private bool IsPrivateFile(GxFileType acl)
+ {
+ if (acl.HasFlag(GxFileType.Private))
+ {
+ return true;
+ }
+ else if (acl.HasFlag(GxFileType.PublicRead))
+ {
+ return false;
+ }
+ return defaultAclPrivate;
+ }
+
+#pragma warning disable CA1054 // Uri parameters should not be strings
+ protected static bool UrlExists(string url)
+ {
+ if (string.IsNullOrEmpty(url))
+ {
+ return false;
+ }
+ bool exists = false;
+ HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri(url));
+ request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
+
+ try
+ {
+ using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
+ {
+ exists = response.StatusCode == HttpStatusCode.OK;
+ }
+ }
+ catch (WebException)
+ {
+
+ }
+ return exists;
+ }
+#pragma warning restore CA1054 // Uri parameters should not be strings
+ }
+}
+#pragma warning restore CA1031 // Do not catch general exception types
\ No newline at end of file
diff --git a/dotnet/test/DotNetUnitTest/resources/text.txt b/dotnet/test/DotNetUnitTest/resources/text.txt
new file mode 100644
index 000000000..e04a19003
--- /dev/null
+++ b/dotnet/test/DotNetUnitTest/resources/text.txt
@@ -0,0 +1 @@
+My uploaded file test
\ No newline at end of file