1
0

Compare commits

..

No commits in common. "4d9e5a932644f0454462792b8a5656da6394abd6" and "8a2a7d15e270da18eb74f72511a59d70bc724246" have entirely different histories.

21 changed files with 113 additions and 227 deletions

View File

@ -3,7 +3,7 @@
xmlns:semi="https://irihi.tech/semi"
xmlns:ursa="https://irihi.tech/ursa/themes/semi"
x:Class="Flawless.Client.App"
RequestedThemeVariant="Light">
RequestedThemeVariant="Default">
<Application.Styles>
<semi:SemiTheme Locale="zh-cn"/>

View File

@ -21,9 +21,8 @@ public static class PathUtility
=> Path.Combine(SettingService.C.AppSetting.RepositoryPath, login, owner, repo,
AppDefaultValues.RepoLocalStorageManagerFolder, AppDefaultValues.RepoLocalStorageDepotFolder);
public static string ConvertBytesToBestDisplay(long bytes)
public static string ConvertBytesToBestDisplay(ulong bytes)
{
if (bytes < 0) return string.Empty;
string[] units = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
int unitIndex = 0;
double size = bytes;

View File

@ -124,16 +124,14 @@ public class LocalFileTreeAccessor
if (!WorkPath.IsPathValid(workPath) || IgnoredDirectories.Any(d => workPath.StartsWith(d)))
continue;
var inf = new FileInfo(f);
_currentFiles.Add(workPath, new WorkspaceFile(inf.LastWriteTimeUtc, workPath, inf.Length));
var modifyTime = File.GetLastWriteTimeUtc(f);
_currentFiles.Add(workPath, new WorkspaceFile { WorkPath = workPath, ModifyTime = modifyTime });
}
LastScanTimeUtc = DateTime.UtcNow;
// Find those are changed
var changes = _currentFiles.Values.Where(v =>
_baseline.TryGetValue(v.WorkPath, out var fi) &&
(fi.ModifyTime != v.ModifyTime || fi.Size != v.Size));
var changes = _currentFiles.Values.Where(v => _baseline.TryGetValue(v.WorkPath, out var fi) && fi.ModifyTime != v.ModifyTime);
var news = _currentFiles.Values.Where(v => !_baseline.ContainsKey(v.WorkPath));
var removed = _baseline.Values.Where(v => !_currentFiles.ContainsKey(v.WorkPath));

View File

@ -955,9 +955,6 @@ namespace Flawless.Client.Remote
[JsonPropertyName("workPath")]
public string WorkPath { get; set; }
[JsonPropertyName("size")]
public long Size { get; set; }
}

View File

@ -156,7 +156,7 @@ public class RepositoryFileTreeAccessor : IDisposable, IAsyncDisposable, IEnumer
public IEnumerator<WorkspaceFile> GetEnumerator()
{
return _cached.Values
.Select(cv => new WorkspaceFile(cv.FileInfo.ModifyTime, cv.FileInfo.Path, (long)cv.FileInfo.Size)).GetEnumerator();
.Select(cv => new WorkspaceFile(cv.FileInfo.ModifyTime, cv.FileInfo.Path)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()

View File

@ -1177,7 +1177,7 @@ public class RepositoryService : BaseService<RepositoryService>
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, x.Size)).ToArray());
rsp.FilePaths.Select(x => new WorkspaceFile(x.ModifyTime.UtcDateTime, x.WorkPath)).ToArray());
}
catch (Exception e)
{
@ -1205,12 +1205,11 @@ public class RepositoryService : BaseService<RepositoryService>
return true;
}
public async ValueTask<CommitManifest?> CommitWorkspaceAsync
(RepositoryModel repo, IEnumerable<ChangeInfo> changes, string message, bool forceBaseline = false)
public async ValueTask<CommitManifest?> CommitWorkspaceAsBaselineAsync
(RepositoryModel repo, IEnumerable<ChangeInfo> changes, string message)
{
// Check if current version is the latest
var api = Api.C;
if (!await UpdateCommitsHistoryFromServerAsync(repo)) return null;
var requireUpdate = await IsPointedToCommitNotPeekResultFromServerAsync(repo);
if (!requireUpdate.HasValue) return null;
@ -1222,13 +1221,11 @@ public class RepositoryService : BaseService<RepositoryService>
var localDb = GetRepositoryLocalDatabase(repo);
var (manifestList, miniManifestList) = CreateCommitManifestByCurrentBaselineAndChanges(localDb.LocalAccessor, changes);
var snapshot = manifestList.Select(l => $"{l.ModifyTime.ToBinary()}${l.WorkPath}");
Guid commitId;
var manifestList = CreateCommitManifestByCurrentBaselineAndChanges(localDb.LocalAccessor, changes);
try
{
// Renew for once.
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
{
@ -1236,64 +1233,33 @@ public class RepositoryService : BaseService<RepositoryService>
return null;
}
// Decide create new depot, create full depot or create addon depot.
var hasAddOrModify = localDb.LocalAccessor.Changes.Any(x =>
x.Value.Type == ChangeInfoType.Add || x.Value.Type == ChangeInfoType.Modify);
var newDepot = forceBaseline || hasAddOrModify || localDb.RepoAccessor == null;
// Generate depot
var tempDepotPath = await CreateDepotIntoTempFileAsync(repo, manifestList);
if (tempDepotPath == null) return null;
if (newDepot)
// 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()))
{
var changesSize = localDb.LocalAccessor.Changes.Values.Sum(x => (long) x.File.Size);
var baselineSize = localDb.RepoAccessor?.Sum(x => (long) x.Size) ?? 0;
var changesRatio = changesSize / (double) baselineSize;
var isBaseline = forceBaseline || localDb.CurrentCommit == null || changesRatio > 0.8f;
var depotFiles = isBaseline ? manifestList : miniManifestList;
// Generate depot
var tempDepotPath = await CreateDepotIntoTempFileAsync(repo, depotFiles);
if (tempDepotPath == null) return null;
Console.WriteLine($"Create new {(isBaseline ? "baseline" : "overlay")} depot at \"{tempDepotPath}\"");
// Upload and create commit
await using var str = File.OpenRead(tempDepotPath);
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
{
api.ClearGateway();
return null;
}
var requireDepot = isBaseline ? [] : new[] {repo.Commits.First().DepotId.ToString()};
var rsp = await api.Gateway.CreateCommit(repo.OwnerName, repo.Name,
new StreamPart(str, Path.GetFileName(tempDepotPath)), message, snapshot, requireDepot, "");
commitId = rsp.CommitId;
// Move depot file to destination
var depotsPath = PathUtility.GetWorkspaceDepotCachePath(Api.Current.Username.Value!, repo.OwnerName, repo.Name);
var finalPath = Path.Combine(depotsPath, rsp.MainDepotId.ToString());
Directory.CreateDirectory(depotsPath);
File.Move(tempDepotPath, finalPath, true);
api.ClearGateway();
return null;
}
else
{
// Upload and create commit
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
{
api.ClearGateway();
return null;
}
var rsp = await api.Gateway.CreateCommit(repo.OwnerName, repo.Name, null!,
message, snapshot, [], repo.Commits.First().DepotId.ToString());
commitId = rsp.CommitId;
}
var rsp = await api.Gateway.CreateCommit(repo.OwnerName, repo.Name,
new StreamPart(str, Path.GetFileName(tempDepotPath)), message, snapshot, [], string.Empty);
// Move depot file to destination
var depotsPath = PathUtility.GetWorkspaceDepotCachePath(Api.Current.Username.Value!, 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, commitId);
var manifest = await DownloadManifestFromServerAsync(repo, rsp.CommitId);
if (manifest == null) return null;
var accessor = await DownloadDepotsAndUseLocalCachesToGenerateRepositoryFileTreeAccessorFromServerAsync(repo, commitId);
var accessor = await DownloadDepotsAndUseLocalCachesToGenerateRepositoryFileTreeAccessorFromServerAsync(repo, rsp.CommitId);
if (accessor == null) return null; //todo this is a really fatal issue...
if (localDb.RepoAccessor != null)
{
@ -1310,11 +1276,10 @@ public class RepositoryService : BaseService<RepositoryService>
// Point to latest state.
localDb.RepoAccessor = accessor;
localDb.CurrentCommit = commitId;
localDb.CurrentCommit = rsp.CommitId;
localDb.LocalAccessor.SetBaseline(accessor);
SaveRepositoryLocalDatabaseChanges(repo);
Console.WriteLine($"Successful create commit as {commitId}");
return manifest;
}
catch (Exception e)
@ -1325,12 +1290,11 @@ public class RepositoryService : BaseService<RepositoryService>
}
}
private (List<WorkspaceFile>, List<WorkspaceFile>) CreateCommitManifestByCurrentBaselineAndChanges
public List<WorkspaceFile> CreateCommitManifestByCurrentBaselineAndChanges
(LocalFileTreeAccessor accessor, IEnumerable<ChangeInfo> changes, bool hard = false)
{
// Create a new depot file manifest.
var files = accessor.BaselineFiles.Values.ToList();
var overlayFsStatus = new List<WorkspaceFile>();
foreach (var c in changes)
{
switch (c.Type)
@ -1355,7 +1319,6 @@ public class RepositoryService : BaseService<RepositoryService>
}
files.Add(c.File);
overlayFsStatus.Add(c.File);
break;
}
case ChangeInfoType.Remove:
@ -1386,13 +1349,12 @@ public class RepositoryService : BaseService<RepositoryService>
}
files[idx] = c.File;
overlayFsStatus.Add(c.File);
break;
}
}
}
return (files, overlayFsStatus);
return files;
}
public async ValueTask<string?> CreateDepotIntoTempFileAsync(RepositoryModel repo, IEnumerable<WorkspaceFile> depotFiles)

View File

@ -32,8 +32,6 @@ public partial class LocalChangesNode : ReactiveModel
[Reactive] private DateTime? _modifiedTime;
[Reactive] private LocalChangesNode? _parent;
[Reactive] private long _size;
public bool Included
{
@ -61,8 +59,7 @@ public partial class LocalChangesNode : ReactiveModel
Type = "Folder",
FullPath = folderPath,
Contents = new(),
Parent = parent,
Size = -1
Parent = parent
};
}
@ -73,8 +70,7 @@ public partial class LocalChangesNode : ReactiveModel
Type = file.Type.ToString(),
FullPath = file.File.WorkPath,
ModifiedTime = file.File.ModifyTime,
Parent = parent,
Size = (long) file.File.Size
Parent = parent
};
}
}
@ -183,7 +179,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase
new TextColumn<LocalChangesNode, string>(
"Size",
n => PathUtility.ConvertBytesToBestDisplay(n.Size)),
n => PathUtility.ConvertBytesToBestDisplay(GetFileSize(n))),
new TextColumn<LocalChangesNode, DateTime?>(
"ModifiedTime", n => n.ModifiedTime.HasValue ? n.ModifiedTime.Value.ToLocalTime() : null),
@ -225,9 +221,9 @@ public partial class RepositoryViewModel : RoutableViewModelBase
"File Type",
n => n.Contents != null ? "Folder" : Path.GetExtension(n.FullPath)),
new TextColumn<LocalChangesNode, string>(
new TextColumn<LocalChangesNode, ulong>(
"Size",
n => PathUtility.ConvertBytesToBestDisplay(n.Size)),
n => 0),
new TextColumn<LocalChangesNode, DateTime?>(
"ModifiedTime", n => n.ModifiedTime.HasValue ? n.ModifiedTime.Value.ToLocalTime() : null),
@ -236,6 +232,13 @@ public partial class RepositoryViewModel : RoutableViewModelBase
_ = StartupTasksAsync();
}
private ulong GetFileSize(LocalChangesNode node)
{
if (!File.Exists(node.FullPath)) return 0;
var path = Path.Combine(_wsRoot, node.FullPath);
return (ulong)new FileInfo(path).Length;
}
private async Task StartupTasksAsync()
@ -549,7 +552,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase
}
using var l = UIHelper.MakeLoading("Committing changes...");
var manifest = await RepositoryService.C.CommitWorkspaceAsync(Repository, changes, LocalDatabase.CommitMessage!);
var manifest = await RepositoryService.C.CommitWorkspaceAsBaselineAsync(Repository, changes, LocalDatabase.CommitMessage!);
if (manifest == null) return;
LocalDatabase.LocalAccessor.SetBaseline(manifest.Value.FilePaths);

View File

@ -76,14 +76,6 @@ public partial class SettingViewModel : RoutableViewModelBase
{
UIHelper.NotifyError(ex);
}
try
{
}
catch (Exception ex)
{
UIHelper.NotifyError(ex);
}
}
private async Task LoadClientSettings()

View File

@ -9,7 +9,7 @@
MinWidth="400"
x:Class="Flawless.Client.Views.ModalBox.IssueDetailEditView">
<u:Form HorizontalAlignment="Stretch">
<u:Form>
<u:FormItem Label="Title" IsRequired="True">
<TextBox Text="{Binding Title}"/>
</u:FormItem>

View File

@ -3,18 +3,11 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:Flawless.Client.ViewModels"
xmlns:u="https://irihi.tech/ursa"
x:DataType="vm:RepositoryViewModel"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Flawless.Client.Views.RepositoryPage.RepoFileTreePageView">
<Grid RowDefinitions="Auto, *">
<StackPanel Orientation="Horizontal" Spacing="4" Margin="0, 6"
HorizontalAlignment="Stretch" IsVisible="{Binding !IsDeveloperRole}">
<u:IconButton
Icon="{StaticResource SemiIconDownload}" Content="Pull" HorizontalAlignment="Stretch"
Command="{Binding PullLatestRepositoryCommand}"/>
</StackPanel>
<TreeDataGrid Grid.Row="1" Source="{Binding FileTree}">
<Grid ColumnDefinitions="2*, *">
<TreeDataGrid Grid.Column="0" Grid.ColumnSpan="2" Source="{Binding FileTree}">
</TreeDataGrid>
</Grid>
</UserControl>

View File

@ -9,11 +9,15 @@
x:Class="Flawless.Client.Views.RepositoryPage.RepoIssuePageView">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8" Margin="6">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8" Margin="0 0 0 12">
<u:IconButton Icon="{StaticResource SemiIconRefresh}" Content="Refresh"
Command="{Binding RefreshRepositoryIssuesAsyncCommand}"/>
<u:IconButton Icon="{StaticResource SemiIconPlus}" Content="Create"
Command="{Binding CreateIssueCommand}"/>
<!-- <ComboBox ItemsSource="{Binding IssueFilters}" SelectedIndex="0" -->
<!-- Width="120" PlaceholderText="筛选状态"/> -->
<!-- <ComboBox ItemsSource="{Binding IssueTags}" SelectedIndex="0" -->
<!-- Width="160" PlaceholderText="筛选标签"/> -->
</StackPanel>
<ListBox ItemsSource="{Binding Repository.Issues}" SelectionMode="Single">

View File

@ -21,15 +21,15 @@
<TreeDataGrid Grid.Row="1" Grid.Column="0" Source="{Binding LocalChange}" CanUserSortColumns="True"/>
<Border Grid.Row="1" Grid.Column="2" Classes="Shadow" Theme="{StaticResource CardBorder}">
<StackPanel Spacing="8">
<StackPanel Orientation="Horizontal" Spacing="4" HorizontalAlignment="Stretch">
<u:ElasticWrapPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<u:IconButton
Icon="{StaticResource SemiIconDownload}" Content="Pull" HorizontalAlignment="Stretch"
Command="{Binding PullLatestRepositoryCommand}"/>
<u:IconButton
Icon="{StaticResource SemiIconBackward}" Content="Revert" HorizontalAlignment="Stretch"
Icon="{StaticResource SemiIconBackward}" Content="Revert Select" HorizontalAlignment="Stretch"
Command="{Binding RevertSelectedChangesCommand}"/>
</StackPanel>
<u:Divider/>
</u:ElasticWrapPanel>
<u:Divider></u:Divider>
<TextBox Text="{Binding LocalDatabase.CommitMessage}"
Classes="TextArea" MaxHeight="300" Watermark="Description of this commit"/>
<u:IconButton Content="Commit" Icon="{StaticResource SemiIconUpload}" HorizontalAlignment="Stretch"

View File

@ -17,15 +17,15 @@
<Label FontWeight="400" FontSize="28" Content="{Binding Repository.StandaloneName}"/>
</StackPanel>
<TabControl TabStripPlacement="Top" Margin="0 20 0 0">
<!-- <TabItem> -->
<!-- <TabItem.Header> -->
<!-- <StackPanel Orientation="Horizontal" Spacing="6" VerticalAlignment="Center"> -->
<!-- <PathIcon MaxHeight="14" MaxWidth="14" Data="{StaticResource SemiIconBox}"/> -->
<!-- <Label Content="Dashboard"/> -->
<!-- </StackPanel> -->
<!-- </TabItem.Header> -->
<!-- <page:RepoDashboardPageView/> -->
<!-- </TabItem> -->
<TabItem>
<TabItem.Header>
<StackPanel Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
<PathIcon MaxHeight="14" MaxWidth="14" Data="{StaticResource SemiIconBox}"/>
<Label Content="Dashboard"/>
</StackPanel>
</TabItem.Header>
<page:RepoDashboardPageView/>
</TabItem>
<TabItem IsVisible="{Binding IsDeveloperRole}">
<TabItem.Header>
<StackPanel Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">

View File

@ -42,16 +42,16 @@
<u:FormItem Label="Default Storage Location">
<u:PathPicker/>
</u:FormItem>
<!-- <u:FormGroup Header="Workspace Refresh"> -->
<!-- <StackPanel Spacing="4"> -->
<!-- <CheckBox Theme="{StaticResource CardCheckBox}" Content="Open Workspace" -->
<!-- IsChecked="{Binding SettingModel.RefreshWorkspaceOnOpen}"/> -->
<!-- <CheckBox Theme="{StaticResource CardCheckBox}" Content="Filesystem Changed" -->
<!-- IsChecked="{Binding SettingModel.RefreshWorkspaceOnFilesystemChanges}"/> -->
<!-- </StackPanel> -->
<!-- </u:FormGroup> -->
<u:FormGroup Header="Workspace Refresh">
<StackPanel Spacing="4">
<CheckBox Theme="{StaticResource CardCheckBox}" Content="Open Workspace"
IsChecked="{Binding SettingModel.RefreshWorkspaceOnOpen}"/>
<CheckBox Theme="{StaticResource CardCheckBox}" Content="Filesystem Changed"
IsChecked="{Binding SettingModel.RefreshWorkspaceOnFilesystemChanges}"/>
</StackPanel>
</u:FormGroup>
<u:FormGroup Header="Extern Tools">
<!-- <u:PathPicker u:FormItem.Label="Open Folder"/> -->
<u:PathPicker u:FormItem.Label="Open Folder"/>
<u:PathPicker u:FormItem.Label="Diff Tool"/>
</u:FormGroup>
<StackPanel Orientation="Horizontal" Spacing="4">
@ -84,11 +84,11 @@
<Grid RowDefinitions="Auto, *" Margin="10">
<StackPanel Orientation="Horizontal">
<Button Content="Refresh"
<Button Content="Refresh Users"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Command="{Binding CreateUserCommand}"/>
<Button Content="Add"
<Button Content="Add Users"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Command="{Binding CreateUserCommand}"/>

View File

@ -6,14 +6,11 @@ public struct WorkspaceFile : IEquatable<WorkspaceFile>
public DateTime ModifyTime { get; set; }
public string WorkPath { get; set; }
public ulong Size { get; set; }
public WorkspaceFile(DateTime modifyTime, string workPath, long size)
public WorkspaceFile(DateTime modifyTime, string workPath)
{
ModifyTime = modifyTime;
WorkPath = workPath;
Size = (ulong) size;
}
public bool Equals(WorkspaceFile other)

View File

@ -1,4 +1,5 @@
using Flawless.Communication.Request;
using System.Net;
using Flawless.Communication.Request;
using Flawless.Communication.Response;
using Flawless.Server.Models;
using Flawless.Server.Services;
@ -9,26 +10,15 @@ using Microsoft.EntityFrameworkCore;
namespace Flawless.Server.Controllers;
[ApiController, Authorize, Route("api/admin")]
[ApiController, Authorize(Roles = "admin"), Route("api/admin")]
public class AdminController(
UserManager<AppUser> userManager,
AccessControlService accessControlService,
AppDbContext dbContext) : ControllerBase
{
private async ValueTask<ActionResult?> TestIfValid()
{
var user = (await userManager.GetUserAsync(HttpContext.User))!;
if (user.Admin == false) return BadRequest(new FailedResponse("Only admin can do this!"));
return null;
}
[HttpPost("superuser/{username}")]
public async Task<IActionResult> SetSuperuserAsync(string username, bool toSuper)
{
var t = await TestIfValid();
if (t != null) return t;
var user = await userManager.FindByNameAsync(username);
var optUser = (await userManager.GetUserAsync(HttpContext.User))!;
@ -44,31 +34,15 @@ public class AdminController(
[HttpGet("superuser/{username}")]
public async Task<ActionResult<bool>> GetSuperuserAsync(string username)
{
var t = await TestIfValid();
if (t != null) return t;
var user = await userManager.FindByNameAsync(username);
if (user == null) return BadRequest(new FailedResponse("User does not exist!"));
return user.Admin;
}
[HttpGet("user/list")]
public async Task<ActionResult<List<AppUser>>> GetUsersAsync()
{
var t = await TestIfValid();
if (t != null) return t;
var users = await userManager.Users.ToListAsync();
return users;
}
[HttpPost("user/delete/{username}")]
public async Task<IActionResult> DeleteUserAsync(string username)
{
var t = await TestIfValid();
if (t != null) return t;
var user = await userManager.FindByNameAsync(username);
if (user == null) return BadRequest(new FailedResponse("User does not exist!"));
@ -93,9 +67,6 @@ public class AdminController(
[HttpPost("user/disable/{username}")]
public async Task<IActionResult> DisableUserAsync(string username)
{
var t = await TestIfValid();
if (t != null) return t;
var user = await userManager.FindByNameAsync(username);
if (user == null) return BadRequest(new FailedResponse("User does not exist!"));
@ -108,9 +79,6 @@ public class AdminController(
[HttpPost("user/reset_password")]
public async Task<IActionResult> ResetPasswordAsync(ResetPasswordRequest r)
{
var t = await TestIfValid();
if (t != null) return t;
if (r.Identity == null) return BadRequest(new FailedResponse("Identity (User Id) is not set!"));
var user = await userManager.FindByIdAsync(r.Identity);
@ -125,9 +93,6 @@ public class AdminController(
[HttpPost("access_control/ip_whitelist")]
public async Task<IActionResult> SetIpWhitelistAsync([FromBody] string[] ips)
{
var t = await TestIfValid();
if (t != null) return t;
await accessControlService.UpdatePolicyAsync(IpPolicyType.Whitelist, ips);
return Ok();
}
@ -135,18 +100,12 @@ public class AdminController(
[HttpGet("access_control/ip_whitelist")]
public async Task<ActionResult<IEnumerable<string>>> GetIpWhitelistAsync()
{
var t = await TestIfValid();
if (t != null) return t;
return Ok(await accessControlService.GetIpListAsync(IpPolicyType.Whitelist));
}
[HttpPost("access_control/ip_blacklist")]
public async Task<IActionResult> SetIpBlacklistAsync([FromBody] string[] ips)
{
var t = await TestIfValid();
if (t != null) return t;
await accessControlService.UpdatePolicyAsync(IpPolicyType.Blacklist, ips);
return Ok();
}
@ -154,9 +113,6 @@ public class AdminController(
[HttpGet("access_control/ip_blacklist")]
public async Task<ActionResult<IEnumerable<string>>> GetIpBlacklistAsync()
{
var t = await TestIfValid();
if (t != null) return t;
return Ok(await accessControlService.GetIpListAsync(IpPolicyType.Blacklist));
}
@ -168,9 +124,6 @@ public class AdminController(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 50)
{
var t = await TestIfValid();
if (t != null) return t;
var query = dbContext.SystemLogs.AsQueryable();
// 时间过滤

View File

@ -410,11 +410,11 @@ public class RepositoryInnieController(
var dateTimeStr = s.Substring(0, idx);
var pathStr = s.Substring(idx + 1);
return new WorkspaceFile(DateTime.FromBinary(long.Parse(dateTimeStr)), pathStr, 0);
return new WorkspaceFile(DateTime.FromBinary(long.Parse(dateTimeStr)), pathStr);
}).ToArray();
var sizeMap = new Dictionary<WorkspaceFile, long>();
var test = new HashSet<WorkspaceFile>(actualFs);
var createNewDepot = false;
RepositoryDepot mainDepot;
List<DepotLabel> depotLabels = new();
@ -423,7 +423,7 @@ public class RepositoryInnieController(
{
// Use existed depots
var mainDepotId = Guid.Parse(req.MainDepotId!);
var preDepot = await StencilWorkspaceSnapshotViaDatabaseAsync(rp.Id, mainDepotId, test, sizeMap, null);
var preDepot = await StencilWorkspaceSnapshotViaDatabaseAsync(rp.Id, mainDepotId, test);
if (preDepot == null)
return BadRequest(new FailedResponse("Not a valid main depot!", "NoMainDepot"));
@ -441,14 +441,14 @@ public class RepositoryInnieController(
{
// Get a new depot from request - this will flat to direct depot reference to avoid deep search that
// raise memory issue.
var directDependDepots = new HashSet<Guid>();
var actualRequiredDepots = new HashSet<Guid>();
// Read and create a new depot
using var cacheStream = new MemoryStream(new byte[req.Depot.Length], true);
await req.Depot.CopyToAsync(cacheStream, HttpContext.RequestAborted);
// Look if main depot can do this...
await StencilWorkspaceSnapshotAsync(cacheStream, test, sizeMap);
await StencilWorkspaceSnapshotAsync(cacheStream, test);
// Oh no, will we need to load their parents?
var unresolvedCount = test.Count;
@ -457,13 +457,21 @@ public class RepositoryInnieController(
// Yes
foreach (var subDepot in req.RequiredDepots?.Select(Guid.Parse) ?? [])
{
await StencilWorkspaceSnapshotViaDatabaseAsync(rp.Id, subDepot, test, sizeMap, directDependDepots);
await StencilWorkspaceSnapshotViaDatabaseAsync(rp.Id, subDepot, test);
var rest = test.Count;
if (rest == 0)
{
actualRequiredDepots.Add(subDepot);
break;
}
// Quit if nothing to do.
unresolvedCount = rest;
if (rest == 0) break;
// If test is changed?
if (unresolvedCount != rest)
{
actualRequiredDepots.Add(subDepot);
unresolvedCount = rest;
}
}
// If still not able to resolve
@ -481,7 +489,7 @@ public class RepositoryInnieController(
await dbContext.Repositories
.Where(r => r.Id == rp.Id)
.SelectMany(r => r.Depots)
.Where(cm => directDependDepots.Contains(cm.DepotId))
.Where(cm => actualRequiredDepots.Contains(cm.DepotId))
.ToArrayAsync());
// Create commit and let it alloc a main key
@ -502,14 +510,6 @@ public class RepositoryInnieController(
depotLabels.AddRange(mainDepot.Dependencies.Select(d => new DepotLabel(d.DepotId, d.Length)));
}
// Update size info of FilePaths
for (var i = 0; i < actualFs.Length; i++)
{
var ws = actualFs[i];
ws.Size = (ulong)sizeMap.GetValueOrDefault(ws, 0);
actualFs[i] = ws;
}
// Create commit info
var commit = new RepositoryCommit
@ -549,8 +549,7 @@ public class RepositoryInnieController(
return Ok(new CommitSuccessResponse(commit.CommittedOn, commit.Id, mainDepot.DepotId));
}
private static async ValueTask StencilWorkspaceSnapshotAsync(Stream depotStream,
HashSet<WorkspaceFile> unresolved, Dictionary<WorkspaceFile, long> sizeMap)
private static async ValueTask StencilWorkspaceSnapshotAsync(Stream depotStream, HashSet<WorkspaceFile> unresolved)
{
// Get version
depotStream.Seek(0, SeekOrigin.Begin); // Fix: Get an invalid version code due to offset is bad.
@ -569,21 +568,14 @@ public class RepositoryInnieController(
{
ModifyTime = inf.ModifyTime,
WorkPath = inf.Path,
Size = 0
};
if (!sizeMap.TryAdd(test, (long)inf.Size)) sizeMap[test] = (long)inf.Size;
unresolved.Remove(test);
if (unresolved.Count == 0) break; // Early quit to avoid extra performance loss.
}
}
private async Task<RepositoryDepot?> StencilWorkspaceSnapshotViaDatabaseAsync(
Guid rpId, Guid depotId,
HashSet<WorkspaceFile> unresolved,
Dictionary<WorkspaceFile, long> sizeMap,
HashSet<Guid>? directDependDepots)
private async Task<RepositoryDepot?> StencilWorkspaceSnapshotViaDatabaseAsync(Guid rpId, Guid depotId, HashSet<WorkspaceFile> unresolved)
{
var depot = await dbContext.Repositories
.SelectMany(r => r.Depots)
@ -591,28 +583,21 @@ public class RepositoryInnieController(
.ThenInclude(repositoryDepot => repositoryDepot.Dependencies)
.FirstOrDefaultAsync(r => r.DepotId == depotId);
await RecurringAsync(transformer, rpId, depot, unresolved, sizeMap, directDependDepots);
await RecurringAsync(transformer, rpId, depot, unresolved);
return depot;
static async ValueTask RecurringAsync(
PathTransformer transformer, Guid rpId,
RepositoryDepot? depot, HashSet<WorkspaceFile> unresolved,
Dictionary<WorkspaceFile, long> sizeMap,
HashSet<Guid>? directDependDepots)
static async ValueTask RecurringAsync(PathTransformer transformer, Guid rpId, RepositoryDepot? depot, HashSet<WorkspaceFile> unresolved)
{
if (depot == null) return;
var path = transformer.GetDepotPath(rpId, depot.DepotId);
// require do not extend lifetime scope.
await using (var fs = new BufferedStream(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)))
{
var before = unresolved.Count;
await StencilWorkspaceSnapshotAsync(fs, unresolved, sizeMap);
if (before != unresolved.Count && directDependDepots != null) directDependDepots.Add(depot.DepotId);
}
await StencilWorkspaceSnapshotAsync(fs, unresolved);
for (var i = 0; i < depot.Dependencies.Count && unresolved.Count > 0; i++)
await RecurringAsync(transformer, rpId, depot.Dependencies[i], unresolved, sizeMap, directDependDepots);
await RecurringAsync(transformer, rpId, depot.Dependencies[i], unresolved);
}
}
}

View File

@ -39,7 +39,7 @@ public class RepositoryOutieController(AppDbContext dbContext, UserManager<AppUs
}
[HttpPost("repo_create")]
public async Task<ActionResult<RepositoryInfoResponse>> CreateRepositoryAsync([FromQuery] string repositoryName, [FromQuery] string? description)
public async Task<ActionResult<RepositoryInfoResponse>> CreateRepositoryAsync([FromQuery] string repositoryName, [FromQuery] string description)
{
repositoryName = repositoryName.Trim().Replace(' ', '_');
if (repositoryName.Length <= 3)

View File

@ -189,6 +189,7 @@ public static class Program
if (auth?.Any() ?? false)
{
var adminOnly = auth.Any(a => a.Policy?.ToLower() == "admin");
var id = p.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (id == null) throw new SecurityTokenExpiredException("User is not defined in the token!");
@ -200,6 +201,8 @@ public static class Program
var u = await db.FindByIdAsync(id!);
if (u == null) throw new SecurityTokenExpiredException("User is not existed.");
if (adminOnly && u.Admin == false)
throw new SecurityException("This api is Admin called only!");
if (u.SecurityStamp != stamp) throw new SecurityTokenExpiredException("SecurityStamp is mismatched.");
// if (u.LockoutEnabled) throw new SecurityTokenExpiredException("User has been locked."); //todo Fix lockout prob

View File

@ -9,7 +9,7 @@
"ConnectionStrings": {
"CoreDb": "Server=localhost;Port=5432;User Id=postgres;Database=flawless"
},
"LocalStoragePath": "/Users/cardidi/flawless-data",
"LocalStoragePath": "./data/development",
"User": {
"PublicRegister": true
},

View File

@ -9,7 +9,7 @@
"ConnectionStrings": {
"CoreDb": "Server=localhost;Port=5432;User Id=postgres;Database=flawless"
},
"LocalStoragePath": "/Users/cardidi/flawless-data",
"LocalStoragePath": "./data/development",
"User": {
"PublicRegister": true
},