client for test
This commit is contained in:
@@ -1,16 +1,15 @@
|
|||||||
import jsonfile from 'jsonfile'
|
import jsonfile from 'jsonfile'
|
||||||
import { promises as fs } from 'fs'
|
import { promises as fs } from 'fs'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { MemoryManager } from './helpers/memory_manager'
|
import { DatabaseScheme } from './schemes/database_scheme'
|
||||||
import { QueueManager, FileItemTask } from './helpers/queue_manager'
|
import { FileItemTask } from './schemes/local_resources_scheme'
|
||||||
|
|
||||||
const defaultData = {
|
const defaultData: DatabaseScheme = {
|
||||||
app_config: {
|
app_config: {
|
||||||
app_type: 'client',
|
app_type: 'client',
|
||||||
user_info: {
|
user_info: {
|
||||||
id: '',
|
id: '',
|
||||||
email: '',
|
email: '',
|
||||||
password: '',
|
|
||||||
departmentId: '',
|
departmentId: '',
|
||||||
name: '',
|
name: '',
|
||||||
},
|
},
|
||||||
@@ -25,6 +24,7 @@ const defaultData = {
|
|||||||
network: {
|
network: {
|
||||||
usersInLan: [],
|
usersInLan: [],
|
||||||
serverIp: '',
|
serverIp: '',
|
||||||
|
announcement: '',
|
||||||
},
|
},
|
||||||
local_resources: {
|
local_resources: {
|
||||||
directory_schemes: {
|
directory_schemes: {
|
||||||
@@ -32,18 +32,18 @@ const defaultData = {
|
|||||||
department: { id: uuidv4(), path: '', structure: {}, totalSize: 0 },
|
department: { id: uuidv4(), path: '', structure: {}, totalSize: 0 },
|
||||||
shared: { id: uuidv4(), path: '', structure: {}, totalSize: 0 },
|
shared: { id: uuidv4(), path: '', structure: {}, totalSize: 0 },
|
||||||
},
|
},
|
||||||
|
task_schemes: {
|
||||||
|
send_file_queue: [],
|
||||||
|
receive_file_queue: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export class JsonDatabase<T, Q> {
|
export class Database {
|
||||||
private readonly filePath: string
|
private readonly filePath: string
|
||||||
private memoryStore: MemoryManager<Q>
|
|
||||||
private queueStore: QueueManager
|
|
||||||
|
|
||||||
constructor(filePath: string) {
|
constructor(filePath: string) {
|
||||||
this.filePath = filePath
|
this.filePath = filePath
|
||||||
this.memoryStore = new MemoryManager<Q>()
|
|
||||||
this.queueStore = new QueueManager()
|
|
||||||
|
|
||||||
// Ensure the file exists and is not empty
|
// Ensure the file exists and is not empty
|
||||||
this.ensureFileExists().then(() => {
|
this.ensureFileExists().then(() => {
|
||||||
@@ -76,12 +76,12 @@ export class JsonDatabase<T, Q> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read JSON from file
|
// Read JSON from file
|
||||||
async read(): Promise<T> {
|
async read(): Promise<DatabaseScheme> {
|
||||||
try {
|
try {
|
||||||
return await jsonfile.readFile(this.filePath)
|
return await jsonfile.readFile(this.filePath)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.code === 'ENOENT') {
|
if (error.code === 'ENOENT') {
|
||||||
return defaultData as T // Return defaultData if file does not exist
|
return defaultData
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@@ -89,53 +89,90 @@ export class JsonDatabase<T, Q> {
|
|||||||
|
|
||||||
// Update JSON file with atomic write
|
// Update JSON file with atomic write
|
||||||
async update(
|
async update(
|
||||||
updateCallback: (data: Awaited<T>) => Awaited<T> | Promise<Awaited<T>>,
|
updateCallback: (
|
||||||
|
data: Awaited<DatabaseScheme>,
|
||||||
|
) => Awaited<DatabaseScheme> | Promise<Awaited<DatabaseScheme>>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let data = await this.read()
|
let data = await this.read()
|
||||||
data = await updateCallback(data)
|
data = await updateCallback(data)
|
||||||
await jsonfile.writeFile(this.filePath, data, { spaces: 2 })
|
await jsonfile.writeFile(this.filePath, data, { spaces: 2 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate and return a new UUID
|
// **Queue Methods Integrated Here**
|
||||||
generateUUID(): string {
|
|
||||||
return uuidv4()
|
/** Push a task to the send queue */
|
||||||
|
async pushToSendQueue(task: FileItemTask): Promise<void> {
|
||||||
|
await this.update((data) => {
|
||||||
|
data.local_resources.task_schemes.send_file_queue.push(task)
|
||||||
|
return data
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// MemoryStore Operations
|
/** Push a task to the receive queue */
|
||||||
setMemory(uuid: string, value: Q): void {
|
async pushToReceiveQueue(task: FileItemTask): Promise<void> {
|
||||||
this.memoryStore.set(uuid, value)
|
await this.update((data) => {
|
||||||
|
data.local_resources.task_schemes.receive_file_queue.push(task)
|
||||||
|
return data
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getMemory(uuid: string): Q | undefined {
|
/** Pop (remove) the first task from the send queue */
|
||||||
return this.memoryStore.get(uuid)
|
async popFromSendQueue(): Promise<FileItemTask | undefined> {
|
||||||
|
let poppedTask: FileItemTask | undefined
|
||||||
|
await this.update((data) => {
|
||||||
|
poppedTask = data.local_resources.task_schemes.send_file_queue.shift()
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
return poppedTask
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteMemory(uuid: string): boolean {
|
/** Pop (remove) the first task from the receive queue */
|
||||||
return this.memoryStore.delete(uuid)
|
async popFromReceiveQueue(): Promise<FileItemTask | undefined> {
|
||||||
|
let poppedTask: FileItemTask | undefined
|
||||||
|
await this.update((data) => {
|
||||||
|
poppedTask = data.local_resources.task_schemes.receive_file_queue.shift()
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
return poppedTask
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMemory(uuid: string): boolean {
|
/** View the first task in the send queue without removing it */
|
||||||
return this.memoryStore.has(uuid)
|
async seekSendQueue(): Promise<FileItemTask | undefined> {
|
||||||
|
const data = await this.read()
|
||||||
|
return data.local_resources.task_schemes.send_file_queue[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue Operations
|
/** View the first task in the receive queue without removing it */
|
||||||
pushQueue(task: FileItemTask): void {
|
async seekReceiveQueue(): Promise<FileItemTask | undefined> {
|
||||||
this.queueStore.push(task)
|
const data = await this.read()
|
||||||
|
return data.local_resources.task_schemes.receive_file_queue[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
popQueue(): FileItemTask | undefined {
|
/** Get the length of the send queue */
|
||||||
return this.queueStore.pop()
|
async sendQueueSize(): Promise<number> {
|
||||||
|
const data = await this.read()
|
||||||
|
return data.local_resources.task_schemes.send_file_queue.length
|
||||||
}
|
}
|
||||||
|
|
||||||
seekQueue(): FileItemTask | undefined {
|
/** Get the length of the receive queue */
|
||||||
return this.queueStore.seek()
|
async receiveQueueSize(): Promise<number> {
|
||||||
|
const data = await this.read()
|
||||||
|
return data.local_resources.task_schemes.receive_file_queue.length
|
||||||
}
|
}
|
||||||
|
|
||||||
queueSize(): number {
|
/** Clear all tasks from the send queue */
|
||||||
return this.queueStore.size()
|
async clearSendQueue(): Promise<void> {
|
||||||
|
await this.update((data) => {
|
||||||
|
data.local_resources.task_schemes.send_file_queue = []
|
||||||
|
return data
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
clearQueue(): void {
|
/** Clear all tasks from the receive queue */
|
||||||
this.queueStore.clear()
|
async clearReceiveQueue(): Promise<void> {
|
||||||
|
await this.update((data) => {
|
||||||
|
data.local_resources.task_schemes.receive_file_queue = []
|
||||||
|
return data
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
export class MemoryManager<T> {
|
|
||||||
private storage: Map<string, T>
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.storage = new Map<string, T>()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a value in the memory store
|
|
||||||
set(uuid: string, value: T): void {
|
|
||||||
this.storage.set(uuid, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a value from the memory store
|
|
||||||
get(uuid: string): T | undefined {
|
|
||||||
return this.storage.get(uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a key exists
|
|
||||||
has(uuid: string): boolean {
|
|
||||||
return this.storage.has(uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete a key-value pair
|
|
||||||
delete(uuid: string): boolean {
|
|
||||||
return this.storage.delete(uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all keys
|
|
||||||
keys(): string[] {
|
|
||||||
return Array.from(this.storage.keys())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all values
|
|
||||||
values(): T[] {
|
|
||||||
return Array.from(this.storage.values())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear all stored values
|
|
||||||
clear(): void {
|
|
||||||
this.storage.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
export interface FileItemTask {
|
|
||||||
ip: string
|
|
||||||
path: string
|
|
||||||
userName: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export class QueueManager {
|
|
||||||
private queue: FileItemTask[]
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.queue = []
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a new task to the queue
|
|
||||||
push(task: FileItemTask): void {
|
|
||||||
this.queue.push(task)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove and return the first task (FIFO)
|
|
||||||
pop(): FileItemTask | undefined {
|
|
||||||
return this.queue.shift()
|
|
||||||
}
|
|
||||||
|
|
||||||
// View the first task without removing it
|
|
||||||
seek(): FileItemTask | undefined {
|
|
||||||
return this.queue[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get queue length
|
|
||||||
size(): number {
|
|
||||||
return this.queue.length
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear all tasks
|
|
||||||
clear(): void {
|
|
||||||
this.queue = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export interface LocalResourcesScheme {
|
export interface LocalResourcesScheme {
|
||||||
directory_schemes: DirectorySchemes
|
directory_schemes: DirectorySchemes
|
||||||
|
task_schemes: TaskSchemes
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DirectorySchemes {
|
export interface DirectorySchemes {
|
||||||
@@ -14,3 +15,14 @@ export interface DirectoryInfo {
|
|||||||
structure: any
|
structure: any
|
||||||
totalSize: number
|
totalSize: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TaskSchemes {
|
||||||
|
send_file_queue: FileItemTask[]
|
||||||
|
receive_file_queue: FileItemTask[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileItemTask {
|
||||||
|
ip: string
|
||||||
|
path: string
|
||||||
|
userName: string
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,20 +4,19 @@ import { FileEncryptor } from './file_encryptor'
|
|||||||
import { TcpCommunicator } from './tcp_communicator'
|
import { TcpCommunicator } from './tcp_communicator'
|
||||||
import { operationCodes } from '../network/operation_codes'
|
import { operationCodes } from '../network/operation_codes'
|
||||||
import { ParsedMessage } from '../network/message_handler'
|
import { ParsedMessage } from '../network/message_handler'
|
||||||
import { JsonDatabase } from '../database/database'
|
import { Database } from '../database/database'
|
||||||
import { DatabaseScheme } from '../database/schemes/database_scheme'
|
|
||||||
import { NetworkUserScheme } from '../database/schemes/network_scheme'
|
import { NetworkUserScheme } from '../database/schemes/network_scheme'
|
||||||
|
|
||||||
export class BackupManager {
|
export class BackupManager {
|
||||||
private fileEncryptor: FileEncryptor | null = null
|
private fileEncryptor: FileEncryptor | null = null
|
||||||
private readonly db: JsonDatabase<DatabaseScheme, any>
|
private readonly db: Database
|
||||||
private readonly clientPort: number
|
private readonly clientPort: number
|
||||||
private isBusy: boolean = false
|
private isBusy: boolean = false
|
||||||
private intervalId: NodeJS.Timeout | null = null
|
private intervalId: NodeJS.Timeout | null = null
|
||||||
private stopRequested: boolean = false
|
private stopRequested: boolean = false
|
||||||
|
|
||||||
constructor(pathToDatabaseFile: string, clientPort: number) {
|
constructor(pathToDatabaseFile: string, clientPort: number) {
|
||||||
this.db = new JsonDatabase(pathToDatabaseFile)
|
this.db = new Database(pathToDatabaseFile)
|
||||||
this.clientPort = clientPort
|
this.clientPort = clientPort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ import path from 'path'
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import { ParsedMessage } from '../network/message_handler'
|
import { ParsedMessage } from '../network/message_handler'
|
||||||
import { JsonDatabase } from '../database/database'
|
import { Database } from '../database/database'
|
||||||
import { DatabaseScheme } from '../database/schemes/database_scheme'
|
|
||||||
|
|
||||||
export class BackupRetrievalWorker {
|
export class BackupRetrievalWorker {
|
||||||
private db: JsonDatabase<DatabaseScheme, any>
|
private db: Database
|
||||||
private readonly clientPort: number
|
private readonly clientPort: number
|
||||||
private readonly destinationPath: string
|
private readonly destinationPath: string
|
||||||
private encryptionKey: Buffer | null = null
|
private encryptionKey: Buffer | null = null
|
||||||
@@ -18,7 +17,7 @@ export class BackupRetrievalWorker {
|
|||||||
private isBusy: boolean = false
|
private isBusy: boolean = false
|
||||||
|
|
||||||
constructor(pathToDatabaseFile: string, clientPort: number, destinationPath: string) {
|
constructor(pathToDatabaseFile: string, clientPort: number, destinationPath: string) {
|
||||||
this.db = new JsonDatabase<DatabaseScheme, any>(pathToDatabaseFile)
|
this.db = new Database(pathToDatabaseFile)
|
||||||
this.clientPort = clientPort
|
this.clientPort = clientPort
|
||||||
this.destinationPath = destinationPath
|
this.destinationPath = destinationPath
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ import path from 'path'
|
|||||||
import { TcpCommunicator } from './tcp_communicator'
|
import { TcpCommunicator } from './tcp_communicator'
|
||||||
import { operationCodes } from '../network/operation_codes'
|
import { operationCodes } from '../network/operation_codes'
|
||||||
import { ParsedMessage } from '../network/message_handler'
|
import { ParsedMessage } from '../network/message_handler'
|
||||||
import { JsonDatabase } from '../database/database'
|
import { Database } from '../database/database'
|
||||||
import { DatabaseScheme } from '../database/schemes/database_scheme'
|
|
||||||
|
|
||||||
export class DepartmentSharer {
|
export class DepartmentSharer {
|
||||||
private readonly db: JsonDatabase<DatabaseScheme, any>
|
private readonly db: Database
|
||||||
private departmentDirectory: string | null = null
|
private departmentDirectory: string | null = null
|
||||||
private readonly clientPort: number
|
private readonly clientPort: number
|
||||||
private isBusy: boolean = false
|
private isBusy: boolean = false
|
||||||
@@ -16,7 +15,7 @@ export class DepartmentSharer {
|
|||||||
private intervalId: NodeJS.Timeout | null = null
|
private intervalId: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
constructor(pathToDatabaseFile: string, clientPort: number) {
|
constructor(pathToDatabaseFile: string, clientPort: number) {
|
||||||
this.db = new JsonDatabase(pathToDatabaseFile)
|
this.db = new Database(pathToDatabaseFile)
|
||||||
this.clientPort = clientPort
|
this.clientPort = clientPort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { promises as fs, watch, FSWatcher } from 'fs'
|
import { promises as fs, watch, FSWatcher } from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { JsonDatabase } from '../database/database'
|
import { Database } from '../database/database'
|
||||||
import { DatabaseScheme } from '../database/schemes/database_scheme'
|
|
||||||
|
|
||||||
export class DirectoryWatcher {
|
export class DirectoryWatcher {
|
||||||
private readonly watchers: Map<string, FSWatcher> // Stores watchers with directory ID as the key
|
private readonly watchers: Map<string, FSWatcher> // Stores watchers with directory ID as the key
|
||||||
private readonly db: JsonDatabase<DatabaseScheme, any>
|
private readonly db: Database
|
||||||
private totalSizes: Map<string, number> // Stores total sizes per directory ID
|
private totalSizes: Map<string, number> // Stores total sizes per directory ID
|
||||||
|
|
||||||
constructor(pathToDatabaseFile: string) {
|
constructor(pathToDatabaseFile: string) {
|
||||||
this.db = new JsonDatabase<DatabaseScheme, any>(pathToDatabaseFile)
|
this.db = new Database(pathToDatabaseFile)
|
||||||
this.watchers = new Map<string, FSWatcher>()
|
this.watchers = new Map<string, FSWatcher>()
|
||||||
this.totalSizes = new Map<string, number>()
|
this.totalSizes = new Map<string, number>()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import path from 'path'
|
|||||||
import { TcpCommunicator } from './tcp_communicator'
|
import { TcpCommunicator } from './tcp_communicator'
|
||||||
import { operationCodes } from '../network/operation_codes'
|
import { operationCodes } from '../network/operation_codes'
|
||||||
import { ParsedMessage } from '../network/message_handler'
|
import { ParsedMessage } from '../network/message_handler'
|
||||||
import { JsonDatabase } from '../database/database'
|
import { Database } from '../database/database'
|
||||||
import { DatabaseScheme } from '../database/schemes/database_scheme'
|
|
||||||
|
|
||||||
interface FileSendTask {
|
interface FileSendTask {
|
||||||
ip: string
|
ip: string
|
||||||
@@ -13,7 +12,7 @@ interface FileSendTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class FileSharer {
|
export class FileSharer {
|
||||||
private readonly db: JsonDatabase<DatabaseScheme, any>
|
private readonly db: Database
|
||||||
private readonly clientPort: number
|
private readonly clientPort: number
|
||||||
private isBusy: boolean = false
|
private isBusy: boolean = false
|
||||||
private tcpCommunicator: TcpCommunicator | null = null
|
private tcpCommunicator: TcpCommunicator | null = null
|
||||||
@@ -21,7 +20,7 @@ export class FileSharer {
|
|||||||
private intervalId: NodeJS.Timeout | null = null
|
private intervalId: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
constructor(pathToDatabaseFile: string, clientPort: number) {
|
constructor(pathToDatabaseFile: string, clientPort: number) {
|
||||||
this.db = new JsonDatabase(pathToDatabaseFile)
|
this.db = new Database(pathToDatabaseFile)
|
||||||
this.clientPort = clientPort
|
this.clientPort = clientPort
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +42,9 @@ export class FileSharer {
|
|||||||
|
|
||||||
// Method to process the queue
|
// Method to process the queue
|
||||||
private async processQueue(): Promise<void> {
|
private async processQueue(): Promise<void> {
|
||||||
for (let i = 0; i < this.db.queueSize(); i++) {
|
const queueSize = await this.db.sendQueueSize()
|
||||||
const task = this.db.popQueue()
|
for (let i = 0; i < queueSize; i++) {
|
||||||
|
const task = await this.db.popFromSendQueue()
|
||||||
if (task) {
|
if (task) {
|
||||||
this.log(`Processing task for file: ${task.path} to IP: ${task.ip}`)
|
this.log(`Processing task for file: ${task.path} to IP: ${task.ip}`)
|
||||||
const success = await this.sendFile(task)
|
const success = await this.sendFile(task)
|
||||||
@@ -54,7 +54,7 @@ export class FileSharer {
|
|||||||
`Failed to send file: ${task.path} to IP: ${task.ip}. Re-adding to queue.`,
|
`Failed to send file: ${task.path} to IP: ${task.ip}. Re-adding to queue.`,
|
||||||
'error',
|
'error',
|
||||||
)
|
)
|
||||||
this.db.pushQueue(task)
|
await this.db.pushToSendQueue(task)
|
||||||
} else {
|
} else {
|
||||||
this.log(`File sent successfully: ${task.path} to IP: ${task.ip}`)
|
this.log(`File sent successfully: ${task.path} to IP: ${task.ip}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ import { UdpClient } from '../network/udp/udp_client'
|
|||||||
import { TcpCommunicator } from './tcp_communicator'
|
import { TcpCommunicator } from './tcp_communicator'
|
||||||
import { operationCodes } from '../network/operation_codes'
|
import { operationCodes } from '../network/operation_codes'
|
||||||
import { ParsedMessage } from '../network/message_handler'
|
import { ParsedMessage } from '../network/message_handler'
|
||||||
import { JsonDatabase } from '../database/database'
|
import { Database } from '../database/database'
|
||||||
import { DatabaseScheme } from '../database/schemes/database_scheme'
|
|
||||||
|
|
||||||
export class NetworkScanner {
|
export class NetworkScanner {
|
||||||
private db: JsonDatabase<DatabaseScheme, any>
|
private db: Database
|
||||||
private readonly udpPort: number
|
private readonly udpPort: number
|
||||||
private readonly tcpPort: number
|
private readonly tcpPort: number
|
||||||
private readonly okPage: string
|
private readonly okPage: string
|
||||||
@@ -27,7 +26,7 @@ export class NetworkScanner {
|
|||||||
errorPage: string,
|
errorPage: string,
|
||||||
databaseResetPage: string,
|
databaseResetPage: string,
|
||||||
) {
|
) {
|
||||||
this.db = new JsonDatabase<DatabaseScheme, any>(pathToDatabaseFile)
|
this.db = new Database(pathToDatabaseFile)
|
||||||
this.udpPort = udpPort
|
this.udpPort = udpPort
|
||||||
this.tcpPort = tcpPort
|
this.tcpPort = tcpPort
|
||||||
this.okPage = okPage
|
this.okPage = okPage
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
import { JsonDatabase } from '../database/database'
|
import { Database } from '../database/database'
|
||||||
import { DatabaseScheme } from '../database/schemes/database_scheme'
|
|
||||||
import { NetworkUserScheme } from '../database/schemes/network_scheme'
|
import { NetworkUserScheme } from '../database/schemes/network_scheme'
|
||||||
import { TcpCommunicator } from './tcp_communicator'
|
import { TcpCommunicator } from './tcp_communicator'
|
||||||
import { operationCodes } from '../network/operation_codes'
|
import { operationCodes } from '../network/operation_codes'
|
||||||
import { ParsedMessage } from '../network/message_handler'
|
import { ParsedMessage } from '../network/message_handler'
|
||||||
|
|
||||||
export class UsersInfoFetcher {
|
export class UsersInfoFetcher {
|
||||||
private db: JsonDatabase<DatabaseScheme, any>
|
private db: Database
|
||||||
private tcpCommunicator: TcpCommunicator | null = null
|
private tcpCommunicator: TcpCommunicator | null = null
|
||||||
private readonly clientPort: number
|
private readonly clientPort: number
|
||||||
private intervalId: NodeJS.Timeout | null = null
|
private intervalId: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
constructor(pathToDatabaseFile: string, clientPort: number) {
|
constructor(pathToDatabaseFile: string, clientPort: number) {
|
||||||
this.db = new JsonDatabase(pathToDatabaseFile)
|
this.db = new Database(pathToDatabaseFile)
|
||||||
this.clientPort = clientPort
|
this.clientPort = clientPort
|
||||||
this.tcpCommunicator = null
|
this.tcpCommunicator = null
|
||||||
}
|
}
|
||||||
@@ -39,21 +38,21 @@ export class UsersInfoFetcher {
|
|||||||
const response = await this.waitForResponse()
|
const response = await this.waitForResponse()
|
||||||
|
|
||||||
if (response && response.metaInfo) {
|
if (response && response.metaInfo) {
|
||||||
await this.db.update((dbData) => {
|
await this.db.update((data) => {
|
||||||
const userIndex = dbData.network.usersInLan.findIndex((user) => user.ip === ip)
|
const userIndex = data.network.usersInLan.findIndex((user) => user.ip === ip)
|
||||||
|
|
||||||
if (userIndex !== -1) {
|
if (userIndex !== -1) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
dbData.network.usersInLan[userIndex].id = response.metaInfo.id
|
data.network.usersInLan[userIndex].id = response.metaInfo.id
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
dbData.network.usersInLan[userIndex].name = response.metaInfo.name
|
data.network.usersInLan[userIndex].name = response.metaInfo.name
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
dbData.network.usersInLan[userIndex].departmentId = response.metaInfo.departmentId
|
data.network.usersInLan[userIndex].departmentId = response.metaInfo.departmentId
|
||||||
} else {
|
} else {
|
||||||
this.log(`User with IP ${ip} not found in the database.`, 'error')
|
this.log(`User with IP ${ip} not found in the database.`, 'error')
|
||||||
}
|
}
|
||||||
|
|
||||||
return dbData
|
return data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
export interface FileItemTask {
|
|
||||||
ip: string
|
|
||||||
path: string
|
|
||||||
userName: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const compareFnFileItemTask = (task1: FileItemTask, task2: FileItemTask) =>
|
|
||||||
task1.ip === task2.ip && task1.path === task2.path
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
interface PoolRequest {
|
|
||||||
type: PoolOperation // Renamed to PoolOperation
|
|
||||||
clientId: string // Add clientId to the request
|
|
||||||
data: PoolDataBundle
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PoolDataBundle {
|
|
||||||
port?: number
|
|
||||||
ip?: string
|
|
||||||
operationCode?: string // Keep operationCode here
|
|
||||||
metaInfo?: { [key: string]: any }
|
|
||||||
fileContent?: Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
type PoolOperation = 'open' | 'send' | 'close' // Define the allowed PoolOperations
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
interface RegisteredClient {
|
|
||||||
id: string
|
|
||||||
ip: string
|
|
||||||
port: number
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// Define a type for the message structure
|
|
||||||
interface WorkerMessage {
|
|
||||||
type: 'changeContent' | 'showAlert' | 'log'
|
|
||||||
page?: string
|
|
||||||
message?: string
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
import { JsonDatabase } from '../database/database'
|
import { Database } from '../database/database'
|
||||||
import { DatabaseScheme } from '../database/schemes/database_scheme'
|
import { DatabaseScheme } from '../database/schemes/database_scheme'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { EncryptionKeyScheme, UserInfoScheme } from '../database/schemes/app_config_scheme'
|
import { EncryptionKeyScheme, UserInfoScheme } from '../database/schemes/app_config_scheme'
|
||||||
import { FileItemTask } from '../database/helpers/queue_manager'
|
import {DirectoryInfo, DirectorySchemes, FileItemTask} from '../database/schemes/local_resources_scheme'
|
||||||
import { DirectoryInfo, DirectorySchemes } from '../database/schemes/local_resources_scheme'
|
|
||||||
|
|
||||||
const pathToDatabaseFile = path.join(__dirname, '..', 'database.json')
|
const pathToDatabaseFile = path.join(__dirname, '..', 'database.json')
|
||||||
|
|
||||||
class IpcDatabaseHandler {
|
class IpcDatabaseHandler {
|
||||||
private readonly db: JsonDatabase<DatabaseScheme, any>
|
private readonly db: Database
|
||||||
|
|
||||||
constructor(pathToDatabaseFile: string) {
|
constructor(pathToDatabaseFile: string) {
|
||||||
this.db = new JsonDatabase(pathToDatabaseFile)
|
this.db = new Database(pathToDatabaseFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAppType(): Promise<string> {
|
async getAppType(): Promise<string> {
|
||||||
@@ -86,8 +85,7 @@ class IpcDatabaseHandler {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
} catch (e) {
|
||||||
catch (e) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +123,7 @@ class IpcDatabaseHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async addTaskToSendFileQueue(task: FileItemTask) {
|
async addTaskToSendFileQueue(task: FileItemTask) {
|
||||||
this.db.pushQueue(task)
|
await this.db.pushToSendQueue(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetInternalDatabase(): Promise<boolean> {
|
async resetInternalDatabase(): Promise<boolean> {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { TcpCommunicator } from '../helpers/tcp_communicator'
|
import { TcpCommunicator } from '../helpers/tcp_communicator'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { JsonDatabase } from '../database/database'
|
import { Database } from '../database/database'
|
||||||
import { DatabaseScheme } from '../database/schemes/database_scheme'
|
|
||||||
import { operationCodes } from '../network/operation_codes'
|
import { operationCodes } from '../network/operation_codes'
|
||||||
|
|
||||||
dotenv.config({ path: path.join(__dirname, '..', '..', '.env') })
|
dotenv.config({ path: path.join(__dirname, '..', '..', '.env') })
|
||||||
@@ -10,12 +9,12 @@ const pathToDatabaseFile = path.join(__dirname, '..', 'database.json')
|
|||||||
|
|
||||||
class IpcUCHandler {
|
class IpcUCHandler {
|
||||||
private readonly TCP_PORT: number
|
private readonly TCP_PORT: number
|
||||||
private readonly db: JsonDatabase<DatabaseScheme, any>
|
private readonly db: Database
|
||||||
private tcpCommunicator: TcpCommunicator | null = null
|
private tcpCommunicator: TcpCommunicator | null = null
|
||||||
|
|
||||||
constructor(pathToDatabaseFile: string) {
|
constructor(pathToDatabaseFile: string) {
|
||||||
this.TCP_PORT = process.env.TCP_PORT ? parseInt(process.env.TCP_PORT) : 41234
|
this.TCP_PORT = process.env.TCP_PORT ? parseInt(process.env.TCP_PORT) : 41234
|
||||||
this.db = new JsonDatabase(pathToDatabaseFile)
|
this.db = new Database(pathToDatabaseFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
async openUcSocket(): Promise<boolean> {
|
async openUcSocket(): Promise<boolean> {
|
||||||
|
|||||||
@@ -2,15 +2,13 @@ import { app, BrowserWindow, ipcMain, IpcMainInvokeEvent } from 'electron'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { promises as fs } from 'fs'
|
import { promises as fs } from 'fs'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
|
|
||||||
import { WorkerManager } from '../helpers/worker_manager'
|
import { WorkerManager } from '../helpers/worker_manager'
|
||||||
import { DirectoryWatcher } from '../helpers/directory_watcher'
|
import { DirectoryWatcher } from '../helpers/directory_watcher'
|
||||||
import { WindowManager } from '../helpers/window_manager'
|
import { WindowManager } from '../helpers/window_manager'
|
||||||
|
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
import { JsonDatabase } from '../database/database'
|
import { Database } from '../database/database'
|
||||||
import { DatabaseScheme } from '../database/schemes/database_scheme'
|
|
||||||
|
|
||||||
// Load environment variables
|
// Load environment variables
|
||||||
dotenv.config({ path: path.join(__dirname, '..', '..', '.env') })
|
dotenv.config({ path: path.join(__dirname, '..', '..', '.env') })
|
||||||
@@ -21,7 +19,7 @@ const HOST = getLocalIp()
|
|||||||
|
|
||||||
let mainWindow: BrowserWindow | null = null
|
let mainWindow: BrowserWindow | null = null
|
||||||
let windowManager: WindowManager | null = null
|
let windowManager: WindowManager | null = null
|
||||||
let db: JsonDatabase<DatabaseScheme, any> | null = null
|
let db: Database | null = null
|
||||||
let workerManager: WorkerManager | null = null
|
let workerManager: WorkerManager | null = null
|
||||||
let directoryWatcher: DirectoryWatcher | null = null
|
let directoryWatcher: DirectoryWatcher | null = null
|
||||||
let announcementWatcher: NodeJS.Timeout | null = null
|
let announcementWatcher: NodeJS.Timeout | null = null
|
||||||
@@ -129,7 +127,7 @@ app.whenReady().then(async () => {
|
|||||||
|
|
||||||
windowManager = new WindowManager(mainWindow, pathToPagesDir)
|
windowManager = new WindowManager(mainWindow, pathToPagesDir)
|
||||||
workerManager = new WorkerManager(pathToWorkerDir, windowManager)
|
workerManager = new WorkerManager(pathToWorkerDir, windowManager)
|
||||||
db = new JsonDatabase<DatabaseScheme, any>(pathToDatabaseFile)
|
db = new Database(pathToDatabaseFile)
|
||||||
|
|
||||||
await db.update((data) => {
|
await db.update((data) => {
|
||||||
data.app_config.server_found = false
|
data.app_config.server_found = false
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { contextBridge, ipcRenderer } from 'electron'
|
import { contextBridge, ipcRenderer } from 'electron'
|
||||||
import { FileItemTask } from '../interfaces/file_item_task'
|
|
||||||
import { ipcDatabaseHandler } from '../ipc-handlers/database_handler'
|
import { ipcDatabaseHandler } from '../ipc-handlers/database_handler'
|
||||||
import { EncryptionKeyScheme, UserInfoScheme } from '../database/schemes/app_config_scheme'
|
import { EncryptionKeyScheme, UserInfoScheme } from '../database/schemes/app_config_scheme'
|
||||||
import { ipcUCHandler } from '../ipc-handlers/uc_handler'
|
import { ipcUCHandler } from '../ipc-handlers/uc_handler'
|
||||||
import { ParsedMessage } from '../network/message_handler'
|
import { ParsedMessage } from '../network/message_handler'
|
||||||
import { NetworkUserScheme } from '../database/schemes/network_scheme'
|
import { NetworkUserScheme } from '../database/schemes/network_scheme'
|
||||||
import { DirectoryInfo, DirectorySchemes } from "../database/schemes/local_resources_scheme";
|
import {DirectoryInfo, DirectorySchemes, FileItemTask} from '../database/schemes/local_resources_scheme'
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('databaseAPI', {
|
contextBridge.exposeInMainWorld('databaseAPI', {
|
||||||
getAppType: (): Promise<string> => ipcDatabaseHandler.getAppType(),
|
getAppType: (): Promise<string> => ipcDatabaseHandler.getAppType(),
|
||||||
@@ -19,7 +18,8 @@ contextBridge.exposeInMainWorld('databaseAPI', {
|
|||||||
ipcDatabaseHandler.writeUserInfo(userInfo),
|
ipcDatabaseHandler.writeUserInfo(userInfo),
|
||||||
writeEncryptionKey: (encryptionKey: EncryptionKeyScheme): Promise<boolean> =>
|
writeEncryptionKey: (encryptionKey: EncryptionKeyScheme): Promise<boolean> =>
|
||||||
ipcDatabaseHandler.writeEncryptionKey(encryptionKey),
|
ipcDatabaseHandler.writeEncryptionKey(encryptionKey),
|
||||||
writeDirectoryPath: (id: string, path: string): Promise<boolean> => ipcDatabaseHandler.writeDirectoryPath(id, path),
|
writeDirectoryPath: (id: string, path: string): Promise<boolean> =>
|
||||||
|
ipcDatabaseHandler.writeDirectoryPath(id, path),
|
||||||
addTaskToSendFileQueue: (task: FileItemTask): Promise<void> =>
|
addTaskToSendFileQueue: (task: FileItemTask): Promise<void> =>
|
||||||
ipcDatabaseHandler.addTaskToSendFileQueue(task),
|
ipcDatabaseHandler.addTaskToSendFileQueue(task),
|
||||||
resetInternalDatabase: (): Promise<boolean> => ipcDatabaseHandler.resetInternalDatabase(),
|
resetInternalDatabase: (): Promise<boolean> => ipcDatabaseHandler.resetInternalDatabase(),
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import { operationCodes } from '../operation_codes'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import checkDiskSpace from 'check-disk-space'
|
import checkDiskSpace from 'check-disk-space'
|
||||||
import { JsonManager } from '../../helpers/json_manager'
|
|
||||||
import { OperationPlugin } from '../operations_base/operation_plugin'
|
import { OperationPlugin } from '../operations_base/operation_plugin'
|
||||||
|
import {Database} from "../../database/database";
|
||||||
|
|
||||||
|
const pathToDatabaseFile = path.join(__dirname, '..', '..', 'database.json')
|
||||||
|
|
||||||
export class UserToUserOperations implements OperationPlugin {
|
export class UserToUserOperations implements OperationPlugin {
|
||||||
public static readonly operationCodes = {
|
public static readonly operationCodes = {
|
||||||
@@ -46,12 +48,14 @@ export class UserToUserOperations implements OperationPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsonManager = new JsonManager(
|
const database = new Database(pathToDatabaseFile)
|
||||||
path.join(__dirname, '..', '..', 'json_files', 'application.json'),
|
|
||||||
)
|
|
||||||
try {
|
try {
|
||||||
await jsonManager.writeValue('announcement', parsedMessage.metaInfo.message)
|
await database.update((data: any) => {
|
||||||
console.log(`Announcement saved: ${parsedMessage.metaInfo.message}`)
|
if(!parsedMessage.metaInfo) return data;
|
||||||
|
|
||||||
|
data.app_config.announcement = parsedMessage.metaInfo.message
|
||||||
|
return data
|
||||||
|
})
|
||||||
return { operationCode: operationCodes.OK, metaInfo: { message: 'Announcement saved.' } }
|
return { operationCode: operationCodes.OK, metaInfo: { message: 'Announcement saved.' } }
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`Error saving announcement: ${error.message}`)
|
console.error(`Error saving announcement: ${error.message}`)
|
||||||
@@ -62,11 +66,10 @@ export class UserToUserOperations implements OperationPlugin {
|
|||||||
public static async handleGetUserInformation(
|
public static async handleGetUserInformation(
|
||||||
parsedMessage: ParsedMessage,
|
parsedMessage: ParsedMessage,
|
||||||
): Promise<ParsedMessage> {
|
): Promise<ParsedMessage> {
|
||||||
const jsonManager = new JsonManager(
|
const database = new Database(pathToDatabaseFile)
|
||||||
path.join(__dirname, '..', '..', 'json_files', 'userConfig.json'),
|
|
||||||
)
|
|
||||||
try {
|
try {
|
||||||
const userInfo = await jsonManager.readValue('user_info')
|
const data = await database.read()
|
||||||
|
const userInfo = data.app_config.user_info
|
||||||
return { operationCode: operationCodes.OK, metaInfo: userInfo }
|
return { operationCode: operationCodes.OK, metaInfo: userInfo }
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`Error fetching user info: ${error.message}`)
|
console.error(`Error fetching user info: ${error.message}`)
|
||||||
@@ -154,18 +157,16 @@ export class UserToUserOperations implements OperationPlugin {
|
|||||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing share info.' } }
|
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing share info.' } }
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsonManager = new JsonManager(
|
const database = new Database(pathToDatabaseFile)
|
||||||
path.join(__dirname, '..', '..', 'json_files', 'application.json'),
|
|
||||||
)
|
|
||||||
const { userName, relativeFilePath } = parsedMessage.metaInfo
|
const { userName, relativeFilePath } = parsedMessage.metaInfo
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const appInfo = await jsonManager.readValue('shareDirectory')
|
const data = await database.read()
|
||||||
const shareDirectory = appInfo?.path || ''
|
const shareDirectory = data.local_resources.directory_schemes.shared.path
|
||||||
|
|
||||||
console.log(`\n\nShare directory: ${shareDirectory}\n\n`)
|
console.log(`\n\nShare directory: ${shareDirectory}\n\n`)
|
||||||
|
|
||||||
if (!shareDirectory) {
|
if (!shareDirectory || shareDirectory === '') {
|
||||||
return {
|
return {
|
||||||
operationCode: operationCodes.ERR,
|
operationCode: operationCodes.ERR,
|
||||||
metaInfo: { message: 'Share directory missing.' },
|
metaInfo: { message: 'Share directory missing.' },
|
||||||
@@ -175,6 +176,11 @@ export class UserToUserOperations implements OperationPlugin {
|
|||||||
const fullFilePath = path.join(shareDirectory, userName, relativeFilePath)
|
const fullFilePath = path.join(shareDirectory, userName, relativeFilePath)
|
||||||
await fs.mkdir(path.dirname(fullFilePath), { recursive: true })
|
await fs.mkdir(path.dirname(fullFilePath), { recursive: true })
|
||||||
await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer)
|
await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer)
|
||||||
|
await database.pushToReceiveQueue({
|
||||||
|
ip: '',
|
||||||
|
path: fullFilePath,
|
||||||
|
userName,
|
||||||
|
})
|
||||||
console.log(`File shared: ${fullFilePath}`)
|
console.log(`File shared: ${fullFilePath}`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -195,14 +201,12 @@ export class UserToUserOperations implements OperationPlugin {
|
|||||||
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' } }
|
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' } }
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsonManager = new JsonManager(
|
const database = new Database(pathToDatabaseFile)
|
||||||
path.join(__dirname, '..', '..', 'json_files', 'application.json'),
|
|
||||||
)
|
|
||||||
const { userName } = parsedMessage.metaInfo
|
const { userName } = parsedMessage.metaInfo
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const appInfo = await jsonManager.readValue('departmentDirectory')
|
const data = await database.read()
|
||||||
const departmentDir = appInfo?.path || ''
|
const departmentDir = data.local_resources.directory_schemes.department.path
|
||||||
const userDepartmentDir = path.join(departmentDir, '..', 'DEPARTMENT_SHARE', userName)
|
const userDepartmentDir = path.join(departmentDir, '..', 'DEPARTMENT_SHARE', userName)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -245,19 +249,9 @@ export class UserToUserOperations implements OperationPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Path to the application.json to read the shareDirectory field
|
// Path to the application.json to read the shareDirectory field
|
||||||
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json')
|
const database = new Database(pathToDatabaseFile)
|
||||||
const jsonManager = new JsonManager(pathToApplicationJson)
|
const data = await database.read()
|
||||||
|
let appInfo = data.local_resources.directory_schemes.department
|
||||||
// Read application configuration asynchronously
|
|
||||||
let appInfo
|
|
||||||
try {
|
|
||||||
appInfo = await jsonManager.readValue('departmentDirectory')
|
|
||||||
} catch (error: any) {
|
|
||||||
return {
|
|
||||||
operationCode: operationCodes.ERR,
|
|
||||||
metaInfo: { message: `Error reading application config: ${error.message}` },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!appInfo || !appInfo.path) {
|
if (!appInfo || !appInfo.path) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user