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

Transfer progress in Finder #16569

Merged
merged 21 commits into from
Nov 26, 2024
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

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