#File Operations

Learn how to manage files with operations like rename, copy, move, and bulk operations.

#Renaming Files

Rename assets to better organize your storage:

import { Walbucket } from '@walbucket/sdk';

const walbucket = new Walbucket({
  apiKey: process.env.WALBUCKET_API_KEY!,
  network: 'testnet',
  sponsorPrivateKey: process.env.SPONSOR_PRIVATE_KEY!,
});

async function renameFile(assetId: string, newName: string) {
  try {
    await walbucket.rename(assetId, newName);
    console.log(`Renamed to: ${newName}`);
  } catch (error) {
    console.error('Rename failed:', error.message);
  }
}

// Simple rename
await renameFile(assetId, 'project-report-final.pdf');
typescript

#Batch Rename

Rename multiple files with a pattern:

async function batchRename(assets: any[], pattern: string) {
  const results = [];
  
  for (let i = 0; i < assets.length; i++) {
    const asset = assets[i];
    const newName = pattern
      .replace('{index}', String(i + 1).padStart(3, '0'))
      .replace('{original}', asset.name)
      .replace('{date}', new Date().toISOString().split('T')[0]);
    
    try {
      await walbucket.rename(asset.assetId, newName);
      results.push({ success: true, assetId: asset.assetId, newName });
      console.log(`✓ ${asset.name} → ${newName}`);
    } catch (error) {
      results.push({ 
        success: false, 
        assetId: asset.assetId, 
        error: error.message 
      });
      console.error(`✗ Failed to rename ${asset.name}`);
    }
  }
  
  return results;
}

// Usage: Rename photos with sequential numbers
const photos = await walbucket.list(userAddress);
await batchRename(photos, 'photo-{index}.jpg');

// Usage: Add date prefix
await batchRename(photos, '{date}-{original}');
typescript

#Copying Files

Create duplicates of files:

async function copyFile(assetId: string, newName: string) {
  try {
    await walbucket.copy(assetId, newName);
    console.log(`Created copy: ${newName}`);
  } catch (error) {
    console.error('Copy failed:', error.message);
  }
}

// Create a copy
await copyFile(originalId, 'backup-document.pdf');
typescript

#Backup Strategy

Implement automatic backups:

async function createBackup(assetId: string) {
  const asset = await walbucket.getAsset(assetId);
  if (!asset) {
    throw new Error('Asset not found');
  }
  
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
  const backupName = `backup-${timestamp}-${asset.name}`;
  
  await walbucket.copy(assetId, backupName);
  
  console.log(`Backup created: ${backupName}`);
  return backupName;
}

// Create backups for important files
const importantFiles = ['doc-id-1', 'doc-id-2', 'doc-id-3'];
for (const fileId of importantFiles) {
  await createBackup(fileId);
}
typescript

#Versioning System

Implement file versioning:

interface Version {
  version: number;
  assetId: string;
  name: string;
  createdAt: number;
}

class FileVersioning {
  private versions: Map<string, Version[]> = new Map();
  
  async createVersion(originalId: string, description?: string) {
    const asset = await walbucket.getAsset(originalId);
    if (!asset) throw new Error('Asset not found');
    
    // Get existing versions
    const fileVersions = this.versions.get(originalId) || [];
    const nextVersion = fileVersions.length + 1;
    
    // Create versioned name
    const baseName = asset.name.replace(/\.[^.]+$/, '');
    const extension = asset.name.split('.').pop();
    const versionName = `${baseName}-v${nextVersion}${description ? `-${description}` : ''}.${extension}`;
    
    // Copy file with version name
    await walbucket.copy(originalId, versionName);
    
    // Track version
    const version: Version = {
      version: nextVersion,
      assetId: originalId, // In practice, get the new asset ID
      name: versionName,
      createdAt: Date.now(),
    };
    
    fileVersions.push(version);
    this.versions.set(originalId, fileVersions);
    
    console.log(`Created version ${nextVersion}: ${versionName}`);
    return version;
  }
  
  getVersionHistory(originalId: string): Version[] {
    return this.versions.get(originalId) || [];
  }
  
  async restoreVersion(originalId: string, version: number) {
    const versions = this.versions.get(originalId);
    if (!versions) throw new Error('No versions found');
    
    const targetVersion = versions.find(v => v.version === version);
    if (!targetVersion) throw new Error(`Version ${version} not found`);
    
    // Copy the version back as the main file
    const asset = await walbucket.getAsset(originalId);
    await walbucket.copy(targetVersion.assetId, asset.name);
    
    console.log(`Restored to version ${version}`);
  }
}

// Usage
const versioning = new FileVersioning();

// Create versions
await versioning.createVersion(fileId, 'initial-draft');
await versioning.createVersion(fileId, 'review-updates');
await versioning.createVersion(fileId, 'final');

// View history
const history = versioning.getVersionHistory(fileId);
console.log(`File has ${history.length} versions`);

// Restore to previous version
await versioning.restoreVersion(fileId, 2);
typescript

#Moving Files

Move files between folders:

async function moveFile(assetId: string, targetFolderId: string) {
  try {
    await walbucket.moveToFolder(assetId, targetFolderId);
    console.log('File moved successfully');
  } catch (error) {
    console.error('Move failed:', error.message);
  }
}

// Move to specific folder
await moveFile(assetId, folderId);

// Move to root
await moveFile(assetId, undefined);
typescript

#Bulk Move

Move multiple files at once:

async function bulkMove(assetIds: string[], targetFolderId: string) {
  const results = [];
  
  console.log(`Moving ${assetIds.length} files...`);
  
  for (const assetId of assetIds) {
    try {
      await walbucket.moveToFolder(assetId, targetFolderId);
      results.push({ success: true, assetId });
      console.log(`✓ Moved: ${assetId}`);
    } catch (error) {
      results.push({ success: false, assetId, error: error.message });
      console.error(`✗ Failed: ${assetId}`);
    }
  }
  
  const successful = results.filter(r => r.success).length;
  console.log(`Moved ${successful}/${assetIds.length} files`);
  
  return results;
}

// Move selected files to archive folder
const selectedFiles = ['id1', 'id2', 'id3'];
await bulkMove(selectedFiles, archiveFolderId);
typescript

#Deleting Files

Delete files permanently:

async function deleteFile(assetId: string) {
  const confirmed = confirm('Are you sure you want to delete this file?');
  if (!confirmed) return;
  
  try {
    await walbucket.delete(assetId);
    console.log('File deleted');
  } catch (error) {
    console.error('Delete failed:', error.message);
  }
}
typescript

#Bulk Delete

Delete multiple files:

async function bulkDelete(assetIds: string[]) {
  const confirmed = confirm(
    `Delete ${assetIds.length} files? This cannot be undone.`
  );
  if (!confirmed) return;
  
  const results = [];
  
  for (const assetId of assetIds) {
    try {
      await walbucket.delete(assetId);
      results.push({ success: true, assetId });
      console.log(`✓ Deleted: ${assetId}`);
    } catch (error) {
      results.push({ success: false, assetId, error: error.message });
      console.error(`✗ Failed: ${assetId}`);
    }
  }
  
  const successful = results.filter(r => r.success).length;
  console.log(`Deleted ${successful}/${assetIds.length} files`);
  
  return results;
}
typescript

#Complete File Manager

Build a complete file management system:

class FileManager {
  private walbucket: Walbucket;
  
  constructor(walbucket: Walbucket) {
    this.walbucket = walbucket;
  }
  
  // Rename with validation
  async rename(assetId: string, newName: string): Promise<boolean> {
    if (!newName.trim()) {
      throw new Error('Name cannot be empty');
    }
    
    if (newName.length > 255) {
      throw new Error('Name too long (max 255 characters)');
    }
    
    try {
      await this.walbucket.rename(assetId, newName);
      return true;
    } catch (error) {
      console.error('Rename failed:', error);
      return false;
    }
  }
  
  // Copy with automatic name generation
  async copy(assetId: string, baseName?: string): Promise<string | null> {
    try {
      const asset = await this.walbucket.getAsset(assetId);
      if (!asset) throw new Error('Asset not found');
      
      const name = baseName || this.generateCopyName(asset.name);
      await this.walbucket.copy(assetId, name);
      
      return name;
    } catch (error) {
      console.error('Copy failed:', error);
      return null;
    }
  }
  
  // Move with validation
  async move(
    assetId: string, 
    targetFolderId: string | null
  ): Promise<boolean> {
    try {
      await this.walbucket.moveToFolder(assetId, targetFolderId || undefined);
      return true;
    } catch (error) {
      console.error('Move failed:', error);
      return false;
    }
  }
  
  // Delete with confirmation
  async delete(assetId: string, skipConfirm: boolean = false): Promise<boolean> {
    if (!skipConfirm) {
      const confirmed = confirm('Delete this file permanently?');
      if (!confirmed) return false;
    }
    
    try {
      await this.walbucket.delete(assetId);
      return true;
    } catch (error) {
      console.error('Delete failed:', error);
      return false;
    }
  }
  
  // Batch operations
  async batchOperation(
    assetIds: string[],
    operation: 'delete' | 'move',
    options?: { targetFolderId?: string }
  ) {
    const results = [];
    
    for (const assetId of assetIds) {
      let success = false;
      
      if (operation === 'delete') {
        success = await this.delete(assetId, true);
      } else if (operation === 'move') {
        success = await this.move(assetId, options?.targetFolderId || null);
      }
      
      results.push({ assetId, success });
    }
    
    return results;
  }
  
  // Helper: Generate copy name
  private generateCopyName(originalName: string): string {
    const timestamp = Date.now();
    const parts = originalName.split('.');
    const ext = parts.pop();
    const base = parts.join('.');
    
    return `${base}-copy-${timestamp}.${ext}`;
  }
}

// Usage
const manager = new FileManager(walbucket);

// Rename
await manager.rename(assetId, 'new-name.pdf');

// Copy
await manager.copy(assetId);

// Move
await manager.move(assetId, folderId);

// Batch delete
await manager.batchOperation(['id1', 'id2', 'id3'], 'delete');

// Batch move
await manager.batchOperation(
  ['id1', 'id2', 'id3'], 
  'move', 
  { targetFolderId: folderId }
);
typescript

#React Component Example

import { useState } from 'react';
import { Walbucket } from '@walbucket/sdk';

function FileOperations({ assetId, asset, walbucket, onUpdate }) {
  const [isRenaming, setIsRenaming] = useState(false);
  const [newName, setNewName] = useState(asset.name);
  
  async function handleRename() {
    try {
      await walbucket.rename(assetId, newName);
      setIsRenaming(false);
      onUpdate();
    } catch (error) {
      alert('Rename failed: ' + error.message);
    }
  }
  
  async function handleCopy() {
    if (confirm('Create a copy of this file?')) {
      try {
        const copyName = `Copy of ${asset.name}`;
        await walbucket.copy(assetId, copyName);
        alert('File copied successfully');
        onUpdate();
      } catch (error) {
        alert('Copy failed: ' + error.message);
      }
    }
  }
  
  async function handleDelete() {
    if (confirm('Delete this file permanently?')) {
      try {
        await walbucket.delete(assetId);
        alert('File deleted');
        onUpdate();
      } catch (error) {
        alert('Delete failed: ' + error.message);
      }
    }
  }
  
  return (
    <div className="file-operations">
      {isRenaming ? (
        <div className="rename-form">
          <input
            value={newName}
            onChange={(e) => setNewName(e.target.value)}
            onKeyPress={(e) => e.key === 'Enter' && handleRename()}
          />
          <button onClick={handleRename}>Save</button>
          <button onClick={() => setIsRenaming(false)}>Cancel</button>
        </div>
      ) : (
        <div className="file-actions">
          <button onClick={() => setIsRenaming(true)}>Rename</button>
          <button onClick={handleCopy}>Copy</button>
          <button onClick={handleDelete}>Delete</button>
        </div>
      )}
    </div>
  );
}
typescript

#Best Practices

#Naming Conventions

// Good: Clear, descriptive names
await walbucket.rename(id, 'project-report-2024-Q4.pdf');
await walbucket.rename(id, 'logo-primary-color.png');

// Avoid: Generic or unclear names
// await walbucket.rename(id, 'file1.pdf');
// await walbucket.rename(id, 'untitled.png');
typescript

#Error Handling

async function safeRename(assetId: string, newName: string) {
  try {
    await walbucket.rename(assetId, newName);
    return { success: true };
  } catch (error) {
    if (error.message.includes('E_NOT_OWNER')) {
      return { success: false, error: 'Permission denied' };
    }
    if (error.message.includes('already exists')) {
      return { success: false, error: 'Name already in use' };
    }
    return { success: false, error: 'Operation failed' };
  }
}
typescript