From 01d183a01a66bcecfd3f579309426e7a5cb7d65a Mon Sep 17 00:00:00 2001 From: David Kocher Date: Sun, 5 Jan 2025 18:26:16 +0100 Subject: [PATCH 1/3] Reuse delegating listener. --- .../core/shared/DefaultSearchFeature.java | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/ch/cyberduck/core/shared/DefaultSearchFeature.java b/core/src/main/java/ch/cyberduck/core/shared/DefaultSearchFeature.java index 6cbbd58ed20..3a36a5f2377 100644 --- a/core/src/main/java/ch/cyberduck/core/shared/DefaultSearchFeature.java +++ b/core/src/main/java/ch/cyberduck/core/shared/DefaultSearchFeature.java @@ -20,6 +20,7 @@ import ch.cyberduck.core.ListProgressListener; import ch.cyberduck.core.ListService; import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProxyListProgressListener; import ch.cyberduck.core.Session; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.ConnectionCanceledException; @@ -40,33 +41,23 @@ public AttributedList search(final Path workdir, final Filter filter return session.getFeature(ListService.class).list(workdir, new SearchListProgressListener(filter, listener)).filter(filter); } - private static final class SearchListProgressListener implements ListProgressListener { + private static final class SearchListProgressListener extends ProxyListProgressListener { private final Filter filter; - private final ListProgressListener delegate; public SearchListProgressListener(final Filter filter, final ListProgressListener delegate) { + super(delegate); this.filter = filter; - this.delegate = delegate; } @Override public void chunk(final Path directory, final AttributedList list) throws ConnectionCanceledException { - delegate.chunk(directory, list.filter(filter)); + super.chunk(directory, list.filter(filter)); } @Override - public void finish(final Path directory, final AttributedList list, final Optional e) { - delegate.finish(directory, list, e); - } - - @Override - public ListProgressListener reset() { + public ListProgressListener reset() throws ConnectionCanceledException { + super.reset(); return this; } - - @Override - public void message(final String message) { - delegate.message(message); - } } } From 6233fd73bbbb70b82b433f2aa6ffc38913f7e6d4 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Sun, 5 Jan 2025 18:43:29 +0100 Subject: [PATCH 2/3] Add worker for find feature. --- .../core/worker/AttributesWorker.java | 14 ++-- .../ch/cyberduck/core/worker/FindWorker.java | 75 +++++++++++++++++++ .../ch/cyberduck/core/worker/ListWorker.java | 27 +------ .../WorkerCanceledListProgressListener.java | 46 ++++++++++++ 4 files changed, 133 insertions(+), 29 deletions(-) create mode 100644 core/src/main/java/ch/cyberduck/core/worker/FindWorker.java create mode 100644 core/src/main/java/ch/cyberduck/core/worker/WorkerCanceledListProgressListener.java diff --git a/core/src/main/java/ch/cyberduck/core/worker/AttributesWorker.java b/core/src/main/java/ch/cyberduck/core/worker/AttributesWorker.java index ef63135aa5b..88228568d6e 100644 --- a/core/src/main/java/ch/cyberduck/core/worker/AttributesWorker.java +++ b/core/src/main/java/ch/cyberduck/core/worker/AttributesWorker.java @@ -16,7 +16,8 @@ */ import ch.cyberduck.core.Cache; -import ch.cyberduck.core.CachingAttributesFinderFeature; +import ch.cyberduck.core.CachingListProgressListener; +import ch.cyberduck.core.DisabledListProgressListener; import ch.cyberduck.core.LocaleFactory; import ch.cyberduck.core.Path; import ch.cyberduck.core.PathAttributes; @@ -44,10 +45,13 @@ public AttributesWorker(final Cache cache, final Path file) { @Override public PathAttributes run(final Session session) throws BackgroundException { log.debug("Read latest attributes for file {}", file); - final AttributesFinder find = new CachingAttributesFinderFeature(session, cache, session.getFeature(AttributesFinder.class)); - final PathAttributes attr = find.find(file); - log.debug("Return {} for file {}", attr, file); - return attr; + return session.getFeature(AttributesFinder.class).find(file, new WorkerCanceledListProgressListener(this, + new CachingListProgressListener(new DisabledListProgressListener(), cache))); + } + + @Override + public void cleanup(final PathAttributes result) { + log.debug("Return {} for file {}", result, file); } @Override diff --git a/core/src/main/java/ch/cyberduck/core/worker/FindWorker.java b/core/src/main/java/ch/cyberduck/core/worker/FindWorker.java new file mode 100644 index 00000000000..d990f7f193c --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/worker/FindWorker.java @@ -0,0 +1,75 @@ +package ch.cyberduck.core.worker; + +/* + * 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.Cache; +import ch.cyberduck.core.CachingListProgressListener; +import ch.cyberduck.core.DisabledListProgressListener; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.Find; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Objects; + +public class FindWorker extends Worker { + private static final Logger log = LogManager.getLogger(FindWorker.class.getName()); + + private final Cache cache; + private final Path file; + + public FindWorker(final Cache cache, final Path file) { + this.file = file; + this.cache = cache; + } + + @Override + public Boolean run(final Session session) throws BackgroundException { + log.debug("Find file {}", file); + return session.getFeature(Find.class).find(file, new WorkerCanceledListProgressListener(this, + new CachingListProgressListener(new DisabledListProgressListener(), cache))); + } + + @Override + public void cleanup(final Boolean result) { + log.debug("Return {} for file {}", result, file); + } + + @Override + public Boolean initialize() { + return false; + } + + @Override + public final boolean equals(final Object o) { + if(this == o) { + return true; + } + if(o == null || getClass() != o.getClass()) { + return false; + } + final FindWorker that = (FindWorker) o; + return Objects.equals(file, that.file); + } + + @Override + public int hashCode() { + return Objects.hashCode(file); + } +} diff --git a/core/src/main/java/ch/cyberduck/core/worker/ListWorker.java b/core/src/main/java/ch/cyberduck/core/worker/ListWorker.java index ce3cca221b2..fb812e22a08 100644 --- a/core/src/main/java/ch/cyberduck/core/worker/ListWorker.java +++ b/core/src/main/java/ch/cyberduck/core/worker/ListWorker.java @@ -19,14 +19,13 @@ import ch.cyberduck.core.AttributedList; import ch.cyberduck.core.Cache; +import ch.cyberduck.core.CachingListProgressListener; import ch.cyberduck.core.ListProgressListener; import ch.cyberduck.core.ListService; import ch.cyberduck.core.LocaleFactory; import ch.cyberduck.core.Path; -import ch.cyberduck.core.ProxyListProgressListener; import ch.cyberduck.core.Session; import ch.cyberduck.core.exception.BackgroundException; -import ch.cyberduck.core.exception.ConnectionCanceledException; import ch.cyberduck.core.exception.ListCanceledException; import org.apache.logging.log4j.LogManager; @@ -46,7 +45,7 @@ public class ListWorker extends Worker> { public ListWorker(final Cache cache, final Path directory, final ListProgressListener listener) { this.cache = cache; this.directory = directory; - this.listener = new ConnectionCancelListProgressListener(this, directory, listener); + this.listener = new WorkerCanceledListProgressListener(this, new CachingListProgressListener(listener, cache)); } @Override @@ -133,29 +132,9 @@ public int hashCode() { @Override public String toString() { - final StringBuilder sb = new StringBuilder("SessionListWorker{"); + final StringBuilder sb = new StringBuilder("ListWorker{"); sb.append("directory=").append(directory); sb.append('}'); return sb.toString(); } - - private static final class ConnectionCancelListProgressListener extends ProxyListProgressListener { - private final Worker worker; - private final Path directory; - - public ConnectionCancelListProgressListener(final Worker worker, final Path directory, final ListProgressListener proxy) { - super(proxy); - this.worker = worker; - this.directory = directory; - } - - @Override - public void chunk(final Path directory, final AttributedList list) throws ConnectionCanceledException { - log.info("Retrieved chunk of {} items in {}", list.size(), this.directory); - if(worker.isCanceled()) { - throw new ListCanceledException(list); - } - super.chunk(this.directory, list); - } - } } diff --git a/core/src/main/java/ch/cyberduck/core/worker/WorkerCanceledListProgressListener.java b/core/src/main/java/ch/cyberduck/core/worker/WorkerCanceledListProgressListener.java new file mode 100644 index 00000000000..d70d17ede1b --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/worker/WorkerCanceledListProgressListener.java @@ -0,0 +1,46 @@ +package ch.cyberduck.core.worker; + +/* + * 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.AttributedList; +import ch.cyberduck.core.ListProgressListener; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProxyListProgressListener; +import ch.cyberduck.core.exception.ConnectionCanceledException; +import ch.cyberduck.core.exception.ListCanceledException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +final class WorkerCanceledListProgressListener extends ProxyListProgressListener { + private static final Logger log = LogManager.getLogger(WorkerCanceledListProgressListener.class.getName()); + + private final Worker worker; + + public WorkerCanceledListProgressListener(final Worker worker, final ListProgressListener proxy) { + super(proxy); + this.worker = worker; + } + + @Override + public void chunk(final Path directory, final AttributedList list) throws ConnectionCanceledException { + log.info("Retrieved chunk of {} items in {}", list.size(), directory); + if(worker.isCanceled()) { + throw new ListCanceledException(list); + } + super.chunk(directory, list); + } +} From 2a614ab0f9bdfda047cf189345e4b51439644d82 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Sun, 5 Jan 2025 18:45:27 +0100 Subject: [PATCH 3/3] Cache contents in listener. Make sure empty list is cached for directory not found. --- .../core/CachingAttributesFinderFeature.java | 13 +----- .../ch/cyberduck/core/CachingFindFeature.java | 7 +-- .../core/CachingListProgressListener.java | 43 +++++++++++-------- .../core/shared/ListFilteringFeature.java | 13 +++++- .../ch/cyberduck/core/worker/ListWorker.java | 5 --- .../core/CachingListProgressListenerTest.java | 19 ++++---- 6 files changed, 52 insertions(+), 48 deletions(-) diff --git a/core/src/main/java/ch/cyberduck/core/CachingAttributesFinderFeature.java b/core/src/main/java/ch/cyberduck/core/CachingAttributesFinderFeature.java index 45341140cf8..bfee9043319 100644 --- a/core/src/main/java/ch/cyberduck/core/CachingAttributesFinderFeature.java +++ b/core/src/main/java/ch/cyberduck/core/CachingAttributesFinderFeature.java @@ -1,7 +1,7 @@ package ch.cyberduck.core; /* - * Copyright (c) 2002-2021 iterate GmbH. All rights reserved. + * Copyright (c) 2002-2025 iterate GmbH. All rights reserved. * https://cyberduck.io/ * * This program is free software; you can redistribute it and/or modify @@ -55,16 +55,7 @@ public PathAttributes find(final Path file, final ListProgressListener listener) log.debug("Cached directory listing does not contain {}", file); throw new NotfoundException(file.getAbsolute()); } - final CachingListProgressListener caching = new CachingListProgressListener(cache); - try { - final PathAttributes attr = delegate.find(file, new ProxyListProgressListener(listener, caching)); - caching.cache(); - return attr; - } - catch(NotfoundException e) { - caching.cache(); - throw e; - } + return delegate.find(file, new CachingListProgressListener(listener, cache)); } @Override diff --git a/core/src/main/java/ch/cyberduck/core/CachingFindFeature.java b/core/src/main/java/ch/cyberduck/core/CachingFindFeature.java index 0f03b8b0a69..63fe0aead1a 100644 --- a/core/src/main/java/ch/cyberduck/core/CachingFindFeature.java +++ b/core/src/main/java/ch/cyberduck/core/CachingFindFeature.java @@ -1,7 +1,7 @@ package ch.cyberduck.core; /* - * Copyright (c) 2002-2021 iterate GmbH. All rights reserved. + * Copyright (c) 2002-2025 iterate GmbH. All rights reserved. * https://cyberduck.io/ * * This program is free software; you can redistribute it and/or modify @@ -54,10 +54,7 @@ public boolean find(final Path file, final ListProgressListener listener) throws log.debug("Cached directory listing does not contain {}", file); return false; } - final CachingListProgressListener caching = new CachingListProgressListener(cache); - final boolean found = delegate.find(file, new ProxyListProgressListener(listener, caching)); - caching.cache(); - return found; + return delegate.find(file, new CachingListProgressListener(listener, cache)); } @Override diff --git a/core/src/main/java/ch/cyberduck/core/CachingListProgressListener.java b/core/src/main/java/ch/cyberduck/core/CachingListProgressListener.java index 82955d8de0a..0e28802e984 100644 --- a/core/src/main/java/ch/cyberduck/core/CachingListProgressListener.java +++ b/core/src/main/java/ch/cyberduck/core/CachingListProgressListener.java @@ -15,37 +15,46 @@ * GNU General Public License for more details. */ +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.exception.NotfoundException; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.HashMap; -import java.util.Map; +import java.util.Optional; -public class CachingListProgressListener extends DisabledListProgressListener { +public class CachingListProgressListener extends ProxyListProgressListener { private static final Logger log = LogManager.getLogger(CachingListProgressListener.class); private final Cache cache; - private final Map> contents = new HashMap<>(1); - public CachingListProgressListener(final Cache cache) { + public CachingListProgressListener(final ListProgressListener proxy, final Cache cache) { + super(proxy); this.cache = cache; } @Override - public void chunk(final Path directory, final AttributedList list) { - contents.put(directory, list); - } - - /** - * Add enumerated contents to cache - */ - public void cache() { - for(Map.Entry> entry : contents.entrySet()) { - final AttributedList list = entry.getValue(); + public void finish(final Path directory, final AttributedList list, final Optional e) { + // Add enumerated contents to cache + if(e.isPresent()) { + if(e.get() instanceof NotfoundException) { + // Parent directory not found + log.debug("Cache empty contents for directory {} after failure {}", directory, e.get().toString()); + cache.put(directory, AttributedList.emptyList()); + } + else { + log.warn("Failure {} caching contents for {}", e.get().toString(), directory); + } + } + else { if(!(AttributedList.emptyList() == list)) { - log.debug("Cache directory listing for {}", entry.getKey()); - cache.put(entry.getKey(), list); + log.debug("Cache contents for {}", directory); + cache.put(directory, list); + } + else { + log.warn("Skip caching directory listing for {}", directory); } } + super.finish(directory, list, e); } } diff --git a/core/src/main/java/ch/cyberduck/core/shared/ListFilteringFeature.java b/core/src/main/java/ch/cyberduck/core/shared/ListFilteringFeature.java index d8315eb96a2..f113ae70551 100644 --- a/core/src/main/java/ch/cyberduck/core/shared/ListFilteringFeature.java +++ b/core/src/main/java/ch/cyberduck/core/shared/ListFilteringFeature.java @@ -30,6 +30,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.Optional; + public abstract class ListFilteringFeature { private static final Logger log = LogManager.getLogger(ListFilteringFeature.class); @@ -44,7 +46,16 @@ public ListFilteringFeature(final Session session) { * @return Null if not found */ protected Path search(final Path file, final ListProgressListener listener) throws BackgroundException { - final AttributedList list = session._getFeature(ListService.class).list(file.getParent(), listener); + final Path directory = file.getParent(); + final AttributedList list; + try { + list = session._getFeature(ListService.class).list(directory, listener); + } + catch(BackgroundException e) { + listener.finish(directory, AttributedList.emptyList(), Optional.of(e)); + throw e; + } + listener.finish(directory, list, Optional.empty()); // Try to match path only as the version might have changed in the meantime final Path found = list.find(new ListFilteringPredicate(session.getCaseSensitivity(), file)); if(null == found) { diff --git a/core/src/main/java/ch/cyberduck/core/worker/ListWorker.java b/core/src/main/java/ch/cyberduck/core/worker/ListWorker.java index fb812e22a08..9658043c9ba 100644 --- a/core/src/main/java/ch/cyberduck/core/worker/ListWorker.java +++ b/core/src/main/java/ch/cyberduck/core/worker/ListWorker.java @@ -92,11 +92,6 @@ public void cleanup(final AttributedList list) { log.debug("Notify listener {} with empty result set for {}", listener, directory); listener.finish(directory, AttributedList.emptyList(), Optional.empty()); } - else { - log.debug("Cache contents for {}", directory); - // Cache directory listing - cache.put(directory, list); - } } @Override diff --git a/core/src/test/java/ch/cyberduck/core/CachingListProgressListenerTest.java b/core/src/test/java/ch/cyberduck/core/CachingListProgressListenerTest.java index ce3d9951c2a..69a21e5f19a 100644 --- a/core/src/test/java/ch/cyberduck/core/CachingListProgressListenerTest.java +++ b/core/src/test/java/ch/cyberduck/core/CachingListProgressListenerTest.java @@ -18,6 +18,7 @@ import org.junit.Test; import java.util.EnumSet; +import java.util.Optional; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -25,32 +26,32 @@ public class CachingListProgressListenerTest { @Test - public void testEmptyList() { + public void testEmptyList() throws Exception { final PathCache cache = new PathCache(1); - final CachingListProgressListener listener = new CachingListProgressListener(cache); + final CachingListProgressListener listener = new CachingListProgressListener(new DisabledListProgressListener(), cache); final Path directory = new Path("/", EnumSet.of(Path.Type.directory)); listener.chunk(directory, new AttributedList<>()); assertFalse(cache.isCached(directory)); - listener.cache(); + listener.finish(directory, new AttributedList<>(), Optional.empty()); assertTrue(cache.isCached(directory)); } @Test - public void testNoResult() { + public void testNoResult() throws Exception { final PathCache cache = new PathCache(1); - final CachingListProgressListener listener = new CachingListProgressListener(cache); + final CachingListProgressListener listener = new CachingListProgressListener(new DisabledListProgressListener(), cache); final Path directory = new Path("/", EnumSet.of(Path.Type.directory)); listener.chunk(directory, AttributedList.emptyList()); - listener.cache(); + listener.finish(directory, AttributedList.emptyList(), Optional.empty()); assertFalse(cache.isCached(directory)); } @Test - public void testNoChunk() { + public void testNoChunk() throws Exception { final PathCache cache = new PathCache(1); final Path directory = new Path("/", EnumSet.of(Path.Type.directory)); - final CachingListProgressListener listener = new CachingListProgressListener(cache); - listener.cache(); + final CachingListProgressListener listener = new CachingListProgressListener(new DisabledListProgressListener(), cache); + listener.finish(directory, AttributedList.emptyList(), Optional.empty()); assertFalse(cache.isCached(directory)); } } \ No newline at end of file