@Override
  protected void handleFileTypeCollision(
      LocalRepoTransaction transaction,
      UUID fromRepositoryId,
      File file,
      Class<? extends RepoFileDto> fromFileType) {
    // In contrast to CloudStore, Subshare does not rename the collision-file immediately.
    // Therefore,
    // this method is invoked when a type-collision was already handled somewhere else and we're
    // re-downloading
    // here.

    final RemoteRepository remoteRepository =
        transaction.getDao(RemoteRepositoryDao.class).getRemoteRepositoryOrFail(fromRepositoryId);
    final LastSyncToRemoteRepo lastSyncToRemoteRepo =
        transaction.getDao(LastSyncToRemoteRepoDao.class).getLastSyncToRemoteRepo(remoteRepository);

    final File localRoot = getLocalRepoManager().getLocalRoot();
    final RepoFile repoFile = transaction.getDao(RepoFileDao.class).getRepoFile(localRoot, file);

    if (lastSyncToRemoteRepo != null
        && repoFile.getLocalRevision() <= lastSyncToRemoteRepo.getLocalRepositoryRevisionSynced()) {
      file.deleteRecursively();
      return;
    }
    super.handleFileTypeCollision(transaction, fromRepositoryId, file, fromFileType);
  }
  public void clearCryptoRepoFileDeleted(String path) {
    path = prefixPath(path);
    try (final LocalRepoTransaction transaction = getLocalRepoManager().beginWriteTransaction(); ) {
      getCryptree(transaction).clearCryptoRepoFileDeleted(path);

      transaction.commit();
    }
  }
  private void putPaddingMetaData(String path, SsNormalFileDto fromNormalFileDto) {
    path = prefixPath(path); // does a null-check
    assertNotNull("fromNormalFileDto", fromNormalFileDto);

    final File file = getFile(path);
    try (final LocalRepoTransaction transaction = getLocalRepoManager().beginWriteTransaction(); ) {
      final RepoFile repoFile =
          transaction
              .getDao(RepoFileDao.class)
              .getRepoFile(getLocalRepoManager().getLocalRoot(), file);
      if (!(repoFile instanceof NormalFile)) {
        throw new IllegalStateException(
            String.format(
                "RepoFile is not an instance of NormalFile! repoFile=%s file=%s", repoFile, file));
      }

      final SsNormalFile normalFile = (SsNormalFile) repoFile;
      normalFile.setLengthWithPadding(fromNormalFileDto.getLengthWithPadding());

      final Map<Long, SsFileChunk> offset2FileChunk =
          new HashMap<>(normalFile.getFileChunks().size());
      for (FileChunk fc : normalFile.getFileChunks())
        offset2FileChunk.put(fc.getOffset(), (SsFileChunk) fc);

      for (final FileChunkDto fcDto : fromNormalFileDto.getFileChunkDtos()) {
        SsFileChunkDto fileChunkDto = (SsFileChunkDto) fcDto;

        // If there is at least 1 byte of real data, the SHA1 (as well as the entire FileChunk
        // object)
        // is created from it and we don't need to store the FileChunk we received from the other
        // side.
        if (fileChunkDto.getLength() > 0) continue;

        boolean isNew = false;
        SsFileChunk fileChunk = offset2FileChunk.get(fileChunkDto.getOffset());
        if (fileChunk == null) {
          isNew = true;
          fileChunk = (SsFileChunk) createObject(FileChunk.class);
          fileChunk.setNormalFile(normalFile);
          fileChunk.setOffset(fileChunkDto.getOffset());
        }
        fileChunk.makeWritable();
        fileChunk.setLength(fileChunkDto.getLength());
        fileChunk.setLengthWithPadding(fileChunkDto.getLengthWithPadding());
        fileChunk.setSha1(fileChunkDto.getSha1());
        fileChunk.makeReadOnly();

        if (isNew) normalFile.getFileChunks().add(fileChunk);
      }

      transaction.commit();
    }
  }
  protected void createAndPersistPreliminaryCollision(
      final LocalRepoManager localRepoManager,
      final File file,
      String localPath,
      Uid cryptoRepoFileId) {
    assertNotNull("localRepoManager", localRepoManager);
    if (localPath == null) assertNotNull("localPath/file", file);

    logger.debug(
        "createAndPersistPreliminaryCollision: localRoot='{}' localRepositoryId={} file='{}' localPath='{}' cryptoRepoFileId={}",
        localRepoManager.getLocalRoot(),
        getRepositoryId(),
        (file == null ? "" : file.getAbsolutePath()),
        (localPath == null ? "" : localPath),
        cryptoRepoFileId);

    try (final LocalRepoTransaction tx = localRepoManager.beginWriteTransaction(); ) {
      if (localPath == null)
        localPath =
            '/'
                + localRepoManager
                    .getLocalRoot()
                    .relativize(file)
                    .replace(FILE_SEPARATOR_CHAR, '/');

      final PreliminaryCollisionDao pcDao = tx.getDao(PreliminaryCollisionDao.class);

      PreliminaryCollision preliminaryCollision = pcDao.getPreliminaryCollision(localPath);
      if (preliminaryCollision == null) {
        preliminaryCollision = new PreliminaryCollision();
        preliminaryCollision.setPath(localPath);
        preliminaryCollision = pcDao.makePersistent(preliminaryCollision);
      }

      final CryptoRepoFileDao crfDao = tx.getDao(CryptoRepoFileDao.class);
      if (cryptoRepoFileId != null) {
        CryptoRepoFile cryptoRepoFile = crfDao.getCryptoRepoFileOrFail(cryptoRepoFileId);
        preliminaryCollision.setCryptoRepoFile(cryptoRepoFile);
      } else if (file != null) {
        final RepoFileDao rfDao = tx.getDao(RepoFileDao.class);
        final RepoFile repoFile = rfDao.getRepoFile(localRepoManager.getLocalRoot(), file);
        if (repoFile != null) {
          final CryptoRepoFile cryptoRepoFile = crfDao.getCryptoRepoFileOrFail(repoFile);
          preliminaryCollision.setCryptoRepoFile(cryptoRepoFile);
        }
      }

      tx.commit();
    } catch (IOException x) {
      throw new RuntimeException(x);
    }
  }
  private void deleteRepoFileRecursively(
      final LocalRepoTransaction transaction, final RepoFile repoFile) {
    final RepoFileDao repoFileDao = transaction.getDao(RepoFileDao.class);
    for (final RepoFile childRepoFile : repoFileDao.getChildRepoFiles(repoFile))
      deleteRepoFileRecursively(transaction, childRepoFile);

    repoFileDao.deletePersistent(repoFile);
  }
  @Override
  protected void assertNoDeleteModificationCollision(
      LocalRepoTransaction transaction, UUID fromRepositoryId, final String path)
      throws CollisionException {
    // super.assertNoDeleteModificationCollision(transaction, fromRepositoryId, path); //
    // DeleteModification is *NOT* used by Subshare!

    final Uid cryptoRepoFileId = getCryptree(transaction).getCryptoRepoFileId(path);
    if (cryptoRepoFileId == null) return;

    final PreliminaryDeletionDao pdDao = transaction.getDao(PreliminaryDeletionDao.class);
    final CryptoRepoFile cryptoRepoFile =
        transaction.getDao(CryptoRepoFileDao.class).getCryptoRepoFileOrFail(cryptoRepoFileId);
    CryptoRepoFile crf = cryptoRepoFile;
    while (crf != null) {
      final String candidateLocalPath = crf.getLocalPathOrFail();
      final PreliminaryDeletion preliminaryDeletion = pdDao.getPreliminaryDeletion(crf);
      if (crf.getDeleted() != null || preliminaryDeletion != null) {
        transaction.addPostCloseListener(
            new LocalRepoTransactionPostCloseAdapter() {
              @Override
              public void postRollback(LocalRepoTransactionPostCloseEvent event) {
                createAndPersistPreliminaryCollision(
                    event.getLocalRepoManager(), null, path, cryptoRepoFileId);
              }

              @Override
              public void postCommit(LocalRepoTransactionPostCloseEvent event) {
                throw new IllegalStateException("Commit is not allowed, anymore!");
              }
            });

        throw new DeleteModificationCollisionException(
            String.format(
                "The associated CryptoRepoFile or one of its parents is marked as deleted! repositoryId=%s path='%s' deletedPath='%s'",
                fromRepositoryId, path, candidateLocalPath));
      }

      crf = crf.getParent();
    }
  }
  @Override
  protected RepoFile syncRepoFile(final LocalRepoTransaction transaction, final File file) {
    assertNotNull("transaction", transaction);
    assertNotNull("file", file);

    final File localRoot = getLocalRepoManager().getLocalRoot();
    final RepoFileDao rfDao = transaction.getDao(RepoFileDao.class);
    RepoFile repoFile = rfDao.getRepoFile(localRoot, file);
    // If the type changed, we must delete the RepoFile here before invoking the super-method,
    // because this would otherwise cause an invocation of Cryptree.preDelete(...) causing
    // the actual file to be deleted.

    if (repoFile != null
        && !LocalRepoSync.create(transaction).isRepoFileTypeCorrect(repoFile, file)) {
      rfDao.deletePersistent(repoFile);
      repoFile = null;
      transaction.flush();
    }
    repoFile = super.syncRepoFile(transaction, file);
    return repoFile;
  }
  @Override
  public void delete(String path) {
    if (isMetaOnly()) {
      path = prefixPath(path);
      final File localRoot = getLocalRepoManager().getLocalRoot();
      try (final LocalRepoTransaction transaction =
          getLocalRepoManager().beginWriteTransaction(); ) {
        final RepoFileDao repoFileDao = transaction.getDao(RepoFileDao.class);
        final File file = getFile(path);
        final RepoFile repoFile = repoFileDao.getRepoFile(localRoot, file);
        if (repoFile != null) deleteRepoFileRecursively(transaction, repoFile);

        transaction.commit();
      }
    } else {
      try {
        super.delete(path);
      } catch (CollisionException x) {
        clearCryptoRepoFileDeleted(path);
        throw x;
      }
    }
  }
  private boolean isMetaOnly() {
    if (metaOnly == null) {
      //			final Iterator<UUID> repoIdIt =
      // getLocalRepoManager().getRemoteRepositoryId2RemoteRootMap().keySet().iterator();
      //			if (! repoIdIt.hasNext())
      //				throw new IllegalStateException("There is no remote-repository!");
      //
      //			final UUID serverRepositoryId = repoIdIt.next();
      try (final LocalRepoTransaction transaction =
          getLocalRepoManager().beginReadTransaction(); ) {
        final LocalRepoStorage lrs =
            LocalRepoStorageFactoryRegistry.getInstance()
                .getLocalRepoStorageFactoryOrFail()
                .getLocalRepoStorageOrCreate(transaction);

        //				final Cryptree cryptree =
        // CryptreeFactoryRegistry.getInstance().getCryptreeFactoryOrFail().getCryptreeOrCreate(transaction, serverRepositoryId);
        metaOnly = lrs.isMetaOnly();
        transaction.commit();
      }
    }
    return metaOnly;
  }
  @Override
  protected File handleFileCollision(
      final LocalRepoTransaction transaction, final UUID fromRepositoryId, final File file) {
    transaction.addPostCloseListener(
        new LocalRepoTransactionPostCloseAdapter() {
          @Override
          public void postRollback(LocalRepoTransactionPostCloseEvent event) {
            createAndPersistPreliminaryCollision(event.getLocalRepoManager(), file, null, null);
          }

          @Override
          public void postCommit(LocalRepoTransactionPostCloseEvent event) {
            throw new IllegalStateException("Commit is not allowed, anymore!");
          }
        });

    throw new CollisionException();
  }