766 lines
27 KiB
C#
766 lines
27 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Threading.Tasks;
|
|
using Flawless.Abstraction;
|
|
using Flawless.Client.Models;
|
|
using Flawless.Core.BinaryDataFormat;
|
|
using Flawless.Core.Modal;
|
|
using Newtonsoft.Json;
|
|
using Refit;
|
|
|
|
namespace Flawless.Client.Service;
|
|
|
|
public class RepositoryService : BaseService<RepositoryService>
|
|
{
|
|
public ObservableCollection<RepositoryModel> Repositories => _repositories;
|
|
|
|
private readonly ObservableCollection<RepositoryModel> _repositories = new();
|
|
|
|
private readonly Dictionary<RepositoryModel, RepositoryLocalDatabaseModel> _localRepoDbModel = new();
|
|
|
|
private readonly HashSet<RepositoryModel> _openedRepos = new();
|
|
|
|
private bool TryCreateRepositoryBaseStorageStructure(RepositoryModel repo)
|
|
{
|
|
var dbPath = PathUtility.GetWorkspaceDbPath(repo.OwnerName, repo.Name);
|
|
if (File.Exists(dbPath)) return false;
|
|
|
|
// Get directories
|
|
var localRepoDb = GetRepositoryLocalDatabase(repo);
|
|
var folderPath = PathUtility.GetWorkspaceManagerPath(repo.OwnerName, repo.Name);
|
|
|
|
// Create initial data.
|
|
Directory.CreateDirectory(folderPath);
|
|
using var writeFs = new StreamWriter(new FileStream(dbPath, FileMode.Create));
|
|
JsonSerializer.CreateDefault().Serialize(writeFs, localRepoDb);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
public bool SaveRepositoryLocalDatabaseChanges(RepositoryModel repo)
|
|
{
|
|
var localRepo = GetRepositoryLocalDatabase(repo);
|
|
localRepo.LastOprationTime = DateTime.Now;
|
|
|
|
var dbPath = PathUtility.GetWorkspaceDbPath(repo.OwnerName, repo.Name);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(dbPath)!);
|
|
|
|
using var writeFs = new StreamWriter(new FileStream(dbPath, FileMode.OpenOrCreate));
|
|
JsonSerializer.CreateDefault().Serialize(writeFs, localRepo);
|
|
|
|
return true;
|
|
}
|
|
|
|
public RepositoryLocalDatabaseModel GetRepositoryLocalDatabase(RepositoryModel repo)
|
|
{
|
|
if (!_localRepoDbModel.TryGetValue(repo, out var localRepo))
|
|
{
|
|
var dbPath = PathUtility.GetWorkspaceDbPath(repo.OwnerName, repo.Name);
|
|
if (File.Exists(dbPath))
|
|
{
|
|
// Use existed target
|
|
using var readFs = new StreamReader(new FileStream(dbPath, FileMode.Open));
|
|
localRepo = (JsonSerializer.CreateDefault().Deserialize(readFs, typeof(RepositoryLocalDatabaseModel))
|
|
as RepositoryLocalDatabaseModel)!; // todo add broken test.
|
|
|
|
localRepo.RootModal = repo;
|
|
localRepo.LocalAccessor = new LocalFileTreeAccessor(repo, []);
|
|
}
|
|
else
|
|
{
|
|
// Create new one.
|
|
localRepo = new RepositoryLocalDatabaseModel
|
|
{
|
|
RootModal = repo,
|
|
LocalAccessor = new LocalFileTreeAccessor(repo, [])
|
|
};
|
|
}
|
|
|
|
_localRepoDbModel.Add(repo, localRepo);
|
|
}
|
|
|
|
return localRepo;
|
|
}
|
|
|
|
public async ValueTask<RepositoryModel?> CreateRepositoryOnServerAsync(string repositoryName, string description)
|
|
{
|
|
RepositoryModel repo;
|
|
var api = Api.C;
|
|
try
|
|
{
|
|
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
|
|
{
|
|
api.ClearGateway();
|
|
return null;
|
|
}
|
|
|
|
var r = await api.Gateway.RepoCreate(repositoryName, description);
|
|
|
|
repo = new RepositoryModel
|
|
{
|
|
Name = r.RepositoryName,
|
|
OwnerName = r.OwnerUsername,
|
|
StandaloneName = RepositoryModel.GetStandaloneName(r.RepositoryName, r.OwnerUsername),
|
|
Description = r.Description,
|
|
Archived = r.IsArchived,
|
|
OwnByCurrentUser = (int) r.Role == (int) RepositoryModel.RepositoryRole.Owner
|
|
};
|
|
|
|
Repositories.Insert(0, repo);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
UIHelper.NotifyError(e);
|
|
Console.WriteLine(e);
|
|
return null;
|
|
}
|
|
|
|
return repo;
|
|
}
|
|
|
|
public async ValueTask<bool> UpdateRepositoriesFromServerAsync()
|
|
{
|
|
var api = Api.C;
|
|
try
|
|
{
|
|
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
|
|
{
|
|
api.ClearGateway();
|
|
return false;
|
|
}
|
|
|
|
var result = (await api.Gateway.RepoList()).Result;
|
|
var dict = result.ToDictionary(rp => RepositoryModel.GetStandaloneName(rp.RepositoryName, rp.OwnerUsername));
|
|
for (var i = 0; i < Repositories.Count; i++)
|
|
{
|
|
var ele = Repositories[i];
|
|
if (!dict.Remove(ele.StandaloneName, out var role))
|
|
{
|
|
Repositories.RemoveAt(i);
|
|
i -= 1;
|
|
continue;
|
|
}
|
|
|
|
ele.Archived = ele.Archived;
|
|
ele.Description = ele.Description;
|
|
}
|
|
|
|
foreach (var (repoStandaloneName, rsp) in dict)
|
|
{
|
|
var repo = new RepositoryModel();
|
|
repo.Name = rsp.RepositoryName;
|
|
repo.OwnerName = rsp.OwnerUsername;
|
|
repo.StandaloneName = repoStandaloneName;
|
|
repo.Description = rsp.Description;
|
|
repo.Archived = rsp.IsArchived;
|
|
repo.OwnByCurrentUser = (int) rsp.Role == (int) RepositoryModel.RepositoryRole.Owner;
|
|
|
|
Repositories.Add(repo);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
UIHelper.NotifyError(e);
|
|
Console.WriteLine(e);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public async ValueTask<bool> UpdateRepositoriesDownloadedStatusFromDiskAsync()
|
|
{
|
|
foreach (var repo in _repositories)
|
|
{
|
|
var isFolderExists = Directory.Exists(PathUtility.GetWorkspacePath(repo.OwnerName, repo.Name));
|
|
var isDbFileExists = File.Exists(PathUtility.GetWorkspaceDbPath(repo.OwnerName, repo.Name));
|
|
repo.IsDownloaded = isFolderExists && isDbFileExists;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public async ValueTask<bool> UpdateMembersFromServerAsync(RepositoryModel repo)
|
|
{
|
|
var api = Api.C;
|
|
try
|
|
{
|
|
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
|
|
{
|
|
api.ClearGateway();
|
|
return false;
|
|
}
|
|
|
|
var members = await api.Gateway.GetUsers(repo.Name, repo.OwnerName);
|
|
|
|
// Update existed
|
|
var dict = members.Result.ToDictionary(m => m.Username);
|
|
for (var i = 0; i < repo.Members.Count; i++)
|
|
{
|
|
var ele = repo.Members[i];
|
|
if (!dict.Remove(ele.Username, out var role))
|
|
{
|
|
repo.Members.RemoveAt(i);
|
|
i -= 1;
|
|
continue;
|
|
}
|
|
|
|
ele.Username = role.Username;
|
|
ele.Role = (RepositoryModel.RepositoryRole) role.Role;
|
|
}
|
|
|
|
// Add missing
|
|
foreach (var role in dict.Values)
|
|
{
|
|
var r = new RepositoryModel.Member
|
|
{
|
|
Username = role.Username,
|
|
Role = (RepositoryModel.RepositoryRole) role.Role
|
|
};
|
|
|
|
repo.Members.Add(r);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
UIHelper.NotifyError(e);
|
|
Console.WriteLine(e);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool IsRepositoryOpened(RepositoryModel repo)
|
|
{
|
|
return _openedRepos.Any(r => r == repo);
|
|
}
|
|
|
|
public async ValueTask<bool> CloseRepositoryAsync(RepositoryModel repo)
|
|
{
|
|
if (_openedRepos.Contains(repo))
|
|
{
|
|
var ls = GetRepositoryLocalDatabase(repo);
|
|
if (ls.RepoAccessor != null) await ls.RepoAccessor!.DisposeAsync();
|
|
ls.RepoAccessor = null;
|
|
|
|
if (!SaveRepositoryLocalDatabaseChanges(repo)) return false;
|
|
|
|
_openedRepos.Remove(repo);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public async ValueTask<bool> OpenRepositoryOnStorageAsync(RepositoryModel repo)
|
|
{
|
|
if (!await UpdateRepositoriesDownloadedStatusFromDiskAsync() || repo.IsDownloaded == false) return false;
|
|
if (!await UpdateCommitsFromServerAsync(repo)) return false;
|
|
var ls = GetRepositoryLocalDatabase(repo);
|
|
|
|
if (ls.CurrentCommit != null)
|
|
{
|
|
var accessor = await DownloadDepotsAndUseLocalCachesToGenerateRepositoryFileTreeAccessorFromServerAsync(repo, ls.CurrentCommit.Value);
|
|
if (accessor == null) return false;
|
|
ls.RepoAccessor = accessor;
|
|
|
|
// Remember to cache accessor everytime it will being used.
|
|
await accessor.CreateCacheAsync();
|
|
ls.LocalAccessor.SetBaseline(accessor);
|
|
}
|
|
|
|
SaveRepositoryLocalDatabaseChanges(repo);
|
|
_openedRepos.Add(repo);
|
|
return true;
|
|
}
|
|
|
|
public async ValueTask<bool> CreateNewRepositoryOnStorageAsync(RepositoryModel repo)
|
|
{
|
|
// Create basic structures.
|
|
if (!TryCreateRepositoryBaseStorageStructure(repo)) return false;
|
|
|
|
SaveRepositoryLocalDatabaseChanges(repo);
|
|
repo.IsDownloaded = true;
|
|
_openedRepos.Add(repo);
|
|
return true;
|
|
}
|
|
|
|
public async ValueTask<bool> CloneRepositoryFromRemoteAsync(RepositoryModel repo)
|
|
{
|
|
// Create basic structures.
|
|
if (!TryCreateRepositoryBaseStorageStructure(repo)) return false;
|
|
|
|
if (!await UpdateCommitsFromServerAsync(repo)) return false;
|
|
var peekCommit = repo.Commits.MaxBy(sl => sl.CommittedOn);
|
|
if (peekCommit == null) return false; // Should not use this function!
|
|
|
|
// Download base repo info
|
|
var accessor = await DownloadDepotsAndUseLocalCachesToGenerateRepositoryFileTreeAccessorFromServerAsync(repo, peekCommit.CommitId);
|
|
if (accessor == null)
|
|
{
|
|
await DeleteFromDiskAsync(repo);
|
|
return false;
|
|
};
|
|
|
|
// Remember to cache accessor everytime it will being used.
|
|
await accessor.CreateCacheAsync();
|
|
|
|
var ls = GetRepositoryLocalDatabase(repo);
|
|
ls.CurrentCommit = peekCommit.CommitId;
|
|
ls.RepoAccessor = accessor;
|
|
ls.LocalAccessor.SetBaseline(accessor);
|
|
|
|
try
|
|
{
|
|
foreach (var f in accessor.Manifest.FilePaths)
|
|
{
|
|
var pfs = WorkPath.ToPlatformPath(f.WorkPath, ls.LocalAccessor.WorkingDirectory);
|
|
var directory = Path.GetDirectoryName(pfs);
|
|
|
|
// Write into fs
|
|
if (directory != null) Directory.CreateDirectory(directory);
|
|
if (!accessor.TryWriteDataIntoStream(f.WorkPath, pfs))
|
|
throw new InvalidDataException($"Can not write {f.WorkPath} into repository.");
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
UIHelper.NotifyError(e);
|
|
Console.WriteLine(e);
|
|
await DeleteFromDiskAsync(repo);
|
|
return false;
|
|
}
|
|
|
|
SaveRepositoryLocalDatabaseChanges(repo);
|
|
repo.IsDownloaded = true;
|
|
_openedRepos.Add(repo);
|
|
return true;
|
|
}
|
|
|
|
public async ValueTask<bool> ShouldUpdateLocalCommitsCacheFromServerAsync(RepositoryModel repo)
|
|
{
|
|
var api = Api.C;
|
|
try
|
|
{
|
|
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
|
|
{
|
|
api.ClearGateway();
|
|
return false;
|
|
}
|
|
|
|
var rsp = await api.Gateway.PeekCommit(repo.OwnerName, repo.Name);
|
|
var emptyRepo = repo.Commits.Count == 0;
|
|
|
|
// If they both empty
|
|
if ((rsp.Result == Guid.Empty) == emptyRepo) return false;
|
|
|
|
if (emptyRepo) return true;
|
|
return rsp.Result == repo.Commits.MaxBy(cm => cm.CommittedOn)!.CommitId;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
UIHelper.NotifyError(e);
|
|
Console.WriteLine(e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async ValueTask<bool> UpdateCommitsFromServerAsync(RepositoryModel repo)
|
|
{
|
|
var api = Api.C;
|
|
try
|
|
{
|
|
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
|
|
{
|
|
api.ClearGateway();
|
|
return false;
|
|
}
|
|
|
|
var rsp = await api.Gateway.ListCommit(repo.OwnerName, repo.Name);
|
|
|
|
// Update existed
|
|
var dict = rsp.Result.ToDictionary(m => m.Id);
|
|
for (var i = 0; i < repo.Commits.Count; i++)
|
|
{
|
|
var ele = repo.Commits[i];
|
|
if (!dict.Remove(ele.CommitId, out var cm))
|
|
{
|
|
repo.Members.RemoveAt(i);
|
|
i -= 1;
|
|
continue;
|
|
}
|
|
|
|
ele.Message = cm.Message;
|
|
ele.DepotId = cm.MainDepotId;
|
|
ele.CommittedOn = cm.CommitedOn.UtcDateTime;
|
|
ele.Author = cm.Author;
|
|
}
|
|
|
|
// Add missing
|
|
foreach (var cm in dict.Values)
|
|
{
|
|
var r = new RepositoryModel.Commit
|
|
{
|
|
CommitId = cm.Id,
|
|
Message = cm.Message,
|
|
DepotId = cm.MainDepotId,
|
|
CommittedOn = cm.CommitedOn.UtcDateTime,
|
|
Author = cm.Author,
|
|
};
|
|
|
|
repo.Commits.Add(r);
|
|
}
|
|
|
|
// Resort them again
|
|
repo.Commits.Sort((l, r) => r.CommittedOn.CompareTo(l.CommittedOn));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
UIHelper.NotifyError(e);
|
|
Console.WriteLine(e);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public async ValueTask<RepositoryFileTreeAccessor?>
|
|
DownloadDepotsAndUseLocalCachesToGenerateRepositoryFileTreeAccessorFromServerAsync
|
|
(RepositoryModel repo, Guid commit, bool storeDownloadedDepots = true)
|
|
{
|
|
if (commit == Guid.Empty) return null;
|
|
|
|
// Get depots list and manifest
|
|
var manifest = await DownloadManifestFromServerAsync(repo, commit);
|
|
if (manifest == null) return null;
|
|
|
|
// Prepare folders
|
|
var depotsRoot = PathUtility.GetWorkspaceDepotCachePath(repo.OwnerName, repo.Name);
|
|
Directory.CreateDirectory(depotsRoot);
|
|
|
|
// Generate download depots list
|
|
var mainDepotLabel = manifest.Value.Depot;
|
|
var willDownload = mainDepotLabel.Where(label =>
|
|
{
|
|
var dpPath = Path.Combine(depotsRoot, label.Id.ToString());
|
|
if (!File.Exists(dpPath)) return true;
|
|
// todo Needs a way to check if that valid.
|
|
return false;
|
|
});
|
|
|
|
// Download them
|
|
var downloadedDepots = await DownloadDepotsAndCopyNetworkStreamIntoNewMemoryStreamFromServerAsync(repo, willDownload);
|
|
if (downloadedDepots == null) return null;
|
|
|
|
try
|
|
{
|
|
|
|
if (storeDownloadedDepots)
|
|
{
|
|
// Write new downloaded depots into disk
|
|
var transform = downloadedDepots.Select(dl => (dl.Item1, dl.Item2!));
|
|
await WriteDownloadedDepotsFromServerToStorageAsync(repo, transform);
|
|
}
|
|
|
|
// Create mapping dictionary
|
|
var streamMap = downloadedDepots.ToDictionary(i => i.Item1, i => i.Item2!);
|
|
foreach (var dl in mainDepotLabel)
|
|
{
|
|
// If this file is not being opened, open it from file system
|
|
if (!streamMap.ContainsKey(dl.Id))
|
|
{
|
|
var dst = Path.Combine(depotsRoot, dl.Id.ToString());
|
|
streamMap.Add(dl.Id, new FileStream(dst, FileMode.Open, FileAccess.Read, FileShare.Read));
|
|
}
|
|
}
|
|
|
|
return new RepositoryFileTreeAccessor(streamMap, manifest.Value);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
UIHelper.NotifyError(e);
|
|
if (downloadedDepots != null)
|
|
foreach (var t in downloadedDepots)
|
|
{
|
|
if (t.Item2 == null) continue;
|
|
try { await t.Item2.DisposeAsync(); }
|
|
catch (Exception ex) { Console.WriteLine(ex); }
|
|
}
|
|
|
|
Console.WriteLine(e);
|
|
return null;
|
|
}
|
|
|
|
}
|
|
|
|
public async ValueTask WriteDownloadedDepotsFromServerToStorageAsync
|
|
(RepositoryModel repo, IEnumerable<(Guid id, Stream stream)> depots)
|
|
{
|
|
var depotsRoot = PathUtility.GetWorkspaceDepotCachePath(repo.OwnerName, repo.Name);
|
|
foreach (var d in depots)
|
|
{
|
|
var dst = Path.Combine(depotsRoot, d.id.ToString());
|
|
await using var ws = new FileStream(dst, FileMode.Create);
|
|
|
|
// Make sure always to be at begin.
|
|
d.stream.Seek(0, SeekOrigin.Begin);
|
|
await d.stream.CopyToAsync(ws);
|
|
d.stream.Seek(0, SeekOrigin.Begin);
|
|
}
|
|
}
|
|
|
|
public async ValueTask<(Guid, Stream)[]?> DownloadDepotsAndCopyNetworkStreamIntoNewMemoryStreamFromServerAsync
|
|
(RepositoryModel repo, IEnumerable<DepotLabel> depotsId)
|
|
{
|
|
try
|
|
{
|
|
var api = Api.C;
|
|
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
|
|
{
|
|
api.ClearGateway();
|
|
return null;
|
|
}
|
|
|
|
var result = new List<(Guid, Stream)>();
|
|
foreach (var dl in depotsId)
|
|
{
|
|
using var rsp = await Api.C.Gateway.FetchDepot(repo.OwnerName, repo.Name, dl.Id.ToString());
|
|
if (rsp.StatusCode != HttpStatusCode.OK)
|
|
{
|
|
Console.WriteLine($"Failed to fetch depot {dl.Id}");
|
|
return null;
|
|
}
|
|
|
|
var memoryStream = new MemoryStream(new byte[dl.Length]);
|
|
await rsp.Content!.CopyToAsync(memoryStream);
|
|
result.Add((dl.Id, memoryStream));
|
|
}
|
|
|
|
return result.ToArray();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
UIHelper.NotifyError(e);
|
|
Console.WriteLine(e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async ValueTask<CommitManifest?> DownloadManifestFromServerAsync(RepositoryModel repo, Guid manifestId)
|
|
{
|
|
try
|
|
{
|
|
var api = Api.C;
|
|
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
|
|
{
|
|
api.ClearGateway();
|
|
return null;
|
|
}
|
|
|
|
var rsp = await api.Gateway.FetchManifest(repo.OwnerName, repo.Name, manifestId.ToString());
|
|
return new(
|
|
rsp.ManifestId,
|
|
rsp.Depot.Select(x => new DepotLabel(x.Id, x.Length)).ToArray(),
|
|
rsp.FilePaths.Select(x => new WorkspaceFile(x.ModifyTime.UtcDateTime, x.WorkPath)).ToArray());
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
UIHelper.NotifyError(e);
|
|
Console.WriteLine(e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async ValueTask<bool> DeleteFromDiskAsync(RepositoryModel repo)
|
|
{
|
|
try
|
|
{
|
|
var path = PathUtility.GetWorkspacePath(repo.OwnerName, repo.Name);
|
|
if (Directory.Exists(path)) Directory.Delete(path, true);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
UIHelper.NotifyError(e);
|
|
Console.WriteLine(e);
|
|
return false;
|
|
}
|
|
|
|
repo.IsDownloaded = false;
|
|
return true;
|
|
}
|
|
|
|
public async ValueTask<CommitManifest?> CommitWorkspaceAsBaselineAsync
|
|
(RepositoryModel repo, IEnumerable<LocalFileTreeAccessor.ChangeRecord> changes, string message)
|
|
{
|
|
var localDb = GetRepositoryLocalDatabase(repo);
|
|
var manifestList = CreateCommitManifestByCurrentBaselineAndChanges(localDb.LocalAccessor, changes);
|
|
var api = Api.C;
|
|
|
|
try
|
|
{
|
|
|
|
// Renew for once.
|
|
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
|
|
{
|
|
api.ClearGateway();
|
|
return null;
|
|
}
|
|
|
|
// Generate depot
|
|
var tempDepotPath = await CreateDepotIntoTempFileAsync(repo, manifestList);
|
|
if (tempDepotPath == null) return null;
|
|
|
|
// Upload and create commit
|
|
await using var str = File.OpenRead(tempDepotPath);
|
|
var snapshot = manifestList.Select(l => $"{l.ModifyTime.ToBinary()}${l.WorkPath}");
|
|
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
|
|
{
|
|
api.ClearGateway();
|
|
return null;
|
|
}
|
|
|
|
var rsp = await api.Gateway.CreateCommit(repo.OwnerName, repo.Name,
|
|
new StreamPart(str, Path.GetFileName(tempDepotPath)), message, snapshot, null!, null!);
|
|
|
|
// Move depot file to destination
|
|
var depotsPath = PathUtility.GetWorkspaceDepotCachePath(repo.OwnerName, repo.Name);
|
|
var finalPath = Path.Combine(depotsPath, rsp.MainDepotId.ToString());
|
|
Directory.CreateDirectory(depotsPath);
|
|
File.Move(tempDepotPath, finalPath, true);
|
|
|
|
// Fetch mapped manifest
|
|
var manifest = await DownloadManifestFromServerAsync(repo, rsp.CommitId);
|
|
if (manifest == null) return null;
|
|
|
|
var accessor = await DownloadDepotsAndUseLocalCachesToGenerateRepositoryFileTreeAccessorFromServerAsync(repo, rsp.CommitId);
|
|
if (accessor == null) return null; //todo this is a really fatal issue...
|
|
if (localDb.RepoAccessor != null)
|
|
{
|
|
try
|
|
{
|
|
await localDb.RepoAccessor.DisposeAsync();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
UIHelper.NotifyError(e);
|
|
Console.WriteLine(e);
|
|
}
|
|
}
|
|
|
|
// Point to newest state.
|
|
localDb.RepoAccessor = accessor;
|
|
localDb.CurrentCommit = rsp.CommitId;
|
|
localDb.LocalAccessor.SetBaseline(accessor);
|
|
SaveRepositoryLocalDatabaseChanges(repo);
|
|
|
|
return manifest;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
UIHelper.NotifyError(e);
|
|
Console.WriteLine(e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public List<WorkspaceFile> CreateCommitManifestByCurrentBaselineAndChanges
|
|
(LocalFileTreeAccessor accessor, IEnumerable<LocalFileTreeAccessor.ChangeRecord> changes, bool hard = false)
|
|
{
|
|
// Create a new depot file manifest.
|
|
var files = accessor.BaselineFiles.Values.ToList();
|
|
foreach (var c in changes)
|
|
{
|
|
switch (c.Type)
|
|
{
|
|
case LocalFileTreeAccessor.ChangeType.Folder:
|
|
{
|
|
if (hard) throw new InvalidProgramException(
|
|
$"Can not commit folder into version control: {c.File.WorkPath}");
|
|
|
|
Console.WriteLine($"Can not commit folder into version control...Ignored: {c.File.WorkPath}");
|
|
continue;
|
|
}
|
|
case LocalFileTreeAccessor.ChangeType.Add:
|
|
{
|
|
if (files.Any(f => f.WorkPath == c.File.WorkPath))
|
|
{
|
|
if (hard) throw new InvalidProgramException(
|
|
$"Can not create an existed record into version control: {c.File.WorkPath}");
|
|
|
|
Console.WriteLine($"Can not create an existed record into version control...Ignored: {c.File.WorkPath}");
|
|
continue;
|
|
}
|
|
|
|
files.Add(c.File);
|
|
break;
|
|
}
|
|
case LocalFileTreeAccessor.ChangeType.Remove:
|
|
{
|
|
var idx = files.FindIndex(f => f.WorkPath == c.File.WorkPath);
|
|
if (idx < 0)
|
|
{
|
|
if (hard) throw new InvalidProgramException(
|
|
$"Can not delete a missed record into version control: {c.File.WorkPath}");
|
|
|
|
Console.WriteLine($"Can not delete a missed record into version control...Ignored: {c.File.WorkPath}");
|
|
continue;
|
|
}
|
|
|
|
files.RemoveAt(idx);
|
|
break;
|
|
}
|
|
case LocalFileTreeAccessor.ChangeType.Modify:
|
|
{
|
|
var idx = files.FindIndex(f => f.WorkPath == c.File.WorkPath);
|
|
if (idx < 0)
|
|
{
|
|
if (hard) throw new InvalidProgramException(
|
|
$"Can not modify a missed record into version control: {c.File.WorkPath}");
|
|
|
|
Console.WriteLine($"Can not modify a missed record into version control...Ignored: {c.File.WorkPath}");
|
|
continue;
|
|
}
|
|
|
|
files[idx] = c.File;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
public async ValueTask<string?> CreateDepotIntoTempFileAsync(RepositoryModel repo, IEnumerable<WorkspaceFile> depotFiles)
|
|
{
|
|
var repoWs = PathUtility.GetWorkspacePath(repo.OwnerName, repo.Name);
|
|
var commitTempFolder = Directory.CreateTempSubdirectory("FlawlessDepot_");
|
|
var depotFile = Path.Combine(commitTempFolder.FullName, "depot.bin");
|
|
|
|
try
|
|
{
|
|
// No UI thread blocked
|
|
await Task.Run(async () =>
|
|
{
|
|
await using var fs = new FileStream(depotFile, FileMode.Create);
|
|
DataTransformer.CreateAndInsertStandardDepotFile(fs, depotFiles,
|
|
wf => File.OpenRead(WorkPath.ToPlatformPath(wf.WorkPath, repoWs)));
|
|
|
|
});
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
UIHelper.NotifyError(e);
|
|
Directory.Delete(repoWs, true);
|
|
Console.WriteLine(e);
|
|
return null;
|
|
}
|
|
|
|
return depotFile;
|
|
}
|
|
} |