package tools
import (
"context"
"database/sql"
"fmt"
"os"
"time"
"skraak/db"
"skraak/utils"
)
type ImportAudioFilesInput struct {
FolderPath string `json:"folder_path"`
DatasetID string `json:"dataset_id"`
LocationID string `json:"location_id"`
ClusterID string `json:"cluster_id"`
Recursive *bool `json:"recursive,omitempty"` }
type ImportAudioFilesOutput struct {
Summary ImportSummary `json:"summary"`
FileIDs []string `json:"file_ids"`
Errors []utils.FileImportError `json:"errors,omitempty"`
}
type ImportSummary struct {
TotalFiles int `json:"total_files"`
ImportedFiles int `json:"imported_files"`
SkippedFiles int `json:"skipped_files"` FailedFiles int `json:"failed_files"`
AudioMothFiles int `json:"audiomoth_files"`
TotalDuration float64 `json:"total_duration_seconds"`
ProcessingTime string `json:"processing_time"`
}
func ImportAudioFiles(
ctx context.Context,
input ImportAudioFilesInput,
) (ImportAudioFilesOutput, error) {
startTime := time.Now()
var output ImportAudioFilesOutput
recursive := true
if input.Recursive != nil {
recursive = *input.Recursive
}
if err := validateImportInput(input, dbPath); err != nil {
return output, fmt.Errorf("validation failed: %w", err)
}
database, err := db.OpenWriteableDB(dbPath)
if err != nil {
return output, fmt.Errorf("failed to open database: %w", err)
}
defer database.Close()
err = utils.EnsureClusterPath(database, input.ClusterID, input.FolderPath)
if err != nil {
return output, fmt.Errorf("failed to set cluster path: %w", err)
}
tx, err := db.BeginLoggedTx(ctx, database, "import_audio_files")
if err != nil {
return output, fmt.Errorf("failed to begin transaction: %w", err)
}
clusterOutput, err := utils.ImportCluster(database, tx.UnderlyingTx(), utils.ClusterImportInput{
FolderPath: input.FolderPath,
DatasetID: input.DatasetID,
LocationID: input.LocationID,
ClusterID: input.ClusterID,
Recursive: recursive,
})
if err != nil {
tx.Rollback()
return output, fmt.Errorf("cluster import failed: %w", err)
}
if err := tx.Commit(); err != nil {
return output, fmt.Errorf("transaction commit failed: %w", err)
}
output = ImportAudioFilesOutput{
Summary: ImportSummary{
TotalFiles: clusterOutput.TotalFiles,
ImportedFiles: clusterOutput.ImportedFiles,
SkippedFiles: clusterOutput.SkippedFiles,
FailedFiles: clusterOutput.FailedFiles,
AudioMothFiles: clusterOutput.AudioMothFiles,
TotalDuration: clusterOutput.TotalDuration,
ProcessingTime: time.Since(startTime).String(),
},
FileIDs: []string{}, Errors: clusterOutput.Errors,
}
return output, nil
}
func validateImportInput(input ImportAudioFilesInput, dbPath string) error {
info, err := os.Stat(input.FolderPath)
if err != nil {
return fmt.Errorf("folder not accessible: %w", err)
}
if !info.IsDir() {
return fmt.Errorf("path is not a directory: %s", input.FolderPath)
}
return validateHierarchyIDs(input.DatasetID, input.LocationID, input.ClusterID, dbPath)
}
func validateHierarchyIDs(datasetID, locationID, clusterID, dbPath string) error {
if err := utils.ValidateShortID(datasetID, "dataset_id"); err != nil {
return err
}
if err := utils.ValidateShortID(locationID, "location_id"); err != nil {
return err
}
if err := utils.ValidateShortID(clusterID, "cluster_id"); err != nil {
return err
}
return db.WithReadDB(dbPath, func(database *sql.DB) error {
if err := db.ValidateDatasetTypeForImport(database, datasetID); err != nil {
return err
}
if err := db.ValidateLocationBelongsToDataset(database, locationID, datasetID); err != nil {
return err
}
if err := db.ClusterBelongsToLocation(database, clusterID, locationID); err != nil {
return err
}
return nil
})
}