Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add password protected notes, closes #771 #772

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions api/lib/src/converter/note.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import '../../butterfly_api.dart';

const kArchiveSignature = 0x50;

NoteData noteDataMigrator(Uint8List data) {
NoteData noteDataMigrator(Uint8List data, {String? password}) {
Archive archive;
if (data.isNotEmpty && data[0] != kArchiveSignature) {
final map = json.decode(utf8.decode(data)) as Map<String, dynamic>;
archive = convertLegacyDataToArchive(map);
} else {
archive = ZipDecoder().decodeBytes(data);
archive = ZipDecoder().decodeBytes(data, password: password);
}
return archiveNoteDataMigrator(archive);
}
Expand Down
8 changes: 0 additions & 8 deletions api/lib/src/models/asset.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
import 'dart:typed_data';

import 'package:butterfly_api/butterfly_api.dart';
import 'package:lw_file_system_api/lw_file_system_api.dart';

enum AssetFileType { note, page, image, markdown, pdf, svg, xopp, archive }

extension AppDocumentLoadExtension on FileSystemFile {
NoteData load({bool disableMigrations = false}) =>
NoteData.fromData(Uint8List.fromList(data),
disableMigrations: disableMigrations);
}

extension AssetLocationFileTypeExtension on AssetLocation {
AssetFileType? get fileType =>
AssetFileTypeHelper.fromFileExtension(fileExtension);
Expand Down
35 changes: 30 additions & 5 deletions api/lib/src/models/data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,39 @@ import 'palette.dart';

final Set<String> validAssetPaths = {kImagesArchiveDirectory};

@immutable
final class NoteFile {
final Uint8List data;

NoteFile(this.data);

bool isEncrypted() => isZipEncrypted(data);

(String?, NoteData)? _data;

NoteData? load({String? password}) {
if (_data != null && _data?.$1 == password) {
return _data?.$2;
}
try {
final data = NoteData.fromData(this.data, password: password);
_data = (password, data);
return data;
} catch (_) {
return null;
}
}
}

final class NoteData extends ArchiveData<NoteData> {
NoteData(super.archive, {super.state});
NoteData(super.archive, {super.state, super.password});

factory NoteData.fromData(Uint8List data, {bool disableMigrations = false}) {
factory NoteData.fromData(Uint8List data,
{bool disableMigrations = false, String? password}) {
if (disableMigrations) {
final archive = ZipDecoder().decodeBytes(data);
final archive = ZipDecoder().decodeBytes(data, password: password);
return NoteData(archive);
}
return noteDataMigrator(data);
return noteDataMigrator(data, password: password);
}

factory NoteData.fromArchive(Archive archive,
Expand Down Expand Up @@ -425,4 +448,6 @@ final class NoteData extends ArchiveData<NoteData> {
final removed = Set<String>.from(state.removed)..remove(path);
return updateState(state.copyWith(removed: removed));
}

NoteFile toFile() => NoteFile(exportAsBytes());
}
4 changes: 2 additions & 2 deletions api/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,8 @@ packages:
dependency: "direct main"
description:
path: "packages/lw_file_system_api"
ref: b9d6c6173bf75247ce5a4d47fab0e48b730a9696
resolved-ref: b9d6c6173bf75247ce5a4d47fab0e48b730a9696
ref: "2036c923bb93afcefaadd8108c7e789513ae7082"
resolved-ref: "2036c923bb93afcefaadd8108c7e789513ae7082"
url: "https://github.com/LinwoodDev/dart_pkgs"
source: git
version: "1.0.0"
Expand Down
2 changes: 1 addition & 1 deletion api/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ dependencies:
git:
url: https://github.com/LinwoodDev/dart_pkgs
path: packages/lw_file_system_api
ref: b9d6c6173bf75247ce5a4d47fab0e48b730a9696
ref: 2036c923bb93afcefaadd8108c7e789513ae7082

dev_dependencies:
test: ^1.25.3
Expand Down
7 changes: 1 addition & 6 deletions app/lib/api/changes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,9 @@ import 'package:butterfly/api/open.dart';
import 'package:butterfly_api/butterfly_api.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:lw_file_system/lw_file_system.dart';
import 'package:phosphor_flutter/phosphor_flutter.dart';

Future<NoteData?> checkFileChanges(
BuildContext context, FileSystemEntity<NoteData>? entity) async {
if (entity is! FileSystemFile<NoteData>) return null;
final data = entity.data;
if (data == null) return null;
Future<NoteData?> checkFileChanges(BuildContext context, NoteData data) async {
final metadata = data.getMetadata();
if (metadata == null) return null;
final version = metadata.fileVersion;
Expand Down
11 changes: 7 additions & 4 deletions app/lib/api/file_system.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import 'package:shared_preferences/shared_preferences.dart';
Uint8List _encode(NoteData data) => Uint8List.fromList(data.exportAsBytes());
NoteData _decode(Uint8List data) => NoteData.fromData(data);

Uint8List _encodeFile(NoteFile file) => file.data;
NoteFile _decodeFile(Uint8List data) => NoteFile(data);

const butterflySubDirectory = '/Linwood/Butterfly';

String? overrideButterflyDirectory;
Expand Down Expand Up @@ -61,7 +64,7 @@ Future<String> Function(ExternalStorage? storage) _getRemoteDirectory(

Future<String> getButterflyDocumentsDirectory([ExternalStorage? storage]) =>
_getRemoteDirectory('Documents')(storage);
typedef DocumentFileSystem = TypedDirectoryFileSystem<NoteData>;
typedef DocumentFileSystem = TypedDirectoryFileSystem<NoteFile>;
typedef TemplateFileSystem = TypedKeyFileSystem<NoteData>;
typedef PackFileSystem = TypedKeyFileSystem<NoteData>;

Expand Down Expand Up @@ -170,12 +173,12 @@ class ButterflyFileSystem {
await fs.createFile('${pack.name}.bfly', pack);
}

TypedDirectoryFileSystem<NoteData> buildDocumentSystem(
TypedDirectoryFileSystem<NoteFile> buildDocumentSystem(
[ExternalStorage? storage]) =>
TypedDirectoryFileSystem.build(
_documentConfig,
onEncode: _encode,
onDecode: _decode,
onEncode: _encodeFile,
onDecode: _decodeFile,
storage: storage,
);
TypedKeyFileSystem<NoteData> buildTemplateSystem(
Expand Down
4 changes: 2 additions & 2 deletions app/lib/cubits/current_index.dart
Original file line number Diff line number Diff line change
Expand Up @@ -976,10 +976,10 @@ class CurrentIndexCubit extends Cubit<CurrentIndex> {
state.absolute ||
location.fileType != AssetFileType.note) {
final document = await fileSystem.createFileWithName(
name: currentData.name, suffix: '.bfly', currentData);
name: currentData.name, suffix: '.bfly', currentData.toFile());
location = document.location;
} else {
await fileSystem.updateFile(location.path, currentData);
await fileSystem.updateFile(location.path, currentData.toFile());
}
state.settingsCubit.addRecentHistory(location);
emit(state.copyWith(location: location, saved: SaveState.saved));
Expand Down
4 changes: 2 additions & 2 deletions app/lib/dialogs/file_system/create.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ class _FileSystemAssetCreateDialogState
if (_formKey.currentState?.validate() ?? false) {
final newPath = '${widget.path}/${_nameController.text}';
if (!widget.isFolder) {
await widget.fileSystem
.createFile(newPath, DocumentDefaults.createDocument());
await widget.fileSystem.createFile(
newPath, DocumentDefaults.createDocument().toFile());
} else {
await widget.fileSystem.createDirectory(newPath);
}
Expand Down
21 changes: 18 additions & 3 deletions app/lib/services/import.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:ui' as ui;
import 'package:archive/archive.dart';
import 'package:butterfly/api/file_system.dart';
import 'package:butterfly/api/image.dart';
import 'package:butterfly/dialogs/name.dart';
import 'package:butterfly/helpers/asset.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
Expand Down Expand Up @@ -88,6 +89,8 @@ class ImportService {
bytes = Uint8List.fromList(List<int>.from(data));
} else if (data is NoteData) {
return data;
} else if (data is NoteFile) {
bytes = data.data;
}
if (type.isEmpty) type = 'note';
final fileType = AssetFileType.values.firstWhereOrNull((element) =>
Expand Down Expand Up @@ -221,7 +224,19 @@ class ImportService {
try {
final documentOpened = document != null;
final realDocument = document ?? DocumentDefaults.createDocument();
final data = NoteData.fromData(bytes);
final file = NoteFile(bytes);
String? password;
if (file.isEncrypted()) {
password = await showDialog<String>(
context: context,
builder: (context) => NameDialog(
title: AppLocalizations.of(context).password,
),
);
if (password == null) return null;
}
final data = file.load(password: password);
if (data == null) return null;
if (!data.isValid) {
await importArchive(bytes);
return null;
Expand Down Expand Up @@ -758,7 +773,7 @@ class ImportService {
if (data.isValid) {
final document = await importBfly(bytes);
if (document != null) {
fileSystem.createFile(document.name ?? '', document);
fileSystem.createFile(document.name ?? '', document.toFile());
}
return document != null;
}
Expand All @@ -767,7 +782,7 @@ class ImportService {
if (!file.name.endsWith(fileExtension)) continue;
final document = await importBfly(file.content, advanced: false);
if (document != null) {
fileSystem.createFile(file.name, document);
fileSystem.createFile(file.name, document.toFile());
}
}
return true;
Expand Down
11 changes: 6 additions & 5 deletions app/lib/views/files/entity.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import 'package:phosphor_flutter/phosphor_flutter.dart';
import 'package:popover/popover.dart';

class FileEntityItem extends StatefulWidget {
final FileSystemEntity<NoteData> entity;
final FileSystemEntity<NoteFile> entity;
final bool active, collapsed, gridView;
final bool? selected;
final VoidCallback onTap, onReload;
Expand Down Expand Up @@ -125,11 +125,12 @@ class _FileEntityItemState extends State<FileEntityItem> {
PhosphorIconData icon = PhosphorIconsLight.folder;
final entity = widget.entity;
try {
if (entity is FileSystemFile<NoteData>) {
if (entity is FileSystemFile<NoteFile>) {
final data = entity.data?.load();
icon = entity.location.fileType.icon(PhosphorIconsStyle.light);
thumbnail = entity.data?.getThumbnail();
thumbnail = data?.getThumbnail();
if (thumbnail?.isEmpty ?? false) thumbnail = null;
metadata = entity.data?.getMetadata();
metadata = data?.getMetadata();
final locale = Localizations.localeOf(context).languageCode;
final dateFormatter = DateFormat.yMd(locale);
final timeFormatter = DateFormat.Hm(locale);
Expand Down Expand Up @@ -238,7 +239,7 @@ class _FileEntityItemState extends State<FileEntityItem> {
class ContextFileRegion extends StatelessWidget {
final ExternalStorage? remote;
final DocumentFileSystem documentSystem;
final FileSystemEntity<NoteData> entity;
final FileSystemEntity<NoteFile> entity;
final SettingsCubit settingsCubit;
final bool editable;
final ValueChanged<bool> onEdit;
Expand Down
2 changes: 1 addition & 1 deletion app/lib/views/files/grid.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class FileEntityGridItem extends StatelessWidget {
final VoidCallback onTap, onDelete, onReload;
final ValueChanged<bool> onEdit, onSelectedChanged;
final Uint8List? thumbnail;
final FileSystemEntity<NoteData> entity;
final FileSystemEntity<NoteFile> entity;
final TextEditingController nameController;

const FileEntityGridItem({
Expand Down
2 changes: 1 addition & 1 deletion app/lib/views/files/list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class FileEntityListTile extends StatelessWidget {
final VoidCallback onTap, onDelete, onReload;
final ValueChanged<bool> onEdit, onSelectedChanged;
final Uint8List? thumbnail;
final FileSystemEntity<NoteData> entity;
final FileSystemEntity<NoteFile> entity;
final TextEditingController nameController;
final Widget actionButton;

Expand Down
28 changes: 15 additions & 13 deletions app/lib/views/files/view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class FilesViewState extends State<FilesView> {
ExternalStorage? _remote;
String _search = '';
late final SettingsCubit _settingsCubit;
Stream<FileSystemEntity<NoteData>?>? _filesStream;
Stream<FileSystemEntity<NoteFile>?>? _filesStream;
final Set<String> _selectedFiles = {};

@override
Expand Down Expand Up @@ -119,9 +119,11 @@ class FilesViewState extends State<FilesView> {
directory: path,
name: name,
suffix: '.bfly',
template.createDocument(
name: name,
));
template
.createDocument(
name: name,
)
.toFile());
reloadFileSystem();
}

Expand Down Expand Up @@ -523,7 +525,7 @@ class FilesViewState extends State<FilesView> {
.getAsset(_locationController.text,
readData: false);
if (directory
is! FileSystemDirectory<NoteData>) {
is! FileSystemDirectory<NoteFile>) {
return;
}
setState(() {
Expand Down Expand Up @@ -608,7 +610,7 @@ class FilesViewState extends State<FilesView> {
BlocBuilder<SettingsCubit, ButterflySettings>(
buildWhen: (previous, current) => previous.starred != current.starred,
builder: (context, settings) =>
StreamBuilder<FileSystemEntity<NoteData>?>(
StreamBuilder<FileSystemEntity<NoteFile>?>(
stream: _filesStream,
builder: (context, snapshot) {
if (snapshot.hasError) {
Expand All @@ -622,7 +624,7 @@ class FilesViewState extends State<FilesView> {
child: Text(AppLocalizations.of(context).noElements));
}
final entity = snapshot.data;
if (entity is! FileSystemDirectory<NoteData>) {
if (entity is! FileSystemDirectory<NoteFile>) {
return Container();
}
final assets = entity.assets.where((e) {
Expand Down Expand Up @@ -711,7 +713,7 @@ class FilesViewState extends State<FilesView> {
}
}

int _sortAssets(FileSystemEntity<NoteData> a, FileSystemEntity<NoteData> b) {
int _sortAssets(FileSystemEntity<NoteFile> a, FileSystemEntity<NoteFile> b) {
try {
final settings = _settingsCubit.state;
// Test if starred
Expand All @@ -729,14 +731,14 @@ class FilesViewState extends State<FilesView> {
if (b is FileSystemDirectory<NoteData>) {
return 1;
}
final aFile = a as FileSystemFile<NoteData>;
final bFile = b as FileSystemFile<NoteData>;
final aFile = a as FileSystemFile<NoteFile>;
final bFile = b as FileSystemFile<NoteFile>;
FileMetadata? aInfo, bInfo;
try {
aInfo = aFile.data?.getMetadata();
aInfo = aFile.data?.load()?.getMetadata();
} catch (_) {}
try {
bInfo = bFile.data?.getMetadata();
bInfo = bFile.data?.load()?.getMetadata();
} catch (_) {}
if (aInfo == null) {
if (bInfo == null) {
Expand Down Expand Up @@ -791,7 +793,7 @@ class _RecentFilesView extends StatefulWidget {
}

class _RecentFilesViewState extends State<_RecentFilesView> {
late Stream<List<FileSystemEntity<NoteData>>> _stream;
late Stream<List<FileSystemEntity<NoteFile>>> _stream;
late final ButterflyFileSystem _fileSystem;
final ScrollController _recentScrollController = ScrollController();

Expand Down
13 changes: 11 additions & 2 deletions app/lib/views/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,17 @@ class _ProjectPageState extends State<ProjectPage> {
if (!absolute) {
final asset = await documentSystem.getAsset(location.path);
if (!mounted) return;
if (location.fileType == AssetFileType.note) {
document = await checkFileChanges(context, asset);
if (asset is FileSystemFile<NoteFile> &&
location.fileType == AssetFileType.note) {
final noteData = await globalImportService.load(
document: defaultDocument,
type: widget.type.isEmpty
? (fileType ?? widget.type)
: widget.type,
data: asset.data);
if (noteData != null) {
document = await checkFileChanges(context, noteData);
}
}
} else {
final data = await documentSystem.loadAbsolute(location.path);
Expand Down
Loading
Loading