From e27058626e8dfe6055fd923e85cfb86351044b55 Mon Sep 17 00:00:00 2001
From: acite <1498045907@qq.com>
Date: Thu, 28 Aug 2025 00:34:26 +0800
Subject: [PATCH] [feat] Partially implemented transmission system
---
 .gitignore                                    |   3 +-
 .idea/.idea.Abyss/.idea/workspace.xml         |  95 +++++--
 Abyss.sln.DotSettings.user                    |   1 +
 .../Controllers/Media/VideoController.cs      |  11 -
 .../Controllers/Task/TaskController.cs        |  60 +++++
 Abyss/Components/Services/ResourceService.cs  |  35 +++
 Abyss/Components/Services/TaskService.cs      | 247 ++++++++++++++++++
 Abyss/Components/Services/UserService.cs      |   3 +
 Abyss/Components/Static/Helpers.cs            |  26 +-
 Abyss/Components/Tools/TemporaryDB.cs         |   6 +
 Abyss/Model/Chip.cs                           |  17 ++
 Abyss/Model/Comment.cs                        |   7 +
 Abyss/Model/Task.cs                           |  16 ++
 Abyss/Model/TaskCreation.cs                   |  23 ++
 Abyss/Model/Video.cs                          |  12 +
 Abyss/Program.cs                              |   3 +
 Abyss/Properties/launchSettings.json          |   3 +-
 Abyss/appsettings.Development.json            |   8 -
 18 files changed, 512 insertions(+), 64 deletions(-)
 create mode 100644 Abyss/Components/Controllers/Task/TaskController.cs
 create mode 100644 Abyss/Components/Services/TaskService.cs
 create mode 100644 Abyss/Components/Tools/TemporaryDB.cs
 create mode 100644 Abyss/Model/Chip.cs
 create mode 100644 Abyss/Model/Comment.cs
 create mode 100644 Abyss/Model/Task.cs
 create mode 100644 Abyss/Model/TaskCreation.cs
 create mode 100644 Abyss/Model/Video.cs
 delete mode 100644 Abyss/appsettings.Development.json
diff --git a/.gitignore b/.gitignore
index 01e899a..b523ad6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,4 +56,5 @@ nunit-*.xml
 # DB
 *.db
 
-appsettings.json
\ No newline at end of file
+appsettings.json
+appsettings.Development.json
\ No newline at end of file
diff --git a/.idea/.idea.Abyss/.idea/workspace.xml b/.idea/.idea.Abyss/.idea/workspace.xml
index 59d545e..1ad86c0 100644
--- a/.idea/.idea.Abyss/.idea/workspace.xml
+++ b/.idea/.idea.Abyss/.idea/workspace.xml
@@ -10,10 +10,24 @@
   
   
     
+      
+      
+      
+      
+      
+      
+      
+      
+      
       
+      
       
+      
       
       
+      
+      
+      
     
@@ -27,27 +41,36 @@
     
   
   
+    
     
     
     
     
     
-    
-    
     
+    
     
     
+    
     
-    
-    
     
+    
     
+    
+    
     
+    
+    
     
     
+    
     
+    
   
   
+  
+    
+  
   {
   "associatedIndex": 3
 }
@@ -56,31 +79,31 @@
     
     
   
-  {
-  "keyToString": {
-    ".NET Launch Settings Profile.Abyss: http.executor": "Run",
-    ".NET Launch Settings Profile.Abyss: https.executor": "Run",
-    ".NET Project.AbyssCli.executor": "Run",
-    "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
-    "ModuleVcsDetector.initialDetectionPerformed": "true",
-    "Publish to folder.Publish Abyss to folder x86.executor": "Run",
-    "Publish to folder.Publish Abyss to folder.executor": "Run",
-    "RunOnceActivity.ShowReadmeOnStart": "true",
-    "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
-    "RunOnceActivity.git.unshallow": "true",
-    "XThreadsFramesViewSplitterKey": "0.30266345",
-    "git-widget-placeholder": "main",
-    "last_opened_file_path": "/opt/security/https/server",
-    "node.js.detected.package.eslint": "true",
-    "node.js.detected.package.tslint": "true",
-    "node.js.selected.package.eslint": "(autodetect)",
-    "node.js.selected.package.tslint": "(autodetect)",
-    "nodejs_package_manager_path": "npm",
-    "settings.editor.selected.configurable": "preferences.pluginManager",
-    "vue.rearranger.settings.migration": "true"
+  
-  
+}]]>
+  
     
       
         
@@ -174,7 +197,8 @@
       
       
       
-      
+      
+      
     
     
   
@@ -224,6 +248,19 @@
           
           
         
+        
+          file://$PROJECT_DIR$/Abyss/Components/Services/TaskService.cs
+          60
+          
+            
+              
+            
+            
+              
+            
+          
+          
+        
       
     
   
diff --git a/Abyss.sln.DotSettings.user b/Abyss.sln.DotSettings.user
index e567e74..674a5bb 100644
--- a/Abyss.sln.DotSettings.user
+++ b/Abyss.sln.DotSettings.user
@@ -2,4 +2,5 @@
 	ForceIncluded
 	ForceIncluded
 	ForceIncluded
+	ForceIncluded
 	ForceIncluded
\ No newline at end of file
diff --git a/Abyss/Components/Controllers/Media/VideoController.cs b/Abyss/Components/Controllers/Media/VideoController.cs
index 3fccbdd..c04968b 100644
--- a/Abyss/Components/Controllers/Media/VideoController.cs
+++ b/Abyss/Components/Controllers/Media/VideoController.cs
@@ -93,16 +93,5 @@ public class VideoController(ILogger logger, ResourceService rs
         return PhysicalFile(d, "video/mp4", enableRangeProcessing: true);
     }
     
-    [HttpGet("{klass}/{id}/nv")]
-    public async Task Nv(string klass, string id, string token)
-    {
-        var d = Helpers.SafePathCombine(VideoFolder, [klass, id, "video.a.mp4"]);
-        if (d == null) return StatusCode(403, new { message = "403 Denied" });
-        
-        var r = await rs.Get(d, token, Ip);
-        if (!r)  return StatusCode(403, new { message = "403 Denied" });
-        return PhysicalFile(d, "video/mp4", enableRangeProcessing: true);
-    }
-    
     private string Ip => HttpContext.Connection.RemoteIpAddress?.ToString() ?? "127.0.0.1";
 }
\ No newline at end of file
diff --git a/Abyss/Components/Controllers/Task/TaskController.cs b/Abyss/Components/Controllers/Task/TaskController.cs
new file mode 100644
index 0000000..d20c1bb
--- /dev/null
+++ b/Abyss/Components/Controllers/Task/TaskController.cs
@@ -0,0 +1,60 @@
+using Abyss.Components.Services;
+using Abyss.Components.Static;
+using Abyss.Model;
+using Microsoft.AspNetCore.Mvc;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Abyss.Components.Controllers.Task;
+
+
+[ApiController]
+[Route("api/[controller]")]
+public class TaskController(ILogger logger, ConfigureService config, TaskService taskService) : Controller
+{
+    public readonly string TaskFolder = Path.Combine(config.MediaRoot, "Tasks");
+    
+    [HttpGet]
+    public async Task Query(string token)
+    {
+        // If the token is invalid, an empty list will be returned, which is part of the design
+        return Json(await taskService.Query(token, Ip));
+    }
+
+    [HttpPost]
+    public async Task Create(string token, [FromBody] TaskCreation creation)
+    {
+        var r = await taskService.Create(token, Ip, creation);
+        if(r == null)
+        { 
+            return BadRequest();
+        }
+        return Ok(JsonConvert.SerializeObject(r, Formatting.Indented));
+    }
+
+    [HttpGet("{id}")]
+    public async Task GetTask(string id)
+    {
+        throw new NotImplementedException();
+    }
+
+    [HttpPatch("{id}")]
+    public async Task PutChip(string id)
+    {
+        throw new NotImplementedException();
+    }
+
+    [HttpPost("{id}")]
+    public async Task VerifyChip(string id)
+    {
+        throw new NotImplementedException();
+    }
+
+    [HttpDelete("{id}")]
+    public async Task DeleteTask(string id)
+    {
+        throw new NotImplementedException();
+    }
+    
+    private string Ip => HttpContext.Connection.RemoteIpAddress?.ToString() ?? "127.0.0.1";
+}
\ No newline at end of file
diff --git a/Abyss/Components/Services/ResourceService.cs b/Abyss/Components/Services/ResourceService.cs
index 2d578da..f8e2126 100644
--- a/Abyss/Components/Services/ResourceService.cs
+++ b/Abyss/Components/Services/ResourceService.cs
@@ -39,6 +39,11 @@ public class ResourceService
 
         _database = new SQLiteAsyncConnection(config.RaDatabase, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create);
         _database.CreateTableAsync().Wait();
+
+        var tasksPath = Helpers.SafePathCombine(_config.MediaRoot, "Tasks");
+        if(tasksPath != null)
+            InsertRaRow(tasksPath, "root", "rw,r-,r-", true).Wait();
+        
     }
 
     // Create UID only for resources, without considering advanced hash security such as adding salt
@@ -335,4 +340,34 @@ public class ResourceService
             return false;
         }
     }
+
+    private async Task InsertRaRow(string fullPath, string owner, string permission, bool update = false)
+    {
+        if (!PermissionRegex.IsMatch(permission))
+        {
+            _logger.LogError($"Invalid permission format: {permission}");
+            return false;
+        }
+        
+        var path = Path.GetRelativePath(_config.MediaRoot, fullPath);
+        
+        if (update)
+            return await _database.InsertOrReplaceAsync(new ResourceAttribute()
+            {
+                Uid = Uid(path),
+                Name = path,
+                Owner = owner,
+                Permission = permission,
+            }) == 1;
+        else
+        {
+            return await _database.InsertAsync(new ResourceAttribute()
+            {
+                Uid = Uid(path),
+                Name = path,
+                Owner = owner,
+                Permission = permission,
+            }) == 1;
+        }
+    }
 }
\ No newline at end of file
diff --git a/Abyss/Components/Services/TaskService.cs b/Abyss/Components/Services/TaskService.cs
new file mode 100644
index 0000000..3290064
--- /dev/null
+++ b/Abyss/Components/Services/TaskService.cs
@@ -0,0 +1,247 @@
+using Abyss.Components.Static;
+using Abyss.Model;
+using Newtonsoft.Json;
+using SQLite;
+using Task = Abyss.Model.Task;
+
+namespace Abyss.Components.Services;
+
+
+
+public class TaskService(ILogger logger, ConfigureService config, ResourceService rs, UserService user)
+{ 
+    public readonly string TaskFolder = Path.Combine(config.MediaRoot, "Tasks");
+    public readonly string VideoFolder = Path.Combine(config.MediaRoot, "Videos");
+    
+    private const ulong MaxChunkSize = 20 * 1024 * 1024;
+    
+    public async Task> Query(string token, string ip)
+    {
+        var r = await rs.Query(TaskFolder, token, ip);
+        var u = user.Validate(token, ip);
+        
+        List s = new();
+        foreach (var i in r ?? [])
+        {
+            var p = Helpers.SafePathCombine(TaskFolder, [i, "task.json"]);
+            var c = JsonConvert.DeserializeObject(await System.IO.File.ReadAllTextAsync(p ?? ""));
+            
+            if(c?.Owner == u) s.Add(i);
+        }
+        
+        return s;
+    }
+
+    public async Task Create(string token, string ip, TaskCreation creation)
+    {
+        if(creation.Name.Length > 64 || creation.Klass.Length > 16 || creation.Size > 10UL * 1024UL * 1024UL * 1024UL || creation.Author.Length > 32)
+            return null;
+        if(creation.Name == "" || creation.Klass == "")
+            return null;
+        if(!IsFileNameSafe(creation.Klass))
+            return null;
+        if (GetAvailableFreeSpace(TaskFolder) - (long)creation.Size < 10 * 1024L * 1024L * 1024L)
+        { // Reserve 10GB of space
+            return null;
+        }
+        
+        switch ((TaskType)creation.Type)
+        {
+            case TaskType.Image:
+                return await CreateImageTask(token, ip, creation);
+            case TaskType.Video:
+                return await CreateVideoTask(token, ip, creation);
+            default:
+                return null;
+        }
+    }
+
+    private async Task CreateVideoTask(string token, string ip, TaskCreation creation)
+    {
+        if(!await rs.Valid(VideoFolder, token, OperationType.Write, ip))
+            return null;
+        var u = user.Validate(token, ip);
+        if(u == null)
+            return null;
+        
+        var r = new TaskCreationResponse()
+        {
+            Id = GenerateUniqueId(TaskFolder),
+            Chips = SliceFile(creation.Size)
+        };
+        
+        Directory.CreateDirectory(Path.Combine(TaskFolder, r.Id.ToString())); 
+        Directory.CreateDirectory(Path.Combine(TaskFolder, r.Id.ToString(), "gallery")); 
+        // It shouldn't be a problem to spell it directly like this, as all the parameters are generated by myself
+
+        Task v = new Task()
+        {
+            Name = creation.Name,
+            Owner = u,
+            Class = creation.Klass,
+            Id = r.Id,
+            Type = TaskType.Video
+        };
+        
+        await System.IO.File.WriteAllTextAsync(
+            Path.Combine(TaskFolder, r.Id.ToString(), "task.json"), 
+            JsonConvert.SerializeObject(v, Formatting.Indented));
+
+        using (var connection = new SQLiteConnection(Path.Combine(TaskFolder, r.Id.ToString(), "task.db")))
+        {
+            connection.CreateTable();
+            connection.InsertAll(r.Chips.Select(x => new Chip()
+            {
+                Addr = x.Addr,
+                Hash = "",
+                Id = x.Id,
+                Size = x.Size,
+                State = ChipState.Created
+            }));
+            connection.Close();
+        }
+
+        CreateEmptyFile(Path.Combine(TaskFolder, r.Id.ToString(), "video.mp4"), (long)creation.Size);
+        return r;
+    }
+
+    private async Task CreateImageTask(string token, string ip, TaskCreation creation)
+    {
+        throw new NotImplementedException();
+    }
+    
+    public static uint GenerateUniqueId(string parentDirectory)
+    {
+        string[] directories = Directory.GetDirectories(parentDirectory);
+        HashSet existingIds = new HashSet();
+
+        foreach (string dirPath in directories)
+        {
+            string dirName = new DirectoryInfo(dirPath).Name;
+            if (uint.TryParse(dirName, out uint id))
+            {
+                if (id != 0)
+                {
+                    existingIds.Add(id);
+                }
+            }
+        }
+
+        uint newId = 1;
+        while (existingIds.Contains(newId))
+        {
+            newId++;
+            if (newId == uint.MaxValue)
+            {
+                return 0;
+            }
+        }
+
+        return newId;
+    }
+    
+    public static List SliceFile(ulong fileSize)
+    {
+        var tasks = new List();
+        if (fileSize == 0)
+        {
+            return tasks;
+        }
+
+        ulong remainingSize = fileSize;
+        ulong currentAddr = 0;
+        uint id = 0;
+
+        while (remainingSize > 0)
+        {
+            ulong chunkSize = remainingSize > MaxChunkSize ? MaxChunkSize : remainingSize;
+
+            tasks.Add(new ChipDesc
+            {
+                Id = id,
+                Addr = currentAddr,
+                Size = chunkSize
+            });
+
+            currentAddr += chunkSize;
+            remainingSize -= chunkSize;
+            id++;
+        }
+
+        return tasks;
+    }
+    
+    public static bool IsFileNameSafe(string fileName)
+    {
+        if (string.IsNullOrWhiteSpace(fileName))
+        {
+            return false;
+        }
+
+        if (fileName.Contains(Path.DirectorySeparatorChar) ||
+            fileName.Contains(Path.AltDirectorySeparatorChar))
+        {
+            return false;
+        }
+
+        char[] invalidChars = Path.GetInvalidFileNameChars();
+        if (fileName.Any(c => invalidChars.Contains(c)))
+        {
+            return false;
+        }
+
+        string[] reservedNames = {
+            "CON", "PRN", "AUX", "NUL",
+            "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
+            "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
+        };
+        string nameWithoutExtension = Path.GetFileNameWithoutExtension(fileName).ToUpperInvariant();
+        if (reservedNames.Contains(nameWithoutExtension))
+        {
+            return false;
+        }
+
+        return true;
+    }
+    
+    public static void CreateEmptyFile(string filePath, long sizeInBytes)
+    {
+        using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
+        {
+            fs.SetLength(sizeInBytes);
+        }
+    }
+    
+    public static long GetAvailableFreeSpace(string directoryPath)
+    {
+        try
+        {
+            if (string.IsNullOrEmpty(directoryPath))
+            {
+                return -1;
+            }
+
+            string rootPath = Path.GetPathRoot(directoryPath) ?? "";
+            
+            if (string.IsNullOrEmpty(rootPath))
+            {
+                return -1;
+            }
+
+            DriveInfo driveInfo = new DriveInfo(rootPath);
+
+            if (driveInfo.IsReady)
+            {
+                return driveInfo.AvailableFreeSpace;
+            }
+            else
+            {
+                return -1;
+            }
+        }
+        catch (Exception ex)
+        {
+            return -1;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Abyss/Components/Services/UserService.cs b/Abyss/Components/Services/UserService.cs
index ad60d98..574c321 100644
--- a/Abyss/Components/Services/UserService.cs
+++ b/Abyss/Components/Services/UserService.cs
@@ -7,6 +7,7 @@ using Abyss.Model;
 using Microsoft.Extensions.Caching.Memory;
 using NSec.Cryptography;
 using SQLite;
+using Task = System.Threading.Tasks.Task;
 
 namespace Abyss.Components.Services;
 
@@ -27,6 +28,8 @@ public class UserService
         _database.CreateTableAsync().Wait();
         var rootUser = _database.Table().Where(x => x.Name == "root").FirstOrDefaultAsync().Result;
         
+        _cache.Set("acite", $"acite@127.0.0.1", DateTimeOffset.Now.AddDays(1));
+        
         if (rootUser == null)
         {
             var key = GenerateKeyPair();
diff --git a/Abyss/Components/Static/Helpers.cs b/Abyss/Components/Static/Helpers.cs
index 3781aa5..849be90 100644
--- a/Abyss/Components/Static/Helpers.cs
+++ b/Abyss/Components/Static/Helpers.cs
@@ -67,7 +67,6 @@ public static class StringArrayExtensions
 {
     public static string[] SortLikeWindows(this string[] array)
     {
-        if (array == null) return null;
         if (array.Length == 0) return array;
         
         Array.Sort(array, new WindowsFileNameComparer());
@@ -76,7 +75,6 @@ public static class StringArrayExtensions
 
     public static string[] SortLikeWindowsDescending(this string[] array)
     {
-        if (array == null) return null;
         if (array.Length == 0) return array;
         
         Array.Sort(array, new WindowsFileNameComparerDescending());
@@ -85,14 +83,14 @@ public static class StringArrayExtensions
 
     public static void SortLikeWindowsInPlace(this string[] array)
     {
-        if (array == null || array.Length == 0) return;
+        if (array.Length == 0) return;
         
         Array.Sort(array, new WindowsFileNameComparer());
     }
 
     public static void SortLikeWindowsDescendingInPlace(this string[] array)
     {
-        if (array == null || array.Length == 0) return;
+        if (array.Length == 0) return;
         
         Array.Sort(array, new WindowsFileNameComparerDescending());
     }
@@ -100,8 +98,8 @@ public static class StringArrayExtensions
 
 public class WindowsFileNameComparer : IComparer
 {
-    private static readonly Regex _regex = new Regex(@"(\d+|\D+)", RegexOptions.Compiled);
-    private static readonly CompareInfo _compareInfo = CultureInfo.InvariantCulture.CompareInfo;
+    private static readonly Regex Regex = new Regex(@"(\d+|\D+)", RegexOptions.Compiled);
+    private static readonly CompareInfo CompareInfo = CultureInfo.InvariantCulture.CompareInfo;
     
     public int Compare(string? x, string? y)
     {
@@ -110,8 +108,8 @@ public class WindowsFileNameComparer : IComparer
         if (y == null) return 1;
         if (ReferenceEquals(x, y)) return 0;
         
-        var partsX = _regex.Matches(x);
-        var partsY = _regex.Matches(y);
+        var partsX = Regex.Matches(x);
+        var partsY = Regex.Matches(y);
         
         int minLength = Math.Min(partsX.Count, partsY.Count);
         
@@ -130,7 +128,7 @@ public class WindowsFileNameComparer : IComparer
                 int comparison;
                 if (ContainsChinese(partX) || ContainsChinese(partY))
                 {
-                    comparison = _compareInfo.Compare(partX, partY, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace);
+                    comparison = CompareInfo.Compare(partX, partY, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace);
                 }
                 else
                 {
@@ -161,20 +159,20 @@ public class WindowsFileNameComparer : IComparer
 
 public class WindowsFileNameComparerDescending : IComparer
 {
-    private static readonly WindowsFileNameComparer _ascendingComparer = new WindowsFileNameComparer();
+    private static readonly WindowsFileNameComparer AscendingComparer = new WindowsFileNameComparer();
     
-    public int Compare(string x, string y)
+    public int Compare(string? x, string? y)
     {
-        return _ascendingComparer.Compare(y, x);
+        return AscendingComparer.Compare(y, x);
     }
 }
 
 public static class StringNaturalCompare
 {
-    private static readonly WindowsFileNameComparer _comparer = new WindowsFileNameComparer();
+    private static readonly WindowsFileNameComparer Comparer = new WindowsFileNameComparer();
     
     public static int Compare(string x, string y)
     {
-        return _comparer.Compare(x, y);
+        return Comparer.Compare(x, y);
     }
 }
\ No newline at end of file
diff --git a/Abyss/Components/Tools/TemporaryDB.cs b/Abyss/Components/Tools/TemporaryDB.cs
new file mode 100644
index 0000000..b7a99a6
--- /dev/null
+++ b/Abyss/Components/Tools/TemporaryDB.cs
@@ -0,0 +1,6 @@
+namespace Abyss.Components.Tools;
+
+public class TemporaryDB
+{
+    
+}
\ No newline at end of file
diff --git a/Abyss/Model/Chip.cs b/Abyss/Model/Chip.cs
new file mode 100644
index 0000000..4af46ac
--- /dev/null
+++ b/Abyss/Model/Chip.cs
@@ -0,0 +1,17 @@
+namespace Abyss.Model;
+
+public enum ChipState
+{
+    Created,
+    Uploaded,
+    Verified
+}
+
+public class Chip
+{
+    public uint Id { get; set; }
+    public ulong Addr { get; set; }
+    public ulong Size { get; set; }
+    public string Hash { get; set; } = "";
+    public ChipState State { get; set; }
+}
\ No newline at end of file
diff --git a/Abyss/Model/Comment.cs b/Abyss/Model/Comment.cs
new file mode 100644
index 0000000..48f72d7
--- /dev/null
+++ b/Abyss/Model/Comment.cs
@@ -0,0 +1,7 @@
+namespace Abyss.Model;
+
+public class Comment
+{
+    public string username = "";
+    public string text = "";
+}
\ No newline at end of file
diff --git a/Abyss/Model/Task.cs b/Abyss/Model/Task.cs
new file mode 100644
index 0000000..6292db5
--- /dev/null
+++ b/Abyss/Model/Task.cs
@@ -0,0 +1,16 @@
+namespace Abyss.Model;
+
+public enum TaskType
+{
+    Video = 1,
+    Image = 2,
+}
+
+public class Task
+{
+    public uint Id;
+    public string Owner = "";
+    public string Class = "";
+    public string Name = "";
+    public TaskType Type;
+}
\ No newline at end of file
diff --git a/Abyss/Model/TaskCreation.cs b/Abyss/Model/TaskCreation.cs
new file mode 100644
index 0000000..7d5611b
--- /dev/null
+++ b/Abyss/Model/TaskCreation.cs
@@ -0,0 +1,23 @@
+namespace Abyss.Model;
+
+public class TaskCreation
+{
+    public int Type { get; set; }
+    public ulong Size { get; set; }
+    public string Klass { get; set; } = "";
+    public string Name { get; set; } = "";
+    public string Author  { get; set; } = "";
+}
+
+public class ChipDesc
+{
+    public uint Id;
+    public ulong Addr;
+    public ulong Size;
+}
+
+public class TaskCreationResponse // As Array
+{
+    public uint Id;
+    public List Chips = new();
+}
\ No newline at end of file
diff --git a/Abyss/Model/Video.cs b/Abyss/Model/Video.cs
new file mode 100644
index 0000000..6cbc051
--- /dev/null
+++ b/Abyss/Model/Video.cs
@@ -0,0 +1,12 @@
+namespace Abyss.Model;
+
+public class Video
+{
+    public string name;
+    public ulong duration;
+    public List gallery = new();
+    public List comment = new();
+    public bool star;
+    public uint like;
+    public string author;
+}
\ No newline at end of file
diff --git a/Abyss/Program.cs b/Abyss/Program.cs
index 01d9fcc..3b53873 100644
--- a/Abyss/Program.cs
+++ b/Abyss/Program.cs
@@ -1,4 +1,5 @@
 using System.Threading.RateLimiting;
+using Abyss.Components.Controllers.Task;
 using Abyss.Components.Services;
 using Microsoft.AspNetCore.RateLimiting;
 
@@ -17,6 +18,8 @@ public class Program
         builder.Services.AddSingleton();
         builder.Services.AddSingleton();
         builder.Services.AddSingleton();
+        builder.Services.AddSingleton();
+        builder.Services.AddSingleton();
         
         builder.Services.AddRateLimiter(options =>
         {
diff --git a/Abyss/Properties/launchSettings.json b/Abyss/Properties/launchSettings.json
index 7d84003..ba42f88 100644
--- a/Abyss/Properties/launchSettings.json
+++ b/Abyss/Properties/launchSettings.json
@@ -17,7 +17,8 @@
       "launchBrowser": false,
       "applicationUrl": "https://localhost:7013;http://localhost:5198",
       "environmentVariables": {
-        "ASPNETCORE_ENVIRONMENT": "Development"
+        "ASPNETCORE_ENVIRONMENT": "Development",
+        "MEDIA_ROOT" : "/storage"
       }
     }
   }
diff --git a/Abyss/appsettings.Development.json b/Abyss/appsettings.Development.json
deleted file mode 100644
index 0c208ae..0000000
--- a/Abyss/appsettings.Development.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "Logging": {
-    "LogLevel": {
-      "Default": "Information",
-      "Microsoft.AspNetCore": "Warning"
-    }
-  }
-}