Skip to content

Commit f0616fd

Browse files
authored
VCST-2934: Rename export/import to backup/restore functionality (#2904)
1 parent 965a2e9 commit f0616fd

13 files changed

+402
-343
lines changed

src/VirtoCommerce.Platform.Core/PlatformOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class PlatformOptions
3939
// Default path to store export files
4040
public string DefaultExportFolder { get; set; } = "export";
4141

42-
public string DefaultExportFileName { get; set; } = "exported_{0:yyyyMMddHHmmss}.zip";
42+
public string DefaultExportFileName { get; set; } = "vc_backup_{0:yyyyMMddHHmmss}.zip";
4343

4444
// Local path to running process like WkhtmlToPdf
4545
public string ProcessesPath { get; set; }
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading.Tasks;
4+
using Hangfire;
5+
using Hangfire.Server;
6+
using Microsoft.AspNetCore.Authorization;
7+
using Microsoft.AspNetCore.Mvc;
8+
using Microsoft.AspNetCore.StaticFiles;
9+
using Microsoft.Extensions.Options;
10+
using VirtoCommerce.Platform.Core;
11+
using VirtoCommerce.Platform.Core.Exceptions;
12+
using VirtoCommerce.Platform.Core.ExportImport;
13+
using VirtoCommerce.Platform.Core.ExportImport.PushNotifications;
14+
using VirtoCommerce.Platform.Core.PushNotifications;
15+
using VirtoCommerce.Platform.Core.Security;
16+
using VirtoCommerce.Platform.Hangfire;
17+
18+
using Permissions = VirtoCommerce.Platform.Core.PlatformConstants.Security.Permissions;
19+
20+
namespace VirtoCommerce.Platform.Web.Controllers.Api
21+
{
22+
[Route("api/platform")]
23+
[ApiExplorerSettings(IgnoreApi = true)]
24+
[Authorize]
25+
public class PlatformBackupRestoreController : Controller
26+
{
27+
private readonly IPlatformExportImportManager _platformExportManager;
28+
private readonly IPushNotificationManager _pushNotifier;
29+
private readonly IUserNameResolver _userNameResolver;
30+
private readonly PlatformOptions _platformOptions;
31+
32+
public PlatformBackupRestoreController(
33+
IPlatformExportImportManager platformExportManager,
34+
IPushNotificationManager pushNotifier,
35+
IUserNameResolver userNameResolver,
36+
IOptions<PlatformOptions> options)
37+
{
38+
_platformExportManager = platformExportManager;
39+
_pushNotifier = pushNotifier;
40+
_userNameResolver = userNameResolver;
41+
_platformOptions = options.Value;
42+
}
43+
44+
45+
[HttpPost]
46+
[Route("export")]
47+
[Authorize(Permissions.PlatformExport)]
48+
public ActionResult<PlatformExportPushNotification> ProcessExport([FromBody] PlatformImportExportRequest exportRequest)
49+
{
50+
var notification = new PlatformExportPushNotification(_userNameResolver.GetCurrentUserName())
51+
{
52+
Title = "Platform backup task",
53+
Description = "starting backup..."
54+
};
55+
_pushNotifier.Send(notification);
56+
57+
var jobId = BackgroundJob.Enqueue(() => PlatformBackupBackgroundAsync(exportRequest, notification, JobCancellationToken.Null, null));
58+
notification.JobId = jobId;
59+
return Ok(notification);
60+
}
61+
62+
[HttpPost]
63+
[Route("import")]
64+
[Authorize(Permissions.PlatformImport)]
65+
public ActionResult<PlatformImportPushNotification> ProcessImport([FromBody] PlatformImportExportRequest importRequest)
66+
{
67+
var notification = new PlatformImportPushNotification(_userNameResolver.GetCurrentUserName())
68+
{
69+
Title = "Platform restore task",
70+
Description = "starting restore..."
71+
};
72+
_pushNotifier.Send(notification);
73+
74+
var jobId = BackgroundJob.Enqueue(() => PlatformRestoreBackgroundAsync(importRequest, notification, JobCancellationToken.Null, null));
75+
notification.JobId = jobId;
76+
77+
return Ok(notification);
78+
}
79+
80+
[HttpPost]
81+
[Route("exortimport/tasks/{jobId}/cancel")]
82+
public ActionResult Cancel([FromRoute] string jobId)
83+
{
84+
BackgroundJob.Delete(jobId);
85+
return Ok();
86+
}
87+
88+
[HttpGet]
89+
[Route("export/download/{fileName}")]
90+
[Authorize(Permissions.PlatformExport)]
91+
public ActionResult DownloadExportFile([FromRoute] string fileName)
92+
{
93+
var localPath = GetSafeFullPath(_platformOptions.DefaultExportFolder, fileName);
94+
95+
//Load source data only from local file system
96+
using (System.IO.File.Open(localPath, FileMode.Open))
97+
{
98+
var provider = new FileExtensionContentTypeProvider();
99+
if (!provider.TryGetContentType(localPath, out var contentType))
100+
{
101+
contentType = "application/octet-stream";
102+
}
103+
return PhysicalFile(localPath, contentType);
104+
}
105+
}
106+
107+
108+
public async Task PlatformRestoreBackgroundAsync(PlatformImportExportRequest importRequest, PlatformImportPushNotification pushNotification, IJobCancellationToken cancellationToken, PerformContext context)
109+
{
110+
void ProgressCallback(ExportImportProgressInfo x)
111+
{
112+
pushNotification.Path(x);
113+
pushNotification.JobId = context.BackgroundJob.Id;
114+
_pushNotifier.Send(pushNotification);
115+
}
116+
117+
var now = DateTime.UtcNow;
118+
try
119+
{
120+
var cancellationTokenWrapper = new JobCancellationTokenWrapper(cancellationToken);
121+
122+
var localPath = GetSafeFullPath(_platformOptions.LocalUploadFolderPath, importRequest.FileUrl);
123+
124+
//Load source data only from local file system
125+
using (var stream = new FileStream(localPath, FileMode.Open))
126+
{
127+
var manifest = importRequest.ToManifest();
128+
manifest.Created = now;
129+
await _platformExportManager.ImportAsync(stream, manifest, ProgressCallback, cancellationTokenWrapper);
130+
}
131+
}
132+
catch (JobAbortedException)
133+
{
134+
//do nothing
135+
}
136+
catch (Exception ex)
137+
{
138+
pushNotification.Errors.Add(ex.ExpandExceptionMessage());
139+
}
140+
finally
141+
{
142+
pushNotification.Description = "Platform restore process completed successfully.";
143+
pushNotification.Finished = DateTime.UtcNow;
144+
await _pushNotifier.SendAsync(pushNotification);
145+
}
146+
}
147+
148+
public async Task PlatformBackupBackgroundAsync(PlatformImportExportRequest exportRequest, PlatformExportPushNotification pushNotification, IJobCancellationToken cancellationToken, PerformContext context)
149+
{
150+
void ProgressCallback(ExportImportProgressInfo x)
151+
{
152+
pushNotification.Path(x);
153+
pushNotification.JobId = context.BackgroundJob.Id;
154+
_pushNotifier.Send(pushNotification);
155+
}
156+
157+
try
158+
{
159+
var fileName = string.Format(_platformOptions.DefaultExportFileName, DateTime.UtcNow);
160+
var localTmpFolder = Path.GetFullPath(Path.Combine(_platformOptions.DefaultExportFolder));
161+
var localTmpPath = Path.Combine(localTmpFolder, Path.GetFileName(fileName));
162+
163+
if (!Directory.Exists(localTmpFolder))
164+
{
165+
Directory.CreateDirectory(localTmpFolder);
166+
}
167+
168+
if (System.IO.File.Exists(localTmpPath))
169+
{
170+
System.IO.File.Delete(localTmpPath);
171+
}
172+
173+
//Import first to local tmp folder because Azure blob storage doesn't support some special file access mode
174+
using (var stream = System.IO.File.OpenWrite(localTmpPath))
175+
{
176+
var manifest = exportRequest.ToManifest();
177+
await _platformExportManager.ExportAsync(stream, manifest, ProgressCallback, new JobCancellationTokenWrapper(cancellationToken));
178+
pushNotification.DownloadUrl = $"api/platform/export/download/{fileName}";
179+
}
180+
}
181+
catch (JobAbortedException)
182+
{
183+
//do nothing
184+
}
185+
catch (Exception ex)
186+
{
187+
pushNotification.Errors.Add(ex.ExpandExceptionMessage());
188+
}
189+
finally
190+
{
191+
pushNotification.Description = "Platform backup process completed successfully.";
192+
pushNotification.Finished = DateTime.UtcNow;
193+
await _pushNotifier.SendAsync(pushNotification);
194+
}
195+
}
196+
197+
private static string GetSafeFullPath(string basePath, string relativePath)
198+
{
199+
var baseFullPath = Path.GetFullPath(basePath);
200+
var result = Path.GetFullPath(Path.Combine(baseFullPath, relativePath));
201+
202+
if (!result.StartsWith(baseFullPath + Path.DirectorySeparatorChar))
203+
{
204+
throw new PlatformException($"Invalid path {relativePath}");
205+
}
206+
207+
return result;
208+
}
209+
}
210+
}

src/VirtoCommerce.Platform.Web/Controllers/Api/PlatformExportImportController.cs

Lines changed: 1 addition & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using Hangfire.Server;
99
using Microsoft.AspNetCore.Authorization;
1010
using Microsoft.AspNetCore.Mvc;
11-
using Microsoft.AspNetCore.StaticFiles;
1211
using Microsoft.Extensions.Options;
1312
using VirtoCommerce.Platform.Core;
1413
using VirtoCommerce.Platform.Core.Common;
@@ -165,68 +164,6 @@ public ActionResult<PlatformExportManifest> LoadExportManifest([FromQuery] strin
165164
return Ok(retVal);
166165
}
167166

168-
[HttpPost]
169-
[Route("export")]
170-
[Authorize(Permissions.PlatformExport)]
171-
public ActionResult<PlatformExportPushNotification> ProcessExport([FromBody] PlatformImportExportRequest exportRequest)
172-
{
173-
var notification = new PlatformExportPushNotification(_userNameResolver.GetCurrentUserName())
174-
{
175-
Title = "Platform export task",
176-
Description = "starting export...."
177-
};
178-
_pushNotifier.Send(notification);
179-
180-
var jobId = BackgroundJob.Enqueue(() => PlatformExportBackgroundAsync(exportRequest, notification, JobCancellationToken.Null, null));
181-
notification.JobId = jobId;
182-
return Ok(notification);
183-
}
184-
185-
[HttpPost]
186-
[Route("import")]
187-
[Authorize(Permissions.PlatformImport)]
188-
public ActionResult<PlatformImportPushNotification> ProcessImport([FromBody] PlatformImportExportRequest importRequest)
189-
{
190-
var notification = new PlatformImportPushNotification(_userNameResolver.GetCurrentUserName())
191-
{
192-
Title = "Platform import task",
193-
Description = "starting import...."
194-
};
195-
_pushNotifier.Send(notification);
196-
197-
var jobId = BackgroundJob.Enqueue(() => PlatformImportBackgroundAsync(importRequest, notification, JobCancellationToken.Null, null));
198-
notification.JobId = jobId;
199-
200-
return Ok(notification);
201-
}
202-
203-
[HttpPost]
204-
[Route("exortimport/tasks/{jobId}/cancel")]
205-
public ActionResult Cancel([FromRoute] string jobId)
206-
{
207-
BackgroundJob.Delete(jobId);
208-
return Ok();
209-
}
210-
211-
[HttpGet]
212-
[Route("export/download/{fileName}")]
213-
[Authorize(Permissions.PlatformExport)]
214-
public ActionResult DownloadExportFile([FromRoute] string fileName)
215-
{
216-
var localPath = GetSafeFullPath(_platformOptions.DefaultExportFolder, fileName);
217-
218-
//Load source data only from local file system
219-
using (System.IO.File.Open(localPath, FileMode.Open))
220-
{
221-
var provider = new FileExtensionContentTypeProvider();
222-
if (!provider.TryGetContentType(localPath, out var contentType))
223-
{
224-
contentType = "application/octet-stream";
225-
}
226-
return PhysicalFile(localPath, contentType);
227-
}
228-
}
229-
230167
private async Task<IList<SampleDataInfo>> InnerDiscoverSampleDataAsync()
231168
{
232169
var sampleDataUrl = _platformOptions.SampleDataUrl;
@@ -342,100 +279,12 @@ await DownloadFileAsync(new Uri(url), tmpFilePath, async (bytesReceived, bytesTo
342279
finally
343280
{
344281
await _settingsManager.SetValueAsync(PlatformConstants.Settings.Setup.SampleDataState.Name, SampleDataState.Completed);
345-
pushNotification.Description = "Import finished";
346-
pushNotification.Finished = DateTime.UtcNow;
347-
await _pushNotifier.SendAsync(pushNotification);
348-
}
349-
}
350-
351-
public async Task PlatformImportBackgroundAsync(PlatformImportExportRequest importRequest, PlatformImportPushNotification pushNotification, IJobCancellationToken cancellationToken, PerformContext context)
352-
{
353-
void progressCallback(ExportImportProgressInfo x)
354-
{
355-
pushNotification.Path(x);
356-
pushNotification.JobId = context.BackgroundJob.Id;
357-
_pushNotifier.Send(pushNotification);
358-
}
359-
360-
var now = DateTime.UtcNow;
361-
try
362-
{
363-
var cancellationTokenWrapper = new JobCancellationTokenWrapper(cancellationToken);
364-
365-
var localPath = GetSafeFullPath(_platformOptions.LocalUploadFolderPath, importRequest.FileUrl);
366-
367-
//Load source data only from local file system
368-
using (var stream = new FileStream(localPath, FileMode.Open))
369-
{
370-
var manifest = importRequest.ToManifest();
371-
manifest.Created = now;
372-
await _platformExportManager.ImportAsync(stream, manifest, progressCallback, cancellationTokenWrapper);
373-
}
374-
}
375-
catch (JobAbortedException)
376-
{
377-
//do nothing
378-
}
379-
catch (Exception ex)
380-
{
381-
pushNotification.Errors.Add(ex.ExpandExceptionMessage());
382-
}
383-
finally
384-
{
385-
pushNotification.Description = "Import finished";
282+
pushNotification.Description = "Sample data import process completed successfully.";
386283
pushNotification.Finished = DateTime.UtcNow;
387284
await _pushNotifier.SendAsync(pushNotification);
388285
}
389286
}
390287

391-
public async Task PlatformExportBackgroundAsync(PlatformImportExportRequest exportRequest, PlatformExportPushNotification pushNotification, IJobCancellationToken cancellationToken, PerformContext context)
392-
{
393-
void progressCallback(ExportImportProgressInfo x)
394-
{
395-
pushNotification.Path(x);
396-
pushNotification.JobId = context.BackgroundJob.Id;
397-
_pushNotifier.Send(pushNotification);
398-
}
399-
400-
try
401-
{
402-
var fileName = string.Format(_platformOptions.DefaultExportFileName, DateTime.UtcNow);
403-
var localTmpFolder = Path.GetFullPath(Path.Combine(_platformOptions.DefaultExportFolder));
404-
var localTmpPath = Path.Combine(localTmpFolder, Path.GetFileName(fileName));
405-
406-
if (!Directory.Exists(localTmpFolder))
407-
{
408-
Directory.CreateDirectory(localTmpFolder);
409-
}
410-
411-
if (System.IO.File.Exists(localTmpPath))
412-
{
413-
System.IO.File.Delete(localTmpPath);
414-
}
415-
416-
//Import first to local tmp folder because Azure blob storage doesn't support some special file access mode
417-
using (var stream = System.IO.File.OpenWrite(localTmpPath))
418-
{
419-
var manifest = exportRequest.ToManifest();
420-
await _platformExportManager.ExportAsync(stream, manifest, progressCallback, new JobCancellationTokenWrapper(cancellationToken));
421-
pushNotification.DownloadUrl = $"api/platform/export/download/{fileName}";
422-
}
423-
}
424-
catch (JobAbortedException)
425-
{
426-
//do nothing
427-
}
428-
catch (Exception ex)
429-
{
430-
pushNotification.Errors.Add(ex.ExpandExceptionMessage());
431-
}
432-
finally
433-
{
434-
pushNotification.Description = "Export finished";
435-
pushNotification.Finished = DateTime.UtcNow;
436-
await _pushNotifier.SendAsync(pushNotification);
437-
}
438-
}
439288

440289
private static string GetSafeFullPath(string basePath, string relativePath)
441290
{

0 commit comments

Comments
 (0)