Skip to content

Commit

Permalink
Detect trash/archive and put them in separate folder
Browse files Browse the repository at this point in the history
fixes #138
closes #140
  • Loading branch information
TheLastGimbus committed Jan 13, 2023
1 parent 6e6bacd commit 666eff1
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 16 deletions.
46 changes: 31 additions & 15 deletions bin/gpth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:console_bars/console_bars.dart';
import 'package:gpth/date_extractor.dart';
import 'package:gpth/duplicate.dart';
import 'package:gpth/extras.dart';
import 'package:gpth/folder_classify.dart';
import 'package:gpth/interactive.dart' as interactive;
import 'package:gpth/media.dart';
import 'package:gpth/utils.dart';
Expand Down Expand Up @@ -197,6 +198,9 @@ void main(List<String> arguments) async {
/// All "year folders" that we found
final yearFolders = <Directory>[];

Directory? archiveFolder;
Directory? trashFolder;

/// All album folders - that is, folders that were aside yearFolders and were
/// not matching "Photos from ...." name
final albumFolders = <Directory>[];
Expand All @@ -207,14 +211,13 @@ void main(List<String> arguments) async {

// recursive=true makes it find everything nicely even if user id dumb 😋
await for (final d in input.list(recursive: true).whereType<Directory>()) {
isYear(Directory dir) => p.basename(dir.path).startsWith('Photos from ');
if (isYear(d)) {
if (isYearFolder(d)) {
yearFolders.add(d);
} // if not year but got any year brothers
else if (await d.parent
.list()
.whereType<Directory>()
.any((e) => isYear(e))) {
} else if (archiveFolder == null && await isArchiveFolder(d)) {
archiveFolder = d;
} else if (trashFolder == null && await isTrashFolder(d)) {
trashFolder = d;
} else if (await isAlbumFolder(d)) {
albumFolders.add(d);
}
}
Expand All @@ -223,6 +226,14 @@ void main(List<String> arguments) async {
media.add(Media(file));
}
}
await for (final file
in (archiveFolder?.list().wherePhotoVideo() ?? Stream.empty())) {
media.add(Media(file, isArchived: true));
}
await for (final file
in (trashFolder?.list().wherePhotoVideo() ?? Stream.empty())) {
media.add(Media(file, isTrashed: true));
}

print('Found ${media.length} photos/videos in input folder');

Expand Down Expand Up @@ -291,14 +302,19 @@ void main(List<String> arguments) async {
);
await for (final m in Stream.fromIterable(media)) {
final date = m.dateTaken;
final folder = args['divide-to-dates']
? Directory(
date == null
? p.join(output.path, 'date-unknown')
: p.join(output.path, '${date.year}', '${date.month}'),
)
: output;
if (args['divide-to-dates']) await folder.create(recursive: true);
final folder = Directory(
m.isArchived || m.isTrashed
? p.join(output.path, m.isArchived ? 'Archive' : 'Trash')
: args['divide-to-dates']
? date == null
? p.join(output.path, 'date-unknown')
: p.join(output.path, '${date.year}', '${date.month}')
: output.path,
);
// i think checking vars like this is bit faster than calling fs every time
if (args['divide-to-dates'] || m.isArchived || m.isTrashed) {
await folder.create(recursive: true);
}
final freeFile =
findNotExistingName(File(p.join(folder.path, p.basename(m.file.path))));
final c = args['copy']
Expand Down
71 changes: 71 additions & 0 deletions lib/folder_classify.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/// This file contains utils for determining type of a folder
/// Whether it's a legendary "year folder", album, trash, etc
import 'dart:convert';
import 'dart:io';

import 'package:gpth/utils.dart';
import 'package:path/path.dart' as p;

/// if json indicates that photo was put in archive folder
/// returns null if couldn't determine it (f.e. it's an album json)
bool? jsonIsArchived(String jsonString) {
try {
final json = jsonDecode(jsonString);
if (json['photoTakenTime'] != null) {
return json['archived'] == true;
} else {
return null;
}
} catch (e) {
return null;
}
}

/// if json indicates that photo was trashed
/// returns null if couldn't determine it (f.e. it's an album json)
bool? jsonIsTrashed(String jsonString) {
try {
final json = jsonDecode(jsonString);
if (json['photoTakenTime'] != null) {
return json['trashed'] == true;
} else {
return null;
}
} catch (e) {
return null;
}
}

bool isYearFolder(Directory dir) =>
p.basename(dir.path).startsWith('Photos from ');

Future<bool> isAlbumFolder(Directory dir) =>
dir.parent.list().whereType<Directory>().any((e) => isYearFolder(e));

Future<bool> isArchiveFolder(Directory dir) async {
var one = false; // there is at least one element (every() is true with empty)
final logic = await dir
.list()
.whereType<File>()
.where((e) => e.path.endsWith('.json'))
.every((e) {
one = true;
final a = jsonIsArchived(e.readAsStringSync());
return a != null && a;
});
return one && logic;
}

Future<bool> isTrashFolder(Directory dir) async {
var one = false; // there is at least one element (every() is true with empty)
final logic = await dir
.list()
.whereType<File>()
.where((e) => e.path.endsWith('.json'))
.every((e) {
one = true;
final t = jsonIsTrashed(e.readAsStringSync());
return t != null && t;
});
return one && logic;
}
12 changes: 11 additions & 1 deletion lib/media.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,23 @@ class Media {
/// higher the worse
int? dateTakenAccuracy;

// if those are true, we put them in separate folder
bool isArchived = false;
bool isTrashed = false;

//cache
Digest? _hash;

/// will be used for finding duplicates/albums
Digest get hash => _hash ??= sha256.convert(file.readAsBytesSync());

Media(this.file, {this.dateTaken, this.dateTakenAccuracy});
Media(
this.file, {
this.dateTaken,
this.dateTakenAccuracy,
this.isArchived = false,
this.isTrashed = false,
});

@override
String toString() => 'Media($file, dateTaken: $dateTaken)';
Expand Down
27 changes: 27 additions & 0 deletions test/gpth_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:gpth/album.dart';
import 'package:gpth/date_extractor.dart';
import 'package:gpth/duplicate.dart';
import 'package:gpth/extras.dart';
import 'package:gpth/folder_classify.dart';
import 'package:gpth/media.dart';
import 'package:gpth/utils.dart';
import 'package:path/path.dart';
Expand Down Expand Up @@ -131,6 +132,32 @@ AQACEQMRAD8AIcgXf//Z""";
test('test getDiskFree()', () async {
expect(await getDiskFree('.'), isNotNull);
});
test('test isArchived() and isTrashed() on jsons', () {
final normalJson = '''{ "title": "example.jpg",
"photoTakenTime": {"timestamp": "1663252650"},
"photoLastModifiedTime": {"timestamp": "1673339757"}
}''';
final archivedJson = '''{ "title": "example.jpg",
"photoTakenTime": {"timestamp": "1663252650"},
"archived": true,
"photoLastModifiedTime": {"timestamp": "1673339757"}
}''';
final trashedJson = '''{ "title": "example.jpg",
"photoTakenTime": {"timestamp": "1663252650"},
"trashed": true,
"photoLastModifiedTime": {"timestamp": "1673339757"}
}''';
final albumJson =
'{"title": "Green", "description": "","access": "protected"}';
expect(jsonIsArchived(normalJson), false);
expect(jsonIsTrashed(normalJson), false);
expect(jsonIsArchived(archivedJson), true);
expect(jsonIsTrashed(archivedJson), false);
expect(jsonIsArchived(trashedJson), false);
expect(jsonIsTrashed(trashedJson), true);
expect(jsonIsArchived(albumJson), null);
expect(jsonIsTrashed(albumJson), null);
});
});

/// Delete all shitty files as we promised
Expand Down

0 comments on commit 666eff1

Please sign in to comment.