You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

188 lines
6.9 KiB

import 'dart:convert';
import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:flutter/foundation.dart';
import 'package:logic_circuits_simulator/models.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
import 'package:uuid/uuid.dart';
class ProjectsState extends ChangeNotifier {
ProjectsState() {
_init();
}
List<ProjectEntry> get projects => index.projects;
ProjectsIndex index = const ProjectsIndex(projects: []);
Future<Directory> _getProjectsDir() async {
final appDir = await getApplicationDocumentsDirectory();
final result = Directory(path.join(appDir.path, 'LogicCircuitsSimulator', 'projects'));
if (!await result.exists()) {
await result.create(recursive: true);
}
return result;
}
Future<File> _getIndexFile() async {
final result = File(path.join((await _getProjectsDir()).path, 'index.json'));
return result;
}
Future<void> _updateIndex(ProjectsIndex newIndex) async {
// Sort projects when updating: latest update first
index = newIndex.copyWith(projects: newIndex.projects.toList()..sort((p1, p2) => p2.lastUpdate.compareTo(p1.lastUpdate)));
final indexFile = await _getIndexFile();
await indexFile.writeAsString(const JsonEncoder.withIndent(" ").convert(index.toJson()));
notifyListeners();
}
void _init() async {
final indexFile = await _getIndexFile();
if (await indexFile.exists()) {
index = ProjectsIndex.fromJson(jsonDecode(await indexFile.readAsString()));
notifyListeners();
}
}
Future<void> newProject(String projectName) async {
final id = const Uuid().v4();
final project = ProjectEntry(
lastUpdate: DateTime.now(),
projectName: projectName,
projectId: id,
);
await _updateIndex(index.copyWith(projects: index.projects + [project]));
final projectDir = await Directory(path.join((await _getProjectsDir()).path, id)).create();
await Directory(path.join(projectDir.path, 'components')).create();
if (kDebugMode) {
print('Created new project in ${projectDir.path}');
}
}
Future<void> deleteProject(String projectId) async {
await _updateIndex(index.copyWith(projects: index.projects.where((p) => p.projectId != projectId).toList()));
await Directory(path.join((await _getProjectsDir()).path, projectId)).delete(recursive: true);
}
Future<void> updateProject(ProjectEntry project) async {
if (!index.projects.map((p) => p.projectId).contains(project.projectId)) {
throw Exception('Project not in index!');
}
await _updateIndex(
index.copyWith(
projects: index.projects
.map((p) => p.projectId == project.projectId ? project : p)
.toList()
)
);
}
Future<T> archiveProject<T>(ProjectEntry project, Future<T> Function(Archive archive) callback) async {
final projectsDir = await _getProjectsDir();
// Create dir where export is prepared
final exportDir = Directory(path.join(projectsDir.path, '.export'));
await exportDir.create();
// Write index.json with only that project
final exportIndex = index.copyWith(
projects: index.projects.where((p) => p.projectId == project.projectId).toList(growable: false),
);
final exportIndexFile = File(path.join(exportDir.path, 'index.json'));
await exportIndexFile.writeAsString(jsonEncode(exportIndex));
final exportProjectIdFile = File(path.join(exportDir.path, 'projectId.txt'));
await exportProjectIdFile.writeAsString(project.projectId);
// Copy project folder
final projectDir = Directory(path.join(projectsDir.path, project.projectId));
final exportProjectDir = Directory(path.join(exportDir.path, project.projectId));
await exportProjectDir.create();
await for (final entry in projectDir.list(recursive: true, followLinks: false)) {
final filename = path.relative(entry.path, from: projectDir.path);
if (entry is Directory) {
final newDir = Directory(path.join(exportProjectDir.path, filename));
await newDir.create(recursive: true);
}
else if (entry is File) {
await entry.copy(path.join(exportProjectDir.path, filename));
}
else if (entry is Link) {
final newLink = Link(path.join(exportProjectDir.path, filename));
await newLink.create(await entry.target());
}
}
// Create archive
final archive = createArchiveFromDirectory(exportDir, includeDirName: false);
final result = await callback(archive);
// Remove preparation dir
await exportDir.delete(recursive: true);
return result;
}
Future<ProjectEntry?> importProject({required Archive archive, required Future<bool> Function() onConflictingId, required Future<bool> Function(String name) onConflictingName}) async {
final projectsDir = await _getProjectsDir();
// Create dir where import is prepared
final importDir = Directory(path.join(projectsDir.path, '.import'));
await importDir.create();
extractArchiveToDisk(archive, importDir.path);
final projectIdFile = File(path.join(importDir.path, 'projectId.txt'));
final projectId = (await projectIdFile.readAsString()).trim();
final indexFile = File(path.join(importDir.path, 'index.json'));
final importIndex = ProjectsIndex.fromJson(jsonDecode(await indexFile.readAsString()));
if (index.projects.map((p) => p.projectId).contains(projectId)) {
if (!await onConflictingId()) {
return null;
}
}
final importIndexEntry = importIndex.projects.where((p) => p.projectId == projectId).first;
final importProjectName = importIndexEntry.projectName;
if (index.projects.where((p) => p.projectId != projectId).map((p) => p.projectName).contains(importProjectName)) {
if (!await onConflictingName(importProjectName)) {
return null;
}
}
await _updateIndex(index.copyWith(
projects: index.projects.where((p) => p.projectId != projectId).followedBy([importIndexEntry]).toList(),
));
// Copy project folder
final projectDir = Directory(path.join(projectsDir.path, projectId));
if (await projectDir.exists()) {
await projectDir.delete(recursive: true);
}
await projectDir.create();
final importProjectDir = Directory(path.join(importDir.path, projectId));
await for (final entry in importProjectDir.list(recursive: true, followLinks: false)) {
final filename = path.relative(entry.path, from: importProjectDir.path);
if (entry is Directory) {
final newDir = Directory(path.join(projectDir.path, filename));
await newDir.create(recursive: true);
}
else if (entry is File) {
await entry.copy(path.join(projectDir.path, filename));
}
else if (entry is Link) {
final newLink = Link(path.join(projectDir.path, filename));
await newLink.create(await entry.target());
}
}
await importDir.delete(recursive: true);
return index.projects.where((p) => p.projectId == projectId).first;
}
}