Compare commits
2 commits
f7be24e615
...
4267e0d864
| Author | SHA1 | Date | |
|---|---|---|---|
| 4267e0d864 | |||
| 97fbbc7b71 |
8 changed files with 548 additions and 126 deletions
|
|
@ -666,7 +666,7 @@ public static async Task Auth(HttpContext context, Func<Task> next, IQueryCollec
|
||||||
"dm/message/send", "dm/message/delete", "dm/message/react", "dm/key/update", "dm/message/pin/toggle",
|
"dm/message/send", "dm/message/delete", "dm/message/react", "dm/key/update", "dm/message/pin/toggle",
|
||||||
"dm/message/react/add", "dm/message/react/remove",
|
"dm/message/react/add", "dm/message/react/remove",
|
||||||
"user/storage/public/update", "user/storage/private/update", "dm/read",
|
"user/storage/public/update", "user/storage/private/update", "dm/read",
|
||||||
"group/create", "user/group/list", "group/info",
|
"group/create", "user/group/list", "group/info", "group/settings/update",
|
||||||
"user/invite/accept", "user/invite/decline", "user/invite/revoke", "user/dm/list", "user/group/list",
|
"user/invite/accept", "user/invite/decline", "user/invite/revoke", "user/dm/list", "user/group/list",
|
||||||
"user/storage/public/update", "user/storage/private/update", "group/storage/public/update", "group/storage/private/update", "group/storage/private/getentry", "dm/read", "group/read",
|
"user/storage/public/update", "user/storage/private/update", "group/storage/public/update", "group/storage/private/update", "group/storage/private/getentry", "dm/read", "group/read",
|
||||||
"dm/message/send", "dm/message/delete", "dm/message/react/add", "dm/message/react/remove", "dm/message/pin/toggle",
|
"dm/message/send", "dm/message/delete", "dm/message/react/add", "dm/message/react/remove", "dm/message/pin/toggle",
|
||||||
|
|
@ -806,23 +806,32 @@ public static async Task Auth(HttpContext context, Func<Task> next, IQueryCollec
|
||||||
break;
|
break;
|
||||||
case "group/create":
|
case "group/create":
|
||||||
{
|
{
|
||||||
Universal2String groupCreate = JsonSerializer.Deserialize(
|
Universal3String groupCreate = JsonSerializer.Deserialize(
|
||||||
serializedBody.string2,
|
serializedBody.string2,
|
||||||
AppJsonSerializerContext.Default.Universal2String
|
AppJsonSerializerContext.Default.Universal3String
|
||||||
);
|
);
|
||||||
await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupCreate(id, groupCreate.string1, groupCreate.string2), password));
|
await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupCreate(id, groupCreate.string1, groupCreate.string2, groupCreate.string3), password));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "group/info":
|
case "group/info":
|
||||||
await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupInfo(serializedBody.string2), password));
|
await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupInfo(serializedBody.string2), password));
|
||||||
break;
|
break;
|
||||||
|
case "group/settings/update":
|
||||||
|
{
|
||||||
|
Universal3String req = JsonSerializer.Deserialize(
|
||||||
|
serializedBody.string2,
|
||||||
|
AppJsonSerializerContext.Default.Universal3String
|
||||||
|
);
|
||||||
|
await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupSettingsUpdate(id, req.string1, req.string2, req.string3), password));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "group/invite":
|
case "group/invite":
|
||||||
{
|
{
|
||||||
Universal2String payload = JsonSerializer.Deserialize(
|
Universal3String payload = JsonSerializer.Deserialize(
|
||||||
serializedBody.string2,
|
serializedBody.string2,
|
||||||
AppJsonSerializerContext.Default.Universal2String
|
AppJsonSerializerContext.Default.Universal3String
|
||||||
);
|
);
|
||||||
await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupInvite(id, payload.string1, payload.string2), password));
|
await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupInvite(id, payload.string1, payload.string2, payload.string3), password));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "group/invite/revoke":
|
case "group/invite/revoke":
|
||||||
|
|
|
||||||
|
|
@ -256,7 +256,23 @@ public class Utils
|
||||||
string content = Encoding.UTF8.GetString(await Fs.ReadFile($"{path}/{invite}"));
|
string content = Encoding.UTF8.GetString(await Fs.ReadFile($"{path}/{invite}"));
|
||||||
if (!first) groupBuilder.Append(',');
|
if (!first) groupBuilder.Append(',');
|
||||||
first = false;
|
first = false;
|
||||||
groupBuilder.Append($"\"{groupId};{userId}\": {{ \"timestamp\": {content}, \"receiver\": \"{userId}\", \"groupId\": \"{groupId}\" }}");
|
|
||||||
|
string timestampStr;
|
||||||
|
string keysPayload = "";
|
||||||
|
if (content.StartsWith("{"))
|
||||||
|
{
|
||||||
|
var data = JsonSerializer.Deserialize<LarpixServer.Utils.Jsons.Universal2String>(content, LarpixServer.Utils.Jsons.AppJsonSerializerContext.Default.Universal2String);
|
||||||
|
timestampStr = data.string1;
|
||||||
|
if (!string.IsNullOrEmpty(data.string2)) {
|
||||||
|
keysPayload = $", \"keysPayload\": {JsonSerializer.Serialize(data.string2, LarpixServer.Utils.Jsons.AppJsonSerializerContext.Default.String)}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timestampStr = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
groupBuilder.Append($"\"{groupId};{userId}\": {{ \"timestamp\": {timestampStr}, \"receiver\": \"{userId}\", \"groupId\": \"{groupId}\"{keysPayload} }}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
groupBuilder.Append("}");
|
groupBuilder.Append("}");
|
||||||
|
|
@ -297,7 +313,23 @@ public class Utils
|
||||||
string content = Encoding.UTF8.GetString(await Fs.ReadFile($"{path}/{invite}"));
|
string content = Encoding.UTF8.GetString(await Fs.ReadFile($"{path}/{invite}"));
|
||||||
if (!first) groupBuilder.Append(',');
|
if (!first) groupBuilder.Append(',');
|
||||||
first = false;
|
first = false;
|
||||||
groupBuilder.Append($"\"{groupId};{userId}\": {{ \"timestamp\": {content}, \"sender\": \"{userId}\", \"groupId\": \"{groupId}\" }}");
|
|
||||||
|
string timestampStr;
|
||||||
|
string keysPayload = "";
|
||||||
|
if (content.StartsWith("{"))
|
||||||
|
{
|
||||||
|
var data = JsonSerializer.Deserialize<LarpixServer.Utils.Jsons.Universal2String>(content, LarpixServer.Utils.Jsons.AppJsonSerializerContext.Default.Universal2String);
|
||||||
|
timestampStr = data.string1;
|
||||||
|
if (!string.IsNullOrEmpty(data.string2)) {
|
||||||
|
keysPayload = $", \"keysPayload\": {JsonSerializer.Serialize(data.string2, LarpixServer.Utils.Jsons.AppJsonSerializerContext.Default.String)}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timestampStr = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
groupBuilder.Append($"\"{groupId};{userId}\": {{ \"timestamp\": {timestampStr}, \"sender\": \"{userId}\", \"groupId\": \"{groupId}\"{keysPayload} }}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
groupBuilder.Append("}");
|
groupBuilder.Append("}");
|
||||||
|
|
@ -347,6 +379,10 @@ public class Utils
|
||||||
|
|
||||||
string dirPath = $"{ACCOUNTS_DATA_DIR}/{id}/storage/public";
|
string dirPath = $"{ACCOUNTS_DATA_DIR}/{id}/storage/public";
|
||||||
string path = $"{dirPath}/{entry}";
|
string path = $"{dirPath}/{entry}";
|
||||||
|
SemaphoreSlim userLock = GetUserLock(id);
|
||||||
|
await userLock.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath);
|
long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath);
|
||||||
long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0;
|
long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0;
|
||||||
|
|
||||||
|
|
@ -356,6 +392,11 @@ public class Utils
|
||||||
}
|
}
|
||||||
|
|
||||||
await Fs.WriteFile(path, contentBytes);
|
await Fs.WriteFile(path, contentBytes);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
userLock.Release();
|
||||||
|
}
|
||||||
return "success:storage.entry.updated";
|
return "success:storage.entry.updated";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -377,6 +418,10 @@ public class Utils
|
||||||
|
|
||||||
string dirPath = $"{ACCOUNTS_DATA_DIR}/{id}/storage/public";
|
string dirPath = $"{ACCOUNTS_DATA_DIR}/{id}/storage/public";
|
||||||
string path = $"{dirPath}/{entry}";
|
string path = $"{dirPath}/{entry}";
|
||||||
|
SemaphoreSlim userLock = GetUserLock(id);
|
||||||
|
await userLock.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath);
|
long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath);
|
||||||
long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0;
|
long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0;
|
||||||
|
|
||||||
|
|
@ -386,6 +431,11 @@ public class Utils
|
||||||
}
|
}
|
||||||
|
|
||||||
await Fs.WriteFile(path, contentBytes);
|
await Fs.WriteFile(path, contentBytes);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
userLock.Release();
|
||||||
|
}
|
||||||
return "success:storage.entry.updated";
|
return "success:storage.entry.updated";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -421,6 +471,10 @@ public class Utils
|
||||||
|
|
||||||
string dirPath = $"{ACCOUNTS_DATA_DIR}/{id}/storage/private";
|
string dirPath = $"{ACCOUNTS_DATA_DIR}/{id}/storage/private";
|
||||||
string path = $"{dirPath}/{entry}";
|
string path = $"{dirPath}/{entry}";
|
||||||
|
SemaphoreSlim userLock = GetUserLock(id);
|
||||||
|
await userLock.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath);
|
long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath);
|
||||||
long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0;
|
long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0;
|
||||||
|
|
||||||
|
|
@ -430,6 +484,11 @@ public class Utils
|
||||||
}
|
}
|
||||||
|
|
||||||
await Fs.WriteFile(path, contentBytes);
|
await Fs.WriteFile(path, contentBytes);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
userLock.Release();
|
||||||
|
}
|
||||||
return "success:storage.entry.updated";
|
return "success:storage.entry.updated";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,24 +37,32 @@ public class Fs
|
||||||
|
|
||||||
public static void ProcessCacheSpace()
|
public static void ProcessCacheSpace()
|
||||||
{
|
{
|
||||||
while (fileCache.Count >= FILE_CACHE_SIZE)
|
if (fileCache.Count >= FILE_CACHE_SIZE)
|
||||||
{
|
{
|
||||||
var firstKey = fileCache.Keys.FirstOrDefault();
|
foreach (var kvp in fileCache)
|
||||||
if (firstKey != null) fileCache.TryRemove(firstKey, out _);
|
{
|
||||||
|
fileCache.TryRemove(kvp.Key, out _);
|
||||||
|
if (fileCache.Count < FILE_CACHE_SIZE) break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (existCache.Count >= EXIST_CACHE_SIZE)
|
if (existCache.Count >= EXIST_CACHE_SIZE)
|
||||||
{
|
{
|
||||||
var firstKey = existCache.Keys.FirstOrDefault();
|
foreach (var kvp in existCache)
|
||||||
if (firstKey != null) existCache.TryRemove(firstKey, out _);
|
{
|
||||||
|
existCache.TryRemove(kvp.Key, out _);
|
||||||
|
if (existCache.Count < EXIST_CACHE_SIZE) break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (dirCache.Count >= DIR_CACHE_SIZE)
|
if (dirCache.Count >= DIR_CACHE_SIZE)
|
||||||
{
|
{
|
||||||
var firstKey = dirCache.Keys.FirstOrDefault();
|
foreach (var kvp in dirCache)
|
||||||
if (firstKey != null) dirCache.TryRemove(firstKey, out _);
|
{
|
||||||
|
dirCache.TryRemove(kvp.Key, out _);
|
||||||
|
if (dirCache.Count < DIR_CACHE_SIZE) break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ulong ClearCache(string pattern)
|
public static ulong ClearCache(string pattern)
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,10 @@ public class Groups
|
||||||
await Fs.WriteFile(groupPath, Encoding.UTF8.GetBytes(JsonSerializer.Serialize(fileGroup, AppJsonSerializerContext.Default.Universal3String)));
|
await Fs.WriteFile(groupPath, Encoding.UTF8.GetBytes(JsonSerializer.Serialize(fileGroup, AppJsonSerializerContext.Default.Universal3String)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<string> GroupCreate(string id, string name, string body)
|
public static async Task<string> GroupCreate(string id, string name, string body, string historyVisible = "false")
|
||||||
{
|
{
|
||||||
await Room.Requests.createLock.WaitAsync();
|
SemaphoreSlim roomLock = Room.Requests.GetRoomLock(id);
|
||||||
|
await roomLock.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string groupsDir = $"{ROOMS_DIR}/groups";
|
string groupsDir = $"{ROOMS_DIR}/groups";
|
||||||
|
|
@ -84,6 +85,7 @@ public class Groups
|
||||||
await Fs.WriteFile($"{groupPath}/keys/last", Encoding.UTF8.GetBytes("0"));
|
await Fs.WriteFile($"{groupPath}/keys/last", Encoding.UTF8.GetBytes("0"));
|
||||||
await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("false"));
|
await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("false"));
|
||||||
await Fs.WriteFile($"{groupPath}/name", Encoding.UTF8.GetBytes(name));
|
await Fs.WriteFile($"{groupPath}/name", Encoding.UTF8.GetBytes(name));
|
||||||
|
await Fs.WriteFile($"{groupPath}/history_visible", Encoding.UTF8.GetBytes(historyVisible == "true" ? "true" : "false"));
|
||||||
|
|
||||||
// body is the key encrypted for the creator
|
// body is the key encrypted for the creator
|
||||||
await Fs.WriteFile($"{groupPath}/keys/0/{id};{DOMAIN}", Encoding.UTF8.GetBytes(body));
|
await Fs.WriteFile($"{groupPath}/keys/0/{id};{DOMAIN}", Encoding.UTF8.GetBytes(body));
|
||||||
|
|
@ -111,7 +113,7 @@ public class Groups
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Room.Requests.createLock.Release();
|
roomLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,6 +151,103 @@ public class Groups
|
||||||
return $"{ROOMS_DIR}/groups/{domain}/{id}";
|
return $"{ROOMS_DIR}/groups/{domain}/{id}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<string> GroupSettingsUpdate(string userId, string groupId, string name, string historyVisible)
|
||||||
|
{
|
||||||
|
string groupPath = GetGroupPath(groupId);
|
||||||
|
if (!Fs.Exists(groupPath)) return "error:group.not.found";
|
||||||
|
|
||||||
|
string memberPath = $"{groupPath}/members/{userId};{Utils.Utils.DOMAIN}";
|
||||||
|
|
||||||
|
SemaphoreSlim roomLock = Room.Requests.GetRoomLock(groupId);
|
||||||
|
await roomLock.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Fs.Exists(memberPath)) return "error:not.a.member";
|
||||||
|
if (!string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name)) return "error:invalid.name";
|
||||||
|
await Fs.WriteFile($"{groupPath}/name", Encoding.UTF8.GetBytes(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(historyVisible))
|
||||||
|
{
|
||||||
|
bool currentState = false;
|
||||||
|
if (Fs.Exists($"{groupPath}/history_visible"))
|
||||||
|
{
|
||||||
|
currentState = Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/history_visible")) == "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool newState = historyVisible == "true";
|
||||||
|
|
||||||
|
if (currentState != newState)
|
||||||
|
{
|
||||||
|
await Fs.WriteFile($"{groupPath}/history_visible", Encoding.UTF8.GetBytes(newState ? "true" : "false"));
|
||||||
|
|
||||||
|
string timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
|
||||||
|
|
||||||
|
Message infoMsg = new();
|
||||||
|
infoMsg.attachment = "";
|
||||||
|
infoMsg.author = userId;
|
||||||
|
infoMsg.timestamp = timestamp;
|
||||||
|
infoMsg.type = "larp.info.history";
|
||||||
|
infoMsg.content = newState ? "{blah(history.visible)}" : "{blah(history.hidden)}";
|
||||||
|
infoMsg.key = "";
|
||||||
|
|
||||||
|
ulong lastMessageId = 0;
|
||||||
|
if (Fs.Exists($"{groupPath}/messages/last"))
|
||||||
|
{
|
||||||
|
lastMessageId = ulong.Parse(Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/messages/last")));
|
||||||
|
}
|
||||||
|
ulong newMessageId = lastMessageId + 1;
|
||||||
|
|
||||||
|
infoMsg.pervious = lastMessageId.ToString();
|
||||||
|
|
||||||
|
await Fs.WriteFile($"{groupPath}/messages/{newMessageId}", Encoding.UTF8.GetBytes(JsonSerializer.Serialize(infoMsg, AppJsonSerializerContext.Default.Message)));
|
||||||
|
await Fs.WriteFile($"{groupPath}/messages/last", Encoding.UTF8.GetBytes(newMessageId.ToString()));
|
||||||
|
|
||||||
|
string[] members = Fs.ReadDirectory($"{groupPath}/members");
|
||||||
|
foreach (string memberWD in members)
|
||||||
|
{
|
||||||
|
string memberStr = Path.GetFileName(memberWD);
|
||||||
|
bool isLocal = Account.Utils.IsUserLocal(memberStr, out string d);
|
||||||
|
string memberId = Account.Utils.GetValidIdOrZero(memberStr);
|
||||||
|
if (isLocal)
|
||||||
|
{
|
||||||
|
if (memberId == userId)
|
||||||
|
{
|
||||||
|
await UpdateUserGroup(memberId, groupId, "true", timestamp);
|
||||||
|
await Utils.Utils.SendWsMessage(memberId, $"group_message:{groupId}:read");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
SemaphoreSlim userLock = Account.Utils.GetUserLock(memberId);
|
||||||
|
await userLock.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await UpdateUserGroup(memberId, groupId, "false", timestamp);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
userLock.Release();
|
||||||
|
}
|
||||||
|
await Utils.Utils.SendWsMessage(memberId, $"group_message:{groupId}:unread");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
roomLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "success";
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<string> GroupInfo(string groupId)
|
public static async Task<string> GroupInfo(string groupId)
|
||||||
{
|
{
|
||||||
string groupPath = GetGroupPath(groupId);
|
string groupPath = GetGroupPath(groupId);
|
||||||
|
|
@ -181,19 +280,26 @@ public class Groups
|
||||||
newRequired = Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/keys/newrequired"));
|
newRequired = Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/keys/newrequired"));
|
||||||
}
|
}
|
||||||
|
|
||||||
var info = new Universal5String
|
string historyVisible = "false";
|
||||||
|
if (Fs.Exists($"{groupPath}/history_visible"))
|
||||||
|
{
|
||||||
|
historyVisible = Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/history_visible"));
|
||||||
|
}
|
||||||
|
|
||||||
|
var info = new Universal6String
|
||||||
{
|
{
|
||||||
string1 = groupId,
|
string1 = groupId,
|
||||||
string2 = name,
|
string2 = name,
|
||||||
string3 = memberCount,
|
string3 = memberCount,
|
||||||
string4 = lastKeyIndex,
|
string4 = lastKeyIndex,
|
||||||
string5 = newRequired
|
string5 = newRequired,
|
||||||
|
string6 = historyVisible
|
||||||
};
|
};
|
||||||
|
|
||||||
return JsonSerializer.Serialize(info, AppJsonSerializerContext.Default.Universal5String);
|
return JsonSerializer.Serialize(info, AppJsonSerializerContext.Default.Universal6String);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<string> GroupInvite(string id, string groupId, string targetId)
|
public static async Task<string> GroupInvite(string id, string groupId, string targetId, string keysPayload = "")
|
||||||
{
|
{
|
||||||
bool isLocal = Account.Utils.IsUserLocal(targetId, out string domain);
|
bool isLocal = Account.Utils.IsUserLocal(targetId, out string domain);
|
||||||
string tId = Account.Utils.GetValidIdOrZero(targetId);
|
string tId = Account.Utils.GetValidIdOrZero(targetId);
|
||||||
|
|
@ -201,6 +307,54 @@ public class Groups
|
||||||
string groupPath = GetGroupPath(groupId);
|
string groupPath = GetGroupPath(groupId);
|
||||||
if (!Fs.Exists(groupPath)) return "error:group.not.found";
|
if (!Fs.Exists(groupPath)) return "error:group.not.found";
|
||||||
|
|
||||||
|
// Validate keys payload based on historyVisible
|
||||||
|
bool historyVisible = false;
|
||||||
|
string historyFile = $"{groupPath}/history_visible";
|
||||||
|
if (Fs.Exists(historyFile))
|
||||||
|
{
|
||||||
|
string histContent = Encoding.UTF8.GetString(await Fs.ReadFile(historyFile));
|
||||||
|
historyVisible = (histContent == "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!historyVisible)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(keysPayload))
|
||||||
|
{
|
||||||
|
return "error:history.hidden.keys.provided";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(keysPayload)) return "error:missing.keys";
|
||||||
|
|
||||||
|
System.Collections.Generic.Dictionary<string, string> keysDict = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
keysDict = JsonSerializer.Deserialize<System.Collections.Generic.Dictionary<string, string>>(keysPayload, AppJsonSerializerContext.Default.DictionaryStringString);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
if (keysDict == null) return "error:missing.keys";
|
||||||
|
|
||||||
|
string lastKeyFile = $"{groupPath}/keys/last";
|
||||||
|
if (Fs.Exists(lastKeyFile))
|
||||||
|
{
|
||||||
|
string lastKeyIndexStr = Encoding.UTF8.GetString(await Fs.ReadFile(lastKeyFile));
|
||||||
|
if (int.TryParse(lastKeyIndexStr, out int lastKeyIndex))
|
||||||
|
{
|
||||||
|
for (int i = 0; i <= lastKeyIndex; i++)
|
||||||
|
{
|
||||||
|
if (!keysDict.ContainsKey(i.ToString()))
|
||||||
|
{
|
||||||
|
return "error:missing.keys";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else return "error:missing.keys";
|
||||||
|
}
|
||||||
|
else return "error:missing.keys";
|
||||||
|
}
|
||||||
|
|
||||||
// Check if inviter is in group
|
// Check if inviter is in group
|
||||||
if (!Fs.Exists($"{groupPath}/members/{id};{DOMAIN}")) return "error:not.in.group";
|
if (!Fs.Exists($"{groupPath}/members/{id};{DOMAIN}")) return "error:not.in.group";
|
||||||
|
|
||||||
|
|
@ -219,9 +373,15 @@ public class Groups
|
||||||
string inviteFile = ACCOUNTS_DATA_DIR + $"/{id2}/groupinvites/recv/{groupId};{id}";
|
string inviteFile = ACCOUNTS_DATA_DIR + $"/{id2}/groupinvites/recv/{groupId};{id}";
|
||||||
if (Fs.Exists(inviteFile)) return "info:user.already.invited";
|
if (Fs.Exists(inviteFile)) return "info:user.already.invited";
|
||||||
|
|
||||||
byte[] timestamp = Encoding.UTF8.GetBytes(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString());
|
LarpixServer.Utils.Jsons.Universal2String inviteData = new LarpixServer.Utils.Jsons.Universal2String
|
||||||
|
{
|
||||||
|
string1 = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(),
|
||||||
|
string2 = keysPayload ?? ""
|
||||||
|
};
|
||||||
|
byte[] inviteBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(inviteData, AppJsonSerializerContext.Default.Universal2String));
|
||||||
|
|
||||||
Fs.CreateDirectory(ACCOUNTS_DATA_DIR + $"/{id}/groupinvites/sent");
|
Fs.CreateDirectory(ACCOUNTS_DATA_DIR + $"/{id}/groupinvites/sent");
|
||||||
await Fs.WriteFile(ACCOUNTS_DATA_DIR + $"/{id}/groupinvites/sent/{groupId};{id2}", timestamp);
|
await Fs.WriteFile(ACCOUNTS_DATA_DIR + $"/{id}/groupinvites/sent/{groupId};{id2}", inviteBytes);
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
|
|
@ -230,7 +390,7 @@ public class Groups
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Fs.CreateDirectory(ACCOUNTS_DATA_DIR + $"/{id2}/groupinvites/recv");
|
Fs.CreateDirectory(ACCOUNTS_DATA_DIR + $"/{id2}/groupinvites/recv");
|
||||||
await Fs.WriteFile(inviteFile, timestamp);
|
await Fs.WriteFile(inviteFile, inviteBytes);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -301,14 +461,17 @@ public class Groups
|
||||||
if (iId == "0") return "error:invalid.user";
|
if (iId == "0") return "error:invalid.user";
|
||||||
|
|
||||||
string inviteFile = ACCOUNTS_DATA_DIR + $"/{id}/groupinvites/recv/{groupId};{iId}";
|
string inviteFile = ACCOUNTS_DATA_DIR + $"/{id}/groupinvites/recv/{groupId};{iId}";
|
||||||
if (!Fs.Exists(inviteFile)) return "error:no.invite";
|
|
||||||
|
|
||||||
string groupPath = GetGroupPath(groupId);
|
string groupPath = GetGroupPath(groupId);
|
||||||
if (!Fs.Exists(groupPath)) return "error:group.not.found";
|
if (!Fs.Exists(groupPath)) return "error:group.not.found";
|
||||||
|
|
||||||
await Room.Requests.createLock.WaitAsync();
|
SemaphoreSlim roomLock = Room.Requests.GetRoomLock(groupId);
|
||||||
|
await roomLock.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (!Fs.Exists(inviteFile)) return "error:no.invite";
|
||||||
|
string inviteContent = Encoding.UTF8.GetString(await Fs.ReadFile(inviteFile));
|
||||||
|
|
||||||
// Delete the invites
|
// Delete the invites
|
||||||
Fs.DeleteFile(inviteFile);
|
Fs.DeleteFile(inviteFile);
|
||||||
if (Account.Utils.IsUserLocal(inviterId, out string domain))
|
if (Account.Utils.IsUserLocal(inviterId, out string domain))
|
||||||
|
|
@ -319,9 +482,57 @@ public class Groups
|
||||||
// Add to members
|
// Add to members
|
||||||
await Fs.WriteFile($"{groupPath}/members/{id};{DOMAIN}", Array.Empty<byte>());
|
await Fs.WriteFile($"{groupPath}/members/{id};{DOMAIN}", Array.Empty<byte>());
|
||||||
|
|
||||||
// Require new key
|
// Read invite data
|
||||||
await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("true"));
|
string keysPayload = "";
|
||||||
|
if (inviteContent.StartsWith("{"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = JsonSerializer.Deserialize<LarpixServer.Utils.Jsons.Universal2String>(inviteContent, LarpixServer.Utils.Jsons.AppJsonSerializerContext.Default.Universal2String);
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
keysPayload = data.string2 ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Require new key if history is not visible or no keys were provided
|
||||||
|
bool historyVisible = false;
|
||||||
|
if (Fs.Exists($"{groupPath}/history_visible"))
|
||||||
|
{
|
||||||
|
historyVisible = Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/history_visible")) == "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (historyVisible && !string.IsNullOrEmpty(keysPayload))
|
||||||
|
{
|
||||||
|
System.Collections.Generic.Dictionary<string, string> keysDict = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
keysDict = JsonSerializer.Deserialize<System.Collections.Generic.Dictionary<string, string>>(keysPayload, AppJsonSerializerContext.Default.DictionaryStringString);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
if (keysDict != null)
|
||||||
|
{
|
||||||
|
foreach (var kvp in keysDict)
|
||||||
|
{
|
||||||
|
string keyIndex = kvp.Key;
|
||||||
|
string keyData = kvp.Value;
|
||||||
|
string newKeyDir = $"{groupPath}/keys/{keyIndex}";
|
||||||
|
Fs.CreateDirectory(newKeyDir);
|
||||||
|
await Fs.WriteFile($"{newKeyDir}/{id};{DOMAIN}", Encoding.UTF8.GetBytes(keyData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("true"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("true"));
|
||||||
|
}
|
||||||
string timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
|
string timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
|
||||||
|
|
||||||
Message joinMsg = new();
|
Message joinMsg = new();
|
||||||
|
|
@ -343,11 +554,26 @@ public class Groups
|
||||||
|
|
||||||
await UpdateUserGroup(id, groupId, "true", timestamp);
|
await UpdateUserGroup(id, groupId, "true", timestamp);
|
||||||
|
|
||||||
|
// Notify all members (including the new one) via WebSocket
|
||||||
|
string[] currentMembers = Fs.ReadDirectory($"{groupPath}/members");
|
||||||
|
foreach (string memberWD in currentMembers)
|
||||||
|
{
|
||||||
|
string memberStr = Path.GetFileName(memberWD);
|
||||||
|
string memberIdStr = Account.Utils.GetValidIdOrZero(memberStr);
|
||||||
|
if (Account.Utils.IsUserLocal(memberStr, out string d))
|
||||||
|
{
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Utils.Utils.SendWsMessage(memberIdStr, $"group_message:{groupId}:unread");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return "success:joined";
|
return "success:joined";
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Room.Requests.createLock.Release();
|
roomLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -381,11 +607,13 @@ public class Groups
|
||||||
string groupPath = GetGroupPath(groupId);
|
string groupPath = GetGroupPath(groupId);
|
||||||
|
|
||||||
if (!Fs.Exists(groupPath)) return "error:group.not.found";
|
if (!Fs.Exists(groupPath)) return "error:group.not.found";
|
||||||
if (!Fs.Exists($"{groupPath}/members/{id};{DOMAIN}")) return "error:not.in.group";
|
|
||||||
|
|
||||||
await Room.Requests.createLock.WaitAsync();
|
SemaphoreSlim roomLock = Room.Requests.GetRoomLock(groupId);
|
||||||
|
await roomLock.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (!Fs.Exists($"{groupPath}/members/{id};{DOMAIN}")) return "error:not.in.group";
|
||||||
|
|
||||||
// 1. Check if newrequired is true
|
// 1. Check if newrequired is true
|
||||||
string newRequiredPath = $"{groupPath}/keys/newrequired";
|
string newRequiredPath = $"{groupPath}/keys/newrequired";
|
||||||
if (!Fs.Exists(newRequiredPath) || Encoding.UTF8.GetString(await Fs.ReadFile(newRequiredPath)) != "true")
|
if (!Fs.Exists(newRequiredPath) || Encoding.UTF8.GetString(await Fs.ReadFile(newRequiredPath)) != "true")
|
||||||
|
|
@ -438,7 +666,7 @@ public class Groups
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Room.Requests.createLock.Release();
|
roomLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -480,11 +708,20 @@ public class Groups
|
||||||
catch { contentBytes = Encoding.UTF8.GetBytes(contentBase64); }
|
catch { contentBytes = Encoding.UTF8.GetBytes(contentBase64); }
|
||||||
string dirPath = $"{Utils.Utils.ROOMS_DIR}/groups/{domain}/{id}/storage/public";
|
string dirPath = $"{Utils.Utils.ROOMS_DIR}/groups/{domain}/{id}/storage/public";
|
||||||
string path = $"{dirPath}/{entry}";
|
string path = $"{dirPath}/{entry}";
|
||||||
|
SemaphoreSlim roomLock = Room.Requests.GetRoomLock(groupId);
|
||||||
|
await roomLock.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
Fs.CreateDirectory(dirPath);
|
Fs.CreateDirectory(dirPath);
|
||||||
long currentTotalSize = Utils.Utils.GetDirectorySize(dirPath);
|
long currentTotalSize = Utils.Utils.GetDirectorySize(dirPath);
|
||||||
long fileExistingSize = Fs.Exists(path) ? new System.IO.FileInfo(path).Length : 0;
|
long fileExistingSize = Fs.Exists(path) ? new System.IO.FileInfo(path).Length : 0;
|
||||||
if (currentTotalSize - fileExistingSize + contentBytes.Length > Utils.Utils.PUBLIC_STORAGE_LIMIT) return "error:storage.limit.exceeded";
|
if (currentTotalSize - fileExistingSize + contentBytes.Length > Utils.Utils.PUBLIC_STORAGE_LIMIT) return "error:storage.limit.exceeded";
|
||||||
await Fs.WriteFile(path, contentBytes);
|
await Fs.WriteFile(path, contentBytes);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
roomLock.Release();
|
||||||
|
}
|
||||||
return "success:storage.entry.updated";
|
return "success:storage.entry.updated";
|
||||||
}
|
}
|
||||||
public static async Task<string> GetGroupPrivateStorageEntry(string userId, string groupId, string entry)
|
public static async Task<string> GetGroupPrivateStorageEntry(string userId, string groupId, string entry)
|
||||||
|
|
@ -512,14 +749,60 @@ public class Groups
|
||||||
catch { contentBytes = Encoding.UTF8.GetBytes(contentBase64); }
|
catch { contentBytes = Encoding.UTF8.GetBytes(contentBase64); }
|
||||||
string dirPath = $"{Utils.Utils.ROOMS_DIR}/groups/{domain}/{id}/storage/private";
|
string dirPath = $"{Utils.Utils.ROOMS_DIR}/groups/{domain}/{id}/storage/private";
|
||||||
string path = $"{dirPath}/{entry}";
|
string path = $"{dirPath}/{entry}";
|
||||||
|
SemaphoreSlim roomLock = Room.Requests.GetRoomLock(groupId);
|
||||||
|
await roomLock.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
Fs.CreateDirectory(dirPath);
|
Fs.CreateDirectory(dirPath);
|
||||||
long currentTotalSize = Utils.Utils.GetDirectorySize(dirPath);
|
long currentTotalSize = Utils.Utils.GetDirectorySize(dirPath);
|
||||||
long fileExistingSize = Fs.Exists(path) ? new System.IO.FileInfo(path).Length : 0;
|
long fileExistingSize = Fs.Exists(path) ? new System.IO.FileInfo(path).Length : 0;
|
||||||
if (currentTotalSize - fileExistingSize + contentBytes.Length > Utils.Utils.PRIVATE_STORAGE_LIMIT) return "error:storage.limit.exceeded";
|
if (currentTotalSize - fileExistingSize + contentBytes.Length > Utils.Utils.PRIVATE_STORAGE_LIMIT) return "error:storage.limit.exceeded";
|
||||||
await Fs.WriteFile(path, contentBytes);
|
await Fs.WriteFile(path, contentBytes);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
roomLock.Release();
|
||||||
|
}
|
||||||
return "success:storage.entry.updated";
|
return "success:storage.entry.updated";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<string> GroupDelete(string groupId)
|
||||||
|
{
|
||||||
|
string[] parts = groupId.Split(';');
|
||||||
|
string id = parts[0];
|
||||||
|
string domain = parts.Length > 1 ? parts[1] : Utils.Utils.DOMAIN;
|
||||||
|
string groupPath = $"{Utils.Utils.ROOMS_DIR}/groups/{domain}/{id}";
|
||||||
|
|
||||||
|
if (!Fs.Exists(groupPath)) return "error:group.not.found";
|
||||||
|
|
||||||
|
SemaphoreSlim roomLock = Room.Requests.GetRoomLock(groupId);
|
||||||
|
await roomLock.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string membersDir = $"{groupPath}/members";
|
||||||
|
if (Fs.Exists(membersDir))
|
||||||
|
{
|
||||||
|
string[] memberFiles = Fs.ReadDirectory(membersDir);
|
||||||
|
foreach (string memberFile in memberFiles)
|
||||||
|
{
|
||||||
|
string memberId = memberFile.Split(';')[0];
|
||||||
|
string userGroupPath = $"{Utils.Utils.ACCOUNTS_DATA_DIR}/{memberId}/groups/{id};{domain}";
|
||||||
|
if (Fs.Exists(userGroupPath))
|
||||||
|
{
|
||||||
|
Fs.DeleteFile(userGroupPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fs.DeleteDirectory(groupPath);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
roomLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "success";
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<string> GroupLeave(string userId, string groupId)
|
public static async Task<string> GroupLeave(string userId, string groupId)
|
||||||
{
|
{
|
||||||
string[] parts = groupId.Split(';');
|
string[] parts = groupId.Split(';');
|
||||||
|
|
@ -531,12 +814,14 @@ public class Groups
|
||||||
return "error:group.not.found";
|
return "error:group.not.found";
|
||||||
|
|
||||||
string memberPath = $"{groupPath}/members/{userId};{Utils.Utils.DOMAIN}";
|
string memberPath = $"{groupPath}/members/{userId};{Utils.Utils.DOMAIN}";
|
||||||
|
|
||||||
|
SemaphoreSlim roomLock = Room.Requests.GetRoomLock(groupId);
|
||||||
|
await roomLock.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
if (!Fs.Exists(memberPath))
|
if (!Fs.Exists(memberPath))
|
||||||
return "error:not.a.member";
|
return "error:not.a.member";
|
||||||
|
|
||||||
await Room.Requests.createLock.WaitAsync();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Remove from members
|
// Remove from members
|
||||||
Fs.DeleteFile(memberPath);
|
Fs.DeleteFile(memberPath);
|
||||||
|
|
||||||
|
|
@ -547,7 +832,7 @@ public class Groups
|
||||||
Fs.DeleteFile(userGroupPath);
|
Fs.DeleteFile(userGroupPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Require new key
|
// Always require new key when someone leaves
|
||||||
await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("true"));
|
await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("true"));
|
||||||
|
|
||||||
// Add info message
|
// Add info message
|
||||||
|
|
@ -569,10 +854,32 @@ public class Groups
|
||||||
}
|
}
|
||||||
await Fs.WriteFile($"{groupPath}/messages/last", Encoding.UTF8.GetBytes(msgId.ToString()));
|
await Fs.WriteFile($"{groupPath}/messages/last", Encoding.UTF8.GetBytes(msgId.ToString()));
|
||||||
await Fs.WriteFile($"{groupPath}/messages/{msgId}", Encoding.UTF8.GetBytes(JsonSerializer.Serialize(msg, AppJsonSerializerContext.Default.Message)));
|
await Fs.WriteFile($"{groupPath}/messages/{msgId}", Encoding.UTF8.GetBytes(JsonSerializer.Serialize(msg, AppJsonSerializerContext.Default.Message)));
|
||||||
|
|
||||||
|
// Notify remaining members via WebSocket
|
||||||
|
string[] currentMembers = Fs.ReadDirectory($"{groupPath}/members");
|
||||||
|
foreach (string memberWD in currentMembers)
|
||||||
|
{
|
||||||
|
string memberStr = Path.GetFileName(memberWD);
|
||||||
|
string memberIdStr = Account.Utils.GetValidIdOrZero(memberStr);
|
||||||
|
if (Account.Utils.IsUserLocal(memberStr, out string d))
|
||||||
|
{
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Utils.Utils.SendWsMessage(memberIdStr, $"group_message:{groupId}:unread");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Room.Requests.createLock.Release();
|
roomLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If members count == 0, delete group folder and we are done.
|
||||||
|
// Doing it outside the lock since GroupDelete takes the lock again.
|
||||||
|
if (Fs.ReadDirectory($"{groupPath}/members").Length == 0)
|
||||||
|
{
|
||||||
|
await GroupDelete(groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return "success";
|
return "success";
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,23 @@ namespace LarpixServer.Room;
|
||||||
public class Requests
|
public class Requests
|
||||||
{
|
{
|
||||||
public static SemaphoreSlim createLock = new SemaphoreSlim(1, 1);
|
public static SemaphoreSlim createLock = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
private static SemaphoreSlim[]? _roomLocksArray = null;
|
||||||
|
|
||||||
|
public static SemaphoreSlim GetRoomLock(string roomId)
|
||||||
|
{
|
||||||
|
if (_roomLocksArray == null)
|
||||||
|
{
|
||||||
|
int size = LOCK_SIZE > 0 ? LOCK_SIZE : 65536;
|
||||||
|
var newArray = Enumerable.Range(0, size).Select(_ => new SemaphoreSlim(1, 1)).ToArray();
|
||||||
|
Interlocked.CompareExchange(ref _roomLocksArray, newArray, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
int hash = roomId.GetHashCode();
|
||||||
|
int index = (hash & 0x7FFFFFFF) % _roomLocksArray.Length;
|
||||||
|
return _roomLocksArray[index];
|
||||||
|
}
|
||||||
|
|
||||||
public const int MAX_DM_KEY_SIZE = 8192;
|
public const int MAX_DM_KEY_SIZE = 8192;
|
||||||
|
|
||||||
private static bool IsSafeDmId(string dmId)
|
private static bool IsSafeDmId(string dmId)
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,14 @@ public class Shared
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<string> MessageSend(string id, string roomType, string roomId, string content, string attachment, string responded, string keyIndex = "0")
|
public static async Task<string> MessageSend(string id, string roomType, string roomId, string content, string attachment, string responded, string keyIndex = "0")
|
||||||
|
{
|
||||||
|
string msgDir = $"{GetRoomPath(roomType, roomId)}/messages";
|
||||||
|
|
||||||
|
long nextId = 0;
|
||||||
|
Message msg = new Message();
|
||||||
|
SemaphoreSlim roomLock = Room.Requests.GetRoomLock(roomId);
|
||||||
|
await roomLock.WaitAsync();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (!await IsMemberOfRoom(id, roomType, roomId)) return "error:forbidden";
|
if (!await IsMemberOfRoom(id, roomType, roomId)) return "error:forbidden";
|
||||||
|
|
||||||
|
|
@ -154,13 +162,6 @@ public class Shared
|
||||||
return $"error:message.too.long:{sizeStr}";
|
return $"error:message.too.long:{sizeStr}";
|
||||||
}
|
}
|
||||||
|
|
||||||
string msgDir = $"{GetRoomPath(roomType, roomId)}/messages";
|
|
||||||
|
|
||||||
long nextId = 0;
|
|
||||||
Message msg = new Message();
|
|
||||||
await Room.Requests.createLock.WaitAsync();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
long last = 0;
|
long last = 0;
|
||||||
if (Fs.Exists($"{msgDir}/last"))
|
if (Fs.Exists($"{msgDir}/last"))
|
||||||
{
|
{
|
||||||
|
|
@ -218,7 +219,7 @@ public class Shared
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Room.Requests.createLock.Release();
|
roomLock.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] members = Fs.ReadDirectory($"{GetRoomPath(roomType, roomId)}/members");
|
string[] members = Fs.ReadDirectory($"{GetRoomPath(roomType, roomId)}/members");
|
||||||
|
|
@ -266,6 +267,10 @@ public class Shared
|
||||||
string msgPath = $"{GetRoomPath(roomType, roomId)}/messages/{msgId}";
|
string msgPath = $"{GetRoomPath(roomType, roomId)}/messages/{msgId}";
|
||||||
if (!Fs.Exists(msgPath)) return "error:message.not.found";
|
if (!Fs.Exists(msgPath)) return "error:message.not.found";
|
||||||
|
|
||||||
|
SemaphoreSlim roomLock = Room.Requests.GetRoomLock(roomId);
|
||||||
|
await roomLock.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
Message msg = JsonSerializer.Deserialize(
|
Message msg = JsonSerializer.Deserialize(
|
||||||
Encoding.UTF8.GetString(await Fs.ReadFile(msgPath)),
|
Encoding.UTF8.GetString(await Fs.ReadFile(msgPath)),
|
||||||
AppJsonSerializerContext.Default.Message
|
AppJsonSerializerContext.Default.Message
|
||||||
|
|
@ -293,6 +298,11 @@ public class Shared
|
||||||
{
|
{
|
||||||
Fs.DeleteDirectory(attDir);
|
Fs.DeleteDirectory(attDir);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
roomLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
string[] members = Fs.ReadDirectory($"{GetRoomPath(roomType, roomId)}/members");
|
string[] members = Fs.ReadDirectory($"{GetRoomPath(roomType, roomId)}/members");
|
||||||
foreach(string memberWD in members)
|
foreach(string memberWD in members)
|
||||||
|
|
@ -331,7 +341,8 @@ public class Shared
|
||||||
string reactDir = $"{GetRoomPath(roomType, roomId)}/reactions/{msgId}";
|
string reactDir = $"{GetRoomPath(roomType, roomId)}/reactions/{msgId}";
|
||||||
if (!Fs.Exists(reactDir)) Fs.CreateDirectory(reactDir);
|
if (!Fs.Exists(reactDir)) Fs.CreateDirectory(reactDir);
|
||||||
|
|
||||||
await Room.Requests.createLock.WaitAsync();
|
SemaphoreSlim roomLock = Room.Requests.GetRoomLock(roomId);
|
||||||
|
await roomLock.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
long last = 0;
|
long last = 0;
|
||||||
|
|
@ -346,7 +357,7 @@ public class Shared
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Room.Requests.createLock.Release();
|
roomLock.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] members = Fs.ReadDirectory($"{GetRoomPath(roomType, roomId)}/members");
|
string[] members = Fs.ReadDirectory($"{GetRoomPath(roomType, roomId)}/members");
|
||||||
|
|
@ -411,7 +422,8 @@ public class Shared
|
||||||
string pinnedFilePath = $"{pinnedDir}/{msgId}";
|
string pinnedFilePath = $"{pinnedDir}/{msgId}";
|
||||||
bool isNowPinned = false;
|
bool isNowPinned = false;
|
||||||
|
|
||||||
await Room.Requests.createLock.WaitAsync();
|
SemaphoreSlim roomLock = Room.Requests.GetRoomLock(roomId);
|
||||||
|
await roomLock.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Fs.Exists(pinnedFilePath))
|
if (Fs.Exists(pinnedFilePath))
|
||||||
|
|
@ -450,7 +462,7 @@ public class Shared
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Room.Requests.createLock.Release();
|
roomLock.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] members = Fs.ReadDirectory($"{GetRoomPath(roomType, roomId)}/members");
|
string[] members = Fs.ReadDirectory($"{GetRoomPath(roomType, roomId)}/members");
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using System.Collections.Generic;
|
||||||
[JsonSerializable(typeof(Universal3String))]
|
[JsonSerializable(typeof(Universal3String))]
|
||||||
[JsonSerializable(typeof(Universal4String))]
|
[JsonSerializable(typeof(Universal4String))]
|
||||||
[JsonSerializable(typeof(Universal5String))]
|
[JsonSerializable(typeof(Universal5String))]
|
||||||
|
[JsonSerializable(typeof(Universal6String))]
|
||||||
[JsonSerializable(typeof(ServerInfo))]
|
[JsonSerializable(typeof(ServerInfo))]
|
||||||
[JsonSerializable(typeof(Message))]
|
[JsonSerializable(typeof(Message))]
|
||||||
[JsonSerializable(typeof(Dictionary<string, string>))]
|
[JsonSerializable(typeof(Dictionary<string, string>))]
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,15 @@ public class Universal5String
|
||||||
public string string5 { get; set; }
|
public string string5 { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Universal6String
|
||||||
|
{
|
||||||
|
public string string1 { get; set; }
|
||||||
|
public string string2 { get; set; }
|
||||||
|
public string string3 { get; set; }
|
||||||
|
public string string4 { get; set; }
|
||||||
|
public string string5 { get; set; }
|
||||||
|
public string string6 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class KeyExchangePayload
|
public class KeyExchangePayload
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue