DocSpace-buildtools/common/ASC.Migration/Core/Providers/OwnCloud/OwnCloudMigration.cs

557 lines
21 KiB
C#
Raw Normal View History

2022-06-05 15:44:37 +00:00
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL 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 details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using ASC.Migration.OwnCloud.Models;
namespace ASC.Migration.OwnCloud;
[ApiMigrator("OwncloudMigrate")]
public class OwnCloudMigration : AbstractMigration<OCMigrationInfo, OCMigratingUser, OCMigratingContacts, OCMigratingCalendar, OCMigratingFiles, OCMigratingMail>
{
private string _takeouts;
public string[] TempParse;
private string _tmpFolder;
2022-06-16 08:13:53 +00:00
private readonly GlobalFolderHelper _globalFolderHelper;
private readonly IDaoFactory _daoFactory;
private readonly FileSecurity _fileSecurity;
private readonly FileStorageService<int> _fileStorageService;
2022-06-05 15:44:37 +00:00
private readonly SecurityContext _securityContext;
private readonly TenantManager _tenantManager;
private readonly UserManager _userManager;
2022-06-16 08:13:53 +00:00
public OwnCloudMigration(
GlobalFolderHelper globalFolderHelper,
IDaoFactory daoFactory,
FileSecurity fileSecurity,
FileStorageService<int> fileStorageService,
SecurityContext securityContext,
TenantManager tenantManager,
UserManager userManager,
MigrationLogger migrationLogger) : base(migrationLogger)
2022-06-05 15:44:37 +00:00
{
2022-06-16 08:13:53 +00:00
_globalFolderHelper = globalFolderHelper;
_daoFactory = daoFactory;
_fileSecurity = fileSecurity;
_fileStorageService = fileStorageService;
2022-06-05 15:44:37 +00:00
_securityContext = securityContext;
_tenantManager = tenantManager;
_userManager = userManager;
}
public override void Init(string path, CancellationToken cancellationToken)
{
_cancellationToken = cancellationToken;
var files = Directory.GetFiles(path);
if (!files.Any() || !files.Any(f => f.EndsWith(".zip")))
{
throw new Exception("Folder must not be empty and should contain only .zip files.");
}
for (var i = 0; i < files.Length; i++)
{
if (files[i].EndsWith(".zip"))
{
var creationTime = File.GetCreationTimeUtc(files[i]);
_takeouts = files[i];
}
}
_migrationInfo = new OCMigrationInfo();
_migrationInfo.MigratorName = GetType().CustomAttributes.First().ConstructorArguments.First().Value.ToString();
_tmpFolder = path;
}
public override Task<MigrationApiInfo> Parse()
{
ReportProgress(0, MigrationResource.Unzipping);
try
{
try
{
ZipFile.ExtractToDirectory(_takeouts, _tmpFolder);
}
catch (Exception ex)
{
Log($"Couldn't to unzip {_takeouts}", ex);
}
if (_cancellationToken.IsCancellationRequested) { ReportProgress(100, MigrationResource.MigrationCanceled); return null; }
ReportProgress(30, MigrationResource.UnzippingFinished);
var bdFile = "";
try
{
bdFile = Directory.GetFiles(Directory.GetDirectories(_tmpFolder)[0], "*.bak")[0];
if (bdFile == null)
{
throw new Exception();
}
}
catch (Exception ex)
{
_migrationInfo.failedArchives.Add(Path.GetFileName(_takeouts));
Log("Archive must not be empty and should contain .bak files.", ex);
}
ReportProgress(40, MigrationResource.DumpParse);
var users = DBExtractUser(bdFile);
var progress = 40;
foreach (var item in users)
{
if (_cancellationToken.IsCancellationRequested) { ReportProgress(100, MigrationResource.MigrationCanceled); return null; }
ReportProgress(progress, MigrationResource.DataProcessing);
progress += 30 / users.Count;
if (item.Data.DisplayName != null)
{
try
{
var userName = item.Data.DisplayName.Split(' ');
item.Data.DisplayName = userName.Length > 1 ? string.Format("{0} {1}", userName[0], userName[1]).Trim() : userName[0].Trim();
2022-06-16 08:13:53 +00:00
var user = new OCMigratingUser(_globalFolderHelper, _daoFactory, _fileSecurity, _fileStorageService, _tenantManager, _userManager, item.Uid, item, Directory.GetDirectories(_tmpFolder)[0], Log);
2022-06-05 15:44:37 +00:00
user.Parse();
foreach (var element in user.ModulesList)
{
if (!_migrationInfo.Modules.Exists(x => x.MigrationModule == element.MigrationModule))
{
_migrationInfo.Modules.Add(new MigrationModules(element.MigrationModule, element.Module));
}
}
_migrationInfo.Users.Add(item.Uid, user);
}
catch (Exception ex)
{
Log($"Couldn't parse user {item.Data.DisplayName}", ex);
}
}
}
var groups = DBExtractGroup(bdFile);
progress = 80;
foreach (var item in groups)
{
ReportProgress(progress, MigrationResource.DataProcessing);
progress += 10 / groups.Count;
var group = new OCMigratingGroups(_userManager, item, Log);
group.Parse();
if (group.Module.MigrationModule != null)
{
_migrationInfo.Groups.Add(group);
if (!_migrationInfo.Modules.Exists(x => x.MigrationModule == group.Module.MigrationModule))
{
_migrationInfo.Modules.Add(new MigrationModules(group.Module.MigrationModule, group.Module.Module));
}
}
}
ReportProgress(90, MigrationResource.ClearingTemporaryData);
}
catch (Exception ex)
{
_migrationInfo.failedArchives.Add(Path.GetFileName(_takeouts));
Log($"Couldn't parse users from {Path.GetFileNameWithoutExtension(_takeouts)} archive", ex);
}
ReportProgress(100, MigrationResource.DataProcessingCompleted);
return Task.FromResult(_migrationInfo.ToApiInfo());
}
public List<OCGroup> DBExtractGroup(string dbFile)
{
var groups = new List<OCGroup>();
var sqlFile = File.ReadAllText(dbFile);
var groupList = GetDumpChunk("oc_groups", sqlFile);
if (groupList == null)
{
return groups;
}
foreach (var group in groupList)
{
groups.Add(new OCGroup
{
GroupGid = group.Trim('\''),
UsersUid = new List<string>()
});
}
var usersInGroups = GetDumpChunk("oc_group_user", sqlFile);
foreach (var user in usersInGroups)
{
var userGroupGid = user.Split(',').First().Trim('\'');
var userUid = user.Split(',').Last().Trim('\'');
groups.Find(ggid => userGroupGid == ggid.GroupGid).UsersUid.Add(userUid);
}
return groups;
}
public List<OCUser> DBExtractUser(string dbFile)
{
var userDataList = new Dictionary<string, OCUser>();
var sqlFile = File.ReadAllText(dbFile);
var accountsData = GetDumpChunk("oc_accounts_data", sqlFile);
if (accountsData != null)
{
throw new Exception();
}
var accounts = GetDumpChunk("oc_accounts", sqlFile);
if (accounts == null)
{
return userDataList.Values.ToList();
}
foreach (var account in accounts)
{
var userId = account.Split(',')[2].Trim('\'');
userDataList.Add(userId, new OCUser
{
Uid = account.Split(',')[2].Trim('\''),
Data = new OCUserData
{
DisplayName = account.Split(',')[4].Trim('\''),
Email = account.Split(',')[1].Trim('\'')
},
Addressbooks = null,
Calendars = new List<OCCalendars>(),
Storages = new OCStorages()
});
}
var calendarsData = GetDumpChunk("oc_calendars", sqlFile);
if (calendarsData != null)
{
foreach (var calendarData in calendarsData)
{
var values = calendarData.Split(',')
.Select(s => s.Trim('\'')).ToArray();
var uid = values[1].Split('/').Last();
userDataList.TryGetValue(uid, out var user);
if (user == null)
{
continue;
}
user.Calendars.Add(new OCCalendars()
{
Id = int.Parse(values[0]),
CalendarObject = new List<OCCalendarObjects>(),
DisplayName = values[2]
});
}
}
var calendars = userDataList.Values
.SelectMany(u => u.Calendars)
.ToDictionary(c => c.Id, c => c);
var calendarObjects = GetDumpChunk("oc_calendarobjects", sqlFile);
if (calendarObjects != null)
{
foreach (var calendarObject in calendarObjects)
{
var values = calendarObject.Split(',')
.Select(s => s.Trim('\'')).ToArray();
var calId = int.Parse(values[3]);
calendars.TryGetValue(calId, out var cal);
if (cal == null)
{
continue;
}
cal.CalendarObject.Add(new OCCalendarObjects()
{
Id = int.Parse(values[0]),
CalendarData = Encoding.UTF8.GetBytes(values[1]
.Replace("\\r", "")
.Replace("\\n", "\n")),
});
}
}
var addressBooks = GetDumpChunk("oc_addressbooks", sqlFile);
if (addressBooks != null)
{
foreach (var addressBook in addressBooks)
{
var values = addressBook.Split(',')
.Select(s => s.Trim('\'')).ToArray();
var uid = values[1].Split('/').Last();
userDataList.TryGetValue(uid, out var user);
if (user == null)
{
continue;
}
user.Addressbooks = new OCAddressbooks();
user.Addressbooks.Id = int.Parse(values[0]);
user.Addressbooks.Cards = new List<OCCards>();
}
}
var addressBooksDict = userDataList.Values
.Select(u => u.Addressbooks)
.Where(x => x != null)
.ToDictionary(b => b.Id, b => b);
var cards = GetDumpChunk("oc_cards", sqlFile);
if (cards != null)
{
foreach (var card in cards)
{
var values = card.Split(',')
.Select(s => s.Trim('\'')).ToArray();
var bookId = int.Parse(values[1]);
addressBooksDict.TryGetValue(bookId, out var book);
if (book == null)
{
continue;
}
book.Cards.Add(new OCCards()
{
Id = int.Parse(values[0]),
CardData = Encoding.UTF8.GetBytes(values[2]
.Replace("\\r", "")
.Replace("\\n", "\n")),
});
}
}
var storages = GetDumpChunk("oc_storages", sqlFile);
if (storages != null)
{
foreach (var storage in storages)
{
var values = storage.Split(',')
.Select(s => s.Trim('\'')).ToArray();
var uid = values[0].Split(':').Last();
userDataList.TryGetValue(uid, out var user);
if (user == null)
{
continue;
}
user.Storages.NumericId = int.Parse(values[1]);
user.Storages.Id = values[0];
user.Storages.FileCache = new List<OCFileCache>();
}
}
var storagesDict = userDataList.Values
.Select(u => u.Storages)
.ToDictionary(s => s.NumericId, s => s);
var fileCaches = GetDumpChunk("oc_filecache", sqlFile);
if (fileCaches != null)
{
foreach (var cache in fileCaches)
{
var values = cache.Split(',')
.Select(s => s.Trim('\'')).ToArray();
var storageId = int.Parse(values[1]);
storagesDict.TryGetValue(storageId, out var storage);
if (storage == null)
{
continue;
}
storage.FileCache.Add(new OCFileCache()
{
FileId = int.Parse(values[0]),
Path = values[2],
Share = new List<OCShare>()
});
}
}
var files = userDataList.Values
.SelectMany(u => u.Storages.FileCache)
.ToDictionary(f => f.FileId, f => f);
var shares = GetDumpChunk("oc_share", sqlFile);
if (shares != null)
{
foreach (var share in shares)
{
var values = share.Split(',')
.Select(s => s.Trim('\'')).ToArray();
var fileId = int.Parse(values[9]);
files.TryGetValue(fileId, out var file);
if (file == null)
{
continue;
}
file.Share.Add(new OCShare()
{
Id = int.Parse(values[0]),
ShareWith = values[2],
Premissions = int.Parse(values[11])
});
}
}
return userDataList.Values.ToList();
}
private IEnumerable<string> GetDumpChunk(string tableName, string dump)
{
var regex = new Regex($"INSERT INTO `{tableName}` VALUES (.*);");
var match = regex.Match(dump);
if (!match.Success)
{
return null;
}
var entryRegex = new Regex(@"(\(.*?\))[,;]");
var accountDataMatches = entryRegex.Matches(match.Groups[1].Value + ";");
return accountDataMatches.Cast<Match>()
.Select(m => m.Groups[1].Value.Trim(new[] { '(', ')' }));
}
public override Task Migrate(MigrationApiInfo migrationApiInfo)
{
ReportProgress(0, MigrationResource.PreparingForMigration);
_migrationInfo.Merge(migrationApiInfo);
var usersForImport = _migrationInfo.Users
.Where(u => u.Value.ShouldImport)
.Select(u => u.Value);
_importedUsers = new List<Guid>();
var failedUsers = new List<OCMigratingUser>();
var usersCount = usersForImport.Count();
var progressStep = 25 / usersCount;
var i = 1;
foreach (var user in usersForImport)
{
if (_cancellationToken.IsCancellationRequested) { ReportProgress(100, MigrationResource.MigrationCanceled); return null; }
ReportProgress(GetProgress() + progressStep, string.Format(MigrationResource.UserMigration, user.DisplayName, i++, usersCount));
try
{
user.dataСhange(migrationApiInfo.Users.Find(element => element.Key == user.Key));
user.Migrate();
_importedUsers.Add(user.Guid);
}
catch (Exception ex)
{
failedUsers.Add(user);
Log($"Couldn't migrate user {user.DisplayName} ({user.Email})", ex);
}
}
var groupsForImport = _migrationInfo.Groups
.Where(g => g.ShouldImport)
.Select(g => g);
var groupsCount = groupsForImport.Count();
if (groupsCount != 0)
{
progressStep = 25 / groupsForImport.Count();
//Create all groups
i = 1;
foreach (var group in groupsForImport)
{
if (_cancellationToken.IsCancellationRequested) { ReportProgress(100, MigrationResource.MigrationCanceled); return null; }
ReportProgress(GetProgress() + progressStep, string.Format(MigrationResource.GroupMigration, group.GroupName, i++, groupsCount));
try
{
group.UsersGuidList = _migrationInfo.Users
.Where(user => group.UserUidList.Exists(u => user.Key == u))
.Select(u => u)
.ToDictionary(k => k.Key, v => v.Value.Guid);
group.Migrate();
}
catch (Exception ex)
{
Log($"Couldn't migrate group {group.GroupName} ", ex);
}
}
}
i = 1;
foreach (var user in usersForImport)
{
if (_cancellationToken.IsCancellationRequested) { ReportProgress(100, MigrationResource.MigrationCanceled); return null; }
if (failedUsers.Contains(user))
{
ReportProgress(GetProgress() + progressStep, string.Format(MigrationResource.UserSkipped, user.DisplayName, i, usersCount));
continue;
}
var smallStep = progressStep / 3;
try
{
user.MigratingContacts.Migrate();
}
catch (Exception ex)
{
Log($"Couldn't migrate user {user.DisplayName} ({user.Email}) contacts", ex);
}
finally
{
ReportProgress(GetProgress() + smallStep, string.Format(MigrationResource.MigratingUserContacts, user.DisplayName, i, usersCount));
}
/*try
{
user.MigratingCalendar.Migrate();
}
catch (Exception ex)
{
Log($"Couldn't migrate user {user.DisplayName} ({user.Email}) calendar", ex);
}
finally
{
ReportProgress(GetProgress() + smallStep, String.Format(MigrationResource.UserCalendarMigration, user.DisplayName, i, usersCount));
}*/
try
{
var currentUser = _securityContext.CurrentAccount;
_securityContext.AuthenticateMe(user.Guid);
user.MigratingFiles.SetUsersDict(usersForImport.Except(failedUsers));
user.MigratingFiles.SetGroupsDict(groupsForImport);
user.MigratingFiles.Migrate();
_securityContext.AuthenticateMe(currentUser.ID);
}
catch (Exception ex)
{
Log($"Couldn't migrate user {user.DisplayName} ({user.Email}) files", ex);
}
finally
{
ReportProgress(GetProgress() + smallStep, string.Format(MigrationResource.MigratingUserFiles, user.DisplayName, i, usersCount));
}
i++;
}
if (Directory.Exists(_tmpFolder))
{
Directory.Delete(_tmpFolder, true);
}
ReportProgress(100, MigrationResource.MigrationCompleted);
return Task.CompletedTask;
}
}