|
@@ -1,4 +1,3 @@
|
|
|
-import 'dart:collection';
|
|
|
|
|
import 'dart:io';
|
|
import 'dart:io';
|
|
|
import 'package:file_upload_processor/handlers/base_api.dart';
|
|
import 'package:file_upload_processor/handlers/base_api.dart';
|
|
|
import 'package:intl/intl.dart';
|
|
import 'package:intl/intl.dart';
|
|
@@ -61,9 +60,9 @@ class FileUploadApi extends BaseApi {
|
|
|
),
|
|
),
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- print('File uploaded to Supabase: $timestampedFilename');
|
|
|
|
|
|
|
+ print('+File uploaded to <$bucket>: $timestampedFilename');
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
- print('Error uploading to Supabase: $e');
|
|
|
|
|
|
|
+ print('!Error uploading to Supabase: $e');
|
|
|
rethrow;
|
|
rethrow;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -163,12 +162,20 @@ class FileUploadApi extends BaseApi {
|
|
|
files.add(dataFilePath);
|
|
files.add(dataFilePath);
|
|
|
}
|
|
}
|
|
|
bytes.clear();
|
|
bytes.clear();
|
|
|
|
|
+ //upload to supabase storage
|
|
|
|
|
+ await uploadToSupabase(rawFilePath, filename, supabaseClient,
|
|
|
|
|
+ bucket: 'csvhich', timestamped: false, upsert: true);
|
|
|
|
|
+ //upload to supabase storage archive timestamped
|
|
|
await uploadToSupabase(rawFilePath, filename, supabaseClient,
|
|
await uploadToSupabase(rawFilePath, filename, supabaseClient,
|
|
|
bucket: 'csvhich_archive', timestamped: true, upsert: false);
|
|
bucket: 'csvhich_archive', timestamped: true, upsert: false);
|
|
|
|
|
+ //insert data to supabase csvhichupdates
|
|
|
|
|
+ await supabaseClient
|
|
|
|
|
+ .from('csvhichupdates')
|
|
|
|
|
+ .insert({'filename': filename});
|
|
|
|
|
|
|
|
for (var file in files) {
|
|
for (var file in files) {
|
|
|
final fileProcess = FileProcess(file, supabaseClient);
|
|
final fileProcess = FileProcess(file, supabaseClient);
|
|
|
- await fileProcess.go();
|
|
|
|
|
|
|
+ await fileProcess.go(donttouchdb: false);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -363,7 +370,7 @@ class FileProcess {
|
|
|
|
|
|
|
|
// Assign each value to the corresponding header
|
|
// Assign each value to the corresponding header
|
|
|
for (int j = 0; j < headers.length; j++) {
|
|
for (int j = 0; j < headers.length; j++) {
|
|
|
- row[headers[j]] = values[j].removeQuotes.trim().nullIfEmpty;
|
|
|
|
|
|
|
+ row[headers[j]] = values[j].trim().removeQuotes.trim().nullIfEmpty;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Add the row map to the data list
|
|
// Add the row map to the data list
|
|
@@ -375,7 +382,7 @@ class FileProcess {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
List<String> get filesTomonitor => _headers.keys.toList();
|
|
List<String> get filesTomonitor => _headers.keys.toList();
|
|
|
- Future<void> go() async {
|
|
|
|
|
|
|
+ Future<void> go({bool donttouchdb = false}) async {
|
|
|
if (!filesTomonitor.contains(filename)) return;
|
|
if (!filesTomonitor.contains(filename)) return;
|
|
|
final mapsToInsert = await parseCsv();
|
|
final mapsToInsert = await parseCsv();
|
|
|
final scopeName = scopes[filename] ?? "";
|
|
final scopeName = scopes[filename] ?? "";
|
|
@@ -386,12 +393,12 @@ class FileProcess {
|
|
|
List<Map<String, dynamic>> oldComparable = [];
|
|
List<Map<String, dynamic>> oldComparable = [];
|
|
|
|
|
|
|
|
//load old data
|
|
//load old data
|
|
|
- for (var e in splitList(scopeInNew, headerToNb(scopeInNew))) {
|
|
|
|
|
|
|
+ for (var e in chunkList(scopeInNew, 30)) {
|
|
|
final res = await supabase
|
|
final res = await supabase
|
|
|
.from(tables[filename]!)
|
|
.from(tables[filename]!)
|
|
|
.select()
|
|
.select()
|
|
|
.inFilter(scopeName, e)
|
|
.inFilter(scopeName, e)
|
|
|
- .limit(100000);
|
|
|
|
|
|
|
+ .limit(300000);
|
|
|
|
|
|
|
|
oldIds.addAll(res.map((e) => {"id": e["id"]}));
|
|
oldIds.addAll(res.map((e) => {"id": e["id"]}));
|
|
|
oldComparable.addAll(res.map((e) => e..remove("id")));
|
|
oldComparable.addAll(res.map((e) => e..remove("id")));
|
|
@@ -400,89 +407,70 @@ class FileProcess {
|
|
|
final comparisonResult = compareLists(oldComparable, mapsToInsert);
|
|
final comparisonResult = compareLists(oldComparable, mapsToInsert);
|
|
|
final indexToRemove = comparisonResult.removeIndices;
|
|
final indexToRemove = comparisonResult.removeIndices;
|
|
|
final indexToMaintain = comparisonResult.maintainIndices;
|
|
final indexToMaintain = comparisonResult.maintainIndices;
|
|
|
- final indexToInsert = comparisonResult.insertIndices;
|
|
|
|
|
|
|
+ final dataToInsert = comparisonResult.insertData;
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- // removing index to remove with id
|
|
|
|
|
- for (var e in splitList(
|
|
|
|
|
- indexToRemove.map((e) => oldIds[e]['id']).toList(),
|
|
|
|
|
- headerToNb(indexToRemove.map((e) => oldIds[e]['id']).toList()))) {
|
|
|
|
|
- await supabase
|
|
|
|
|
- .from(tables[filename]!) // Replace with your actual table name
|
|
|
|
|
- .delete()
|
|
|
|
|
- .inFilter('id', e);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!donttouchdb)
|
|
|
|
|
+ for (var e in chunkList(
|
|
|
|
|
+ indexToRemove.map((f) => oldIds[f]['id']).toList(), 100)) {
|
|
|
|
|
+ await supabase
|
|
|
|
|
+ .from(tables[filename]!) // Replace with your actual table name
|
|
|
|
|
+ .delete()
|
|
|
|
|
+ .inFilter('id', e);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// insering new data
|
|
// insering new data
|
|
|
- await supabase
|
|
|
|
|
- .from(tables[filename]!) // Replace with your actual table name
|
|
|
|
|
- .insert(indexToInsert.map((e) => mapsToInsert.elementAt(e)).toList());
|
|
|
|
|
|
|
+ if (!donttouchdb)
|
|
|
|
|
+ await supabase
|
|
|
|
|
+ .from(tables[filename]!) // Replace with your actual table name
|
|
|
|
|
+ .insert(dataToInsert);
|
|
|
} catch (e, stackTrace) {
|
|
} catch (e, stackTrace) {
|
|
|
print('Error: $e\n$stackTrace');
|
|
print('Error: $e\n$stackTrace');
|
|
|
}
|
|
}
|
|
|
print(
|
|
print(
|
|
|
- " insert:${indexToInsert.length} remove:${indexToRemove.length} maintain:${indexToMaintain.length}");
|
|
|
|
|
|
|
+ " insert:${dataToInsert.length} remove:${indexToRemove.length} maintain:${indexToMaintain.length}");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
({
|
|
({
|
|
|
List<int> maintainIndices,
|
|
List<int> maintainIndices,
|
|
|
List<int> removeIndices,
|
|
List<int> removeIndices,
|
|
|
- List<int> insertIndices
|
|
|
|
|
|
|
+ // List<int> insertIndices
|
|
|
|
|
+ List<Map> insertData
|
|
|
}) compareLists(
|
|
}) compareLists(
|
|
|
List<Map<String, dynamic>> map1, List<Map<String, dynamic>> map2) {
|
|
List<Map<String, dynamic>> map1, List<Map<String, dynamic>> map2) {
|
|
|
List<int> maintainIndices = [];
|
|
List<int> maintainIndices = [];
|
|
|
List<int> removeIndices = [];
|
|
List<int> removeIndices = [];
|
|
|
- List<int> insertIndices = [];
|
|
|
|
|
|
|
+ List<Map<String, dynamic>> insertData = map2;
|
|
|
|
|
|
|
|
// Find indices to maintain and remove in map1
|
|
// Find indices to maintain and remove in map1
|
|
|
for (int i = 0; i < map1.length; i++) {
|
|
for (int i = 0; i < map1.length; i++) {
|
|
|
- if (map2.contains(map1[i])) {
|
|
|
|
|
|
|
+ final pos = insertData.findMap(map1[i]);
|
|
|
|
|
+ if (pos > -1) {
|
|
|
maintainIndices.add(i); // Item exists in both lists
|
|
maintainIndices.add(i); // Item exists in both lists
|
|
|
|
|
+ insertData.removeAt(pos);
|
|
|
} else {
|
|
} else {
|
|
|
removeIndices.add(i); // Item does not exist in map2
|
|
removeIndices.add(i); // Item does not exist in map2
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Find indices to insert from map2
|
|
|
|
|
- for (int i = 0; i < map2.length; i++) {
|
|
|
|
|
- if (!map1.contains(map2[i])) {
|
|
|
|
|
- insertIndices.add(i); // Item does not exist in map1
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
return (
|
|
return (
|
|
|
maintainIndices: maintainIndices,
|
|
maintainIndices: maintainIndices,
|
|
|
removeIndices: removeIndices,
|
|
removeIndices: removeIndices,
|
|
|
- insertIndices: insertIndices
|
|
|
|
|
|
|
+ insertData: insertData
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- List<List<T>> splitList<T>(List<T> originalList, int maxSize) {
|
|
|
|
|
- List<List<T>> sublists = [];
|
|
|
|
|
-
|
|
|
|
|
- for (int i = 0; i < originalList.length; i += maxSize) {
|
|
|
|
|
- // Create a sublist for the current chunk
|
|
|
|
|
- List<T> sublist = originalList.sublist(
|
|
|
|
|
- i,
|
|
|
|
|
- (i + maxSize > originalList.length) ? originalList.length : i + maxSize,
|
|
|
|
|
- );
|
|
|
|
|
- sublists.add(sublist);
|
|
|
|
|
|
|
+ List<List<T>> chunkList<T>(List<T> list, int chunkSize) {
|
|
|
|
|
+ if (chunkSize <= 0) {
|
|
|
|
|
+ throw ArgumentError('chunkSize must be greater than 0');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return sublists;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- int headerToNb(List list) {
|
|
|
|
|
- //header max of 16k
|
|
|
|
|
- final maxheader = 4 * 1024 * 8;
|
|
|
|
|
- if (list.isEmpty) {
|
|
|
|
|
- return list.length;
|
|
|
|
|
|
|
+ List<List<T>> chunks = [];
|
|
|
|
|
+ for (var i = 0; i < list.length; i += chunkSize) {
|
|
|
|
|
+ chunks.add(list.sublist(
|
|
|
|
|
+ i, i + chunkSize > list.length ? list.length : i + chunkSize));
|
|
|
}
|
|
}
|
|
|
- final length1 = (list.toString().length / list.length).ceil() * 8;
|
|
|
|
|
- final lengthurl = 200 * 8;
|
|
|
|
|
- final res = ((maxheader - lengthurl) / length1).floor();
|
|
|
|
|
- //print("header2nb: $res");
|
|
|
|
|
- return res;
|
|
|
|
|
|
|
+ return chunks;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -494,16 +482,39 @@ extension RemoveQuotes on String {
|
|
|
String get removeQuotes {
|
|
String get removeQuotes {
|
|
|
if (isEmpty) return this; // Return if the string is empty
|
|
if (isEmpty) return this; // Return if the string is empty
|
|
|
|
|
|
|
|
- // Check if the first character is a quote
|
|
|
|
|
- bool startsWithQuote = startsWith('"') || startsWith("'");
|
|
|
|
|
- // Check if the last character is a quote
|
|
|
|
|
- bool endsWithQuote = endsWith('"') || endsWith("'");
|
|
|
|
|
-
|
|
|
|
|
// Remove the first and last characters if they are quotes
|
|
// Remove the first and last characters if they are quotes
|
|
|
String result = this;
|
|
String result = this;
|
|
|
|
|
+ // Check if the first character is a quote
|
|
|
|
|
+ bool startsWithQuote = result.startsWith('"') || result.startsWith("'");
|
|
|
if (startsWithQuote) result = result.substring(1);
|
|
if (startsWithQuote) result = result.substring(1);
|
|
|
|
|
+ // Check if the last character is a quote
|
|
|
|
|
+ bool endsWithQuote = result.endsWith('"') || result.endsWith("'");
|
|
|
if (endsWithQuote) result = result.substring(0, result.length - 1);
|
|
if (endsWithQuote) result = result.substring(0, result.length - 1);
|
|
|
|
|
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+bool mapsAreEqual(Map<String, dynamic> map1, Map<String, dynamic> map2) {
|
|
|
|
|
+ if (map1.length != map2.length) return false;
|
|
|
|
|
+ for (var key in map1.keys) {
|
|
|
|
|
+ if (map1[key] != map2[key]) return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+extension ContainsMap on List<Map<String, dynamic>> {
|
|
|
|
|
+ bool containsMap(Map<String, dynamic> map) {
|
|
|
|
|
+ for (var item in this) {
|
|
|
|
|
+ if (mapsAreEqual(item, map)) return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int findMap(Map<String, dynamic> map) {
|
|
|
|
|
+ for (int i = 0; i < this.length; i++) {
|
|
|
|
|
+ if (mapsAreEqual(this.elementAt(i), map)) return i;
|
|
|
|
|
+ }
|
|
|
|
|
+ return -1;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|