Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract reusable service. #16787

Merged
merged 3 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,19 @@
public class ChecksumProfileMatcher implements ProfileMatcher {
private static final Logger log = LogManager.getLogger(ChecksumProfileMatcher.class.getName());

/**
* Remote list of profiles
*/
private final Set<ProfileDescription> repository;

/**
* @param repository Profiles from remote repository
*/
public ChecksumProfileMatcher(final Set<ProfileDescription> repository) {
this.repository = repository;
}

/**
* Filter locally installed profiles by matching checksum
*
* @param next Description of profile installed in application support directory
* @param repository Remote list of profiles
* @param installed Description of profile installed in application support directory
* @return Non-null if matching profile is found in remote list and not latest version
*/
@Override
public Optional<ProfileDescription> compare(final ProfileDescription next) {
public Optional<ProfileDescription> compare(final Set<ProfileDescription> repository, final ProfileDescription installed) {
// Filter out profiles with matching checksum
final Optional<ProfileDescription> found = repository.stream()
.filter(description -> Objects.equals(description.getChecksum(), next.getChecksum()))
.findFirst();
.filter(description -> Objects.equals(description.getChecksum(), installed.getChecksum()))
.findFirst();
if(found.isPresent()) {
// Found matching checksum. Determine if latest version
if(found.get().isLatest()) {
Expand All @@ -63,7 +52,7 @@ public Optional<ProfileDescription> compare(final ProfileDescription next) {
return found;
}
}
log.warn("Local only profile {}", next);
log.warn("Local only profile {}", installed);
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,14 @@
* GNU General Public License for more details.
*/

import ch.cyberduck.core.Protocol;

import org.apache.commons.lang3.StringUtils;

import java.util.Optional;
import java.util.function.Predicate;
import java.util.Set;

public interface ProfileMatcher {
/**
* @param next Description of profile installed
* @return Non null if profile from server has been updated. Matching profile with later version in repository.
* @param repository Description of available profiles in repository
* @param installed Description of profile installed
* @return Non-null if profile from server has been updated. Matching profile with later version from remote repository.
*/
Optional<ProfileDescription> compare(ProfileDescription next);

class IdentifierProtocolPredicate implements Predicate<Protocol> {
private final Protocol installed;

public IdentifierProtocolPredicate(final Protocol installed) {
this.installed = installed;
}

@Override
public boolean test(final Protocol protocol) {
return StringUtils.equals(installed.getIdentifier(), protocol.getIdentifier())
&& StringUtils.equals(installed.getProvider(), protocol.getProvider());
}
}
Optional<ProfileDescription> compare(Set<ProfileDescription> repository, ProfileDescription installed);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,15 @@
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.preferences.PreferencesFactory;
import ch.cyberduck.core.preferences.SupportDirectoryFinderFactory;
import ch.cyberduck.core.transfer.download.CompareFilter;
import ch.cyberduck.core.transfer.symlink.DisabledDownloadSymlinkResolver;
import ch.cyberduck.core.worker.Worker;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

/**
* Merge local set with latest versions from server
*/
public class ProfilesSynchronizeWorker extends Worker<Set<ProfileDescription>> {
private static final Logger log = LogManager.getLogger(ProfilesSynchronizeWorker.class.getName());

private final ProtocolFactory registry;
private final Local directory;
Expand Down Expand Up @@ -75,57 +67,8 @@ public Set<ProfileDescription> initialize() {

@Override
public Set<ProfileDescription> run(final Session<?> session) throws BackgroundException {
final Set<ProfileDescription> returned = new HashSet<>();
// Find all locally installed profiles
final LocalProfilesFinder localProfilesFinder = new LocalProfilesFinder(registry, directory, ProtocolFactory.BUNDLED_PROFILE_PREDICATE);
final Set<ProfileDescription> installed = localProfilesFinder.find();
// Find all profiles from repository
final RemoteProfilesFinder remoteProfilesFinder = new RemoteProfilesFinder(registry, session, this.filter(session));
final Set<ProfileDescription> remote = remoteProfilesFinder.find();
final ProfileMatcher matcher = new ChecksumProfileMatcher(remote);
// Iterate over every installed profile and find match in repository
installed.forEach(local -> {
// Check for matching remote checksum and download profile if this version is not equal to latest
final Optional<ProfileDescription> match = matcher.compare(local);
if(match.isPresent()) {
// Found matching checksum for profile in remote list which is not marked as latest version
log.warn("Override {} with latest profile verison {}", local, match);
// Remove previous version
local.getProfile().ifPresent(registry::unregister);
// Register updated profile by copying temporary file to application support
match.get().getFile().ifPresent(value -> {
final Local copy = registry.register(value);
if(null != copy) {
final LocalProfileDescription d = new LocalProfileDescription(registry, copy);
log.debug("Add synched profile {}", d);
returned.add(d);
visitor.visit(d);
}
});
}
else {
log.debug("Add local only profile {}", local);
returned.add(local);
visitor.visit(local);
}
});
// Iterate over all fetched profiles and when not installed
remote.forEach(description -> {
if(description.isLatest()) {
// Check if not already added previously when syncing with local list
if(!returned.contains(description)) {
log.debug("Add remote profile {}", description);
returned.add(description);
visitor.visit(description);
}
}
});
localProfilesFinder.cleanup();
remoteProfilesFinder.cleanup();
return returned;
}

protected CompareFilter filter(final Session<?> session) {
return new CompareFilter(new DisabledDownloadSymlinkResolver(), session);
return new ProtocolFactoryProfilesSynchronizer(session).sync(
// Match profiles by ETag and MD5 checksum of profile on disk
new ChecksumProfileMatcher(), visitor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ch.cyberduck.core.profiles;

/*
* Copyright (c) 2002-2025 iterate GmbH. All rights reserved.
* https://cyberduck.io/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

import ch.cyberduck.core.exception.BackgroundException;

import java.util.Set;

public interface ProfilesSynchronizer {
Set<ProfileDescription> sync(ProfileMatcher matcher, ProfilesFinder.Visitor visitor) throws BackgroundException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package ch.cyberduck.core.profiles;

/*
* Copyright (c) 2002-2025 iterate GmbH. All rights reserved.
* https://cyberduck.io/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

import ch.cyberduck.core.Local;
import ch.cyberduck.core.LocalFactory;
import ch.cyberduck.core.ProtocolFactory;
import ch.cyberduck.core.Session;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.preferences.PreferencesFactory;
import ch.cyberduck.core.preferences.SupportDirectoryFinderFactory;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

public class ProtocolFactoryProfilesSynchronizer implements ProfilesSynchronizer {
private static final Logger log = LogManager.getLogger(ProtocolFactoryProfilesSynchronizer.class.getName());

private final ProtocolFactory registry;
private final LocalProfilesFinder local;
private final RemoteProfilesFinder remote;

public ProtocolFactoryProfilesSynchronizer(final Session<?> session) {
this(ProtocolFactory.get(), session, LocalFactory.get(SupportDirectoryFinderFactory.get().find(),
PreferencesFactory.get().getProperty("profiles.folder.name")));
}

public ProtocolFactoryProfilesSynchronizer(final ProtocolFactory registry, final Session<?> session, final Local directory) {
this(ProtocolFactory.get(),
// Find all locally installed profiles
new LocalProfilesFinder(registry, directory, ProtocolFactory.BUNDLED_PROFILE_PREDICATE), session);
}

public ProtocolFactoryProfilesSynchronizer(final ProtocolFactory registry, final LocalProfilesFinder local, final Session<?> session) {
this(registry, local,
// Find all profiles from repository
new RemoteProfilesFinder(registry, session));
}

public ProtocolFactoryProfilesSynchronizer(final ProtocolFactory registry, final RemoteProfilesFinder remote) {
this(registry, LocalFactory.get(SupportDirectoryFinderFactory.get().find(),
PreferencesFactory.get().getProperty("profiles.folder.name")), remote);
}

public ProtocolFactoryProfilesSynchronizer(final ProtocolFactory registry, final Local directory, final RemoteProfilesFinder remote) {
this(registry,
// Find all locally installed profiles
new LocalProfilesFinder(registry, directory, ProtocolFactory.BUNDLED_PROFILE_PREDICATE), remote);
}

public ProtocolFactoryProfilesSynchronizer(final ProtocolFactory registry, final LocalProfilesFinder local, final RemoteProfilesFinder remote) {
this.registry = registry;
this.local = local;
this.remote = remote;
}

@Override
public Set<ProfileDescription> sync(final ProfileMatcher matcher, final ProfilesFinder.Visitor visitor) throws BackgroundException {
final Set<ProfileDescription> result = new HashSet<>();
final Set<ProfileDescription> installed = local.find();
final Set<ProfileDescription> available = remote.find();
// Iterate over every installed profile and find match in repository
installed.forEach(l -> {
// Check for matching remote checksum and download profile if this version is not equal to latest
final Optional<ProfileDescription> match = matcher.compare(available, l);
if(match.isPresent()) {
// Found matching checksum for profile in remote list which is not marked as latest version
log.warn("Override {} with latest profile verison {}", l, match);
// Remove previous version
l.getProfile().ifPresent(registry::unregister);
// Register updated profile by copying temporary file to application support
match.get().getFile().ifPresent(value -> {
final Local copy = registry.register(value);
if(null != copy) {
final LocalProfileDescription d = new LocalProfileDescription(registry, copy);
log.debug("Add synched profile {}", d);
result.add(d);
visitor.visit(d);
}
});
}
else {
log.debug("Add local only profile {}", l);
result.add(l);
visitor.visit(l);
}
});
// Iterate over all fetched profiles and when not installed
available.forEach(description -> {
if(description.isLatest()) {
// Check if not already added previously when syncing with local list
if(!result.contains(description)) {
log.debug("Add remote profile {}", description);
result.add(description);
visitor.visit(description);
}
}
});
local.cleanup();
remote.cleanup();
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,25 +57,32 @@ public class RemoteProfilesFinder implements ProfilesFinder {
private final ProtocolFactory protocols;
private final Session<?> session;
private final TransferPathFilter comparison;
private final Filter<Path> filter;

public RemoteProfilesFinder(final Session<?> session) {
this(ProtocolFactory.get(), session);
}

public RemoteProfilesFinder(final ProtocolFactory protocols, final Session<?> session) {
this(protocols, session, new CompareFilter(new DisabledDownloadSymlinkResolver(), session));
this(protocols, session, new CompareFilter(new DisabledDownloadSymlinkResolver(), session), new ProfileFilter());
}

public RemoteProfilesFinder(final ProtocolFactory protocols, final Session<?> session, final TransferPathFilter comparison) {
public RemoteProfilesFinder(final Session<?> session,
final TransferPathFilter comparison, final Filter<Path> filter) {
this(ProtocolFactory.get(), session, comparison, filter);
}

public RemoteProfilesFinder(final ProtocolFactory protocols, final Session<?> session,
final TransferPathFilter comparison, final Filter<Path> filter) {
this.protocols = protocols;
this.session = session;
this.comparison = comparison;
this.filter = filter;
}

@Override
public Set<ProfileDescription> find(final Visitor visitor) throws BackgroundException {
log.info("Fetch profiles from {}", session.getHost());
final ProfileFilter filter = new ProfileFilter();
final AttributedList<Path> list = session.getFeature(ListService.class).list(new DelegatingHomeFeature(
new DefaultPathHomeFeature(session.getHost())).find(), new DisabledListProgressListener());
return list.filter(filter).toStream().map(file -> visitor.visit(new RemoteProfileDescription(protocols, file,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public void testLocalOnly() throws Exception {
// Local only profile
final ProfileDescription local = new ProfileDescription(
ProtocolFactory.get(), new Checksum(HashAlgorithm.md5, "d41d8cd98f00b204e9800998ecf8427e"), null);
assertFalse(new ChecksumProfileMatcher(Stream.<ProfileDescription>empty().collect(Collectors.toSet())).compare(local).isPresent());
assertFalse(new ChecksumProfileMatcher().compare(
Stream.<ProfileDescription>empty().collect(Collectors.toSet()), local).isPresent());
}

@Test
Expand All @@ -52,7 +53,8 @@ public boolean isLatest() {
};
final ProfileDescription local = new ProfileDescription(
ProtocolFactory.get(), new Checksum(HashAlgorithm.md5, "d41d8cd98f00b204e9800998ecf8427e"), null);
assertFalse(new ChecksumProfileMatcher(Stream.of(remote).collect(Collectors.toSet())).compare(local).isPresent());
assertFalse(new ChecksumProfileMatcher().compare(
Stream.of(remote).collect(Collectors.toSet()), local).isPresent());
}

@Test
Expand All @@ -70,6 +72,7 @@ public Optional<Local> getFile() {
return Optional.of(new NullLocal("Profile.cyberduckprofile"));
}
};
assertTrue(new ChecksumProfileMatcher(Stream.of(remote).collect(Collectors.toSet())).compare(local).isPresent());
assertTrue(new ChecksumProfileMatcher().compare(
Stream.of(remote).collect(Collectors.toSet()), local).isPresent());
}
}
Loading