Skip to content

Commit

Permalink
Merge pull request #16569 from iterate-ch/feature/GH-16568
Browse files Browse the repository at this point in the history
Transfer progress in Finder
  • Loading branch information
dkocher authored Nov 26, 2024
2 parents 604cb16 + 654c60a commit 997e81a
Show file tree
Hide file tree
Showing 28 changed files with 618 additions and 222 deletions.
331 changes: 331 additions & 0 deletions binding/src/main/java/ch/cyberduck/binding/foundation/NSProgress.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@
import ch.cyberduck.core.local.DisabledFilesystemBookmarkResolver;
import ch.cyberduck.core.local.FileManagerWorkingDirectoryFinder;
import ch.cyberduck.core.local.FinderLocal;
import ch.cyberduck.core.local.FinderProgressIconService;
import ch.cyberduck.core.local.LaunchServicesApplicationFinder;
import ch.cyberduck.core.local.LaunchServicesFileDescriptor;
import ch.cyberduck.core.local.LaunchServicesQuarantineService;
import ch.cyberduck.core.local.WorkspaceApplicationLauncher;
import ch.cyberduck.core.local.WorkspaceBrowserLauncher;
import ch.cyberduck.core.local.WorkspaceIconService;
import ch.cyberduck.core.local.WorkspaceSymlinkFeature;
import ch.cyberduck.core.proxy.SystemConfigurationProxy;
import ch.cyberduck.core.threading.AutoreleaseActionOperationBatcher;
Expand Down Expand Up @@ -64,7 +64,7 @@ protected void setFactories() {
this.setDefault("factory.sleeppreventer.class", IOKitSleepPreventer.class.getName());
this.setDefault("factory.reachability.class", SystemConfigurationReachability.class.getName());
this.setDefault("factory.quarantine.class", LaunchServicesQuarantineService.class.getName());
this.setDefault("factory.iconservice.class", WorkspaceIconService.class.getName());
this.setDefault("factory.iconservice.class", FinderProgressIconService.class.getName());
this.setDefault("factory.filedescriptor.class", LaunchServicesFileDescriptor.class.getName());
this.setDefault("factory.workingdirectory.class", FileManagerWorkingDirectoryFinder.class.getName());
this.setDefault("factory.symlink.class", WorkspaceSymlinkFeature.class.getName());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package ch.cyberduck.core.local;

/*
* Copyright (c) 2002-2022 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.binding.foundation.NSProgress;
import ch.cyberduck.binding.foundation.NSURL;
import ch.cyberduck.core.Local;
import ch.cyberduck.core.transfer.Transfer;
import ch.cyberduck.core.transfer.TransferProgress;
import ch.cyberduck.core.transfer.TransferStatus;

public class FinderProgressIconService implements IconService {

@Override
public Icon get(final Transfer.Type type, final Local file) {
return new FinderProgressIcon(type, file);
}

private static final class FinderProgressIcon implements Icon {
private final NSProgress progress;
private final Local file;

public FinderProgressIcon(final Transfer.Type type, final Local file) {
this.file = file;
progress = NSProgress.discreteProgressWithTotalUnitCount(0L);
progress.setKind(NSProgress.NSProgressKindFile);
progress.setCancellable(false);
progress.setPausable(false);
switch(type) {
case download:
progress.setFileOperationKind(NSProgress.NSProgressFileOperationKindDownloading);
break;
case upload:
progress.setFileOperationKind(NSProgress.NSProgressFileOperationKindUploading);
break;
}
progress.setFileURL(NSURL.fileURLWithPath(file.getAbsolute()));
progress.publish();
}

@Override
public boolean update(final TransferProgress status) {
if(TransferStatus.UNKNOWN_LENGTH == status.getSize()) {
return false;
}
if(TransferStatus.UNKNOWN_LENGTH == status.getTransferred()) {
return false;
}
progress.setTotalUnitCount(status.getSize());
progress.setCompletedUnitCount(status.getTransferred());
return true;
}

@Override
public boolean remove() {
progress.unpublish();
return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,42 +1,70 @@
package ch.cyberduck.core.local;

/*
* Copyright (c) 2012 David Kocher. All rights reserved.
* http://cyberduck.ch/
* Copyright (c) 2002-2024 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 2 of the License, or
* 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.
*
* Bug fixes, suggestions and comments should be sent to:
* dkocher@cyberduck.ch
*/

import ch.cyberduck.binding.application.NSImage;
import ch.cyberduck.binding.application.NSWorkspace;
import ch.cyberduck.core.Local;
import ch.cyberduck.core.preferences.PreferencesFactory;
import ch.cyberduck.core.resources.IconCacheFactory;
import ch.cyberduck.core.transfer.TransferStatus;
import ch.cyberduck.core.transfer.Transfer;
import ch.cyberduck.core.transfer.TransferProgress;
import ch.cyberduck.core.unicode.NFDNormalizer;

import org.rococoa.cocoa.foundation.NSUInteger;

import java.math.BigDecimal;
import java.math.RoundingMode;

public final class WorkspaceIconService implements IconService {

private final NSWorkspace workspace = NSWorkspace.sharedWorkspace();
private static final NSWorkspace workspace = NSWorkspace.sharedWorkspace();

@Override
public boolean set(final Local file, final String image) {
return this.update(file, IconCacheFactory.<NSImage>get().iconNamed(image));
public Icon get(final Transfer.Type type, final Local file) {
switch(type) {
case download:
return new Icon() {
// An integer between 0 and 9
private int step = 0;

@Override
public boolean update(final TransferProgress progress) {
if(progress.getSize() > PreferencesFactory.get().getLong("queue.download.icon.threshold")) {
final int fraction = new BigDecimal(progress.getTransferred()).divide(new BigDecimal(progress.getSize()), 1, RoundingMode.DOWN).multiply(BigDecimal.TEN).intValue();
if(fraction >= step) {
// Another 10 percent of the file has been transferred
return WorkspaceIconService.update(file, IconCacheFactory.<NSImage>get().iconNamed(String.format("download%d.icns", step = fraction)));
}
return false;
}
return false;
}

@Override
public boolean remove() {
// The Finder will display the default icon for this file type
return WorkspaceIconService.update(file, null);
}
};
}
return disabled;
}

protected boolean update(final Local file, final NSImage icon) {
public static boolean update(final Local file, final NSImage icon) {
synchronized(NSWorkspace.class) {
// Specify 0 if you want to generate icons in all available icon representation formats
if(workspace.setIcon_forFile_options(icon, file.getAbsolute(), new NSUInteger(0))) {
Expand All @@ -46,25 +74,4 @@ protected boolean update(final Local file, final NSImage icon) {
return false;
}
}

@Override
public boolean set(final Local file, final TransferStatus status) {
if(status.isComplete()) {
return this.remove(file);
}
else {
if(status.getLength() > 0) {
int fraction = (int) (status.getOffset() / (status.getOffset() + status.getLength()) * 10);
return this.set(file, String.format("download%d.icns", ++fraction));
}
else {
return this.set(file, String.format("download%d.icns", 0));
}
}
}

@Override
public boolean remove(final Local file) {
return this.update(file, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@
import ch.cyberduck.core.local.FileManagerTrashFeature;
import ch.cyberduck.core.local.FileManagerWorkingDirectoryFinder;
import ch.cyberduck.core.local.FinderLocal;
import ch.cyberduck.core.local.FinderProgressIconService;
import ch.cyberduck.core.local.LaunchServicesApplicationFinder;
import ch.cyberduck.core.local.LaunchServicesFileDescriptor;
import ch.cyberduck.core.local.LaunchServicesQuarantineService;
import ch.cyberduck.core.local.SecurityScopedFilesystemBookmarkResolver;
import ch.cyberduck.core.local.WorkspaceApplicationBadgeLabeler;
import ch.cyberduck.core.local.WorkspaceApplicationLauncher;
import ch.cyberduck.core.local.WorkspaceBrowserLauncher;
import ch.cyberduck.core.local.WorkspaceIconService;
import ch.cyberduck.core.local.WorkspaceRevealService;
import ch.cyberduck.core.local.WorkspaceSymlinkFeature;
import ch.cyberduck.core.notification.NotificationCenter;
Expand Down Expand Up @@ -100,7 +100,7 @@ protected void setFactories() {
this.setDefault("factory.watchservice.class", FSEventWatchService.class.getName());
this.setDefault("factory.editorfactory.class", FSEventWatchEditorFactory.class.getName());
this.setDefault("factory.notification.class", NotificationCenter.class.getName());
this.setDefault("factory.iconservice.class", WorkspaceIconService.class.getName());
this.setDefault("factory.iconservice.class", FinderProgressIconService.class.getName());
this.setDefault("factory.filedescriptor.class", LaunchServicesFileDescriptor.class.getName());
if(Factory.Platform.osversion.matches("(10|11)\\..*")) {
this.setDefault("factory.schemehandler.class", LaunchServicesSchemeHandler.class.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,42 +28,27 @@
public class WorkspaceIconServiceTest {

@Test
public void testSetProgressNoFile() {
final WorkspaceIconService s = new WorkspaceIconService();
public void testUpdateProgressNoFile() {
final Local file = new Local(PreferencesFactory.get().getProperty("tmp.dir"),
UUID.randomUUID().toString());
assertFalse(s.update(file, NSImage.imageWithContentsOfFile("../../img/download0.icns")));
assertFalse(WorkspaceIconService.update(file, NSImage.imageWithContentsOfFile("../../img/download0.icns")));
}

@Test
public void testSetProgressFolder() throws Exception {
final WorkspaceIconService s = new WorkspaceIconService();
public void testUpdateProgressFolder() throws Exception {
final Local file = new Local(PreferencesFactory.get().getProperty("tmp.dir"),
UUID.randomUUID().toString());
new DefaultLocalDirectoryFeature().mkdir(file);
assertTrue(s.update(file, NSImage.imageWithContentsOfFile("../../img/download0.icns")));
assertTrue(WorkspaceIconService.update(file, NSImage.imageWithContentsOfFile("../../img/download0.icns")));
}

@Test
public void testSetProgress() throws Exception {
final WorkspaceIconService s = new WorkspaceIconService();
public void testUpdateProgress() throws Exception {
final Local file = new Local(PreferencesFactory.get().getProperty("tmp.dir"),
UUID.randomUUID().toString());
LocalTouchFactory.get().touch(file);
assertTrue(s.update(file, NSImage.imageWithContentsOfFile("../../img/download0.icns")));
file.delete();
}

@Test
public void testRemove() throws Exception {
final WorkspaceIconService s = new WorkspaceIconService();
final Local file = new Local(PreferencesFactory.get().getProperty("tmp.dir"),
UUID.randomUUID().toString());
assertFalse(s.remove(file));
LocalTouchFactory.get().touch(file);
assertFalse(s.remove(file));
assertTrue(s.update(file, NSImage.imageWithContentsOfFile("../../img/download0.icns")));
assertTrue(s.remove(file));
assertTrue(WorkspaceIconService.update(file, NSImage.imageWithContentsOfFile("../../img/download0.icns")));
file.delete();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,12 @@
*/

import ch.cyberduck.core.Local;
import ch.cyberduck.core.transfer.TransferStatus;
import ch.cyberduck.core.transfer.Transfer;

public final class DisabledIconService implements IconService {
@Override
public boolean set(final Local file, final String image) {
return false;
}

@Override
public boolean set(final Local file, final TransferStatus progress) {
return false;
}

@Override
public boolean remove(final Local file) {
return false;
public Icon get(final Transfer.Type type, final Local file) {
return disabled;
}
}
48 changes: 31 additions & 17 deletions core/src/main/java/ch/cyberduck/core/local/IconService.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,43 @@
*/

import ch.cyberduck.core.Local;
import ch.cyberduck.core.transfer.TransferStatus;
import ch.cyberduck.core.transfer.Transfer;
import ch.cyberduck.core.transfer.TransferProgress;

public interface IconService {

/**
* @param file File
* @param image Image name
* @return True if icon is set
*/
boolean set(Local file, String image);
Icon disabled = new Icon() {
@Override
public boolean update(final TransferProgress progress) {
return false;
}

/**
* @param file File
* @param progress An integer from -1 and 9. If -1 is passed, the icon should be removed.
* @return True if icon is set
*/
boolean set(Local file, TransferStatus progress);
@Override
public boolean remove() {
return false;
}
};

/**
* Remove custom icon
* Get icon updater to track progress
*
* @param file File
* @return True if icon is set
* @param file Local file
* @return Updater to send continious progress updates to
*/
boolean remove(Local file);
Icon get(Transfer.Type type, Local file);

interface Icon {
/**
* @param progress Transfer status with transferred bytes set in offset
* @return True if icon is set
*/
boolean update(TransferProgress progress);

/**
* Remove custom icon
*
* @return True if icon is set
*/
boolean remove();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
*/

import ch.cyberduck.core.Factory;
import ch.cyberduck.core.Local;
import ch.cyberduck.core.transfer.Transfer;

public class IconServiceFactory extends Factory<IconService> {

Expand All @@ -29,4 +31,8 @@ private IconServiceFactory() {
public static IconService get() {
return new IconServiceFactory().create();
}

public static IconService.Icon iconFor(final Transfer.Type type, final Local file) {
return get().get(type, file);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ public class DisabledProxyFinder implements ProxyFinder {
@Override
public Proxy find(final String target) {
return Proxy.DIRECT;
// return new Proxy(Proxy.Type.HTTPS, "127.0.0.1", 9090);
}
}
Loading

0 comments on commit 997e81a

Please sign in to comment.