2024-09-28 19:41:34 +01:00
package main
import (
2024-10-15 19:06:05 +01:00
library "git.ailur.dev/ailur/fg-library/v2"
2024-10-16 18:05:52 +01:00
nucleusLibrary "git.ailur.dev/ailur/fg-nucleus-library"
2024-09-28 19:41:34 +01:00
2024-11-03 17:03:35 +00:00
"errors"
2024-09-28 19:41:34 +01:00
"os"
"time"
2024-11-03 17:03:35 +00:00
"database/sql"
"path/filepath"
2024-09-28 19:41:34 +01:00
"github.com/google/uuid"
)
var ServiceInformation = library . Service {
Name : "Storage" ,
Permissions : library . Permissions {
Authenticate : false , // This service does not require authentication
Database : true , // This service requires database access to store quotas
BlobStorage : false , // This service *is* the blob storage
InterServiceCommunication : true , // This service does require inter-service communication
} ,
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000003" ) ,
}
2024-10-13 19:20:19 +01:00
var conn library . Database
2024-09-28 19:41:34 +01:00
2024-11-03 17:03:35 +00:00
func logFunc ( message string , messageType uint64 , information library . ServiceInitializationInformation ) {
// Log the error message to the logger service
information . Outbox <- library . InterServiceMessage {
ServiceID : ServiceInformation . ServiceID ,
ForServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000002" ) , // Logger service
MessageType : messageType ,
SentAt : time . Now ( ) ,
Message : message ,
}
}
func respondError ( message string , information library . ServiceInitializationInformation , myFault bool , serviceID uuid . UUID ) {
// Respond with an error message
var err uint64 = 1
if myFault {
// Log the error message to the logger service
logFunc ( message , 2 , information )
err = 2
2024-10-13 19:20:19 +01:00
}
2024-11-03 17:03:35 +00:00
information . Outbox <- library . InterServiceMessage {
ServiceID : ServiceInformation . ServiceID ,
ForServiceID : serviceID ,
MessageType : err ,
SentAt : time . Now ( ) ,
Message : errors . New ( message ) ,
}
}
func checkUserExists ( userID uuid . UUID ) bool {
// Check if a user exists in the database
var userCheck [ ] byte
err := conn . DB . QueryRow ( "SELECT id FROM users WHERE id = $1" , userID ) . Scan ( & userCheck )
2024-09-28 19:41:34 +01:00
if err != nil {
2024-09-29 19:03:13 +01:00
if errors . Is ( err , sql . ErrNoRows ) {
2024-11-03 17:03:35 +00:00
return false
} else {
return false
2024-09-29 19:03:13 +01:00
}
2024-11-03 17:03:35 +00:00
} else {
return uuid . Must ( uuid . FromBytes ( userCheck ) ) == userID
2024-09-28 19:41:34 +01:00
}
}
2024-11-03 17:03:35 +00:00
// addQuota can be used with a negative quota to remove quota from a user
func addQuota ( information library . ServiceInitializationInformation , message library . InterServiceMessage ) {
// Add more quota to a user
if checkUserExists ( message . Message . ( nucleusLibrary . Quota ) . User ) {
_ , err := conn . DB . Exec ( "UPDATE users SET quota = quota + $1 WHERE id = $2" , message . Message . ( nucleusLibrary . Quota ) . Bytes , message . Message . ( nucleusLibrary . Quota ) . User )
if err != nil {
respondError ( err . Error ( ) , information , true , message . ServiceID )
2024-09-29 19:03:13 +01:00
}
} else {
2024-11-03 17:03:35 +00:00
_ , err := conn . DB . Exec ( "INSERT INTO users (id, quota, reserved) VALUES ($1, $2, 0)" , message . Message . ( nucleusLibrary . Quota ) . User , int64 ( information . Configuration [ "defaultQuota" ] . ( float64 ) ) + message . Message . ( nucleusLibrary . Quota ) . Bytes )
2024-09-29 19:03:13 +01:00
if err != nil {
2024-11-03 17:03:35 +00:00
respondError ( err . Error ( ) , information , true , message . ServiceID )
2024-09-29 19:03:13 +01:00
}
2024-09-28 19:41:34 +01:00
}
}
2024-11-03 17:03:35 +00:00
// And so does addReserved
func addReserved ( information library . ServiceInitializationInformation , message library . InterServiceMessage ) {
// Add more reserved space to a user
if checkUserExists ( message . Message . ( nucleusLibrary . Quota ) . User ) {
2024-11-04 17:17:49 +00:00
// Check if the user has enough space
quota , err := getQuota ( message . Message . ( nucleusLibrary . Quota ) . User )
if err != nil {
respondError ( err . Error ( ) , information , true , message . ServiceID )
}
used , err := getUsed ( message . Message . ( nucleusLibrary . Quota ) . User , information )
if err != nil {
respondError ( err . Error ( ) , information , true , message . ServiceID )
}
if used + message . Message . ( nucleusLibrary . Quota ) . Bytes > quota {
respondError ( "insufficient storage" , information , false , message . ServiceID )
return
}
_ , err = conn . DB . Exec ( "UPDATE users SET reserved = reserved + $1 WHERE id = $2" , message . Message . ( nucleusLibrary . Quota ) . Bytes , message . Message . ( nucleusLibrary . Quota ) . User )
2024-11-03 17:03:35 +00:00
if err != nil {
respondError ( err . Error ( ) , information , true , message . ServiceID )
}
} else {
2024-11-04 17:17:49 +00:00
// Check if the user has enough space
if int64 ( information . Configuration [ "defaultQuota" ] . ( float64 ) ) < message . Message . ( nucleusLibrary . Quota ) . Bytes {
respondError ( "insufficient storage" , information , false , message . ServiceID )
return
}
2024-11-03 17:03:35 +00:00
_ , err := conn . DB . Exec ( "INSERT INTO users (id, quota, reserved) VALUES ($1, $2, $3)" , message . Message . ( nucleusLibrary . Quota ) . User , int64 ( information . Configuration [ "defaultQuota" ] . ( float64 ) ) , message . Message . ( nucleusLibrary . Quota ) . Bytes )
if err != nil {
respondError ( err . Error ( ) , information , true , message . ServiceID )
}
2024-09-28 19:41:34 +01:00
}
}
2024-11-03 17:03:35 +00:00
func getQuota ( userID uuid . UUID ) ( int64 , error ) {
// Get the quota for a user
var quota int64
err := conn . DB . QueryRow ( "SELECT quota FROM users WHERE id = $1" , userID ) . Scan ( & quota )
2024-09-28 19:41:34 +01:00
if err != nil {
2024-11-03 17:03:35 +00:00
return 0 , err
}
return quota , nil
}
2024-09-28 19:41:34 +01:00
2024-11-03 17:03:35 +00:00
func getUsed ( userID uuid . UUID , information library . ServiceInitializationInformation ) ( int64 , error ) {
// Get the used space for a user by first getting the reserved space from file storage
var used int64
err := filepath . Walk ( filepath . Join ( information . Configuration [ "path" ] . ( string ) , userID . String ( ) ) , func ( path string , entry os . FileInfo , err error ) error {
if err != nil {
return err
2024-09-28 19:41:34 +01:00
}
2024-11-03 17:03:35 +00:00
used += entry . Size ( )
2024-09-28 19:41:34 +01:00
2024-11-03 17:03:35 +00:00
return nil
} )
if err != nil {
return 0 , err
2024-09-28 19:41:34 +01:00
}
2024-11-03 17:03:35 +00:00
// Then add the reserved space from the database
var reserved int64
err = conn . DB . QueryRow ( "SELECT reserved FROM users WHERE id = $1" , userID ) . Scan ( & reserved )
if err != nil {
return 0 , err
2024-10-20 09:31:59 +01:00
}
2024-11-03 17:03:35 +00:00
return used + reserved , nil
}
2024-09-28 19:41:34 +01:00
2024-11-03 17:03:35 +00:00
func modifyFile ( information library . ServiceInitializationInformation , message library . InterServiceMessage ) {
// Check if the file already exists
path := filepath . Join ( information . Configuration [ "path" ] . ( string ) , message . Message . ( nucleusLibrary . File ) . User . String ( ) , message . Message . ( nucleusLibrary . File ) . Name )
2024-09-28 19:41:34 +01:00
2024-11-03 17:03:35 +00:00
_ , err := os . Stat ( path )
if os . IsNotExist ( err ) {
// Delete the file
err = os . Remove ( path )
if err != nil {
respondError ( err . Error ( ) , information , true , message . ServiceID )
2024-09-28 19:41:34 +01:00
}
}
2024-11-03 17:03:35 +00:00
// Check if the user has enough space
quota , err := getQuota ( message . Message . ( nucleusLibrary . File ) . User )
2024-09-28 19:41:34 +01:00
if err != nil {
2024-11-03 17:03:35 +00:00
respondError ( err . Error ( ) , information , true , message . ServiceID )
2024-09-28 19:41:34 +01:00
}
2024-11-03 17:03:35 +00:00
used , err := getUsed ( message . Message . ( nucleusLibrary . File ) . User , information )
2024-09-28 19:41:34 +01:00
if err != nil {
2024-11-03 17:03:35 +00:00
respondError ( err . Error ( ) , information , true , message . ServiceID )
}
if used + int64 ( len ( message . Message . ( nucleusLibrary . File ) . Bytes ) ) > quota {
respondError ( "insufficient storage" , information , false , message . ServiceID )
return
}
2024-09-28 19:41:34 +01:00
2024-11-03 17:03:35 +00:00
// Add a file to the user's storage
file , err := os . OpenFile ( path , os . O_CREATE | os . O_WRONLY , 0644 )
if err != nil {
respondError ( err . Error ( ) , information , true , message . ServiceID )
2024-09-28 19:41:34 +01:00
}
// Write the file
2024-11-03 17:03:35 +00:00
_ , err = file . Write ( message . Message . ( [ ] byte ) )
2024-09-28 19:41:34 +01:00
if err != nil {
2024-11-03 17:03:35 +00:00
respondError ( err . Error ( ) , information , true , message . ServiceID )
2024-09-28 19:41:34 +01:00
}
// Close the file
2024-11-03 17:03:35 +00:00
err = file . Close ( )
2024-09-28 19:41:34 +01:00
if err != nil {
2024-11-03 17:03:35 +00:00
respondError ( err . Error ( ) , information , true , message . ServiceID )
2024-09-28 19:41:34 +01:00
}
2024-11-03 17:03:35 +00:00
}
2024-09-28 19:41:34 +01:00
2024-11-03 17:03:35 +00:00
func getFile ( information library . ServiceInitializationInformation , message library . InterServiceMessage ) {
// Check if the file exists
path := filepath . Join ( information . Configuration [ "path" ] . ( string ) , message . Message . ( nucleusLibrary . File ) . User . String ( ) , message . Message . ( nucleusLibrary . File ) . Name )
_ , err := os . Stat ( path )
if os . IsNotExist ( err ) {
respondError ( "file not found" , information , false , message . ServiceID )
return
2024-09-28 19:41:34 +01:00
}
// Open the file
2024-11-03 17:03:35 +00:00
file , err := os . Open ( path )
2024-09-28 19:41:34 +01:00
if err != nil {
2024-11-03 17:03:35 +00:00
respondError ( err . Error ( ) , information , true , message . ServiceID )
2024-09-28 19:41:34 +01:00
}
2024-11-03 17:03:35 +00:00
// Respond with the file
// It's their responsibility to close the file
2024-09-28 19:41:34 +01:00
information . Outbox <- library . InterServiceMessage {
2024-10-02 17:07:01 +01:00
ServiceID : ServiceInformation . ServiceID ,
2024-11-03 17:03:35 +00:00
ForServiceID : message . ServiceID ,
MessageType : 1 ,
2024-09-28 19:41:34 +01:00
SentAt : time . Now ( ) ,
2024-11-03 17:03:35 +00:00
Message : file ,
2024-09-28 19:41:34 +01:00
}
}
2024-11-03 17:03:35 +00:00
// processInterServiceMessages listens for incoming messages and processes them
func processInterServiceMessages ( information library . ServiceInitializationInformation ) {
// Listen for incoming messages
for {
message := <- information . Inbox
switch message . MessageType {
case 1 :
// Add quota
addQuota ( information , message )
case 2 :
// Add reserved
addReserved ( information , message )
case 3 :
// Modify file
modifyFile ( information , message )
case 4 :
// Get file
getFile ( information , message )
default :
// Respond with an error message
respondError ( "invalid message type" , information , false , message . ServiceID )
2024-09-28 19:41:34 +01:00
}
}
}
2024-10-20 19:51:16 +01:00
func Main ( information library . ServiceInitializationInformation ) {
2024-09-29 19:03:13 +01:00
// Initiate a connection to the database
// Call service ID 1 to get the database connection information
information . Outbox <- library . InterServiceMessage {
2024-10-02 17:07:01 +01:00
ServiceID : ServiceInformation . ServiceID ,
2024-09-29 19:03:13 +01:00
ForServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) , // Service initialization service
MessageType : 1 , // Request connection information
SentAt : time . Now ( ) ,
Message : nil ,
}
// Wait for the response
response := <- information . Inbox
if response . MessageType == 2 {
// This is the connection information
// Set up the database connection
2024-10-13 19:20:19 +01:00
conn = response . Message . ( library . Database )
2024-09-29 19:03:13 +01:00
// Create the quotas table if it doesn't exist
2024-10-13 19:20:19 +01:00
if conn . DBType == library . Sqlite {
2024-11-03 17:03:35 +00:00
_ , err := conn . DB . Exec ( "CREATE TABLE IF NOT EXISTS quotas (id BLOB PRIMARY KEY, quota BIGINT, reserved BIGINT)" )
2024-10-13 19:20:19 +01:00
if err != nil {
logFunc ( err . Error ( ) , 3 , information )
}
} else {
2024-11-03 17:03:35 +00:00
_ , err := conn . DB . Exec ( "CREATE TABLE IF NOT EXISTS users (id UUID PRIMARY KEY, quota BIGINT, reserved BIGINT)" )
2024-10-13 19:20:19 +01:00
if err != nil {
logFunc ( err . Error ( ) , 3 , information )
}
2024-09-29 19:03:13 +01:00
}
} else {
// This is an error message
// Log the error message to the logger service
logFunc ( response . Message . ( error ) . Error ( ) , 3 , information )
}
2024-11-03 17:03:35 +00:00
// Listen for incoming messages
go processInterServiceMessages ( information )
2024-09-28 19:41:34 +01:00
}