uploadNewImagesOrFiles<DataT extends Data> method

Future<String?> uploadNewImagesOrFiles<DataT extends Data>(
  1. DataT data,
  2. List<XFile> files, {
  3. Data? caller,
  4. bool forceUpdate = false,
})

upload / add a bunch of images

Implementation

Future<String?> uploadNewImagesOrFiles<DataT extends Data>(
  DataT data,
  List<XFile> files, {
  Data? caller,
  bool forceUpdate = false,
}) async {
  final preparedFiles =
      await _normalizeUploadFilesForStorage(data, files, caller: caller);

  // Wenn forceOffline aktiv ist: nur lokal speichern und Request zum Retry vormerken
  bool prefersOffline =
      _dataPrefersCache(caller, type: Helper.SimulatedRequestType.PUT) ??
          false;
  // fallback: nutze Daten-Flag oder globale Option
  try {
    prefersOffline = prefersOffline || (caller as WithOffline).forceOffline;
  } catch (_) {}
  try {
    prefersOffline = prefersOffline || (data as WithOffline).forceOffline;
  } catch (_) {}
  prefersOffline = prefersOffline || Options().forceOffline;

  // Always persist images locally first (even when online), so cache and
  // on-device storage are immediately available with canonical filenames.
  await local.uploadNewImagesOrFiles(
    data,
    preparedFiles,
    caller: caller,
    forceUpdate: forceUpdate,
  );

  if (prefersOffline) {
    final rap = remote.uploadNewImagesOrFiles<DataT>(data, preparedFiles);
    await local.logFailedReq(rap.rd);
    return 'added files offline (queued)';
  }

  await _touchPrueferIfNeeded(
    requestType: Helper.SimulatedRequestType.PUT,
    data: data,
    caller: caller,
  );

  final requestType = Helper.SimulatedRequestType.PUT;
  return _run(
    itPrefersCache: _dataPrefersCache(data, type: requestType),
    offline: () async => 'added files offline',
    online: () => remote.uploadNewImagesOrFiles(
      data,
      preparedFiles,
    ),
    onlineSuccessCB: (body) async {
      // If the backend returns hashes for uploaded images, replace any local placeholders.
      try {
        final decoded = jsonDecode(body ?? '');
        final uploaded = (decoded is Map) ? decoded['uploaded_images'] : null;
        if (uploaded is List) {
          final map = <String, String>{};
          for (final e in uploaded) {
            if (e is Map) {
              final client = e['client_filename']?.toString();
              final hash = e['hash']?.toString();
              if (client != null &&
                  client.isNotEmpty &&
                  hash != null &&
                  hash.isNotEmpty) {
                map[client] = hash;
                final scope = local.scopeFor(data, caller: caller);
                final base =
                    client.contains('/') ? client.split('/').last : client;
                final baseNoLocalPrefix =
                    base.startsWith(LOCALLY_ADDED_PREFIX)
                        ? base.substring(LOCALLY_ADDED_PREFIX.length)
                        : base;
                final localPrefixedBase =
                    '$LOCALLY_ADDED_PREFIX$baseNoLocalPrefix';
                map[base] = hash;
                map[baseNoLocalPrefix] = hash;
                map[localPrefixedBase] = hash;

                String scoped(String b) => scope.isNotEmpty ? '$scope/$b' : b;

                final candidateStoredNames = <String>[
                  scoped(baseNoLocalPrefix),
                  scoped(base),
                  scoped(localPrefixedBase),
                ];
                String storedName = candidateStoredNames.first;
                for (final candidate in candidateStoredNames) {
                  final f = await localFile(candidate);
                  if (f.existsSync()) {
                    storedName = candidate;
                    break;
                  }
                }

                await indexImageHash(
                  hash: hash,
                  storedName: storedName,
                  scope: scope,
                );
                final fallbackStoredName =
                    scope.isNotEmpty ? '$scope/$hash' : hash;
                if (fallbackStoredName != storedName) {
                  try {
                    final fallbackFile = await localFile(fallbackStoredName);
                    if (fallbackFile.existsSync()) {
                      await fallbackFile.delete();
                    }
                  } catch (_) {}
                }
              }
            }
          }
          if (map.isNotEmpty) {
            String? rewrite(String? v) {
              if (v == null) return null;
              // match scoped values like "<scope>/<client_filename>"
              final base = v.contains('/') ? v.split('/').last : v;
              final repl = map[base] ?? map[v];
              return repl ?? v;
            }

            data.mainhash = rewrite(data.mainhash);
            if (data.imagehashes != null) {
              data.imagehashes =
                  data.imagehashes!.map((h) => rewrite(h) ?? h).toList();
            }

            // Persist updated hashes locally so subsequent requests use backend hashes.
            try {
              await local.storeData(
                data,
                forId: caller?.id ?? await rootID,
              );
            } catch (_) {}
          }
        }
      } catch (_) {}
    },
    // onlineSuccessCB: (response) async {},
    onlineFailedCB: (onlineRes, rap) {
      debugPrint('failed to upload images, ' +
          rap.rd.json.toString() +
          ': ' +
          onlineRes.toString());
      var rd = rap.rd;
      //biscchen ugly
      // rd.multipartFiles = rd.multipartFiles.map((_e) async {
      //   var e = await _e;
      //   // e.name = newName;
      //   var newName = LOCALLY_ADDED_PREFIX + e.name;
      //   var newPath =
      //       e.path.substring(0, e.path.length - e.name.length) + newName;
      //   e.saveTo(newPath);
      //   e = XFile(newPath);
      //   return e;
      // }).toList();
      local.logFailedReq(rd);
    },
    requestType: requestType,
  ).last;
}