Skip to content

Commit

Permalink
Added delete method to GridOut + more methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Giorgio Franceschetti committed Mar 12, 2023
1 parent 34069de commit 0dec04b
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 8 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.9.1

- Added a `delete()` method to `GridOut` class
- Added a `toFile()` method to `GridOut` class (allows to write safely on disk - adds a `(n)` suffix if the file already exists)
- Added a `clearBucket()` method to class `GridFs`
- Added a `dropBucket()` mehthod to class `GridFs`
- Fixed an issue on `GridFsFile` `numChunks()` method

## 0.9.0

- Fixed an issue on GridFs `save()` method using MongoDb 6.0
Expand Down
7 changes: 4 additions & 3 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ include: package:lints/recommended.yaml

# For lint rules and documentation, see http://dart-lang.github.io/linter/lints.
# Uncomment to specify additional rules.
# linter:
# rules:
# - camel_case_types
linter:
rules:
- await_only_futures
- unawaited_futures


# analyzer:
Expand Down
99 changes: 99 additions & 0 deletions example/manual/gridfs/delete.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import 'dart:async';
import 'dart:io';

import 'package:mongo_dart/mongo_dart.dart';
import 'package:path/path.dart';

const dbName = 'mongo-dart-example';
const dbAddress = '127.0.0.1';

const defaultUri = 'mongodb://$dbAddress:27017/$dbName';

class MockConsumer implements StreamConsumer<List<int>> {
List<int> data = [];

Future consume(Stream<List<int>> stream) {
var completer = Completer();
stream.listen(_onData, onDone: () => completer.complete(null));
return completer.future;
}

void _onData(List<int> chunk) {
data.addAll(chunk);
}

@override
Future addStream(Stream<List<int>> stream) {
var completer = Completer();
stream.listen(_onData, onDone: () => completer.complete(null));
return completer.future;
}

@override
Future close() => Future.value(true);
}

void main() async {
var db = Db(defaultUri);
await db.open();

Future cleanupDatabase() async {
await db.close();
}

if (!db.masterConnection.serverCapabilities.supportsOpMsg) {
return;
}

var collectionName = 'delete-gridfs';
var gridFS = GridFS(db, collectionName);
await gridFS.dropBucket();

// **** data preparation
var smallData = <int>[
0x00,
0x01,
0x10,
0x11,
0x7e,
0x7f,
0x80,
0x81,
0xfe,
0xff
];

// Set small chunks
GridFS.defaultChunkSize = 9;

// assures at least 3 chunks
var target = GridFS.defaultChunkSize * 3;
var data = <int>[];
while (data.length < target) {
data.addAll(smallData);
}
print('Expected chunks: ${(data.length / GridFS.defaultChunkSize).ceil()}');
var extraData = <String, dynamic>{
'test': [1, 2, 3],
'extraData': 'Test',
'map': {'a': 1}
};

var inputStream = Stream.fromIterable([data]);
var input = gridFS.createFile(inputStream, 'test');
input.extraData = extraData;
await input.save();

var gridOut = await gridFS.findOne(where.eq('_id', input.id));
var consumer = MockConsumer();
var out = IOSink(consumer);
await gridOut?.writeTo(out);

await gridOut?.delete();

print('Out Chunk size: ${gridOut?.chunkSize}'); // 9
print('Out Chunk lengt: ${gridOut?.length}');
print('Out Chunk num: ${gridOut?.numChunks()}');

await cleanupDatabase();
}
3 changes: 3 additions & 0 deletions lib/mongo_dart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'dart:io'
File,
FileMode,
IOSink,
Platform,
SecureSocket,
SecurityContext,
Socket,
Expand All @@ -38,6 +39,7 @@ import 'package:mongo_dart/src/database/utils/dns_lookup.dart';
import 'package:mongo_dart/src/database/utils/map_keys.dart';
import 'package:mongo_dart/src/database/utils/parms_utils.dart';
import 'package:mongo_dart/src/database/utils/split_hosts.dart';
import 'package:mongo_dart/src/extensions/file_ext.dart';
import 'package:mongo_dart_query/mongo_dart_query.dart';
import 'package:pool/pool.dart';
import 'package:mongo_dart/src/auth/auth.dart'
Expand Down Expand Up @@ -66,6 +68,7 @@ import 'src/database/commands/aggregation_commands/count/count_options.dart';
import 'src/database/commands/aggregation_commands/count/count_result.dart';
import 'src/database/commands/base/command_operation.dart';
import 'src/database/commands/diagnostic_commands/ping_command/ping_command.dart';
import 'package:path/path.dart' as p;

export 'package:bson/bson.dart';
export 'package:mongo_dart_query/mongo_aggregation.dart';
Expand Down
38 changes: 38 additions & 0 deletions lib/src/extensions/file_ext.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'dart:io';
import 'package:path/path.dart' as p;

extension FileExt on File {
String get name =>
path.substring(path.lastIndexOf(Platform.pathSeparator) + 1);

String newPathByName(String newFileName) =>
path.substring(0, path.lastIndexOf(Platform.pathSeparator) + 1) +
newFileName;

Future<String> get safePath async => toSafePath(path);

Future<String> toSafePath(String newPath) async {
var basename = p.basenameWithoutExtension(newPath);
var dirname = p.dirname(newPath) + Platform.pathSeparator;
var ext = p.extension(newPath);

String tryPath = newPath;
File newFile = File(tryPath);
var count = 1;
while (await newFile.exists()) {
tryPath = '$dirname$basename($count)$ext';
count++;
newFile = File(tryPath);
}
return tryPath;
}

Future<File> changeFileNameOnly(String newFileName) async =>
rename(newPathByName(newFileName));

Future<File> changeFileNameOnlySafe(String newFileName) async =>
renameSafe(newPathByName(newFileName));

Future<File> renameSafe(String newPath) async =>
rename(await toSafePath(newPath));
}
2 changes: 1 addition & 1 deletion lib/src/gridfs/grid_fs_file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ abstract class GridFSFile {
return completer.future;
}

int numChunks() => (length?.toDouble() ?? 0.0 / (chunkSize)).ceil().toInt();
int numChunks() => ((length ?? 0.0) / chunkSize).ceil();

List<String> get aliases => extraData['aliases'] as List<String>;

Expand Down
45 changes: 45 additions & 0 deletions lib/src/gridfs/grid_out.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,45 @@ class GridOut extends GridFSFile {
return sink.done;
}

/// This method uses a different approach in writing to a file.
/// It uses a temp file to store the data and then renames it to
/// the correct name. If overwriteExisting file is true,
/// and a file with the same name exists, it is overwitten,
/// otherwise a suffix like "(n)" is appended to the file name, where n is
/// a progressive number not yet assigned to any existing file in the system
Future<File> toFile(File file,
{FileMode? mode, bool? overwriteExistingFile}) async {
overwriteExistingFile ??= false;
mode ??= FileMode.writeOnly;
if (mode == FileMode.read) {
throw ArgumentError('Read file mode not valid for method "toFile()"');
}
File tempFile;
String tempFilePath = '${p.dirname(file.path)}${Platform.pathSeparator}'
'${p.basenameWithoutExtension(file.path)}_${Uuid().v4()}'
'${p.extension(file.path)}';
if (mode == FileMode.append || mode == FileMode.writeOnlyAppend) {
tempFile = await file.copy(tempFilePath);
} else {
tempFile = File(tempFilePath);
}
Future<void> addToFile(Map<String, dynamic> chunk) async {
final bytes = chunk['data'] as BsonBinary;
await tempFile.writeAsBytes(bytes.byteList,
mode: FileMode.writeOnlyAppend, flush: true);
}

var chunkList =
await fs.chunks.find(where.eq('files_id', id).sortBy('n')).toList();
for (var chunk in chunkList) {
await addToFile(chunk);
}
if (overwriteExistingFile) {
return tempFile.changeFileNameOnly(file.name);
}
return tempFile.changeFileNameOnlySafe(file.name);
}

Future<int> writeTo(IOSink out) {
var length = 0;
var completer = Completer<int>();
Expand All @@ -28,4 +67,10 @@ class GridOut extends GridFSFile {
.then((_) => completer.complete(length));
return completer.future;
}

/// Removes this document from the bucket
Future<void> delete() async {
await fs.files.deleteOne(where.id(id));
await fs.chunks.deleteMany(where.eq('files_id', id));
}
}
17 changes: 14 additions & 3 deletions lib/src/gridfs/gridfs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ class GridFS {

// T O D O (tsander): Ensure index.

Stream<Map<String, dynamic>> getFileList(SelectorBuilder selectorBuilder) {
return files.find(selectorBuilder.sortBy('filename', descending: true));
}
Stream<Map<String, dynamic>> getFileList(SelectorBuilder selectorBuilder) =>
files.find(selectorBuilder.sortBy('filename', descending: true));

Future<GridOut?> findOne(selector) async {
//var completer = Completer<GridOut>();
Expand All @@ -43,4 +42,16 @@ class GridFS {

GridIn createFile(Stream<List<int>> input, String filename) =>
GridIn._(this, filename, input);

/// **Beware!** This method removes all the documents in this bucket
Future<void> clearBucket() async {
await files.deleteMany(<String, dynamic>{});
await chunks.deleteMany(<String, dynamic>{});
}

/// **Beware!** This method drops this bucket
Future<void> dropBucket() async {
await files.drop();
await chunks.drop();
}
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: mongo_dart
version: 0.9.0
version: 0.9.1
description: MongoDB driver, implemented in pure Dart. All CRUD operations, aggregation pipeline and more!
homepage: https://github.com/mongo-dart/mongo_dart

Expand Down
91 changes: 91 additions & 0 deletions test/file_ext_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import 'dart:io';

import 'package:mongo_dart/src/extensions/file_ext.dart';
import 'package:path/path.dart';
import 'package:test/test.dart';
import 'package:uuid/uuid.dart';

Future<void> main() async {
var dir = Directory(current);
var filename = Uuid().v4();
var file = File('${dir.path}${Platform.pathSeparator}$filename');
await file.writeAsString('Test');
group('Base methods', () {
test('Name', () {
expect(file.name, filename);
});
test('New Path by Name', () {
expect(file.newPathByName('prova'),
'${dir.path}${Platform.pathSeparator}prova');
});
test('Safe Path', () async {
var newPath = await file.safePath;
expect(newPath, '${dir.path}${Platform.pathSeparator}$filename(1)');
});
test('To Safe Path', () async {
var newPath = await file.toSafePath(file.newPathByName('prova'));
expect(newPath, '${dir.path}${Platform.pathSeparator}prova');
});
});

group('Change File Name', () {
test('Change File Name Only', () async {
var changedFile = await file.copy(file.newPathByName('chgf'));
var newFile = await changedFile.changeFileNameOnly('testo');
var name = newFile.name;
await newFile.delete();
expect(name, 'testo');
});

test('Change File Name Only - two tries', () async {
var changedFile = await file.copy(file.newPathByName('chgf2'));
var newFile = await changedFile.changeFileNameOnly('test2');
changedFile = await file.copy(file.newPathByName('chgf2'));
var newFile2 = await changedFile.changeFileNameOnly('test2');
var name = newFile2.name;
var exists = await changedFile.exists();
var exists2 = await newFile2.exists();

await newFile2.delete();
expect(name, 'test2');
expect(exists, isFalse);
expect(exists2, isTrue);
});
});
group('Safe Change File Name', () {
test('Safe Change File Name Only', () async {
var changedFile = await file.copy(file.newPathByName('chgf4'));
var newFile = await changedFile.changeFileNameOnlySafe('test4.ts');
var name = newFile.name;
await newFile.delete();
expect(name, 'test4.ts');
});

test('Change File Name Only - two tries', () async {
var changedFile = await file.copy(file.newPathByName('chgf3'));
var newFile = await changedFile.changeFileNameOnlySafe('test3.1.ts');
changedFile = await file.copy(file.newPathByName('chgf3'));
var newFile2 = await changedFile.changeFileNameOnlySafe('test3.1.ts');
var name = newFile.name;
var name2 = newFile2.name;

var exists = await changedFile.exists();
var exists2 = await newFile.exists();
var exists3 = await newFile2.exists();

await newFile.delete();
await newFile2.delete();

expect(name, 'test3.1.ts');
expect(name2, 'test3.1(1).ts');

expect(exists, isFalse);
expect(exists2, isTrue);
expect(exists3, isTrue);
});
});

tearDownAll(() async {
await file.delete();
});
}

0 comments on commit 0dec04b

Please sign in to comment.