1
0

Compare commits

...

7 Commits

21 changed files with 228 additions and 114 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="Default">
RequestedThemeVariant="Light">
<Application.Styles>
<semi:SemiTheme Locale="zh-cn"/>

View File

@ -21,8 +21,9 @@ public static class PathUtility
=> Path.Combine(SettingService.C.AppSetting.RepositoryPath, login, owner, repo,
AppDefaultValues.RepoLocalStorageManagerFolder, AppDefaultValues.RepoLocalStorageDepotFolder);
public static string ConvertBytesToBestDisplay(ulong bytes)
public static string ConvertBytesToBestDisplay(long 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,14 +124,16 @@ public class LocalFileTreeAccessor
if (!WorkPath.IsPathValid(workPath) || IgnoredDirectories.Any(d => workPath.StartsWith(d)))
continue;
var modifyTime = File.GetLastWriteTimeUtc(f);
_currentFiles.Add(workPath, new WorkspaceFile { WorkPath = workPath, ModifyTime = modifyTime });
var inf = new FileInfo(f);
_currentFiles.Add(workPath, new WorkspaceFile(inf.LastWriteTimeUtc, workPath, inf.Length));
}
LastScanTimeUtc = DateTime.UtcNow;
// Find those are changed
var changes = _currentFiles.Values.Where(v => _baseline.TryGetValue(v.WorkPath, out var fi) && fi.ModifyTime != v.ModifyTime);
var changes = _currentFiles.Values.Where(v =>
_baseline.TryGetValue(v.WorkPath, out var fi) &&
(fi.ModifyTime != v.ModifyTime || fi.Size != v.Size));
var news = _currentFiles.Values.Where(v => !_baseline.ContainsKey(v.WorkPath));
var removed = _baseline.Values.Where(v => !_currentFiles.ContainsKey(v.WorkPath));

View File

@ -955,6 +955,9 @@ 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)).GetEnumerator();
.Select(cv => new WorkspaceFile(cv.FileInfo.ModifyTime, cv.FileInfo.Path, (long)cv.FileInfo.Size)).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)).ToArray());
rsp.FilePaths.Select(x => new WorkspaceFile(x.ModifyTime.UtcDateTime, x.WorkPath, x.Size)).ToArray());
}
catch (Exception e)
{
@ -1205,11 +1205,12 @@ public class RepositoryService : BaseService<RepositoryService>
return true;
}
public async ValueTask<CommitManifest?> CommitWorkspaceAsBaselineAsync
(RepositoryModel repo, IEnumerable<ChangeInfo> changes, string message)
public async ValueTask<CommitManifest?> CommitWorkspaceAsync
(RepositoryModel repo, IEnumerable<ChangeInfo> changes, string message, bool forceBaseline = false)
{
// 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;
@ -1221,11 +1222,13 @@ public class RepositoryService : BaseService<RepositoryService>
var localDb = GetRepositoryLocalDatabase(repo);
var manifestList = CreateCommitManifestByCurrentBaselineAndChanges(localDb.LocalAccessor, changes);
var (manifestList, miniManifestList) = CreateCommitManifestByCurrentBaselineAndChanges(localDb.LocalAccessor, changes);
var snapshot = manifestList.Select(l => $"{l.ModifyTime.ToBinary()}${l.WorkPath}");
Guid commitId;
try
{
// Renew for once.
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
{
@ -1233,33 +1236,64 @@ public class RepositoryService : BaseService<RepositoryService>
return null;
}
// Generate depot
var tempDepotPath = await CreateDepotIntoTempFileAsync(repo, manifestList);
if (tempDepotPath == null) 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;
// 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()))
if (newDepot)
{
api.ClearGateway();
return null;
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);
}
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, rsp.CommitId);
var manifest = await DownloadManifestFromServerAsync(repo, commitId);
if (manifest == null) return null;
var accessor = await DownloadDepotsAndUseLocalCachesToGenerateRepositoryFileTreeAccessorFromServerAsync(repo, rsp.CommitId);
var accessor = await DownloadDepotsAndUseLocalCachesToGenerateRepositoryFileTreeAccessorFromServerAsync(repo, commitId);
if (accessor == null) return null; //todo this is a really fatal issue...
if (localDb.RepoAccessor != null)
{
@ -1276,10 +1310,11 @@ public class RepositoryService : BaseService<RepositoryService>
// Point to latest state.
localDb.RepoAccessor = accessor;
localDb.CurrentCommit = rsp.CommitId;
localDb.CurrentCommit = commitId;
localDb.LocalAccessor.SetBaseline(accessor);
SaveRepositoryLocalDatabaseChanges(repo);
Console.WriteLine($"Successful create commit as {commitId}");
return manifest;
}
catch (Exception e)
@ -1290,11 +1325,12 @@ public class RepositoryService : BaseService<RepositoryService>
}
}
public List<WorkspaceFile> CreateCommitManifestByCurrentBaselineAndChanges
private (List<WorkspaceFile>, 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)
@ -1319,6 +1355,7 @@ public class RepositoryService : BaseService<RepositoryService>
}
files.Add(c.File);
overlayFsStatus.Add(c.File);
break;
}
case ChangeInfoType.Remove:
@ -1349,12 +1386,13 @@ public class RepositoryService : BaseService<RepositoryService>
}
files[idx] = c.File;
overlayFsStatus.Add(c.File);
break;
}
}
}
return files;
return (files, overlayFsStatus);
}
public async ValueTask<string?> CreateDepotIntoTempFileAsync(RepositoryModel repo, IEnumerable<WorkspaceFile> depotFiles)

View File

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

View File

@ -76,6 +76,14 @@ 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>
<u:Form HorizontalAlignment="Stretch">
<u:FormItem Label="Title" IsRequired="True">
<TextBox Text="{Binding Title}"/>
</u:FormItem>

View File

@ -3,11 +3,18 @@
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 ColumnDefinitions="2*, *">
<TreeDataGrid Grid.Column="0" Grid.ColumnSpan="2" Source="{Binding FileTree}">
<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}">
</TreeDataGrid>
</Grid>
</UserControl>

View File

@ -9,15 +9,11 @@
x:Class="Flawless.Client.Views.RepositoryPage.RepoIssuePageView">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8" Margin="0 0 0 12">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8" Margin="6">
<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">
<u:ElasticWrapPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<StackPanel Orientation="Horizontal" Spacing="4" HorizontalAlignment="Stretch">
<u:IconButton
Icon="{StaticResource SemiIconDownload}" Content="Pull" HorizontalAlignment="Stretch"
Command="{Binding PullLatestRepositoryCommand}"/>
<u:IconButton
Icon="{StaticResource SemiIconBackward}" Content="Revert Select" HorizontalAlignment="Stretch"
Icon="{StaticResource SemiIconBackward}" Content="Revert" HorizontalAlignment="Stretch"
Command="{Binding RevertSelectedChangesCommand}"/>
</u:ElasticWrapPanel>
<u:Divider></u:Divider>
</StackPanel>
<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 Users"
<Button Content="Refresh"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Command="{Binding CreateUserCommand}"/>
<Button Content="Add Users"
<Button Content="Add"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Command="{Binding CreateUserCommand}"/>

View File

@ -6,11 +6,14 @@ 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)
public WorkspaceFile(DateTime modifyTime, string workPath, long size)
{
ModifyTime = modifyTime;
WorkPath = workPath;
Size = (ulong) size;
}
public bool Equals(WorkspaceFile other)

View File

@ -1,5 +1,4 @@
using System.Net;
using Flawless.Communication.Request;
using Flawless.Communication.Request;
using Flawless.Communication.Response;
using Flawless.Server.Models;
using Flawless.Server.Services;
@ -10,15 +9,26 @@ using Microsoft.EntityFrameworkCore;
namespace Flawless.Server.Controllers;
[ApiController, Authorize(Roles = "admin"), Route("api/admin")]
[ApiController, Authorize, 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))!;
@ -34,15 +44,31 @@ 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!"));
@ -67,6 +93,9 @@ 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!"));
@ -79,6 +108,9 @@ 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);
@ -93,6 +125,9 @@ 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();
}
@ -100,12 +135,18 @@ 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();
}
@ -113,6 +154,9 @@ 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));
}
@ -124,6 +168,9 @@ 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);
return new WorkspaceFile(DateTime.FromBinary(long.Parse(dateTimeStr)), pathStr, 0);
}).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);
var preDepot = await StencilWorkspaceSnapshotViaDatabaseAsync(rp.Id, mainDepotId, test, sizeMap, null);
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 actualRequiredDepots = new HashSet<Guid>();
var directDependDepots = 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);
await StencilWorkspaceSnapshotAsync(cacheStream, test, sizeMap);
// Oh no, will we need to load their parents?
var unresolvedCount = test.Count;
@ -457,21 +457,13 @@ public class RepositoryInnieController(
// Yes
foreach (var subDepot in req.RequiredDepots?.Select(Guid.Parse) ?? [])
{
await StencilWorkspaceSnapshotViaDatabaseAsync(rp.Id, subDepot, test);
await StencilWorkspaceSnapshotViaDatabaseAsync(rp.Id, subDepot, test, sizeMap, directDependDepots);
var rest = test.Count;
if (rest == 0)
{
actualRequiredDepots.Add(subDepot);
break;
}
// If test is changed?
if (unresolvedCount != rest)
{
actualRequiredDepots.Add(subDepot);
unresolvedCount = rest;
}
// Quit if nothing to do.
unresolvedCount = rest;
if (rest == 0) break;
}
// If still not able to resolve
@ -489,7 +481,7 @@ public class RepositoryInnieController(
await dbContext.Repositories
.Where(r => r.Id == rp.Id)
.SelectMany(r => r.Depots)
.Where(cm => actualRequiredDepots.Contains(cm.DepotId))
.Where(cm => directDependDepots.Contains(cm.DepotId))
.ToArrayAsync());
// Create commit and let it alloc a main key
@ -510,6 +502,14 @@ 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,7 +549,8 @@ public class RepositoryInnieController(
return Ok(new CommitSuccessResponse(commit.CommittedOn, commit.Id, mainDepot.DepotId));
}
private static async ValueTask StencilWorkspaceSnapshotAsync(Stream depotStream, HashSet<WorkspaceFile> unresolved)
private static async ValueTask StencilWorkspaceSnapshotAsync(Stream depotStream,
HashSet<WorkspaceFile> unresolved, Dictionary<WorkspaceFile, long> sizeMap)
{
// Get version
depotStream.Seek(0, SeekOrigin.Begin); // Fix: Get an invalid version code due to offset is bad.
@ -568,14 +569,21 @@ 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)
private async Task<RepositoryDepot?> StencilWorkspaceSnapshotViaDatabaseAsync(
Guid rpId, Guid depotId,
HashSet<WorkspaceFile> unresolved,
Dictionary<WorkspaceFile, long> sizeMap,
HashSet<Guid>? directDependDepots)
{
var depot = await dbContext.Repositories
.SelectMany(r => r.Depots)
@ -583,21 +591,28 @@ public class RepositoryInnieController(
.ThenInclude(repositoryDepot => repositoryDepot.Dependencies)
.FirstOrDefaultAsync(r => r.DepotId == depotId);
await RecurringAsync(transformer, rpId, depot, unresolved);
await RecurringAsync(transformer, rpId, depot, unresolved, sizeMap, directDependDepots);
return depot;
static async ValueTask RecurringAsync(PathTransformer transformer, Guid rpId, RepositoryDepot? depot, HashSet<WorkspaceFile> unresolved)
static async ValueTask RecurringAsync(
PathTransformer transformer, Guid rpId,
RepositoryDepot? depot, HashSet<WorkspaceFile> unresolved,
Dictionary<WorkspaceFile, long> sizeMap,
HashSet<Guid>? directDependDepots)
{
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)))
await StencilWorkspaceSnapshotAsync(fs, unresolved);
{
var before = unresolved.Count;
await StencilWorkspaceSnapshotAsync(fs, unresolved, sizeMap);
if (before != unresolved.Count && directDependDepots != null) directDependDepots.Add(depot.DepotId);
}
for (var i = 0; i < depot.Dependencies.Count && unresolved.Count > 0; i++)
await RecurringAsync(transformer, rpId, depot.Dependencies[i], unresolved);
await RecurringAsync(transformer, rpId, depot.Dependencies[i], unresolved, sizeMap, directDependDepots);
}
}
}

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,7 +189,6 @@ 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!");
@ -201,8 +200,6 @@ 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": "./data/development",
"LocalStoragePath": "/Users/cardidi/flawless-data",
"User": {
"PublicRegister": true
},

View File

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