universal_storage_sync 0.1.0-dev.2
universal_storage_sync: ^0.1.0-dev.2 copied to clipboard
A cross-platform Dart package providing unified API for file storage operations with support for local filesystem and Git-based version control.
Universal Storage Sync #
A cross-platform Dart package providing a unified API for file storage operations with support for local filesystem, GitHub API, and Git-based version control.
Features #
- Unified API: Single interface for different storage providers through
StorageService - Cross-platform: Works on desktop, mobile, and web platforms
- Multiple Providers: FileSystem, GitHub API, and Offline Git storage
- Type-safe Configuration: Strongly typed configuration classes with validation
- Smart Provider Selection: Automatic recommendations based on requirements
- Version Control Ready: Built-in support for Git-based storage with commit messages
- Offline-first: Local operations with optional remote synchronization
- Retry Logic: Built-in retry mechanisms for network operations
- Path Normalization: Cross-platform path handling
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
universal_storage_sync: ^1.0.0
Then run:
dart pub get
Quick Start #
Basic Usage with FileSystem Provider #
import 'package:universal_storage_sync/universal_storage_sync.dart';
Future<void> main() async {
// Create and configure the storage service
final provider = FileSystemStorageProvider();
final storageService = StorageService(provider);
// Initialize with configuration
final config = FileSystemConfig(basePath: '/path/to/storage');
await storageService.initializeWithConfig(config);
// File operations
await storageService.saveFile('hello.txt', 'Hello, World!');
final content = await storageService.readFile('hello.txt');
print(content); // Output: Hello, World!
// List files
final files = await storageService.listDirectory('.');
print('Files: $files');
// Delete file
await storageService.removeFile('hello.txt');
}
Using StorageFactory (Recommended) #
import 'package:universal_storage_sync/universal_storage_sync.dart';
Future<void> main() async {
// Create service using factory - automatically initialized
final config = FileSystemConfig(basePath: '/path/to/storage');
final storageService = await StorageFactory.create(config);
// Ready to use immediately
await storageService.saveFile('hello.txt', 'Hello, World!');
final content = await storageService.readFile('hello.txt');
print(content);
}
Real-World Example: Todo App #
Here's how the library is used in a real Flutter todo application:
import 'package:universal_storage_sync/universal_storage_sync.dart';
class TodoAppState extends ChangeNotifier {
StorageService? _storageService;
List<Todo> todos = [];
bool busy = false;
String? error;
/// Initialize storage for a workspace
Future<void> setWorkspacePath(String pathValue) async {
busy = true;
notifyListeners();
try {
// Validate directory
final directory = Directory(pathValue);
if (!await directory.exists()) {
throw Exception('Directory does not exist: $pathValue');
}
// Initialize storage
final config = FileSystemConfig(basePath: pathValue);
_storageService = StorageService(FileSystemStorageProvider());
await _storageService!.initializeWithConfig(config);
// Load existing todos
await loadTodos();
error = null;
} catch (e) {
error = 'Failed to set workspace: $e';
} finally {
busy = false;
notifyListeners();
}
}
/// Load all todos from storage
Future<void> loadTodos() async {
if (_storageService == null) return;
try {
final fileList = await _storageService!.listDirectory('todos');
final loadedTodos = <Todo>[];
for (final fileName in fileList) {
if (fileName.endsWith('.yaml')) {
final content = await _storageService!.readFile(fileName);
if (content != null) {
final todoData = loadYaml(content) as Map;
loadedTodos.add(Todo.fromJson(Map<String, dynamic>.from(todoData)));
}
}
}
todos = loadedTodos;
notifyListeners();
} catch (e) {
error = 'Failed to load todos: $e';
notifyListeners();
}
}
/// Save a todo with version control message
Future<void> saveTodo(Todo todo) async {
if (_storageService == null) return;
try {
final fileName = 'todos/${todo.id}.yaml';
final yamlContent = todoToYaml(todo);
// Save with commit message for version control providers
await _storageService!.saveFile(
fileName,
yamlContent,
message: 'Save todo: ${todo.title}',
);
// Update local list
final existingIndex = todos.indexWhere((t) => t.id == todo.id);
if (existingIndex >= 0) {
todos[existingIndex] = todo;
} else {
todos.add(todo);
}
notifyListeners();
} catch (e) {
error = 'Failed to save todo: $e';
notifyListeners();
}
}
/// Delete a todo
Future<void> deleteTodo(String todoId) async {
if (_storageService == null) return;
try {
final fileName = 'todos/$todoId.yaml';
await _storageService!.removeFile(
fileName,
message: 'Delete todo: $todoId',
);
todos.removeWhere((todo) => todo.id == todoId);
notifyListeners();
} catch (e) {
error = 'Failed to delete todo: $e';
notifyListeners();
}
}
}
Storage Providers #
π FileSystem Provider #
Local file system storage with excellent performance.
final config = FileSystemConfig(
basePath: '/path/to/data',
databaseName: 'app_db', // For web platforms
);
final service = await StorageFactory.createFileSystem(config);
Best for: Desktop/mobile apps, offline-first, high performance local storage
Features:
- Fast local file operations
- Cross-platform path handling
- Web support via IndexedDB
- No external dependencies
π GitHub API Provider #
Cloud storage using GitHub repositories via REST API.
final config = GitHubApiConfig(
authToken: 'ghp_your_token_here',
repositoryOwner: 'your-username',
repositoryName: 'your-repo',
branchName: 'main',
);
final service = await StorageFactory.createGitHubApi(config);
Best for: Web apps, collaboration, cloud sync, no local Git required
Features:
- Full GitHub integration
- Automatic commit messages
- Branch management
- Collaboration features
- No local Git CLI required
π Offline Git Provider #
Local Git repository with optional remote synchronization.
final config = OfflineGitConfig(
localPath: '/path/to/repo',
branchName: 'main',
authorName: 'Your Name',
authorEmail: 'your@email.com',
remoteUrl: 'https://github.com/user/repo.git',
sshKeyPath: '/path/to/ssh/key',
);
final service = await StorageFactory.createOfflineGit(config);
Best for: Desktop apps, full Git features, offline capability, advanced workflows
Features:
- Full Git version control
- Offline capability
- Remote synchronization
- SSH/HTTPS authentication
- Advanced merge strategies
Provider Comparison #
| Feature | FileSystem | GitHub API | Offline Git |
|---|---|---|---|
| Offline Support | β Full | β None | β Full |
| Version Control | β None | β Git | β Full Git |
| Collaboration | β None | β Excellent | β With Remote |
| Setup Complexity | π’ Simple | π‘ Medium | π΄ Complex |
| Web Support | π‘ Limited | β Full | β None |
| Performance | π’ Fastest | π‘ Network | π’ Fast |
StorageService API #
The main interface for all storage operations:
Core Methods #
class StorageService {
/// Initialize with typed configuration
Future<void> initializeWithConfig(StorageConfig config);
/// Save file with optional commit message
Future<String> saveFile(String path, String content, {String? message});
/// Read file content, returns null if not found
Future<String?> readFile(String path);
/// Remove file with optional commit message
Future<void> removeFile(String path, {String? message});
/// List files in directory
Future<List<String>> listDirectory(String path);
/// Restore file to previous version
Future<void> restoreData(String path, {String? versionId});
/// Sync with remote (if supported)
Future<void> syncRemote({
String? pullMergeStrategy,
String? pushConflictStrategy,
});
/// Check authentication status
Future<bool> isAuthenticated();
/// Access underlying provider
StorageProvider get provider;
}
Configuration Classes #
// FileSystem configuration
final fsConfig = FileSystemConfig(
basePath: '/path/to/data',
databaseName: 'app_db', // For web
);
// GitHub API configuration
final ghConfig = GitHubApiConfig(
authToken: 'token',
repositoryOwner: 'owner',
repositoryName: 'repo',
branchName: 'main',
);
// Offline Git configuration
final gitConfig = OfflineGitConfig(
localPath: '/repo/path',
branchName: 'main',
authorName: 'Author Name',
authorEmail: 'author@email.com',
remoteUrl: 'https://github.com/user/repo.git',
sshKeyPath: '/path/to/key', // or httpsToken: 'token'
);
Smart Provider Selection #
Let the library recommend the best provider for your needs:
// Define your requirements
final requirements = ProviderRequirements(
needsVersionControl: true,
needsOffline: true,
hasGitCli: true,
isWeb: false,
needsCollaboration: true,
);
// Get recommendation
final recommendation = ProviderSelector.recommend(requirements);
print('Recommended: ${recommendation.providerType}');
print('Reason: ${recommendation.reason}');
print('Score: ${recommendation.score}/100');
// Create service using recommendation
final service = await StorageFactory.create(recommendation.configTemplate);
Use Case-Based Selection #
// Quick selection based on use case description
final requirements = ProviderSelector.fromUseCase('team collaboration project');
final recommendation = ProviderSelector.recommend(requirements);
final service = await StorageFactory.create(recommendation.configTemplate);
Advanced Features #
Path Normalization #
Cross-platform path handling:
// Normalize paths for different providers
final path = 'folder\\subfolder//file.txt';
final normalizedPath = PathNormalizer.normalize(path, ProviderType.filesystem);
// Validate paths
final isValid = PathNormalizer.isSafePath('docs/api.md', ProviderType.github);
// Join path segments
final joined = PathNormalizer.join(['docs', 'api', 'index.md'], ProviderType.github);
Retry Operations #
Built-in retry logic for robust operations:
// GitHub operations automatically retry on network errors
await RetryableOperation.github(() async {
return await githubProvider.createFile('test.txt', 'content');
});
// Custom retry configuration
await RetryableOperation.execute(
() => someNetworkOperation(),
maxAttempts: 5,
initialDelay: Duration(milliseconds: 1000),
retryIf: (exception) => exception is NetworkException,
);
Version Control Features #
When using Git-based providers, you can leverage version control features:
// Save with meaningful commit messages
await storageService.saveFile(
'docs/api.md',
apiDocumentation,
message: 'Update API documentation with new endpoints',
);
// Restore to previous version
await storageService.restoreData('docs/api.md', versionId: 'abc123');
// Sync with remote repository
await storageService.syncRemote(
pullMergeStrategy: 'merge',
pushConflictStrategy: 'rebase-local',
);
Error Handling #
Comprehensive exception hierarchy:
try {
await storageService.readFile('file.txt');
} on FileNotFoundException catch (e) {
print('File not found: $e');
} on NetworkException catch (e) {
print('Network error: $e');
} on AuthenticationException catch (e) {
print('Authentication failed: $e');
} on GitHubRateLimitException catch (e) {
print('Rate limit exceeded: $e');
} on ConfigurationException catch (e) {
print('Configuration error: $e');
} on StorageException catch (e) {
print('General storage error: $e');
}
Flutter Integration #
State Management #
// Using with ChangeNotifier
class AppState extends ChangeNotifier {
StorageService? _storageService;
bool busy = false;
String? error;
Future<void> initializeStorage(String workspacePath) async {
busy = true;
notifyListeners();
try {
final config = FileSystemConfig(basePath: workspacePath);
_storageService = await StorageFactory.create(config);
error = null;
} catch (e) {
error = e.toString();
} finally {
busy = false;
notifyListeners();
}
}
}
// Using with Provider
ChangeNotifierProvider(
create: (_) => AppState(),
child: MyApp(),
)
Async File Operations #
// Loading state management
Future<void> loadData() async {
setState(() => isLoading = true);
try {
final content = await storageService.readFile('data.json');
if (content != null) {
setState(() => data = jsonDecode(content));
}
} catch (e) {
setState(() => error = e.toString());
} finally {
setState(() => isLoading = false);
}
}
Testing #
Mock Storage for Testing #
// Create a test storage service
final testConfig = FileSystemConfig(basePath: '/tmp/test');
final testService = await StorageFactory.createFileSystem(testConfig);
// Use in tests
test('should save and retrieve data', () async {
await testService.saveFile('test.txt', 'test content');
final content = await testService.readFile('test.txt');
expect(content, equals('test content'));
});
Platform Support #
| Platform | FileSystem | GitHub API | Offline Git |
|---|---|---|---|
| Desktop | β Full | β Full | β Full |
| Mobile | β Full | β Full | β Limited |
| Web | β IndexedDB | β Full | β None |
Requirements #
- Dart SDK:
>=3.0.0 <4.0.0 - Flutter:
>=3.0.0(for Flutter projects) - For Git operations: Git CLI installed and in PATH
Examples #
Check out the comprehensive examples in the example/ directory:
todo_app/- Complete Flutter todo applicationbasic_usage.dart- Simple file operationsgithub_api_usage.dart- GitHub API integrationgit_usage.dart- Git-based storageprovider_factory_usage.dart- StorageFactory usage patterns
Contributing #
Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
License #
This project is licensed under the MIT License - see the LICENSE file for details.
For more information and detailed documentation, visit our GitHub repository.