File Operations Examples ​
This page demonstrates how to work with files using Archive.zig, including compression, decompression, and batch processing operations.
Basic File Operations ​
Compressing Files ​
zig
const std = @import("std");
const archive = @import("archive");
pub fn compressFileExample(allocator: std.mem.Allocator) !void {
// Create a test file
const test_content = "This is test content for file compression example.\n" ** 100;
try std.fs.cwd().writeFile(.{ .sub_path = "test_input.txt", .data = test_content });
// Read and compress file
const input_data = try std.fs.cwd().readFileAlloc(allocator, "test_input.txt", 10 * 1024 * 1024);
defer allocator.free(input_data);
const compressed = try archive.compress(allocator, input_data, .gzip);
defer allocator.free(compressed);
// Write compressed file
try std.fs.cwd().writeFile(.{ .sub_path = "test_output.gz", .data = compressed });
const ratio = @as(f64, @floatFromInt(compressed.len)) / @as(f64, @floatFromInt(input_data.len)) * 100;
std.debug.print("File compression:\n");
std.debug.print(" Input: {d} bytes\n", .{input_data.len});
std.debug.print(" Output: {d} bytes ({d:.1}%)\n", .{ compressed.len, ratio });
// Clean up
std.fs.cwd().deleteFile("test_input.txt") catch {};
std.fs.cwd().deleteFile("test_output.gz") catch {};
}Decompressing Files ​
zig
pub fn decompressFileExample(allocator: std.mem.Allocator) !void {
// Create test compressed file
const original_content = "This is the original content that will be compressed and then decompressed.\n" ** 50;
const compressed = try archive.compress(allocator, original_content, .zstd);
defer allocator.free(compressed);
try std.fs.cwd().writeFile(.{ .sub_path = "compressed_test.zst", .data = compressed });
// Read and decompress file
const compressed_data = try std.fs.cwd().readFileAlloc(allocator, "compressed_test.zst", 10 * 1024 * 1024);
defer allocator.free(compressed_data);
// Auto-detect and decompress
const decompressed = try archive.autoDecompress(allocator, compressed_data);
defer allocator.free(decompressed);
// Write decompressed file
try std.fs.cwd().writeFile(.{ .sub_path = "decompressed_test.txt", .data = decompressed });
// Verify integrity
const matches = std.mem.eql(u8, original_content, decompressed);
std.debug.print("File decompression:\n");
std.debug.print(" Compressed: {d} bytes\n", .{compressed_data.len});
std.debug.print(" Decompressed: {d} bytes\n", .{decompressed.len});
std.debug.print(" Integrity check: {s}\n", .{if (matches) "PASS" else "FAIL"});
// Clean up
std.fs.cwd().deleteFile("compressed_test.zst") catch {};
std.fs.cwd().deleteFile("decompressed_test.txt") catch {};
}Batch File Processing ​
Compressing Multiple Files ​
zig
pub fn batchCompressionExample(allocator: std.mem.Allocator) !void {
// Create test files
const test_files = [_]struct { name: []const u8, content: []const u8 }{
.{ .name = "document1.txt", .content = "Document 1 content with some text data.\n" ** 20 },
.{ .name = "document2.txt", .content = "Document 2 content with different text.\n" ** 30 },
.{ .name = "data.json", .content = "{\"key\": \"value\", \"array\": [1, 2, 3]}\n" ** 15 },
.{ .name = "config.xml", .content = "<config><setting>value</setting></config>\n" ** 25 },
};
// Create test files
for (test_files) |file_info| {
try std.fs.cwd().writeFile(.{ .sub_path = file_info.name, .data = file_info.content });
}
std.debug.print("Batch compression results:\n");
// Compress each file
for (test_files) |file_info| {
const input_data = try std.fs.cwd().readFileAlloc(allocator, file_info.name, 1024 * 1024);
defer allocator.free(input_data);
const compressed = try archive.compress(allocator, input_data, .gzip);
defer allocator.free(compressed);
const output_name = try std.fmt.allocPrint(allocator, "{s}.gz", .{file_info.name});
defer allocator.free(output_name);
try std.fs.cwd().writeFile(.{ .sub_path = output_name, .data = compressed });
const ratio = @as(f64, @floatFromInt(compressed.len)) / @as(f64, @floatFromInt(input_data.len)) * 100;
std.debug.print(" {s}: {d} -> {d} bytes ({d:.1}%)\n",
.{ file_info.name, input_data.len, compressed.len, ratio });
// Clean up
std.fs.cwd().deleteFile(file_info.name) catch {};
std.fs.cwd().deleteFile(output_name) catch {};
}
}Directory Compression ​
zig
pub fn directoryCompressionExample(allocator: std.mem.Allocator) !void {
// Create test directory structure
try std.fs.cwd().makeDir("test_dir");
try std.fs.cwd().makeDir("test_dir/subdir");
const test_files = [_]struct { path: []const u8, content: []const u8 }{
.{ .path = "test_dir/file1.txt", .content = "Content of file 1\n" ** 10 },
.{ .path = "test_dir/file2.txt", .content = "Content of file 2\n" ** 15 },
.{ .path = "test_dir/subdir/file3.txt", .content = "Content of file 3\n" ** 8 },
.{ .path = "test_dir/subdir/file4.txt", .content = "Content of file 4\n" ** 12 },
};
// Create test files
for (test_files) |file_info| {
try std.fs.cwd().writeFile(.{ .sub_path = file_info.path, .data = file_info.content });
}
// Collect all files in directory
var file_list = std.ArrayList([]const u8).init(allocator);
defer {
for (file_list.items) |path| {
allocator.free(path);
}
file_list.deinit();
}
try collectFilesRecursive(allocator, "test_dir", &file_list);
// Create archive data
var archive_data = std.ArrayList(u8).init(allocator);
defer archive_data.deinit();
for (file_list.items) |file_path| {
const file_data = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024);
defer allocator.free(file_data);
// Simple archive format: path_length + path + data_length + data
const path_len = @as(u32, @intCast(file_path.len));
const data_len = @as(u32, @intCast(file_data.len));
try archive_data.appendSlice(std.mem.asBytes(&path_len));
try archive_data.appendSlice(file_path);
try archive_data.appendSlice(std.mem.asBytes(&data_len));
try archive_data.appendSlice(file_data);
}
// Compress archive
const compressed_archive = try archive.compress(allocator, archive_data.items, .zstd);
defer allocator.free(compressed_archive);
try std.fs.cwd().writeFile(.{ .sub_path = "directory_archive.zst", .data = compressed_archive });
const ratio = @as(f64, @floatFromInt(compressed_archive.len)) / @as(f64, @floatFromInt(archive_data.items.len)) * 100;
std.debug.print("Directory compression:\n");
std.debug.print(" Files: {d}\n", .{file_list.items.len});
std.debug.print(" Original: {d} bytes\n", .{archive_data.items.len});
std.debug.print(" Compressed: {d} bytes ({d:.1}%)\n", .{ compressed_archive.len, ratio });
// Clean up
for (test_files) |file_info| {
std.fs.cwd().deleteFile(file_info.path) catch {};
}
std.fs.cwd().deleteDir("test_dir/subdir") catch {};
std.fs.cwd().deleteDir("test_dir") catch {};
std.fs.cwd().deleteFile("directory_archive.zst") catch {};
}
fn collectFilesRecursive(allocator: std.mem.Allocator, dir_path: []const u8, file_list: *std.ArrayList([]const u8)) !void {
var dir = try std.fs.cwd().openDir(dir_path, .{ .iterate = true });
defer dir.close();
var iterator = dir.iterate();
while (try iterator.next()) |entry| {
const full_path = try std.fs.path.join(allocator, &[_][]const u8{ dir_path, entry.name });
switch (entry.kind) {
.file => try file_list.append(full_path),
.directory => {
try collectFilesRecursive(allocator, full_path, file_list);
allocator.free(full_path);
},
else => allocator.free(full_path),
}
}
}Advanced File Operations ​
Atomic File Operations ​
zig
pub fn atomicFileOperations(allocator: std.mem.Allocator) !void {
const original_content = "Important data that must be handled atomically\n" ** 50;
// Create original file
try std.fs.cwd().writeFile(.{ .sub_path = "important_data.txt", .data = original_content });
// Atomic compression: write to temporary file first
const temp_path = "important_data.txt.gz.tmp";
const final_path = "important_data.txt.gz";
// Read original file
const input_data = try std.fs.cwd().readFileAlloc(allocator, "important_data.txt", 10 * 1024 * 1024);
defer allocator.free(input_data);
// Compress data
const compressed = try archive.compress(allocator, input_data, .gzip);
defer allocator.free(compressed);
// Write to temporary file
try std.fs.cwd().writeFile(.{ .sub_path = temp_path, .data = compressed });
// Verify compressed file by decompressing
const temp_data = try std.fs.cwd().readFileAlloc(allocator, temp_path, 10 * 1024 * 1024);
defer allocator.free(temp_data);
const verified = try archive.decompress(allocator, temp_data, .gzip);
defer allocator.free(verified);
if (!std.mem.eql(u8, original_content, verified)) {
std.fs.cwd().deleteFile(temp_path) catch {};
return error.VerificationFailed;
}
// Atomically move temporary file to final location
try std.fs.cwd().rename(temp_path, final_path);
std.debug.print("Atomic file compression:\n");
std.debug.print(" Original: {d} bytes\n", .{input_data.len});
std.debug.print(" Compressed: {d} bytes\n", .{compressed.len});
std.debug.print(" Verification: PASS\n");
std.debug.print(" Atomic operation: SUCCESS\n");
// Clean up
std.fs.cwd().deleteFile("important_data.txt") catch {};
std.fs.cwd().deleteFile(final_path) catch {};
}File Backup and Compression ​
zig
pub fn backupAndCompress(allocator: std.mem.Allocator, file_path: []const u8) !void {
std.debug.print("Backing up and compressing: {s}\n", .{file_path});
// Create test file if it doesn't exist
const test_content = "This is test content for backup and compression example.\n" ** 100;
try std.fs.cwd().writeFile(.{ .sub_path = file_path, .data = test_content });
// Create backup filename
const backup_path = try std.fmt.allocPrint(allocator, "{s}.backup", .{file_path});
defer allocator.free(backup_path);
const compressed_path = try std.fmt.allocPrint(allocator, "{s}.gz", .{file_path});
defer allocator.free(compressed_path);
// Create backup copy
try std.fs.cwd().copyFile(file_path, std.fs.cwd(), backup_path, .{});
// Read and compress original file
const file_data = try std.fs.cwd().readFileAlloc(allocator, file_path, 10 * 1024 * 1024);
defer allocator.free(file_data);
const compressed = archive.compress(allocator, file_data, .gzip) catch |err| {
// Restore from backup on compression error
std.fs.cwd().copyFile(backup_path, std.fs.cwd(), file_path, .{}) catch {};
std.fs.cwd().deleteFile(backup_path) catch {};
return err;
};
defer allocator.free(compressed);
// Write compressed file
std.fs.cwd().writeFile(.{ .sub_path = compressed_path, .data = compressed }) catch |err| {
// Restore from backup on write error
std.fs.cwd().copyFile(backup_path, std.fs.cwd(), file_path, .{}) catch {};
std.fs.cwd().deleteFile(backup_path) catch {};
return err;
};
// Verify compressed file
const verify_data = try std.fs.cwd().readFileAlloc(allocator, compressed_path, 10 * 1024 * 1024);
defer allocator.free(verify_data);
const decompressed = archive.decompress(allocator, verify_data, .gzip) catch |err| {
// Restore from backup on verification error
std.fs.cwd().copyFile(backup_path, std.fs.cwd(), file_path, .{}) catch {};
std.fs.cwd().deleteFile(backup_path) catch {};
std.fs.cwd().deleteFile(compressed_path) catch {};
return err;
};
defer allocator.free(decompressed);
if (!std.mem.eql(u8, file_data, decompressed)) {
// Restore from backup on data mismatch
std.fs.cwd().copyFile(backup_path, std.fs.cwd(), file_path, .{}) catch {};
std.fs.cwd().deleteFile(backup_path) catch {};
std.fs.cwd().deleteFile(compressed_path) catch {};
return error.DataMismatch;
}
// Success - remove backup
try std.fs.cwd().deleteFile(backup_path);
const ratio = @as(f64, @floatFromInt(compressed.len)) / @as(f64, @floatFromInt(file_data.len)) * 100;
std.debug.print(" Original: {d} bytes\n", .{file_data.len});
std.debug.print(" Compressed: {d} bytes ({d:.1}%)\n", .{ compressed.len, ratio });
std.debug.print(" Backup and compression: SUCCESS\n");
// Clean up
std.fs.cwd().deleteFile(file_path) catch {};
std.fs.cwd().deleteFile(compressed_path) catch {};
}
pub fn backupExample(allocator: std.mem.Allocator) !void {
try backupAndCompress(allocator, "test_backup_file.txt");
}File Integrity Verification ​
zig
pub fn fileIntegrityExample(allocator: std.mem.Allocator) !void {
const original_content = "File integrity verification test content.\n" ** 75;
// Create and compress file
const compressed = try archive.compress(allocator, original_content, .zstd);
defer allocator.free(compressed);
try std.fs.cwd().writeFile(.{ .sub_path = "integrity_test.zst", .data = compressed });
// Function to verify file integrity
const verifyFile = struct {
fn verify(alloc: std.mem.Allocator, file_path: []const u8, expected: []const u8) !bool {
const file_data = try std.fs.cwd().readFileAlloc(alloc, file_path, 10 * 1024 * 1024);
defer alloc.free(file_data);
const decompressed = try archive.autoDecompress(alloc, file_data);
defer alloc.free(decompressed);
return std.mem.eql(u8, expected, decompressed);
}
}.verify;
// Test 1: Verify intact file
const intact_result = try verifyFile(allocator, "integrity_test.zst", original_content);
std.debug.print("File integrity verification:\n");
std.debug.print(" Intact file: {s}\n", .{if (intact_result) "PASS" else "FAIL"});
// Test 2: Simulate corruption
const file_data = try std.fs.cwd().readFileAlloc(allocator, "integrity_test.zst", 10 * 1024 * 1024);
defer allocator.free(file_data);
var corrupted_data = try allocator.dupe(u8, file_data);
defer allocator.free(corrupted_data);
// Corrupt some bytes (but not the header)
if (corrupted_data.len > 20) {
corrupted_data[10] ^= 0xFF;
corrupted_data[15] ^= 0xFF;
corrupted_data[20] ^= 0xFF;
}
try std.fs.cwd().writeFile(.{ .sub_path = "integrity_test_corrupted.zst", .data = corrupted_data });
const corrupted_result = verifyFile(allocator, "integrity_test_corrupted.zst", original_content) catch false;
std.debug.print(" Corrupted file: {s}\n", .{if (corrupted_result) "FAIL (should have failed)" else "PASS (correctly detected corruption)"});
// Test 3: Verify checksum-enabled compression
const config = archive.CompressionConfig.init(.zstd)
.withZstdLevel(10)
.withChecksum();
const checksum_compressed = try archive.compressWithConfig(allocator, original_content, config);
defer allocator.free(checksum_compressed);
try std.fs.cwd().writeFile(.{ .sub_path = "integrity_test_checksum.zst", .data = checksum_compressed });
const checksum_result = try verifyFile(allocator, "integrity_test_checksum.zst", original_content);
std.debug.print(" Checksum-enabled: {s}\n", .{if (checksum_result) "PASS" else "FAIL"});
// Clean up
std.fs.cwd().deleteFile("integrity_test.zst") catch {};
std.fs.cwd().deleteFile("integrity_test_corrupted.zst") catch {};
std.fs.cwd().deleteFile("integrity_test_checksum.zst") catch {};
}Configuration-Based File Operations ​
Filtered File Compression ​
zig
pub fn filteredFileCompression(allocator: std.mem.Allocator) !void {
// Create test directory structure
try std.fs.cwd().makeDir("project");
try std.fs.cwd().makeDir("project/src");
try std.fs.cwd().makeDir("project/build");
try std.fs.cwd().makeDir("project/docs");
const test_files = [_]struct { path: []const u8, content: []const u8 }{
.{ .path = "project/src/main.zig", .content = "const std = @import(\"std\");\n" ** 20 },
.{ .path = "project/src/utils.zig", .content = "pub fn helper() void {}\n" ** 15 },
.{ .path = "project/build/output.exe", .content = "BINARY_DATA" ** 100 },
.{ .path = "project/build/temp.tmp", .content = "TEMP_DATA" ** 50 },
.{ .path = "project/docs/README.md", .content = "# Project Documentation\n" ** 30 },
.{ .path = "project/debug.log", .content = "DEBUG LOG ENTRY\n" ** 40 },
};
// Create test files
for (test_files) |file_info| {
try std.fs.cwd().writeFile(.{ .sub_path = file_info.path, .data = file_info.content });
}
// Configuration to include only source and documentation files
const config = archive.CompressionConfig.init(.zstd)
.includeFiles(&[_][]const u8{ "*.zig", "*.md" })
.excludeFiles(&[_][]const u8{ "*.tmp", "*.log", "*.exe" })
.withRecursive(true)
.withZstdLevel(12);
// Collect and filter files
var all_files = std.ArrayList([]const u8).init(allocator);
defer {
for (all_files.items) |path| {
allocator.free(path);
}
all_files.deinit();
}
try collectFilesRecursive(allocator, "project", &all_files);
var filtered_files = std.ArrayList([]const u8).init(allocator);
defer filtered_files.deinit();
for (all_files.items) |file_path| {
if (config.shouldIncludePath(file_path, false)) {
try filtered_files.append(file_path);
}
}
std.debug.print("Filtered file compression:\n");
std.debug.print(" Total files: {d}\n", .{all_files.items.len});
std.debug.print(" Filtered files: {d}\n", .{filtered_files.items.len});
// Compress filtered files
var total_original: usize = 0;
var total_compressed: usize = 0;
for (filtered_files.items) |file_path| {
const file_data = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024);
defer allocator.free(file_data);
const compressed = try archive.compressWithConfig(allocator, file_data, config);
defer allocator.free(compressed);
const output_path = try std.fmt.allocPrint(allocator, "{s}.zst", .{file_path});
defer allocator.free(output_path);
try std.fs.cwd().writeFile(.{ .sub_path = output_path, .data = compressed });
total_original += file_data.len;
total_compressed += compressed.len;
std.debug.print(" {s}: {d} -> {d} bytes\n", .{ file_path, file_data.len, compressed.len });
// Clean up compressed file
std.fs.cwd().deleteFile(output_path) catch {};
}
const overall_ratio = @as(f64, @floatFromInt(total_compressed)) / @as(f64, @floatFromInt(total_original)) * 100;
std.debug.print(" Overall: {d} -> {d} bytes ({d:.1}%)\n", .{ total_original, total_compressed, overall_ratio });
// Clean up test files and directories
for (test_files) |file_info| {
std.fs.cwd().deleteFile(file_info.path) catch {};
}
std.fs.cwd().deleteDir("project/src") catch {};
std.fs.cwd().deleteDir("project/build") catch {};
std.fs.cwd().deleteDir("project/docs") catch {};
std.fs.cwd().deleteDir("project") catch {};
}Error Handling in File Operations ​
Robust File Processing ​
zig
pub fn robustFileProcessing(allocator: std.mem.Allocator) !void {
const test_files = [_]struct { name: []const u8, content: []const u8, should_fail: bool }{
.{ .name = "good_file.txt", .content = "Good file content\n" ** 50, .should_fail = false },
.{ .name = "empty_file.txt", .content = "", .should_fail = true },
.{ .name = "large_file.txt", .content = "Large file content\n" ** 1000, .should_fail = false },
};
// Create test files
for (test_files) |file_info| {
try std.fs.cwd().writeFile(.{ .sub_path = file_info.name, .data = file_info.content });
}
std.debug.print("Robust file processing:\n");
var success_count: usize = 0;
var error_count: usize = 0;
for (test_files) |file_info| {
std.debug.print(" Processing {s}: ", .{file_info.name});
const result = processFileRobustly(allocator, file_info.name);
if (result) {
std.debug.print("SUCCESS\n");
success_count += 1;
} else |err| {
std.debug.print("ERROR - {}\n", .{err});
error_count += 1;
}
// Clean up
std.fs.cwd().deleteFile(file_info.name) catch {};
const output_name = try std.fmt.allocPrint(allocator, "{s}.gz", .{file_info.name});
defer allocator.free(output_name);
std.fs.cwd().deleteFile(output_name) catch {};
}
std.debug.print(" Results: {d} success, {d} errors\n", .{ success_count, error_count });
}
fn processFileRobustly(allocator: std.mem.Allocator, file_path: []const u8) !void {
// Check if file exists and get info
const file_stat = std.fs.cwd().statFile(file_path) catch |err| switch (err) {
error.FileNotFound => {
std.debug.print("File not found");
return err;
},
error.AccessDenied => {
std.debug.print("Access denied");
return err;
},
else => return err,
};
// Check file size
if (file_stat.size == 0) {
std.debug.print("Empty file");
return error.EmptyFile;
}
if (file_stat.size > 10 * 1024 * 1024) { // 10MB limit
std.debug.print("File too large");
return error.FileTooLarge;
}
// Read file
const file_data = std.fs.cwd().readFileAlloc(allocator, file_path, @intCast(file_stat.size)) catch |err| switch (err) {
error.OutOfMemory => {
std.debug.print("Out of memory");
return err;
},
else => return err,
};
defer allocator.free(file_data);
// Compress file
const compressed = archive.compress(allocator, file_data, .gzip) catch |err| switch (err) {
error.OutOfMemory => {
std.debug.print("Compression out of memory");
return err;
},
error.InvalidData => {
std.debug.print("Invalid data for compression");
return err;
},
else => return err,
};
defer allocator.free(compressed);
// Write compressed file
const output_path = try std.fmt.allocPrint(allocator, "{s}.gz", .{file_path});
defer allocator.free(output_path);
std.fs.cwd().writeFile(.{ .sub_path = output_path, .data = compressed }) catch |err| switch (err) {
error.AccessDenied => {
std.debug.print("Cannot write output");
return err;
},
error.NoSpaceLeft => {
std.debug.print("No space left");
return err;
},
else => return err,
};
}Next Steps ​
- Learn about Streaming for memory-efficient file processing
- Explore Configuration for advanced file filtering
- Check out Auto-Detection for working with unknown formats
- See Builder Pattern for flexible configuration options
