2024-09-28 19:41:34 +01:00
package main
import (
2024-11-15 18:51:33 +00:00
"bytes"
2025-01-08 13:47:05 +00:00
library "git.ailur.dev/ailur/fg-library/v3"
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" ) ,
}
2025-01-08 13:47:05 +00:00
var (
conn library . Database
loggerService = uuid . MustParse ( "00000000-0000-0000-0000-000000000002" )
)
2024-09-28 19:41:34 +01:00
2025-01-08 18:31:34 +00:00
func logFunc ( message string , messageType library . MessageCode , information * library . ServiceInitializationInformation ) {
2025-01-08 13:47:05 +00:00
// Log the message to the logger service
information . SendISMessage ( loggerService , messageType , message )
2024-11-03 17:03:35 +00:00
}
2025-01-08 18:31:34 +00:00
func respondError ( message library . InterServiceMessage , err error , information * library . ServiceInitializationInformation , myFault bool ) {
2024-11-03 17:03:35 +00:00
// Respond with an error message
2025-01-08 13:47:05 +00:00
var errCode = library . BadRequest
2024-11-03 17:03:35 +00:00
if myFault {
// Log the error message to the logger service
2025-01-08 13:47:05 +00:00
logFunc ( err . Error ( ) , 2 , information )
errCode = library . InternalError
2024-11-03 17:03:35 +00:00
}
2025-01-08 13:47:05 +00:00
2025-01-08 18:31:34 +00:00
message . Respond ( errCode , err , information )
2024-11-03 17:03:35 +00:00
}
func checkUserExists ( userID uuid . UUID ) bool {
// Check if a user exists in the database
var userCheck [ ] byte
2024-11-15 18:51:33 +00:00
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 {
2024-11-15 18:51:33 +00:00
return bytes . Equal ( 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
2025-01-08 18:31:34 +00:00
func addQuota ( information * library . ServiceInitializationInformation , message library . InterServiceMessage ) {
2024-11-03 17:03:35 +00:00
// Add more quota to a user
2024-11-15 18:51:33 +00:00
userID := message . Message . ( nucleusLibrary . Quota ) . User
if checkUserExists ( userID ) {
2024-11-03 17:03:35 +00:00
_ , 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 {
2025-01-08 13:47:05 +00:00
respondError ( message , err , information , true )
2024-09-29 19:03:13 +01:00
}
} else {
2024-11-15 18:51:33 +00:00
_ , err := conn . DB . Exec ( "INSERT INTO users (id, quota, reserved) VALUES ($1, $2, 0)" , userID [ : ] , int64 ( information . Configuration [ "defaultQuota" ] . ( int ) ) + message . Message . ( nucleusLibrary . Quota ) . Bytes )
2024-09-29 19:03:13 +01:00
if err != nil {
2025-01-08 13:47:05 +00:00
respondError ( message , err , information , true )
2024-09-29 19:03:13 +01:00
}
2024-09-28 19:41:34 +01:00
}
2024-11-15 18:51:33 +00:00
// Success
2025-01-08 18:31:34 +00:00
message . Respond ( library . Success , nil , information )
2024-09-28 19:41:34 +01:00
}
2024-11-03 17:03:35 +00:00
// And so does addReserved
2025-01-08 18:31:34 +00:00
func addReserved ( information * library . ServiceInitializationInformation , message library . InterServiceMessage ) {
2024-11-03 17:03:35 +00:00
// Add more reserved space to a user
2024-11-15 18:51:33 +00:00
userID := message . Message . ( nucleusLibrary . Quota ) . User
if checkUserExists ( userID ) {
2024-11-04 17:17:49 +00:00
// Check if the user has enough space
2024-11-15 18:51:33 +00:00
quota , err := getQuota ( userID )
2024-11-04 17:17:49 +00:00
if err != nil {
2025-01-08 13:47:05 +00:00
respondError ( message , err , information , true )
2024-11-04 17:17:49 +00:00
}
2024-11-15 18:51:33 +00:00
used , err := getUsed ( userID , information )
2024-11-04 17:17:49 +00:00
if err != nil {
2025-01-08 13:47:05 +00:00
respondError ( message , err , information , true )
2024-11-04 17:17:49 +00:00
}
if used + message . Message . ( nucleusLibrary . Quota ) . Bytes > quota {
2025-01-08 13:47:05 +00:00
respondError ( message , errors . New ( "insufficient storage" ) , information , false )
2024-11-04 17:17:49 +00:00
return
}
2024-11-15 18:51:33 +00:00
_ , err = conn . DB . Exec ( "UPDATE users SET reserved = reserved + $1 WHERE id = $2" , message . Message . ( nucleusLibrary . Quota ) . Bytes , userID [ : ] )
2024-11-03 17:03:35 +00:00
if err != nil {
2025-01-08 13:47:05 +00:00
respondError ( message , err , information , true )
2024-11-03 17:03:35 +00:00
}
} else {
2024-11-04 17:17:49 +00:00
// Check if the user has enough space
2024-11-15 18:37:44 +00:00
if int64 ( information . Configuration [ "defaultQuota" ] . ( int ) ) < message . Message . ( nucleusLibrary . Quota ) . Bytes {
2025-01-08 13:47:05 +00:00
respondError ( message , errors . New ( "insufficient storage" ) , information , false )
2024-11-04 17:17:49 +00:00
return
}
2024-11-15 18:51:33 +00:00
_ , err := conn . DB . Exec ( "INSERT INTO users (id, quota, reserved) VALUES ($1, $2, $3)" , userID [ : ] , int64 ( information . Configuration [ "defaultQuota" ] . ( int ) ) , message . Message . ( nucleusLibrary . Quota ) . Bytes )
2024-11-03 17:03:35 +00:00
if err != nil {
2025-01-08 13:47:05 +00:00
respondError ( message , err , information , true )
2024-11-03 17:03:35 +00:00
}
2024-09-28 19:41:34 +01:00
}
2024-11-15 18:51:33 +00:00
// Success
2025-01-08 18:31:34 +00:00
message . Respond ( library . Success , nil , information )
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
2024-11-15 18:51:33 +00:00
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
2025-01-08 18:31:34 +00:00
func getUsed ( userID uuid . UUID , information * library . ServiceInitializationInformation ) ( int64 , error ) {
2024-11-03 17:03:35 +00:00
// Get the used space for a user by first getting the reserved space from file storage
2024-11-15 18:54:28 +00:00
_ , err := os . Stat ( filepath . Join ( information . Configuration [ "path" ] . ( string ) , userID . String ( ) ) )
if os . IsNotExist ( err ) {
// Create the directory
err = os . Mkdir ( filepath . Join ( information . Configuration [ "path" ] . ( string ) , userID . String ( ) ) , 0755 )
if err != nil {
return 0 , err
}
}
2024-11-03 17:03:35 +00:00
var used int64
2024-11-15 18:54:28 +00:00
err = filepath . Walk ( filepath . Join ( information . Configuration [ "path" ] . ( string ) , userID . String ( ) ) , func ( path string , entry os . FileInfo , err error ) error {
2024-11-03 17:03:35 +00:00
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
2024-11-15 18:51:33 +00:00
err = conn . DB . QueryRow ( "SELECT reserved FROM users WHERE id = $1" , userID [ : ] ) . Scan ( & reserved )
2024-11-03 17:03:35 +00:00
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
2025-01-08 18:31:34 +00:00
func modifyFile ( information * library . ServiceInitializationInformation , message library . InterServiceMessage ) {
2024-11-03 17:03:35 +00:00
// 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
2025-01-08 13:47:05 +00:00
logFunc ( path , 0 , information )
2024-11-03 17:03:35 +00:00
_ , err := os . Stat ( path )
2025-01-08 13:47:05 +00:00
if err == nil {
2024-11-03 17:03:35 +00:00
// Delete the file
err = os . Remove ( path )
if err != nil {
2025-01-08 13:47:05 +00:00
respondError ( message , err , information , true )
2024-09-28 19:41:34 +01:00
}
2025-01-08 13:47:05 +00:00
} else if ! os . IsNotExist ( err ) {
respondError ( message , err , information , true )
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 {
2025-01-08 13:47:05 +00:00
respondError ( message , err , information , true )
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 {
2025-01-08 13:47:05 +00:00
respondError ( message , err , information , true )
2024-11-03 17:03:35 +00:00
}
if used + int64 ( len ( message . Message . ( nucleusLibrary . File ) . Bytes ) ) > quota {
2025-01-08 13:47:05 +00:00
respondError ( message , errors . New ( "insufficient storage" ) , information , false )
2024-11-03 17:03:35 +00:00
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 {
2025-01-08 13:47:05 +00:00
respondError ( message , err , information , true )
2024-09-28 19:41:34 +01:00
}
// Write the file
2024-12-08 16:36:29 +00:00
_ , err = file . Write ( message . Message . ( nucleusLibrary . File ) . Bytes )
2024-09-28 19:41:34 +01:00
if err != nil {
2025-01-08 13:47:05 +00:00
respondError ( message , err , information , true )
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 {
2025-01-08 13:47:05 +00:00
respondError ( message , err , information , true )
2024-09-28 19:41:34 +01:00
}
2024-11-15 18:51:33 +00:00
// Success
information . Outbox <- library . InterServiceMessage {
ServiceID : ServiceInformation . ServiceID ,
ForServiceID : message . ServiceID ,
MessageType : 0 ,
SentAt : time . Now ( ) ,
Message : nil ,
}
2024-11-03 17:03:35 +00:00
}
2024-09-28 19:41:34 +01:00
2025-01-08 18:31:34 +00:00
func getFile ( information * library . ServiceInitializationInformation , message library . InterServiceMessage ) {
2024-11-03 17:03:35 +00:00
// 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 ) {
2025-01-08 13:47:05 +00:00
respondError ( message , errors . New ( "file not found" ) , information , false )
2024-11-03 17:03:35 +00:00
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 {
2025-01-08 13:47:05 +00:00
respondError ( message , err , information , true )
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 ,
2024-11-15 18:51:33 +00:00
MessageType : 0 ,
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
}
}
2025-01-08 18:31:34 +00:00
func deleteFile ( information * library . ServiceInitializationInformation , message library . InterServiceMessage ) {
2024-12-09 21:09:00 +00:00
// 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 ) {
2025-01-08 13:47:05 +00:00
respondError ( message , errors . New ( "file not found" ) , information , false )
2024-12-09 21:09:00 +00:00
return
}
// Delete the file
err = os . Remove ( path )
if err != nil {
2025-01-08 13:47:05 +00:00
respondError ( message , err , information , true )
2024-12-09 21:09:00 +00:00
}
// Success
information . Outbox <- library . InterServiceMessage {
ServiceID : ServiceInformation . ServiceID ,
ForServiceID : message . ServiceID ,
MessageType : 0 ,
SentAt : time . Now ( ) ,
Message : nil ,
}
}
2024-11-03 17:03:35 +00:00
// processInterServiceMessages listens for incoming messages and processes them
2025-01-08 18:31:34 +00:00
func processInterServiceMessages ( information * library . ServiceInitializationInformation ) {
2024-11-03 17:03:35 +00:00
// Listen for incoming messages
for {
2025-01-08 13:47:05 +00:00
message := information . AcceptMessage ( )
2024-11-03 17:03:35 +00:00
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 )
2024-12-09 21:09:00 +00:00
case 5 :
deleteFile ( information , message )
2024-11-03 17:03:35 +00:00
default :
// Respond with an error message
2025-01-08 13:47:05 +00:00
respondError ( message , errors . New ( "invalid message type" ) , information , false )
2024-09-28 19:41:34 +01:00
}
}
}
2025-01-08 18:31:34 +00:00
func Main ( information * library . ServiceInitializationInformation ) {
2025-01-08 13:47:05 +00:00
// Start up the ISM processor
information . StartISProcessor ( )
// Get the database connection
conn , err := information . GetDatabase ( )
if err != nil {
logFunc ( err . Error ( ) , 3 , information )
2024-09-29 19:03:13 +01:00
}
2025-01-08 13:47:05 +00:00
// Create the quotas table if it doesn't exist
if conn . DBType == library . Sqlite {
_ , err := conn . DB . Exec ( "CREATE TABLE IF NOT EXISTS users (id BLOB PRIMARY KEY, quota BIGINT, reserved BIGINT)" )
if err != nil {
logFunc ( err . Error ( ) , 3 , information )
2024-09-29 19:03:13 +01:00
}
} else {
2025-01-08 13:47:05 +00:00
_ , err := conn . DB . Exec ( "CREATE TABLE IF NOT EXISTS users (id BYTEA PRIMARY KEY, quota BIGINT, reserved BIGINT)" )
if err != nil {
logFunc ( err . Error ( ) , 3 , information )
}
2024-09-29 19:03:13 +01:00
}
2024-11-03 17:03:35 +00:00
// Listen for incoming messages
go processInterServiceMessages ( information )
2024-09-28 19:41:34 +01:00
}