DocSpace-buildtools/common/ASC.Data.Backup.Core/Tasks/BackupPortalTask.cs

668 lines
24 KiB
C#
Raw Normal View History

2020-05-20 15:14:44 +00:00
/*
*
* (c) Copyright Ascensio System Limited 2010-2020
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
namespace ASC.Data.Backup.Tasks;
[Scope]
public class BackupPortalTask : PortalTaskBase
2021-08-31 09:40:28 +00:00
{
public string BackupFilePath { get; private set; }
public int Limit { get; private set; }
private BackupsContext BackupRecordContext => _lazyBackupsContext.Value;
private const int MaxLength = 250;
private const int BatchLimit = 5000;
private readonly bool _dump;
private readonly TenantManager _tenantManager;
private readonly TempStream _tempStream;
private readonly Lazy<BackupsContext> _lazyBackupsContext;
public BackupPortalTask(DbFactory dbFactory, DbContextManager<BackupsContext> dbContextManager, IOptionsMonitor<ILog> options, TenantManager tenantManager, CoreBaseSettings coreBaseSettings, StorageFactory storageFactory, StorageFactoryConfig storageFactoryConfig, ModuleProvider moduleProvider, TempStream tempStream)
: base(dbFactory, options, storageFactory, storageFactoryConfig, moduleProvider)
2021-08-31 09:40:28 +00:00
{
_dump = coreBaseSettings.Standalone;
_tenantManager = tenantManager;
_tempStream = tempStream;
_lazyBackupsContext = new Lazy<BackupsContext>(() => dbContextManager.Get(DbFactory.ConnectionStringSettings.ConnectionString));
}
2020-06-16 11:34:44 +00:00
public void Init(int tenantId, string fromConfigPath, string toFilePath, int limit)
{
if (string.IsNullOrEmpty(toFilePath))
2022-01-24 10:49:00 +00:00
throw new ArgumentNullException(nameof(toFilePath));
BackupFilePath = toFilePath;
Limit = limit;
Init(tenantId, fromConfigPath);
2020-06-10 12:35:10 +00:00
}
2020-05-20 15:14:44 +00:00
public override void RunJob()
{
Logger.DebugFormat("begin backup {0}", TenantId);
_tenantManager.SetCurrentTenant(TenantId);
2020-05-20 15:14:44 +00:00
2022-02-09 18:33:50 +00:00
using (var writer = new ZipWriteOperator(_tempStream, BackupFilePath))
2020-05-20 15:14:44 +00:00
{
if (_dump)
{
DoDump(writer);
}
else
2021-08-31 09:40:28 +00:00
{
var modulesToProcess = GetModulesToProcess().ToList();
var fileGroups = GetFilesGroup();
2020-05-20 15:14:44 +00:00
var stepscount = ProcessStorage ? fileGroups.Count : 0;
SetStepsCount(modulesToProcess.Count + stepscount);
2020-05-20 15:14:44 +00:00
foreach (var module in modulesToProcess)
2020-05-20 15:14:44 +00:00
{
DoBackupModule(writer, module);
2020-05-20 15:14:44 +00:00
}
if (ProcessStorage)
2021-08-31 09:40:28 +00:00
{
DoBackupStorage(writer, fileGroups);
2020-05-20 15:14:44 +00:00
}
}
}
Logger.DebugFormat("end backup {0}", TenantId);
}
public List<object[]> ExecuteList(DbCommand command)
{
var list = new List<object[]>();
using (var result = command.ExecuteReader())
2021-08-31 09:40:28 +00:00
{
while (result.Read())
2021-08-31 09:40:28 +00:00
{
var objects = new object[result.FieldCount];
result.GetValues(objects);
list.Add(objects);
2021-05-17 18:27:39 +00:00
}
}
2020-05-20 15:14:44 +00:00
return list;
}
2022-02-09 18:33:50 +00:00
private void DoDump(IDataWriteOperator writer)
{
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(true.ToString())))
{
writer.WriteEntry(KeyHelper.GetDumpKey(), stream);
2022-02-09 18:33:50 +00:00
}
List<string> tables;
var files = new List<BackupFileInfo>();
using (var connection = DbFactory.OpenConnection())
2021-08-31 09:40:28 +00:00
{
var command = connection.CreateCommand();
command.CommandText = "show tables";
tables = ExecuteList(command).Select(r => Convert.ToString(r[0])).ToList();
}
/* using (var dbManager = new DbManager("default", 100000))
{
tables = dbManager.ExecuteList("show tables;").Select(r => Convert.ToString(r[0])).ToList();
}*/
2021-08-31 09:40:28 +00:00
2020-05-20 15:14:44 +00:00
var stepscount = tables.Count * 4; // (schema + data) * (dump + zip)
if (ProcessStorage)
{
var tenants = _tenantManager.GetTenants(false).Select(r => r.Id);
2020-05-20 15:14:44 +00:00
foreach (var t in tenants)
{
files.AddRange(GetFiles(t));
}
2021-08-31 09:40:28 +00:00
2020-05-20 15:14:44 +00:00
stepscount += files.Count * 2 + 1;
Logger.Debug("files:" + files.Count);
}
SetStepsCount(stepscount);
2020-05-20 15:14:44 +00:00
var excluded = ModuleProvider.AllModules.Where(r => IgnoredModules.Contains(r.ModuleName)).SelectMany(r => r.Tables).Select(r => r.Name).ToList();
excluded.AddRange(IgnoredTables);
excluded.Add("res_");
2020-05-20 15:14:44 +00:00
var dir = Path.GetDirectoryName(BackupFilePath);
var subDir = CrossPlatform.PathCombine(dir, Path.GetFileNameWithoutExtension(BackupFilePath));
var schemeDir = CrossPlatform.PathCombine(subDir, KeyHelper.GetDatabaseSchema());
var dataDir = CrossPlatform.PathCombine(subDir, KeyHelper.GetDatabaseData());
2020-05-20 15:14:44 +00:00
if (!Directory.Exists(schemeDir))
{
Directory.CreateDirectory(schemeDir);
}
if (!Directory.Exists(dataDir))
{
Directory.CreateDirectory(dataDir);
}
2020-05-20 15:14:44 +00:00
var dict = tables.ToDictionary(t => t, SelectCount);
tables.Sort((pair1, pair2) => dict[pair1].CompareTo(dict[pair2]));
2020-05-20 15:14:44 +00:00
for (var i = 0; i < tables.Count; i += TasksLimit)
{
var tasks = new List<Task>(TasksLimit * 2);
for (var j = 0; j < TasksLimit && i + j < tables.Count; j++)
2020-05-20 15:14:44 +00:00
{
var t = tables[i + j];
tasks.Add(Task.Run(() => DumpTableScheme(t, schemeDir)));
if (!excluded.Any(t.StartsWith))
2020-05-20 15:14:44 +00:00
{
tasks.Add(Task.Run(() => DumpTableData(t, dataDir, dict[t])));
}
else
{
SetStepCompleted(2);
2020-05-20 15:14:44 +00:00
}
}
Task.WaitAll(tasks.ToArray());
2020-05-20 15:14:44 +00:00
ArchiveDir(writer, subDir);
2020-05-20 15:14:44 +00:00
}
Logger.DebugFormat("dir remove start {0}", subDir);
Directory.Delete(subDir, true);
Logger.DebugFormat("dir remove end {0}", subDir);
2020-05-20 15:14:44 +00:00
if (ProcessStorage)
2020-05-20 15:14:44 +00:00
{
DoDumpStorage(writer, files);
2020-05-20 15:14:44 +00:00
}
}
private IEnumerable<BackupFileInfo> GetFiles(int tenantId)
{
var files = GetFilesToProcess(tenantId).ToList();
2021-11-02 12:12:28 +00:00
var exclude = BackupRecordContext.Backups.AsQueryable().Where(b => b.TenantId == tenantId && b.StorageType == 0 && b.StoragePath != null).ToList();
2022-01-14 13:12:37 +00:00
files = files.Where(f => !exclude.Any(e => f.Path.Replace('\\', '/').Contains($"/file_{e.StoragePath}/"))).ToList();
return files;
2020-05-20 15:14:44 +00:00
}
2020-05-20 15:14:44 +00:00
private void DumpTableScheme(string t, string dir)
{
try
2020-05-20 15:14:44 +00:00
{
Logger.DebugFormat("dump table scheme start {0}", t);
using (var connection = DbFactory.OpenConnection())
2020-05-20 15:14:44 +00:00
{
var command = connection.CreateCommand();
2022-01-14 13:12:37 +00:00
command.CommandText = $"SHOW CREATE TABLE `{t}`";
var createScheme = ExecuteList(command);
var creates = new StringBuilder();
2022-01-14 13:12:37 +00:00
creates.Append($"DROP TABLE IF EXISTS `{t}`;");
creates.AppendLine();
creates.Append(createScheme
.Select(r => Convert.ToString(r[1]))
.FirstOrDefault());
2022-01-14 13:32:06 +00:00
creates.Append(';');
2020-05-20 15:14:44 +00:00
var path = CrossPlatform.PathCombine(dir, t);
using (var stream = File.OpenWrite(path))
{
var bytes = Encoding.UTF8.GetBytes(creates.ToString());
stream.Write(bytes, 0, bytes.Length);
2021-08-31 09:40:28 +00:00
}
SetStepCompleted();
2020-05-20 15:14:44 +00:00
}
Logger.DebugFormat("dump table scheme stop {0}", t);
2020-05-20 15:14:44 +00:00
}
catch (Exception e)
2020-05-20 15:14:44 +00:00
{
Logger.Error(e);
throw;
}
2022-02-09 18:33:50 +00:00
}
2020-05-20 15:14:44 +00:00
private int SelectCount(string t)
{
try
2020-05-20 15:14:44 +00:00
{
using var connection = DbFactory.OpenConnection();
using var analyzeCommand = connection.CreateCommand();
analyzeCommand.CommandText = $"analyze table {t}";
analyzeCommand.ExecuteNonQuery();
using var command = connection.CreateCommand();
command.CommandText = $"select TABLE_ROWS from INFORMATION_SCHEMA.TABLES where TABLE_NAME = '{t}' and TABLE_SCHEMA = '{connection.Database}'";
2020-05-20 15:14:44 +00:00
return int.Parse(command.ExecuteScalar().ToString());
2020-05-20 15:14:44 +00:00
}
catch (Exception e)
{
Logger.Error(e);
throw;
2020-05-20 15:14:44 +00:00
}
}
2020-05-20 15:14:44 +00:00
private void DumpTableData(string t, string dir, int count)
{
try
2020-05-20 15:14:44 +00:00
{
if (count == 0)
2020-05-20 15:14:44 +00:00
{
Logger.DebugFormat("dump table data stop {0}", t);
SetStepCompleted(2);
2022-02-09 18:33:50 +00:00
return;
}
2020-05-20 15:14:44 +00:00
Logger.DebugFormat("dump table data start {0}", t);
2022-01-12 12:34:58 +00:00
bool searchWithPrimary;
string primaryIndex;
var primaryIndexStep = 0;
var primaryIndexStart = 0;
2020-05-20 15:14:44 +00:00
List<string> columns;
2022-02-09 18:33:50 +00:00
using (var connection = DbFactory.OpenConnection())
{
var command = connection.CreateCommand();
2022-01-14 13:12:37 +00:00
command.CommandText = string.Format($"SHOW COLUMNS FROM `{t}`");
columns = ExecuteList(command).Select(r => "`" + Convert.ToString(r[0]) + "`").ToList();
if (command.CommandText.Contains("tenants_quota") || command.CommandText.Contains("webstudio_settings"))
2020-05-20 15:14:44 +00:00
{
2020-06-10 12:35:10 +00:00
2020-05-26 08:52:47 +00:00
}
}
2020-06-10 12:35:10 +00:00
using (var connection = DbFactory.OpenConnection())
{
var command = connection.CreateCommand();
2022-01-14 13:12:37 +00:00
command.CommandText = $"select COLUMN_NAME from information_schema.`COLUMNS` where TABLE_SCHEMA = '{connection.Database}' and TABLE_NAME = '{t}' and COLUMN_KEY = 'PRI' and DATA_TYPE = 'int'";
primaryIndex = ExecuteList(command).ConvertAll(r => Convert.ToString(r[0])).FirstOrDefault();
2020-05-20 15:14:44 +00:00
}
2022-02-09 18:33:50 +00:00
using (var connection = DbFactory.OpenConnection())
{
var command = connection.CreateCommand();
2022-01-14 13:12:37 +00:00
command.CommandText = $"SHOW INDEXES FROM {t} WHERE COLUMN_NAME='{primaryIndex}' AND seq_in_index=1";
var isLeft = ExecuteList(command);
searchWithPrimary = isLeft.Count == 1;
}
2020-05-20 15:14:44 +00:00
if (searchWithPrimary)
{
using var connection = DbFactory.OpenConnection();
var command = connection.CreateCommand();
2022-02-07 14:53:58 +00:00
command.CommandText = $"select max({primaryIndex}), min({primaryIndex}) from {t}";
var minMax = ExecuteList(command).ConvertAll(r => new Tuple<int, int>(Convert.ToInt32(r[0]), Convert.ToInt32(r[1]))).FirstOrDefault();
primaryIndexStart = minMax.Item2;
primaryIndexStep = (minMax.Item1 - minMax.Item2) / count;
2021-08-31 09:40:28 +00:00
if (primaryIndexStep < Limit)
2020-05-26 08:52:47 +00:00
{
primaryIndexStep = Limit;
2020-05-20 15:14:44 +00:00
}
}
2021-08-31 09:40:28 +00:00
var path = CrossPlatform.PathCombine(dir, t);
2021-08-31 09:40:28 +00:00
var offset = 0;
2021-08-31 09:40:28 +00:00
do
{
List<object[]> result;
2021-08-31 09:40:28 +00:00
if (searchWithPrimary)
{
result = GetDataWithPrimary(t, columns, primaryIndex, primaryIndexStart, primaryIndexStep);
primaryIndexStart += primaryIndexStep;
}
else
{
result = GetData(t, columns, offset);
}
2021-08-31 09:40:28 +00:00
offset += Limit;
2021-08-31 09:40:28 +00:00
var resultCount = result.Count;
2021-08-31 09:40:28 +00:00
if (resultCount == 0)
{
break;
}
2021-08-31 09:40:28 +00:00
SaveToFile(path, t, columns, result);
2020-05-20 15:14:44 +00:00
if (resultCount < Limit)
{
break;
}
} while (true);
2020-05-20 15:14:44 +00:00
SetStepCompleted();
Logger.DebugFormat("dump table data stop {0}", t);
2020-05-20 15:14:44 +00:00
}
catch (Exception e)
2020-05-20 15:14:44 +00:00
{
Logger.Error(e);
throw;
2021-08-31 09:40:28 +00:00
}
}
2021-08-31 09:40:28 +00:00
private List<object[]> GetData(string t, List<string> columns, int offset)
{
using var connection = DbFactory.OpenConnection();
var command = connection.CreateCommand();
var selects = string.Join(',', columns);
command.CommandText = $"select {selects} from {t} LIMIT {offset}, {Limit}";
2022-02-09 18:33:50 +00:00
return ExecuteList(command);
}
2021-08-31 09:40:28 +00:00
private List<object[]> GetDataWithPrimary(string t, List<string> columns, string primary, int start, int step)
{
using var connection = DbFactory.OpenConnection();
var command = connection.CreateCommand();
var selects = string.Join(',', columns);
command.CommandText = $"select {selects} from {t} where {primary} BETWEEN {start} and {start + step} ";
return ExecuteList(command);
}
private void SaveToFile(string path, string t, IReadOnlyCollection<string> columns, List<object[]> data)
{
Logger.DebugFormat("save to file {0}", t);
List<object[]> portion;
2022-02-07 14:53:58 +00:00
while ((portion = data.Take(BatchLimit).ToList()).Count > 0)
2020-05-20 15:14:44 +00:00
{
using (var sw = new StreamWriter(path, true))
using (var writer = new JsonTextWriter(sw))
2021-08-31 09:40:28 +00:00
{
writer.QuoteChar = '\'';
writer.DateFormatString = "yyyy-MM-dd HH:mm:ss";
sw.Write("REPLACE INTO `{0}` ({1}) VALUES ", t, string.Join(",", columns));
sw.WriteLine();
for (var j = 0; j < portion.Count; j++)
2020-05-20 15:14:44 +00:00
{
var obj = portion[j];
sw.Write("(");
2020-05-20 15:14:44 +00:00
for (var i = 0; i < obj.Length; i++)
2020-05-20 15:14:44 +00:00
{
2022-02-07 14:53:58 +00:00
var value = obj[i];
2022-01-12 10:27:06 +00:00
if (value is byte[] byteArray)
2020-05-20 15:14:44 +00:00
{
sw.Write("0x");
foreach (var b in byteArray)
2020-05-20 15:14:44 +00:00
{
sw.Write("{0:x2}", b);
2020-05-20 15:14:44 +00:00
}
}
else
2020-05-20 15:14:44 +00:00
{
var ser = new JsonSerializer();
Merge branch 'feature/backend-refactor' into feature/asc-backup-core-refactor # Conflicts: # common/ASC.Data.Backup.Core/ActionInvoker.cs # common/ASC.Data.Backup.Core/BackupAjaxHandler.cs # common/ASC.Data.Backup.Core/Core/DbBackupProvider.cs # common/ASC.Data.Backup.Core/Core/DbHelper.cs # common/ASC.Data.Backup.Core/Core/FileBackupProvider.cs # common/ASC.Data.Backup.Core/Core/NotifyHelper.cs # common/ASC.Data.Backup.Core/Core/ZipOperator.cs # common/ASC.Data.Backup.Core/Exceptions/ThrowHelper.cs # common/ASC.Data.Backup.Core/Extensions/EnumerableExtensions.cs # common/ASC.Data.Backup.Core/Service/BackupWorker.cs # common/ASC.Data.Backup.Core/Storage/DocumentsBackupStorage.cs # common/ASC.Data.Backup.Core/Tasks/BackupPortalTask.cs # common/ASC.Data.Backup.Core/Tasks/ColumnMapper.cs # common/ASC.Data.Backup.Core/Tasks/Data/DataRowInfo.cs # common/ASC.Data.Backup.Core/Tasks/Data/TableInfo.cs # common/ASC.Data.Backup.Core/Tasks/DeletePortalTask.cs # common/ASC.Data.Backup.Core/Tasks/KeyHelper.cs # common/ASC.Data.Backup.Core/Tasks/Modules/MailModuleSpecifics.cs # common/ASC.Data.Backup.Core/Tasks/Modules/ModuleProvider.cs # common/ASC.Data.Backup.Core/Tasks/Modules/ModuleSpecificsBase.cs # common/ASC.Data.Backup.Core/Tasks/Modules/ProjectsModuleSpecifics.cs # common/ASC.Data.Backup.Core/Tasks/PortalTaskBase.cs # common/ASC.Data.Backup.Core/Tasks/RestoreDbModuleTask.cs # common/ASC.Data.Backup.Core/Tasks/RestorePortalTask.cs # common/ASC.Data.Backup.Core/Tasks/TransferPortalTask.cs
2022-02-15 16:56:43 +00:00
ser.Serialize(writer, value);
2020-05-20 15:14:44 +00:00
}
if (i != obj.Length - 1)
2020-05-20 15:14:44 +00:00
{
sw.Write(",");
2020-05-20 15:14:44 +00:00
}
}
sw.Write(")");
2022-02-09 18:33:50 +00:00
if (j != portion.Count - 1)
{
sw.Write(",");
}
else
{
sw.Write(";");
2020-05-20 15:14:44 +00:00
}
2022-02-09 18:33:50 +00:00
sw.WriteLine();
2020-05-20 15:14:44 +00:00
}
}
data = data.Skip(BatchLimit).ToList();
2020-05-20 15:14:44 +00:00
}
}
private void DoDumpStorage(IDataWriteOperator writer, IReadOnlyList<BackupFileInfo> files)
{
Logger.Debug("begin backup storage");
2020-05-20 15:14:44 +00:00
var dir = Path.GetDirectoryName(BackupFilePath);
var subDir = CrossPlatform.PathCombine(dir, Path.GetFileNameWithoutExtension(BackupFilePath));
2020-05-20 15:14:44 +00:00
for (var i = 0; i < files.Count; i += TasksLimit)
2020-05-20 15:14:44 +00:00
{
var storageDir = CrossPlatform.PathCombine(subDir, KeyHelper.GetStorage());
2020-05-20 15:14:44 +00:00
if (!Directory.Exists(storageDir))
{
Directory.CreateDirectory(storageDir);
}
2020-05-20 15:14:44 +00:00
var tasks = new List<Task>(TasksLimit);
for (var j = 0; j < TasksLimit && i + j < files.Count; j++)
2020-05-20 15:14:44 +00:00
{
var t = files[i + j];
tasks.Add(Task.Run(() => DoDumpFile(t, storageDir)));
}
2020-05-20 15:14:44 +00:00
Task.WaitAll(tasks.ToArray());
2020-05-20 15:14:44 +00:00
ArchiveDir(writer, subDir);
2020-05-20 15:14:44 +00:00
Directory.Delete(storageDir, true);
}
2020-05-20 15:14:44 +00:00
var restoreInfoXml = new XElement("storage_restore", files.Select(file => (object)file.ToXElement()).ToArray());
2020-05-20 15:14:44 +00:00
var tmpPath = CrossPlatform.PathCombine(subDir, KeyHelper.GetStorageRestoreInfoZipKey());
Directory.CreateDirectory(Path.GetDirectoryName(tmpPath));
2020-05-20 15:14:44 +00:00
using (var tmpFile = new FileStream(tmpPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.DeleteOnClose))
{
restoreInfoXml.WriteTo(tmpFile);
writer.WriteEntry(KeyHelper.GetStorageRestoreInfoZipKey(), tmpFile);
}
2020-05-20 15:14:44 +00:00
SetStepCompleted();
2020-05-20 15:14:44 +00:00
Directory.Delete(subDir, true);
2020-05-20 15:14:44 +00:00
Logger.Debug("end backup storage");
}
private async Task DoDumpFile(BackupFileInfo file, string dir)
{
var storage = StorageFactory.GetStorage(ConfigPath, file.Tenant.ToString(), file.Module);
var filePath = CrossPlatform.PathCombine(dir, file.GetZipKey());
var dirName = Path.GetDirectoryName(filePath);
2020-05-20 15:14:44 +00:00
Logger.DebugFormat("backup file {0}", filePath);
2020-05-20 15:14:44 +00:00
if (!Directory.Exists(dirName) && !string.IsNullOrEmpty(dirName))
{
Directory.CreateDirectory(dirName);
2020-05-20 15:14:44 +00:00
}
if (!WorkContext.IsMono && filePath.Length > MaxLength)
2020-05-20 15:14:44 +00:00
{
filePath = @"\\?\" + filePath;
}
2020-05-20 15:14:44 +00:00
Merge branch 'develop' into feature/backend-refactor # Conflicts: # common/ASC.Common/Caching/ICacheNotify.cs # common/ASC.Common/Caching/KafkaCache.cs # common/ASC.Common/Caching/MemoryCacheNotify.cs # common/ASC.Common/Threading/DistributedTaskProgress.cs # common/ASC.Common/Threading/DistributedTaskQueue.cs # common/ASC.Core.Common/Notify/Signalr/SignalrServiceClient.cs # common/ASC.Data.Backup.Core/Core/FileBackupProvider.cs # common/ASC.Data.Backup.Core/Storage/BackupRepository.cs # common/ASC.Data.Backup.Core/Storage/ConsumerBackupStorage.cs # common/ASC.Data.Backup.Core/Storage/DataStoreBackupStorage.cs # common/ASC.Data.Backup.Core/Storage/DocumentsBackupStorage.cs # common/ASC.Data.Backup.Core/Tasks/BackupPortalTask.cs # common/ASC.Data.Backup.Core/Tasks/DeletePortalTask.cs # common/ASC.Data.Backup.Core/Tasks/PortalTaskBase.cs # common/ASC.Data.Backup.Core/Tasks/RestorePortalTask.cs # common/ASC.Data.Backup.Core/Tasks/TransferPortalTask.cs # common/ASC.Data.Reassigns/RemoveProgressItem.cs # common/ASC.Data.Storage/BaseStorage.cs # common/ASC.Data.Storage/ChunkedUploader/CommonChunkedUploadSessionHolder.cs # common/ASC.Data.Storage/CrossModuleTransferUtility.cs # common/ASC.Data.Storage/DiscStorage/DiscDataStore.cs # common/ASC.Data.Storage/Encryption/EncryptionOperation.cs # common/ASC.Data.Storage/Extensions.cs # common/ASC.Data.Storage/GoogleCloud/GoogleCloudStorage.cs # common/ASC.Data.Storage/IDataStore.cs # common/ASC.Data.Storage/RackspaceCloud/RackspaceCloudStorage.cs # common/ASC.Data.Storage/S3/S3Storage.cs # common/ASC.Data.Storage/S3/S3UploadGuard.cs # common/ASC.Data.Storage/StaticUploader.cs # common/ASC.Data.Storage/StorageHandler.cs # common/ASC.Data.Storage/StorageUploader.cs # common/ASC.Data.Storage/WebPath.cs # common/services/ASC.ApiSystem/Controllers/PortalController.cs # common/services/ASC.AuditTrail/AuditEventsRepository.cs # common/services/ASC.AuditTrail/AuditReportCreator.cs # common/services/ASC.AuditTrail/LoginEventsRepository.cs # products/ASC.Files/Core/Configuration/FilesSpaceUsageStatManager.cs # products/ASC.Files/Core/Core/Dao/Interfaces/IFolderDao.cs # products/ASC.Files/Core/Core/Dao/Interfaces/ILinkDao.cs # products/ASC.Files/Core/Core/Dao/Interfaces/IProviderDao.cs # products/ASC.Files/Core/Core/Dao/Interfaces/IProviderInfo.cs # products/ASC.Files/Core/Core/Dao/Interfaces/ITagDao.cs # products/ASC.Files/Core/Core/Dao/TeamlabDao/AbstractDao.cs # products/ASC.Files/Core/Core/Dao/TeamlabDao/FolderDao.cs # products/ASC.Files/Core/Core/Dao/TeamlabDao/LinkDao.cs # products/ASC.Files/Core/Core/Dao/TeamlabDao/SecurityDao.cs # products/ASC.Files/Core/Core/Dao/TeamlabDao/TagDao.cs # products/ASC.Files/Core/Core/Entries/ChunkedUploadSession.cs # products/ASC.Files/Core/Core/Entries/EncryptionKeyPair.cs # products/ASC.Files/Core/Core/FileStorageService.cs # products/ASC.Files/Core/Core/FilesIntegration.cs # products/ASC.Files/Core/Core/Security/FileSecurity.cs # products/ASC.Files/Core/Core/Security/IFileSecurity.cs # products/ASC.Files/Core/Core/Security/ISecurityDao.cs # products/ASC.Files/Core/Core/Thirdparty/Box/BoxDaoBase.cs # products/ASC.Files/Core/Core/Thirdparty/Box/BoxFileDao.cs # products/ASC.Files/Core/Core/Thirdparty/Box/BoxFolderDao.cs # products/ASC.Files/Core/Core/Thirdparty/Box/BoxProviderInfo.cs # products/ASC.Files/Core/Core/Thirdparty/Box/BoxStorage.cs # products/ASC.Files/Core/Core/Thirdparty/CrossDao.cs # products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxDaoBase.cs # products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxFileDao.cs # products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxFolderDao.cs # products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxProviderInfo.cs # products/ASC.Files/Core/Core/Thirdparty/Dropbox/DropboxStorage.cs # products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveDaoBase.cs # products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveFileDao.cs # products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveFolderDao.cs # products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveProviderInfo.cs # products/ASC.Files/Core/Core/Thirdparty/GoogleDrive/GoogleDriveStorage.cs # products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveDaoBase.cs # products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveFileDao.cs # products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveFolderDao.cs # products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveProviderInfo.cs # products/ASC.Files/Core/Core/Thirdparty/OneDrive/OneDriveStorage.cs # products/ASC.Files/Core/Core/Thirdparty/ProviderAccountDao.cs # products/ASC.Files/Core/Core/Thirdparty/ProviderDao/ProviderDaoBase.cs # products/ASC.Files/Core/Core/Thirdparty/ProviderDao/ProviderFileDao.cs # products/ASC.Files/Core/Core/Thirdparty/ProviderDao/ProviderFolderDao.cs # products/ASC.Files/Core/Core/Thirdparty/ProviderDao/ProviderSecutiryDao.cs # products/ASC.Files/Core/Core/Thirdparty/ProviderDao/ProviderTagDao.cs # products/ASC.Files/Core/Core/Thirdparty/RegexDaoSelectorBase.cs # products/ASC.Files/Core/Core/Thirdparty/SharePoint/SharePointDaoBase.cs # products/ASC.Files/Core/Core/Thirdparty/SharePoint/SharePointFileDao.cs # products/ASC.Files/Core/Core/Thirdparty/SharePoint/SharePointFolderDao.cs # products/ASC.Files/Core/Core/Thirdparty/SharePoint/SharePointProviderInfo.cs # products/ASC.Files/Core/Core/Thirdparty/Sharpbox/SharpBoxDaoBase.cs # products/ASC.Files/Core/Core/Thirdparty/Sharpbox/SharpBoxFileDao.cs # products/ASC.Files/Core/Core/Thirdparty/Sharpbox/SharpBoxFolderDao.cs # products/ASC.Files/Core/Core/Thirdparty/Sharpbox/SharpBoxProviderInfo.cs # products/ASC.Files/Core/Helpers/DocuSignHelper.cs # products/ASC.Files/Core/Helpers/Global.cs # products/ASC.Files/Core/Helpers/PathProvider.cs # products/ASC.Files/Core/HttpHandlers/FileHandler.ashx.cs # products/ASC.Files/Core/HttpHandlers/SearchHandler.cs # products/ASC.Files/Core/Model/FileEntryWrapper.cs # products/ASC.Files/Core/Model/FileOperationWraper.cs # products/ASC.Files/Core/Model/FileWrapper.cs # products/ASC.Files/Core/Model/FolderContentWrapper.cs # products/ASC.Files/Core/Model/FolderWrapper.cs # products/ASC.Files/Core/Services/DocumentService/Configuration.cs # products/ASC.Files/Core/Services/DocumentService/DocumentServiceConnector.cs # products/ASC.Files/Core/Services/DocumentService/DocumentServiceHelper.cs # products/ASC.Files/Core/Services/DocumentService/DocumentServiceTracker.cs # products/ASC.Files/Core/Services/NotifyService/NotifyClient.cs # products/ASC.Files/Core/Services/WCFService/FileOperations/FileDeleteOperation.cs # products/ASC.Files/Core/Services/WCFService/FileOperations/FileDownloadOperation.cs # products/ASC.Files/Core/Services/WCFService/FileOperations/FileMarkAsReadOperation.cs # products/ASC.Files/Core/Services/WCFService/FileOperations/FileMoveCopyOperation.cs # products/ASC.Files/Core/Services/WCFService/FileOperations/FileOperationsManager.cs # products/ASC.Files/Core/ThirdPartyApp/BoxApp.cs # products/ASC.Files/Core/ThirdPartyApp/GoogleDriveApp.cs # products/ASC.Files/Core/ThirdPartyApp/IThirdPartyApp.cs # products/ASC.Files/Core/Utils/ChunkedUploadSessionHolder.cs # products/ASC.Files/Core/Utils/EntryManager.cs # products/ASC.Files/Core/Utils/FileConverter.cs # products/ASC.Files/Core/Utils/FileMarker.cs # products/ASC.Files/Core/Utils/FileShareLink.cs # products/ASC.Files/Core/Utils/FileSharing.cs # products/ASC.Files/Core/Utils/FileUploader.cs # products/ASC.Files/Core/Utils/MailMergeTask.cs # products/ASC.Files/Core/Utils/SocketManager.cs # products/ASC.Files/Server/Controllers/FilesController.cs # products/ASC.Files/Server/Controllers/PrivacyRoomController.cs # products/ASC.Files/Server/Helpers/FilesControllerHelper.cs # products/ASC.People/Server/Controllers/PeopleController.cs # web/ASC.Web.Api/Controllers/AuthenticationController.cs # web/ASC.Web.Api/Controllers/PortalController.cs # web/ASC.Web.Api/Controllers/SettingsController.cs # web/ASC.Web.Api/Models/BuildVersion.cs # web/ASC.Web.Core/Files/DocumentService.cs # web/ASC.Web.Core/Files/DocumentServiceLicense.cs # web/ASC.Web.Core/Helpers/ApiSystemHelper.cs # web/ASC.Web.Core/Notify/StudioNotifyServiceSender.cs # web/ASC.Web.Core/Notify/StudioPeriodicNotify.cs # web/ASC.Web.Core/Recaptcha.cs # web/ASC.Web.Core/Sms/SmsManager.cs # web/ASC.Web.Core/Sms/SmsProvider.cs # web/ASC.Web.Core/Sms/SmsSender.cs # web/ASC.Web.Core/SpaceUsageStatManager.cs # web/ASC.Web.Core/Utility/UrlShortener.cs
2022-02-23 19:42:34 +00:00
using (var fileStream = await storage.GetReadStreamAsync(file.Domain, file.Path))
using (var tmpFile = File.OpenWrite(filePath))
{
await fileStream.CopyToAsync(tmpFile);
}
2020-05-20 15:14:44 +00:00
SetStepCompleted();
}
2021-08-31 09:40:28 +00:00
private void ArchiveDir(IDataWriteOperator writer, string subDir)
{
Logger.DebugFormat("archive dir start {0}", subDir);
foreach (var enumerateFile in Directory.EnumerateFiles(subDir, "*", SearchOption.AllDirectories))
{
var f = enumerateFile;
if (!WorkContext.IsMono && enumerateFile.Length > MaxLength)
2021-08-31 09:40:28 +00:00
{
f = @"\\?\" + f;
2021-08-31 09:40:28 +00:00
}
2020-09-11 14:12:35 +00:00
using (var tmpFile = new FileStream(f, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.DeleteOnClose))
2020-05-20 15:14:44 +00:00
{
writer.WriteEntry(enumerateFile.Substring(subDir.Length), tmpFile);
2020-05-20 15:14:44 +00:00
}
SetStepCompleted();
}
Logger.DebugFormat("archive dir end {0}", subDir);
}
2020-05-20 15:14:44 +00:00
private List<IGrouping<string, BackupFileInfo>> GetFilesGroup()
{
var files = GetFilesToProcess(TenantId).ToList();
var exclude = BackupRecordContext.Backups.AsQueryable().Where(b => b.TenantId == TenantId && b.StorageType == 0 && b.StoragePath != null).ToList();
2020-05-20 15:14:44 +00:00
2022-01-14 13:12:37 +00:00
files = files.Where(f => !exclude.Any(e => f.Path.Replace('\\', '/').Contains($"/file_{e.StoragePath}/"))).ToList();
2020-05-20 15:14:44 +00:00
return files.GroupBy(file => file.Module).ToList();
}
2020-05-20 15:14:44 +00:00
private void DoBackupModule(IDataWriteOperator writer, IModuleSpecifics module)
{
Logger.DebugFormat("begin saving data for module {0}", module.ModuleName);
var tablesToProcess = module.Tables.Where(t => !IgnoredTables.Contains(t.Name) && t.InsertMethod != InsertMethod.None).ToList();
var tablesCount = tablesToProcess.Count;
var tablesProcessed = 0;
2020-05-20 15:14:44 +00:00
using (var connection = DbFactory.OpenConnection())
2020-05-20 15:14:44 +00:00
{
foreach (var table in tablesToProcess)
2020-05-20 15:14:44 +00:00
{
Logger.DebugFormat("begin load table {0}", table.Name);
using (var data = new DataTable(table.Name))
2020-05-20 15:14:44 +00:00
{
ActionInvoker.Try(
state =>
2020-05-20 15:14:44 +00:00
{
data.Clear();
int counts;
var offset = 0;
do
{
var t = (TableInfo)state;
var dataAdapter = DbFactory.CreateDataAdapter();
dataAdapter.SelectCommand = module.CreateSelectCommand(connection.Fix(), TenantId, t, Limit, offset).WithTimeout(600);
counts = ((DbDataAdapter)dataAdapter).Fill(data);
offset += Limit;
} while (counts == Limit);
},
table,
maxAttempts: 5,
onFailure: error => { throw ThrowHelper.CantBackupTable(table.Name, error); },
onAttemptFailure: error => Logger.Warn("backup attempt failure: {0}", error));
foreach (var col in data.Columns.Cast<DataColumn>().Where(col => col.DataType == typeof(DateTime)))
{
col.DateTimeMode = DataSetDateTime.Unspecified;
}
2020-05-20 15:14:44 +00:00
module.PrepareData(data);
2020-05-20 15:14:44 +00:00
Logger.DebugFormat("end load table {0}", table.Name);
2020-05-20 15:14:44 +00:00
Logger.DebugFormat("begin saving table {0}", table.Name);
2021-08-31 09:40:28 +00:00
using (var file = _tempStream.Create())
{
data.WriteXml(file, XmlWriteMode.WriteSchema);
data.Clear();
2020-05-20 15:14:44 +00:00
writer.WriteEntry(KeyHelper.GetTableZipKey(module, data.TableName), file);
2020-05-20 15:14:44 +00:00
}
Logger.DebugFormat("end saving table {0}", table.Name);
2020-05-20 15:14:44 +00:00
}
2022-02-09 18:33:50 +00:00
Merge branch 'feature/backend-refactor' into feature/asc-backup-core-refactor # Conflicts: # common/ASC.Data.Backup.Core/ActionInvoker.cs # common/ASC.Data.Backup.Core/BackupAjaxHandler.cs # common/ASC.Data.Backup.Core/Core/DbBackupProvider.cs # common/ASC.Data.Backup.Core/Core/DbHelper.cs # common/ASC.Data.Backup.Core/Core/FileBackupProvider.cs # common/ASC.Data.Backup.Core/Core/NotifyHelper.cs # common/ASC.Data.Backup.Core/Core/ZipOperator.cs # common/ASC.Data.Backup.Core/Exceptions/ThrowHelper.cs # common/ASC.Data.Backup.Core/Extensions/EnumerableExtensions.cs # common/ASC.Data.Backup.Core/Service/BackupWorker.cs # common/ASC.Data.Backup.Core/Storage/DocumentsBackupStorage.cs # common/ASC.Data.Backup.Core/Tasks/BackupPortalTask.cs # common/ASC.Data.Backup.Core/Tasks/ColumnMapper.cs # common/ASC.Data.Backup.Core/Tasks/Data/DataRowInfo.cs # common/ASC.Data.Backup.Core/Tasks/Data/TableInfo.cs # common/ASC.Data.Backup.Core/Tasks/DeletePortalTask.cs # common/ASC.Data.Backup.Core/Tasks/KeyHelper.cs # common/ASC.Data.Backup.Core/Tasks/Modules/MailModuleSpecifics.cs # common/ASC.Data.Backup.Core/Tasks/Modules/ModuleProvider.cs # common/ASC.Data.Backup.Core/Tasks/Modules/ModuleSpecificsBase.cs # common/ASC.Data.Backup.Core/Tasks/Modules/ProjectsModuleSpecifics.cs # common/ASC.Data.Backup.Core/Tasks/PortalTaskBase.cs # common/ASC.Data.Backup.Core/Tasks/RestoreDbModuleTask.cs # common/ASC.Data.Backup.Core/Tasks/RestorePortalTask.cs # common/ASC.Data.Backup.Core/Tasks/TransferPortalTask.cs
2022-02-15 16:56:43 +00:00
SetCurrentStepProgress((int)(++tablesProcessed * 100 / (double)tablesCount));
2020-05-20 15:14:44 +00:00
}
}
Logger.DebugFormat("end saving data for module {0}", module.ModuleName);
}
private void DoBackupStorage(IDataWriteOperator writer, List<IGrouping<string, BackupFileInfo>> fileGroups)
{
Logger.Debug("begin backup storage");
foreach (var group in fileGroups)
2020-05-20 15:14:44 +00:00
{
var filesProcessed = 0;
var filesCount = group.Count();
2020-05-20 15:14:44 +00:00
foreach (var file in group)
2020-05-20 15:14:44 +00:00
{
var storage = StorageFactory.GetStorage(ConfigPath, TenantId.ToString(), group.Key);
var file1 = file;
ActionInvoker.Try(state =>
2020-05-20 15:14:44 +00:00
{
var f = (BackupFileInfo)state;
2022-01-25 09:29:11 +00:00
using var fileStream = storage.GetReadStreamAsync(f.Domain, f.Path).Result;
writer.WriteEntry(file1.GetZipKey(), fileStream);
}, file, 5, error => Logger.WarnFormat("can't backup file ({0}:{1}): {2}", file1.Module, file1.Path, error));
2020-05-20 15:14:44 +00:00
SetCurrentStepProgress((int)(++filesProcessed * 100 / (double)filesCount));
2020-05-20 15:14:44 +00:00
}
}
2020-05-20 15:14:44 +00:00
var restoreInfoXml = new XElement(
"storage_restore",
fileGroups
.SelectMany(group => group.Select(file => (object)file.ToXElement()))
.ToArray());
2020-05-20 15:14:44 +00:00
using (var tmpFile = _tempStream.Create())
2020-05-26 08:52:47 +00:00
{
restoreInfoXml.WriteTo(tmpFile);
writer.WriteEntry(KeyHelper.GetStorageRestoreInfoZipKey(), tmpFile);
2020-05-26 08:52:47 +00:00
}
Logger.Debug("end backup storage");
2020-05-20 15:14:44 +00:00
}
2020-05-20 15:14:44 +00:00
}