ente/lib/services/search_service.dart

211 lines
7.2 KiB
Dart
Raw Normal View History

import 'package:dio/dio.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/core/network.dart';
import 'package:photos/db/files_db.dart';
import 'package:photos/events/local_photos_updated_event.dart';
import 'package:photos/models/collection.dart';
import 'package:photos/models/collection_items.dart';
import 'package:photos/models/file.dart';
import 'package:photos/models/location.dart';
import 'package:photos/models/search/holiday_search_result.dart';
import 'package:photos/models/search/location_api_response.dart';
import 'package:photos/models/search/location_search_result.dart';
import 'package:photos/services/collections_service.dart';
import 'package:photos/utils/date_time_util.dart';
class SearchService {
2022-08-11 12:30:15 +00:00
Future<List<File>> _cachedFilesFuture;
final _dio = Network.instance.getDio();
final _config = Configuration.instance;
2022-08-11 14:38:43 +00:00
final _logger = Logger((SearchService).toString());
final _collectionService = CollectionsService.instance;
2022-08-10 10:09:56 +00:00
static const _maximumResultsLimit = 20;
static const List<HolidayData> holidays = [
HolidayData('Christmas', 11, 25),
HolidayData('Christmas Eve', 11, 24),
HolidayData('New Year', 0, 1),
HolidayData('New Year Eve', 11, 31),
];
SearchService._privateConstructor();
static final SearchService instance = SearchService._privateConstructor();
Future<void> init() async {
// Intention of delay is to give more CPU cycles to other tasks
Future.delayed(const Duration(seconds: 5), () async {
/* In case home screen loads before 5 seconds and user starts search,
future will not be null.So here getAllFiles won't run again in that case. */
2022-08-11 12:30:15 +00:00
if (_cachedFilesFuture == null) {
getAllFiles();
}
});
Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) {
2022-08-11 12:30:15 +00:00
_cachedFilesFuture = null;
getAllFiles();
});
}
Future<List<File>> getAllFiles() async {
2022-08-11 12:30:15 +00:00
if (_cachedFilesFuture != null) {
return _cachedFilesFuture;
}
2022-08-11 12:30:15 +00:00
_cachedFilesFuture = FilesDB.instance.getAllFilesFromDB();
return _cachedFilesFuture;
}
2022-08-11 06:20:39 +00:00
Future<List<File>> getFileSearchResults(String query) async {
final List<File> fileSearchResults = [];
2022-08-10 04:48:40 +00:00
final List<File> files = await getAllFiles();
final nonCaseSensitiveRegexForQuery = RegExp(query, caseSensitive: false);
2022-08-11 05:40:29 +00:00
for (File file in files) {
2022-08-11 06:20:39 +00:00
if (fileSearchResults.length >= _maximumResultsLimit) {
2022-08-11 05:40:29 +00:00
break;
}
if (file.title.contains(nonCaseSensitiveRegexForQuery)) {
2022-08-11 06:20:39 +00:00
fileSearchResults.add(file);
}
}
2022-08-11 06:20:39 +00:00
return fileSearchResults;
}
void clearCache() {
2022-08-11 12:30:15 +00:00
_cachedFilesFuture = null;
}
Future<List<LocationSearchResult>> getLocationSearchResults(
String query,
) async {
final List<LocationSearchResult> locationSearchResults = [];
try {
2022-08-10 04:48:40 +00:00
final List<File> allFiles = await SearchService.instance.getAllFiles();
final response = await _dio.get(
_config.getHttpEndpoint() + "/search/location",
2022-08-11 11:48:09 +00:00
queryParameters: {"query": query, "limit": 10},
options: Options(
headers: {"X-Auth-Token": _config.getToken()},
),
);
final matchedLocationSearchResults =
LocationApiResponse.fromMap(response.data);
for (LocationDataFromResponse locationData
in matchedLocationSearchResults.results) {
final List<File> filesInLocation = [];
for (File file in allFiles) {
if (_isValidLocation(file.location) &&
_isLocationWithinBounds(file.location, locationData)) {
filesInLocation.add(file);
}
}
filesInLocation.sort(
(first, second) => second.creationTime.compareTo(first.creationTime),
);
if (filesInLocation.isNotEmpty) {
locationSearchResults.add(
LocationSearchResult(locationData.place, filesInLocation),
);
}
}
} catch (e) {
_logger.severe(e);
}
return locationSearchResults;
}
// getFilteredCollectionsWithThumbnail removes deleted or archived or
// collections which don't have a file from search result
2022-08-11 06:20:39 +00:00
Future<List<CollectionWithThumbnail>> getCollectionSearchResults(
String query,
) async {
final nonCaseSensitiveRegexForQuery = RegExp(query, caseSensitive: false);
2022-08-11 05:15:42 +00:00
/*latestCollectionFiles is to identify collections which have at least one file as we don't display
empty collections and to get the file to pass for tumbnail */
2022-08-10 04:48:40 +00:00
final List<File> latestCollectionFiles =
await _collectionService.getLatestCollectionFiles();
2022-08-11 05:15:42 +00:00
2022-08-11 06:20:39 +00:00
final List<CollectionWithThumbnail> collectionSearchResults = [];
2022-08-11 05:15:42 +00:00
for (File file in latestCollectionFiles) {
2022-08-11 06:20:39 +00:00
if (collectionSearchResults.length >= _maximumResultsLimit) {
2022-08-11 05:40:29 +00:00
break;
}
2022-08-11 05:15:42 +00:00
final Collection collection =
CollectionsService.instance.getCollectionByID(file.collectionID);
if (!collection.isArchived() &&
collection.name.contains(nonCaseSensitiveRegexForQuery)) {
2022-08-11 06:20:39 +00:00
collectionSearchResults.add(CollectionWithThumbnail(collection, file));
2022-08-11 05:15:42 +00:00
}
}
2022-08-11 05:15:42 +00:00
2022-08-11 06:20:39 +00:00
return collectionSearchResults;
}
2022-08-12 06:37:19 +00:00
Future<List<File>> getYearSearchResults(int year) async {
final yearInMicrosecondsSinceEpoch =
DateTime.utc(year).microsecondsSinceEpoch;
final nextYearInMicrosecondsSinceEpoch =
2022-08-12 06:37:19 +00:00
DateTime.utc(year + 1).microsecondsSinceEpoch;
final yearSearchResults =
await FilesDB.instance.getFilesCreatedWithinDurations(
[
[yearInMicrosecondsSinceEpoch, nextYearInMicrosecondsSinceEpoch]
],
null,
);
return yearSearchResults;
}
List<HolidaySearchResult> getHolidaySearchResults(String query) {
final nonCaseSensitiveRegexForQuery = RegExp(query, caseSensitive: false);
final List<HolidaySearchResult> holidaySearchResult = [];
for (HolidayData holiday in holidays) {
if (holiday.name.contains(nonCaseSensitiveRegexForQuery)) {
holidaySearchResult.add(
HolidaySearchResult(
holiday,
_getDurationsOfHolidays(holiday.day, holiday.month),
),
);
}
}
return holidaySearchResult;
}
List<List<int>> _getDurationsOfHolidays(int day, int month) {
List<List<int>> durationsOfHolidays = [];
for (int year = 1970; year < currentYear; year++) {
durationsOfHolidays.add([
DateTime.utc(year, month, day).microsecondsSinceEpoch,
DateTime.utc(year, month, day + 1).microsecondsSinceEpoch,
]);
}
return durationsOfHolidays;
}
bool _isValidLocation(Location location) {
return location != null &&
location.latitude != null &&
location.latitude != 0 &&
location.longitude != null &&
location.longitude != 0;
}
bool _isLocationWithinBounds(
Location location,
LocationDataFromResponse locationData,
) {
//format returned by the api is [lng,lat,lng,lat] where indexes 0 & 1 are southwest and 2 & 3 northeast
return location.longitude > locationData.bbox[0] &&
location.latitude > locationData.bbox[1] &&
location.longitude < locationData.bbox[2] &&
location.latitude < locationData.bbox[3];
}
}