|
| 1 | +# OwlCore.Storage |
| 2 | + |
| 3 | +The most flexible file system abstraction for .NET, built in partnership with the UWP Community. This is a C# library project that provides interfaces and implementations for working with files and folders across different storage systems (Memory, System.IO, HTTP, Zip archives, etc.). |
| 4 | + |
| 5 | +**Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.** |
| 6 | + |
| 7 | +## Working Effectively |
| 8 | + |
| 9 | +### Bootstrap, build, and test the repository: |
| 10 | +- **CRITICAL: Install .NET 9 SDK first** - this project requires .NET 9: |
| 11 | + - `wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh && chmod +x dotnet-install.sh` |
| 12 | + - `./dotnet-install.sh --channel 9.0` |
| 13 | + - `export PATH="/home/runner/.dotnet:$PATH"` |
| 14 | +- **Restore dependencies**: `dotnet restore` -- takes ~1.2 seconds. NEVER CANCEL. Set timeout to 60+ seconds. |
| 15 | +- **Build**: `dotnet build` -- takes ~2.5 seconds. NEVER CANCEL. Set timeout to 60+ seconds. |
| 16 | +- **Test**: `dotnet test` -- takes ~3 seconds, runs 391 tests (2 may fail due to network connectivity). NEVER CANCEL. Set timeout to 60+ seconds. |
| 17 | + |
| 18 | +### Code formatting and validation: |
| 19 | +- **Format code**: `dotnet format` -- fixes whitespace and formatting issues automatically. Takes ~9 seconds. NEVER CANCEL. Set timeout to 60+ seconds. |
| 20 | +- **Check formatting**: `dotnet format --verify-no-changes` -- validates code formatting (will show errors if formatting needed) |
| 21 | +- **Build with warnings**: `dotnet build --verbosity minimal` -- shows the single expected warning about TruncatedStream.DisposeAsync() |
| 22 | + |
| 23 | +### Expected build outputs: |
| 24 | +- Build succeeds with 1 warning (TruncatedStream.DisposeAsync() method hiding inherited member - this is expected) |
| 25 | +- Tests: 389 pass, 2 fail (HttpFile tests fail due to network connectivity - this is expected in sandboxed environments) |
| 26 | +- Build artifacts: `src/bin/Debug/netstandard2.0/OwlCore.Storage.dll` and `src/bin/Debug/net9.0/OwlCore.Storage.dll` |
| 27 | + |
| 28 | +## Validation |
| 29 | + |
| 30 | +- **ALWAYS run build and tests after making changes** to ensure functionality works correctly |
| 31 | +- **ALWAYS run `dotnet format`** before committing changes or the CI (.github/workflows/build.yml) will fail due to formatting issues |
| 32 | + |
| 33 | +## Project Architecture |
| 34 | + |
| 35 | +### Key interfaces (located in src/): |
| 36 | +- **IFile**: Basic file operations (`OpenStreamAsync`) |
| 37 | +- **IFolder**: Basic folder operations (`GetItemsAsync`) |
| 38 | +- **IModifiableFolder**: File/folder creation and deletion |
| 39 | +- **IStorable**: Base interface with `Id` and `Name` properties |
| 40 | +- **IChildFile/IChildFolder**: Files/folders that have a parent |
| 41 | +- **IStorableChild**: Items that can navigate to their parent |
| 42 | + |
| 43 | +### Major implementations: |
| 44 | +- **System.IO**: `SystemFile`, `SystemFolder` (src/System/IO/) - wraps System.IO File/Directory APIs |
| 45 | +- **Memory**: `MemoryFile`, `MemoryFolder` (src/Memory/) - fully in-memory implementations |
| 46 | +- **HTTP**: `HttpFile` (src/System/Net/Http/) - read-only HTTP file access |
| 47 | +- **Zip**: Archive support via System.IO.Compression (src/System/IO/Compression/) |
| 48 | + |
| 49 | +### Test structure (tests/OwlCore.Storage.Tests/): |
| 50 | +- **CommonTests**: Shared test logic via OwlCore.Storage.CommonTests NuGet package |
| 51 | +- **Implementation-specific tests**: SystemIO/, Memory/, Archive/ folders |
| 52 | +- **Feature tests**: CreateRelativeStorageExtensionsTests, FileRangeReadWriteTests, etc. |
| 53 | + |
| 54 | +## Common Tasks |
| 55 | + |
| 56 | +### Repository root structure: |
| 57 | +``` |
| 58 | +.github/ # GitHub workflows and configuration |
| 59 | +src/ # Main library source code |
| 60 | + System/ # System.IO, HTTP, and Compression implementations |
| 61 | + Memory/ # In-memory implementations |
| 62 | + Extensions/ # Extension methods for interfaces |
| 63 | + *.cs # Core interfaces (IFile, IFolder, etc.) |
| 64 | +tests/ # MSTest unit tests |
| 65 | +OwlCore.Storage.sln # Visual Studio solution file |
| 66 | +README.md # Project documentation with proposal details |
| 67 | +``` |
| 68 | + |
| 69 | +### Building for different targets: |
| 70 | +- Project multi-targets `netstandard2.0` and `net9.0` |
| 71 | +- Build creates separate assemblies for each target framework |
| 72 | +- Always use `dotnet build` (not `msbuild` or Visual Studio) for reliable cross-platform builds |
| 73 | + |
| 74 | +### Working with the abstractions: |
| 75 | +- **Start with interfaces**: Always code against IFile/IFolder, not concrete implementations |
| 76 | +- **Use extension methods**: Many operations (copy, move, relative paths) are provided as extensions in src/Extensions/ |
| 77 | +- **Handle async properly**: All operations are async - use proper cancellation tokens |
| 78 | +- **Example basic usage**: |
| 79 | + ```csharp |
| 80 | + // File system access |
| 81 | + var file = new SystemFile("/path/to/file.txt"); |
| 82 | + using var stream = await file.OpenStreamAsync(FileAccess.Read); |
| 83 | + |
| 84 | + // In-memory testing |
| 85 | + var memoryFile = new MemoryFile(new MemoryStream()); |
| 86 | + await memoryFile.WriteTextAsync("Hello World"); |
| 87 | + ``` |
| 88 | + |
| 89 | +### Key extension methods to know: |
| 90 | +- `CreateCopyOfAsync()` - copy files between different storage systems |
| 91 | +- `MoveFromAsync()` - move files between storage systems |
| 92 | +- `GetItemByRelativePathAsync()` - navigate using relative paths ("../folder/file.txt") |
| 93 | +- `CreateFolderByRelativePathAsync()` - create folder structures from relative paths |
| 94 | +- File I/O: `ReadTextAsync()`, `WriteTextAsync()`, `ReadBytesAsync()`, `WriteBytesAsync()` |
| 95 | + |
| 96 | +### Common pitfalls to avoid: |
| 97 | +- **Don't assume file paths exist** - many implementations (Memory, HTTP, Zip) don't have traditional file paths |
| 98 | +- **Use proper FileAccess** - specify Read, Write, or ReadWrite explicitly in OpenStreamAsync() |
| 99 | +- **Handle FileNotFoundException** - thrown when files/folders don't exist |
| 100 | +- **Dispose streams properly** - always use `using` statements or proper disposal |
| 101 | +- **Don't hardcode separators** - use relative path APIs instead of string manipulation |
| 102 | + |
| 103 | +This is a foundational library that enables file system abstraction across many different storage backends. Focus on interface-based programming and leveraging the rich extension method ecosystem. |
0 commit comments