Add missing internal/storage directory
This commit is contained in:
parent
13c50cce17
commit
4171b5a48f
1070
internal/storage/backend.go
Normal file
1070
internal/storage/backend.go
Normal file
File diff suppressed because it is too large
Load Diff
332
internal/storage/parallel.go
Normal file
332
internal/storage/parallel.go
Normal file
@ -0,0 +1,332 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ParallelChunkProcessor handles parallel chunk operations for improved performance
|
||||
type ParallelChunkProcessor struct {
|
||||
backend *Backend
|
||||
workerPool chan struct{} // Semaphore for limiting concurrent operations
|
||||
maxWorkers int
|
||||
}
|
||||
|
||||
// NewParallelChunkProcessor creates a new parallel chunk processor
|
||||
func NewParallelChunkProcessor(backend *Backend, maxWorkers int) *ParallelChunkProcessor {
|
||||
if maxWorkers <= 0 {
|
||||
maxWorkers = runtime.NumCPU() * 2 // Default to 2x CPU cores
|
||||
}
|
||||
|
||||
return &ParallelChunkProcessor{
|
||||
backend: backend,
|
||||
workerPool: make(chan struct{}, maxWorkers),
|
||||
maxWorkers: maxWorkers,
|
||||
}
|
||||
}
|
||||
|
||||
// ChunkResult represents the result of processing a chunk
|
||||
type ChunkResult struct {
|
||||
Index int
|
||||
Data []byte
|
||||
Error error
|
||||
}
|
||||
|
||||
// AssembleFileParallel reassembles a file using parallel chunk loading
|
||||
func (p *ParallelChunkProcessor) AssembleFileParallel(fileHash string, writer io.Writer) error {
|
||||
// Get chunk information
|
||||
chunks, err := p.backend.GetFileChunks(fileHash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get chunks: %w", err)
|
||||
}
|
||||
|
||||
if len(chunks) == 0 {
|
||||
return fmt.Errorf("no chunks found for file %s", fileHash)
|
||||
}
|
||||
|
||||
// For small number of chunks, use sequential processing
|
||||
if len(chunks) <= 3 {
|
||||
return p.backend.AssembleFile(fileHash, writer)
|
||||
}
|
||||
|
||||
// Create result channels
|
||||
results := make(chan ChunkResult, len(chunks))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Launch workers to fetch chunks in parallel
|
||||
for i, chunk := range chunks {
|
||||
wg.Add(1)
|
||||
go func(index int, chunkInfo ChunkInfo) {
|
||||
defer wg.Done()
|
||||
|
||||
// Acquire worker slot
|
||||
p.workerPool <- struct{}{}
|
||||
defer func() { <-p.workerPool }()
|
||||
|
||||
// Fetch chunk data
|
||||
data, err := p.backend.GetChunkData(chunkInfo.ChunkHash)
|
||||
results <- ChunkResult{
|
||||
Index: index,
|
||||
Data: data,
|
||||
Error: err,
|
||||
}
|
||||
}(i, chunk)
|
||||
}
|
||||
|
||||
// Close results channel when all workers are done
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
|
||||
// Collect results in order
|
||||
chunkData := make([][]byte, len(chunks))
|
||||
for result := range results {
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("failed to get chunk data for index %d: %w", result.Index, result.Error)
|
||||
}
|
||||
if result.Data == nil {
|
||||
return fmt.Errorf("chunk %d not found", result.Index)
|
||||
}
|
||||
chunkData[result.Index] = result.Data
|
||||
}
|
||||
|
||||
// Write chunks in order
|
||||
for i, data := range chunkData {
|
||||
if _, err := writer.Write(data); err != nil {
|
||||
return fmt.Errorf("failed to write chunk %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Update access statistics
|
||||
_, err = p.backend.db.Exec(`
|
||||
UPDATE files SET access_count = access_count + 1, last_access = CURRENT_TIMESTAMP
|
||||
WHERE hash = ?
|
||||
`, fileHash)
|
||||
if err != nil {
|
||||
// Log but don't fail on stats update
|
||||
fmt.Printf("Warning: failed to update access stats for %s: %v\n", fileHash, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StoreFileParallel stores a file using parallel chunk processing
|
||||
func (p *ParallelChunkProcessor) StoreFileParallel(reader io.Reader, originalName, contentType, ownerPubkey, accessLevel string) (*FileMetadata, error) {
|
||||
// For small files, use regular storage
|
||||
tempFile, err := createTempFileFromReader(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tempFile.cleanup()
|
||||
|
||||
// If file is small, use regular storage
|
||||
if tempFile.size < int64(p.maxWorkers)*p.backend.chunkSize {
|
||||
return p.backend.StoreFileWithOwner(tempFile.reader(), originalName, contentType, ownerPubkey, accessLevel)
|
||||
}
|
||||
|
||||
// Calculate file hash
|
||||
fileHash := tempFile.hash()
|
||||
|
||||
// Check if file already exists
|
||||
if existing, _ := p.backend.GetFileMetadata(fileHash); existing != nil {
|
||||
return existing, nil
|
||||
}
|
||||
|
||||
// Calculate chunk count
|
||||
chunkCount := int((tempFile.size + p.backend.chunkSize - 1) / p.backend.chunkSize)
|
||||
chunks := make([]ChunkInfo, chunkCount)
|
||||
|
||||
// Process chunks in parallel
|
||||
var wg sync.WaitGroup
|
||||
chunkResults := make(chan struct {
|
||||
index int
|
||||
chunk ChunkInfo
|
||||
err error
|
||||
}, chunkCount)
|
||||
|
||||
for i := 0; i < chunkCount; i++ {
|
||||
wg.Add(1)
|
||||
go func(chunkIndex int) {
|
||||
defer wg.Done()
|
||||
|
||||
// Acquire worker slot
|
||||
p.workerPool <- struct{}{}
|
||||
defer func() { <-p.workerPool }()
|
||||
|
||||
// Calculate chunk boundaries
|
||||
offset := int64(chunkIndex) * p.backend.chunkSize
|
||||
size := p.backend.chunkSize
|
||||
if offset+size > tempFile.size {
|
||||
size = tempFile.size - offset
|
||||
}
|
||||
|
||||
// Read chunk data
|
||||
chunkData, err := tempFile.readChunk(offset, size)
|
||||
if err != nil {
|
||||
chunkResults <- struct {
|
||||
index int
|
||||
chunk ChunkInfo
|
||||
err error
|
||||
}{chunkIndex, ChunkInfo{}, err}
|
||||
return
|
||||
}
|
||||
|
||||
// Store chunk
|
||||
chunk, err := p.storeChunk(fileHash, chunkIndex, chunkData, offset)
|
||||
chunkResults <- struct {
|
||||
index int
|
||||
chunk ChunkInfo
|
||||
err error
|
||||
}{chunkIndex, chunk, err}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Close results channel when all workers are done
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(chunkResults)
|
||||
}()
|
||||
|
||||
// Collect results
|
||||
for result := range chunkResults {
|
||||
if result.err != nil {
|
||||
return nil, fmt.Errorf("failed to store chunk %d: %w", result.index, result.err)
|
||||
}
|
||||
chunks[result.index] = result.chunk
|
||||
}
|
||||
|
||||
// Store metadata in database
|
||||
return p.storeFileMetadata(fileHash, originalName, contentType, ownerPubkey, accessLevel, tempFile.size, chunkCount, chunks)
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
type tempFile struct {
|
||||
path string
|
||||
size int64
|
||||
file *os.File
|
||||
}
|
||||
|
||||
func createTempFileFromReader(reader io.Reader) (*tempFile, error) {
|
||||
file, err := os.CreateTemp("", "parallel_upload_*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create temp file: %w", err)
|
||||
}
|
||||
|
||||
size, err := io.Copy(file, reader)
|
||||
if err != nil {
|
||||
file.Close()
|
||||
os.Remove(file.Name())
|
||||
return nil, fmt.Errorf("failed to copy to temp file: %w", err)
|
||||
}
|
||||
|
||||
return &tempFile{
|
||||
path: file.Name(),
|
||||
size: size,
|
||||
file: file,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tf *tempFile) reader() io.Reader {
|
||||
tf.file.Seek(0, io.SeekStart)
|
||||
return tf.file
|
||||
}
|
||||
|
||||
func (tf *tempFile) hash() string {
|
||||
tf.file.Seek(0, io.SeekStart)
|
||||
hasher := sha256.New()
|
||||
io.Copy(hasher, tf.file)
|
||||
return hex.EncodeToString(hasher.Sum(nil))
|
||||
}
|
||||
|
||||
func (tf *tempFile) readChunk(offset, size int64) ([]byte, error) {
|
||||
data := make([]byte, size)
|
||||
n, err := tf.file.ReadAt(data, offset)
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
return data[:n], nil
|
||||
}
|
||||
|
||||
func (tf *tempFile) cleanup() {
|
||||
if tf.file != nil {
|
||||
tf.file.Close()
|
||||
os.Remove(tf.path)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ParallelChunkProcessor) storeChunk(fileHash string, chunkIndex int, data []byte, offset int64) (ChunkInfo, error) {
|
||||
// Calculate chunk hash
|
||||
hasher := sha256.New()
|
||||
hasher.Write(data)
|
||||
chunkHash := hex.EncodeToString(hasher.Sum(nil))
|
||||
|
||||
// Store chunk to disk
|
||||
chunkPath := filepath.Join(p.backend.chunkDir, chunkHash)
|
||||
if err := os.WriteFile(chunkPath, data, 0644); err != nil {
|
||||
return ChunkInfo{}, fmt.Errorf("failed to write chunk: %w", err)
|
||||
}
|
||||
|
||||
return ChunkInfo{
|
||||
FileHash: fileHash,
|
||||
ChunkIndex: chunkIndex,
|
||||
ChunkHash: chunkHash,
|
||||
Size: int64(len(data)),
|
||||
Offset: offset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *ParallelChunkProcessor) storeFileMetadata(fileHash, originalName, contentType, ownerPubkey, accessLevel string, size int64, chunkCount int, chunks []ChunkInfo) (*FileMetadata, error) {
|
||||
// Start transaction
|
||||
tx, err := p.backend.db.Begin()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// Insert file metadata
|
||||
_, err = tx.Exec(`
|
||||
INSERT INTO files (hash, original_name, size, chunk_count, content_type, storage_type, owner_pubkey, access_level)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, fileHash, originalName, size, chunkCount, contentType, "torrent", ownerPubkey, accessLevel)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to insert file metadata: %w", err)
|
||||
}
|
||||
|
||||
// Insert chunk metadata
|
||||
for _, chunk := range chunks {
|
||||
_, err = tx.Exec(`
|
||||
INSERT INTO chunks (file_hash, chunk_index, chunk_hash, size, offset)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`, chunk.FileHash, chunk.ChunkIndex, chunk.ChunkHash, chunk.Size, chunk.Offset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to insert chunk metadata: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
return &FileMetadata{
|
||||
Hash: fileHash,
|
||||
OriginalName: originalName,
|
||||
Size: size,
|
||||
ChunkCount: chunkCount,
|
||||
ContentType: contentType,
|
||||
StorageType: "torrent",
|
||||
OwnerPubkey: ownerPubkey,
|
||||
AccessLevel: accessLevel,
|
||||
CreatedAt: time.Now(),
|
||||
AccessCount: 0,
|
||||
LastAccess: time.Now(),
|
||||
}, nil
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user