mirror of
https://github.com/iterate-ch/cyberduck.git
synced 2026-05-26 19:10:49 +00:00
Merge pull request #18098 from iterate-ch/bugfix/GH-17744
Load profiles from precompiled index file
This commit is contained in:
@@ -20,6 +20,7 @@ package ch.cyberduck.core.resources;
|
||||
import ch.cyberduck.binding.application.NSGraphics;
|
||||
import ch.cyberduck.binding.application.NSImage;
|
||||
import ch.cyberduck.binding.application.NSWorkspace;
|
||||
import ch.cyberduck.binding.foundation.NSData;
|
||||
import ch.cyberduck.core.Factory;
|
||||
import ch.cyberduck.core.Local;
|
||||
import ch.cyberduck.core.Path;
|
||||
@@ -27,6 +28,7 @@ import ch.cyberduck.core.Permission;
|
||||
import ch.cyberduck.core.local.Application;
|
||||
import ch.cyberduck.core.preferences.PreferencesFactory;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -42,7 +44,7 @@ public class NSImageIconCache implements IconCache<NSImage> {
|
||||
|
||||
private final NSWorkspace workspace = NSWorkspace.sharedWorkspace();
|
||||
|
||||
private NSImage cache(final String name, final NSImage image, final Integer size) {
|
||||
private static NSImage cache(final String name, final NSImage image, final Integer size) {
|
||||
if(null == image) {
|
||||
log.warn("No icon named {}", name);
|
||||
return image;
|
||||
@@ -88,8 +90,7 @@ public class NSImageIconCache implements IconCache<NSImage> {
|
||||
public NSImage documentIcon(final String extension, final Integer size) {
|
||||
NSImage image = this.load(extension, size);
|
||||
if(null == image) {
|
||||
return this.cache(extension,
|
||||
this.convert(extension, workspace.iconForFileType(extension), size), size);
|
||||
return cache(extension, convert(extension, workspace.iconForFileType(extension), size), size);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
@@ -99,8 +100,8 @@ public class NSImageIconCache implements IconCache<NSImage> {
|
||||
final String name = String.format("NSDocument-%s%s", extension, badge.name());
|
||||
NSImage icon = this.iconNamed(name, size);
|
||||
if(null == icon) {
|
||||
icon = this.badge(badge, this.documentIcon(extension, size));
|
||||
this.cache(name, icon, size);
|
||||
icon = badge(badge, this.documentIcon(extension, size));
|
||||
cache(name, icon, size);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
@@ -119,9 +120,9 @@ public class NSImageIconCache implements IconCache<NSImage> {
|
||||
final String name = String.format("NSFolder-%s", badge.name());
|
||||
NSImage folder = this.load(name, size);
|
||||
if(null == folder) {
|
||||
folder = this.convert(name, this.iconNamed("NSFolder", size), size);
|
||||
folder = this.badge(badge, folder);
|
||||
this.cache(name, folder, size);
|
||||
folder = convert(name, this.iconNamed("NSFolder", size), size);
|
||||
folder = badge(badge, folder);
|
||||
cache(name, folder, size);
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
@@ -133,7 +134,7 @@ public class NSImageIconCache implements IconCache<NSImage> {
|
||||
* @param icon Icon
|
||||
* @return Cached icon
|
||||
*/
|
||||
private NSImage badge(final NSImage badge, final NSImage icon) {
|
||||
private static NSImage badge(final NSImage badge, final NSImage icon) {
|
||||
NSImage f = NSImage.imageWithSize(icon.size());
|
||||
f.lockFocus();
|
||||
icon.drawInRect(new NSRect(new NSPoint(0, 0), icon.size()),
|
||||
@@ -155,20 +156,25 @@ public class NSImageIconCache implements IconCache<NSImage> {
|
||||
*/
|
||||
@Override
|
||||
public NSImage iconNamed(final String name, final Integer width, final Integer height) {
|
||||
// Search for an object whose name was set explicitly using the setName: method and currently
|
||||
// resides in the image cache
|
||||
NSImage image = this.load(name, width);
|
||||
if(null == name) {
|
||||
return this.iconNamed("notfound.tiff", width, height);
|
||||
}
|
||||
NSImage image;
|
||||
if(Base64.isBase64(name)) {
|
||||
image = convert(name, NSImage.imageWithData(NSData.dataWithBase64EncodedString(name)), width, height);
|
||||
}
|
||||
else {
|
||||
// Search for an object whose name was set explicitly using the setName: method and currently
|
||||
// resides in the image cache
|
||||
image = this.load(name, width);
|
||||
}
|
||||
if(null == image) {
|
||||
if(null == name) {
|
||||
return this.iconNamed("notfound.tiff", width, height);
|
||||
}
|
||||
else if(name.contains(PreferencesFactory.get().getProperty("local.delimiter"))) {
|
||||
return this.cache(FilenameUtils.getName(name), this.convert(FilenameUtils.getName(name),
|
||||
NSImage.imageWithContentsOfFile(name), width, height), width);
|
||||
if(name.contains(PreferencesFactory.get().getProperty("local.delimiter"))) {
|
||||
return cache(FilenameUtils.getName(name),
|
||||
convert(FilenameUtils.getName(name), NSImage.imageWithContentsOfFile(name), width, height), width);
|
||||
}
|
||||
else {
|
||||
return this.cache(name, this.convert(name,
|
||||
NSImage.imageNamed(name), width, height), width);
|
||||
return cache(name, convert(name, NSImage.imageNamed(name), width, height), width);
|
||||
}
|
||||
}
|
||||
return image;
|
||||
@@ -185,8 +191,7 @@ public class NSImageIconCache implements IconCache<NSImage> {
|
||||
if(file.exists()) {
|
||||
icon = this.load(file.getAbsolute(), size);
|
||||
if(null == icon) {
|
||||
return this.cache(file.getName(),
|
||||
this.convert(file.getName(), workspace.iconForFile(file.getAbsolute()), size), size);
|
||||
return cache(file.getName(), convert(file.getName(), workspace.iconForFile(file.getAbsolute()), size), size);
|
||||
}
|
||||
}
|
||||
if(null == icon) {
|
||||
@@ -207,8 +212,7 @@ public class NSImageIconCache implements IconCache<NSImage> {
|
||||
final String path = workspace.absolutePathForAppBundleWithIdentifier(app.getIdentifier());
|
||||
// Null if the bundle cannot be found
|
||||
if(StringUtils.isNotBlank(path)) {
|
||||
return this.cache(app.getIdentifier(),
|
||||
this.convert(app.getIdentifier(), workspace.iconForFile(path), size), size);
|
||||
return cache(app.getIdentifier(), convert(app.getIdentifier(), workspace.iconForFile(path), size), size);
|
||||
}
|
||||
}
|
||||
if(null == icon) {
|
||||
@@ -275,14 +279,14 @@ public class NSImageIconCache implements IconCache<NSImage> {
|
||||
|
||||
@Override
|
||||
public NSImage aliasIcon(final String extension, final Integer size) {
|
||||
return this.badge(this.iconNamed("aliasbadge.tiff", size), this.documentIcon(extension, size));
|
||||
return badge(this.iconNamed("aliasbadge.tiff", size), this.documentIcon(extension, size));
|
||||
}
|
||||
|
||||
private NSImage convert(final String name, final NSImage icon, final Integer size) {
|
||||
return this.convert(name, icon, size, size);
|
||||
private static NSImage convert(final String name, final NSImage icon, final Integer size) {
|
||||
return convert(name, icon, size, size);
|
||||
}
|
||||
|
||||
private NSImage convert(final String name, final NSImage image, final Integer width, final Integer height) {
|
||||
private static NSImage convert(final String name, final NSImage image, final Integer width, final Integer height) {
|
||||
if(null == image) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Ch.Cyberduck.Core.Refresh.Media.Imaging;
|
||||
using System;
|
||||
using System.Buffers.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Ch.Cyberduck.Core.Refresh.Media.Imaging;
|
||||
|
||||
namespace Ch.Cyberduck.Core.Refresh.Services
|
||||
{
|
||||
@@ -19,32 +21,19 @@ namespace Ch.Cyberduck.Core.Refresh.Services
|
||||
using (IconCache.WriteLock())
|
||||
{
|
||||
bool isDefault = !IconCache.TryGetIcon<Image>(key, out _, classifier);
|
||||
Stream stream = default;
|
||||
bool dispose = true;
|
||||
try
|
||||
images = GetImages(path, (c, s) => c.TryGetIcon<Image>(key, out _, classifier), (c, s, i) =>
|
||||
{
|
||||
stream = GetStream(path);
|
||||
images = GetImages(stream, (c, s) => c.TryGetIcon<Image>(key, out _, classifier), (c, s, i) =>
|
||||
if (isDefault)
|
||||
{
|
||||
if (isDefault)
|
||||
isDefault = false;
|
||||
if (returnDefault)
|
||||
{
|
||||
isDefault = false;
|
||||
if (returnDefault)
|
||||
{
|
||||
image = i;
|
||||
}
|
||||
IconCache.CacheIcon<Image>(key, s, classifier);
|
||||
image = i;
|
||||
}
|
||||
IconCache.CacheIcon(key, s, i, classifier);
|
||||
}, out dispose);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (dispose && stream != null)
|
||||
{
|
||||
stream.Dispose();
|
||||
IconCache.CacheIcon<Image>(key, s, classifier);
|
||||
}
|
||||
}
|
||||
IconCache.CacheIcon(key, s, i, classifier);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,58 +41,94 @@ namespace Ch.Cyberduck.Core.Refresh.Services
|
||||
return images;
|
||||
}
|
||||
|
||||
private IEnumerable<Image> GetImages(Stream stream, GetCacheIconCallback getCache, CacheIconCallback cacheIcon, out bool dispose)
|
||||
private IEnumerable<Image> GetImages(string name, GetCacheIconCallback getCache, CacheIconCallback cacheIcon)
|
||||
{
|
||||
Image source = Image.FromStream(stream);
|
||||
if (!TryGetBase64Images(name, getCache, cacheIcon, out var images))
|
||||
{
|
||||
using var stream = GetStream(name);
|
||||
_ = TryGetImages(stream, getCache, cacheIcon, out images);
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
private bool TryGetBase64Images(string name, GetCacheIconCallback getCache, CacheIconCallback cacheIcon, out IEnumerable<Image> images)
|
||||
{
|
||||
#if NETCOREAPP
|
||||
if (!Base64.IsValid(name))
|
||||
{
|
||||
goto exit;
|
||||
}
|
||||
#endif
|
||||
try
|
||||
{
|
||||
using MemoryStream imageStream = new(Convert.FromBase64String(name), false);
|
||||
return TryGetImages(imageStream, getCache, cacheIcon, out images);
|
||||
}
|
||||
catch { /* We don't have an easy way of validating Base64 input. Let it error out. */ }
|
||||
|
||||
#if NETCOREAPP
|
||||
exit:
|
||||
#endif
|
||||
images = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetImages(Stream stream, GetCacheIconCallback getCache, CacheIconCallback cacheIcon, out IEnumerable<Image> images)
|
||||
{
|
||||
using var source = Image.FromStream(stream);
|
||||
|
||||
if (ImageFormat.Gif.Equals(source.RawFormat))
|
||||
{
|
||||
// Gif cannot be cached/duplicated/or anything else, really.
|
||||
// underlying stream must not be closed (learned the hard way).
|
||||
dispose = false;
|
||||
cacheIcon(IconCache, source.Width, source);
|
||||
return new[] { source };
|
||||
MemoryStream gifStream = new();
|
||||
source.Save(gifStream, ImageFormat.Gif);
|
||||
var image = Image.FromStream(gifStream);
|
||||
cacheIcon(IconCache, source.Width, image);
|
||||
images = [image];
|
||||
}
|
||||
|
||||
dispose = true;
|
||||
using (source)
|
||||
else if (ImageFormat.Icon.Equals(source.RawFormat))
|
||||
{
|
||||
if (ImageFormat.Icon.Equals(source.RawFormat))
|
||||
if (!stream.CanSeek)
|
||||
{
|
||||
source.Dispose();
|
||||
stream.Position = 0;
|
||||
GDIIcon icon = new(stream);
|
||||
foreach (var item in icon.Frames)
|
||||
{
|
||||
cacheIcon(IconCache, item.Width, item);
|
||||
}
|
||||
return icon.Frames;
|
||||
images = null;
|
||||
return false;
|
||||
}
|
||||
else if (ImageFormat.Tiff.Equals(source.RawFormat))
|
||||
{
|
||||
List<Image> frames = new();
|
||||
FrameDimension frameDimension = new(source.FrameDimensionsList[0]);
|
||||
var pageCount = source.GetFrameCount(FrameDimension.Page);
|
||||
for (int i = 0; i < pageCount; i++)
|
||||
{
|
||||
source.SelectActiveFrame(frameDimension, i);
|
||||
if (getCache(IconCache, source.Width)) continue;
|
||||
|
||||
Bitmap copy = new(source);
|
||||
copy.SetResolution(96, 96);
|
||||
frames.Add(copy);
|
||||
cacheIcon(IconCache, copy.Width, copy);
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
else
|
||||
stream.Position = 0;
|
||||
GDIIcon icon = new(stream);
|
||||
foreach (var item in icon.Frames)
|
||||
{
|
||||
cacheIcon(IconCache, item.Width, item);
|
||||
}
|
||||
|
||||
images = icon.Frames;
|
||||
}
|
||||
else if (ImageFormat.Tiff.Equals(source.RawFormat))
|
||||
{
|
||||
List<Image> frames = new();
|
||||
FrameDimension frameDimension = new(source.FrameDimensionsList[0]);
|
||||
var pageCount = source.GetFrameCount(FrameDimension.Page);
|
||||
for (int i = 0; i < pageCount; i++)
|
||||
{
|
||||
source.SelectActiveFrame(frameDimension, i);
|
||||
if (getCache(IconCache, source.Width)) continue;
|
||||
|
||||
Bitmap copy = new(source);
|
||||
copy.SetResolution(96, 96);
|
||||
frames.Add(copy);
|
||||
cacheIcon(IconCache, copy.Width, copy);
|
||||
return new[] { copy };
|
||||
}
|
||||
images = frames;
|
||||
}
|
||||
else
|
||||
{
|
||||
Bitmap copy = new(source);
|
||||
copy.SetResolution(96, 96);
|
||||
cacheIcon(IconCache, copy.Width, copy);
|
||||
images = [copy];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using static Windows.Win32.CorePInvoke;
|
||||
|
||||
namespace Ch.Cyberduck.Core.Refresh.Services
|
||||
{
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
using System;
|
||||
using System.Buffers.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using ch.cyberduck.core;
|
||||
using ch.cyberduck.core.profiles;
|
||||
|
||||
namespace Ch.Cyberduck.Core.Refresh.Services
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
public class WpfIconProvider : IconProvider<BitmapSource>
|
||||
{
|
||||
public WpfIconProvider(IconCache cache, IIconProviderImageSource BitmapSource) : base(cache, BitmapSource)
|
||||
@@ -20,20 +24,36 @@ namespace Ch.Cyberduck.Core.Refresh.Services
|
||||
public override BitmapSource GetDisk(Protocol protocol, int size)
|
||||
=> IconCache.TryGetIcon(protocol, size, out BitmapSource image, "Disk")
|
||||
? image
|
||||
: Get(protocol, protocol.disk(), size, "Disk");
|
||||
: Get(protocol, protocol.disk(), size, "Disk", true);
|
||||
|
||||
public IEnumerable<BitmapSource> GetDisk(Protocol protocol)
|
||||
=> Get(protocol, protocol.disk(), "Disk", false, out _);
|
||||
=> Get(protocol, protocol.disk(), "Disk", false, true, out _);
|
||||
|
||||
public override BitmapSource GetIcon(Protocol protocol, int size)
|
||||
=> IconCache.TryGetIcon(protocol, size, out BitmapSource image, "Icon")
|
||||
? image
|
||||
: Get(protocol, protocol.icon(), size, "Icon");
|
||||
: Get(protocol, protocol.icon(), size, "Icon", true);
|
||||
|
||||
public IEnumerable<BitmapSource> GetIcon(Protocol protocol)
|
||||
=> Get(protocol, protocol.icon(), "Icon", false, out _);
|
||||
=> Get(protocol, protocol.icon(), "Icon", false, true, out _);
|
||||
|
||||
public IEnumerable<BitmapSource> GetResources(string name) => Get(name, name, default, false, out var _);
|
||||
public IEnumerable<BitmapSource> GetResources(string name) => Get(name, name, default, false, false, out var _);
|
||||
|
||||
public BitmapSource GetThumbnail(ProfileDescription profile, int size)
|
||||
{
|
||||
if (profile.getThumbnail() is not { } thumbnail)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var key = thumbnail.GetHashCode();
|
||||
if (!IconCache.TryGetIcon(key, size, out BitmapSource image, "Thumbnail"))
|
||||
{
|
||||
image = Get(key, thumbnail, size, "Thumbnail", true);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
protected override BitmapSource Get(IntPtr nativeIcon, CacheIconCallback cacheIcon)
|
||||
{
|
||||
@@ -43,10 +63,10 @@ namespace Ch.Cyberduck.Core.Refresh.Services
|
||||
}
|
||||
|
||||
protected override BitmapSource Get(string name, int size)
|
||||
=> Get(name, name, size, default);
|
||||
=> Get(name, name, size, default, false);
|
||||
|
||||
protected override BitmapSource Get(string name)
|
||||
=> Get(name, name, default);
|
||||
=> Get(name, name, default, false);
|
||||
|
||||
protected override BitmapSource NearestFit(IEnumerable<BitmapSource> sources, int size, CacheIconCallback cacheCallback)
|
||||
{
|
||||
@@ -104,21 +124,21 @@ namespace Ch.Cyberduck.Core.Refresh.Services
|
||||
return writeableBitmap;
|
||||
}
|
||||
|
||||
private BitmapSource Get(object key, string path, string classifier)
|
||||
private BitmapSource Get(object key, string path, string classifier, bool isBase64)
|
||||
=> IconCache.TryGetIcon(key, out BitmapSource image, classifier)
|
||||
? image
|
||||
: Get(key, path, 0, classifier, true);
|
||||
: Get(key, path, 0, classifier, true, isBase64);
|
||||
|
||||
private BitmapSource Get(object key, string path, int size, string classifier)
|
||||
private BitmapSource Get(object key, string path, int size, string classifier, bool isBase64)
|
||||
=> IconCache.TryGetIcon(key, size, out BitmapSource image, classifier)
|
||||
? image
|
||||
: Get(key, path, size, classifier, false);
|
||||
: Get(key, path, size, classifier, false, isBase64);
|
||||
|
||||
private BitmapSource Get(object key, string path, int size, string classifier, bool returnDefault)
|
||||
private BitmapSource Get(object key, string path, int size, string classifier, bool returnDefault, bool isBase64)
|
||||
{
|
||||
using (IconCache.UpgradeableReadLock())
|
||||
{
|
||||
var images = Get(key, path, classifier, returnDefault, out var image);
|
||||
var images = Get(key, path, classifier, returnDefault, isBase64, out var image);
|
||||
return image ?? NearestFit(images, size, (c, s, i) =>
|
||||
{
|
||||
c.CacheIcon(key, s, i, classifier);
|
||||
@@ -127,7 +147,7 @@ namespace Ch.Cyberduck.Core.Refresh.Services
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<BitmapSource> Get(object key, string path, string classifier, bool returnDefault, out BitmapSource @default)
|
||||
private IEnumerable<BitmapSource> Get(object key, string name, string classifier, bool returnDefault, bool isBase64, out BitmapSource @default)
|
||||
{
|
||||
BitmapSource image = default;
|
||||
var images = IconCache.Filter<BitmapSource>(((object key, string classifier, int) f) => Equals(key, f.key) && Equals(classifier, f.classifier));
|
||||
@@ -136,8 +156,7 @@ namespace Ch.Cyberduck.Core.Refresh.Services
|
||||
using (IconCache.WriteLock())
|
||||
{
|
||||
bool isDefault = !IconCache.TryGetIcon<BitmapSource>(key, out _, classifier);
|
||||
using Stream stream = GetStream(path);
|
||||
images = GetImages(stream, (c, s) => c.TryGetIcon<BitmapSource>(key, s, out _, classifier), (c, s, i) =>
|
||||
images = GetImages(name, (c, s) => c.TryGetIcon<BitmapSource>(key, s, out _, classifier), (c, s, i) =>
|
||||
{
|
||||
if (isDefault)
|
||||
{
|
||||
@@ -149,7 +168,7 @@ namespace Ch.Cyberduck.Core.Refresh.Services
|
||||
IconCache.CacheIcon<BitmapSource>(key, s, classifier);
|
||||
}
|
||||
IconCache.CacheIcon(key, s, i, classifier);
|
||||
});
|
||||
}, isBase64);
|
||||
}
|
||||
}
|
||||
@default = image;
|
||||
@@ -157,6 +176,41 @@ namespace Ch.Cyberduck.Core.Refresh.Services
|
||||
return images;
|
||||
}
|
||||
|
||||
private IEnumerable<BitmapSource> GetImages(string name, GetCacheIconCallback getCache, CacheIconCallback cacheIcon, bool isBase64)
|
||||
{
|
||||
if (!isBase64 || !TryGetBase64Images(name, getCache, cacheIcon, out var images))
|
||||
{
|
||||
using var stream = GetStream(name);
|
||||
images = GetImages(stream, getCache, cacheIcon);
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
private bool TryGetBase64Images(string name, GetCacheIconCallback getCache, CacheIconCallback cacheIcon, out IEnumerable<BitmapSource> images)
|
||||
{
|
||||
#if NETCOREAPP
|
||||
if (!Base64.IsValid(name))
|
||||
{
|
||||
goto exit;
|
||||
}
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
using MemoryStream imageStream = new(Convert.FromBase64String(name), false);
|
||||
images = GetImages(imageStream, getCache, cacheIcon);
|
||||
return true;
|
||||
}
|
||||
catch { /* We don't have an easy way of validating Base64 input. Let it error out. */ }
|
||||
|
||||
#if NETCOREAPP
|
||||
exit:
|
||||
#endif
|
||||
images = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerable<BitmapSource> GetImages(Stream stream, GetCacheIconCallback getCache, CacheIconCallback cacheIcon)
|
||||
{
|
||||
var list = new List<BitmapSource>();
|
||||
|
||||
@@ -18,10 +18,10 @@ namespace Ch.Cyberduck.Core.Refresh.UserControls
|
||||
{
|
||||
d(this.OneWayBind(ViewModel, vm => vm.Name, v => v.ProtocolType.Text));
|
||||
d(this.OneWayBind(ViewModel, vm => vm.Description, v => v.Description.Text));
|
||||
d(this.OneWayBind(ViewModel, vm => vm.Profile, v => v.ProfileIcon.Source, p => wpfIconProvider.GetDisk(p, 32)));
|
||||
d(this.OneWayBind(ViewModel, vm => vm.ProfileDescription, v => v.ProfileIcon.Source, p => wpfIconProvider.GetThumbnail(p, 32)));
|
||||
d(this.OneWayBind(ViewModel, vm => vm.DefaultHostName, v => v.ToolTipEnabled, v => !string.IsNullOrWhiteSpace(v)));
|
||||
d(this.OneWayBind(ViewModel, vm => vm.DefaultHostName, v => v.ToolTip));
|
||||
d(this.OneWayBind(ViewModel, vm => vm.IsEnabled, v => v.Checked.IsEnabled));
|
||||
d(this.OneWayBind(ViewModel, vm => vm.Enabled, v => v.Checked.IsEnabled));
|
||||
d(this.BindCommand(ViewModel, vm => vm.OpenHelp, v => v.HelpButton));
|
||||
d(this.Bind(ViewModel, vm => vm.Installed, v => v.Checked.IsChecked));
|
||||
});
|
||||
|
||||
+24
-13
@@ -1,9 +1,12 @@
|
||||
using ch.cyberduck.core;
|
||||
using ch.cyberduck.core.local;
|
||||
using ch.cyberduck.core.profiles;
|
||||
using java.util;
|
||||
using ReactiveUI;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using Observable = System.Reactive.Linq.Observable;
|
||||
|
||||
namespace Ch.Cyberduck.Core.Refresh.ViewModels.Preferences.Pages
|
||||
{
|
||||
@@ -11,24 +14,25 @@ namespace Ch.Cyberduck.Core.Refresh.ViewModels.Preferences.Pages
|
||||
{
|
||||
private bool installed;
|
||||
|
||||
public ProfileViewModel(ProfileDescription profile)
|
||||
public ProfileViewModel(ProfileDescription description)
|
||||
{
|
||||
ProfileDescription = profile;
|
||||
Profile = (Profile)profile.getProfile().get();
|
||||
Installed = profile.isInstalled() && Profile.isEnabled();
|
||||
IsEnabled = !(Profile.isBundled() || Utils.ConvertFromJavaList<Host>(BookmarkCollection.defaultCollection()).Any(x => x.getProtocol().Equals(Profile)));
|
||||
ProfileDescription = description;
|
||||
Installed = description.isInstalled() && description.isEnabled();
|
||||
Enabled = !description.isBundled()
|
||||
&& !BookmarkCollection.defaultCollection().AsEnumerable<Host>()
|
||||
.Any(host => IsDefaultProfile(description, host));
|
||||
|
||||
OpenHelp = ReactiveCommand.Create(() =>
|
||||
{
|
||||
BrowserLauncherFactory.get().open(ProviderHelpServiceFactory.get().help(Profile));
|
||||
});
|
||||
BrowserLauncherFactory.get().open(ProfileDescription.getHelp());
|
||||
}, Observable.Return(!string.IsNullOrWhiteSpace(ProfileDescription.getHelp())));
|
||||
}
|
||||
|
||||
public bool IsEnabled { get; }
|
||||
public string DefaultHostName => string.Empty;
|
||||
|
||||
public string DefaultHostName => Profile.getDefaultHostname();
|
||||
public string Description => ProfileDescription.getDescription();
|
||||
|
||||
public string Description => Profile.getDescription();
|
||||
public bool Enabled { get; }
|
||||
|
||||
public bool Installed
|
||||
{
|
||||
@@ -36,12 +40,19 @@ namespace Ch.Cyberduck.Core.Refresh.ViewModels.Preferences.Pages
|
||||
set => this.RaiseAndSetIfChanged(ref installed, value);
|
||||
}
|
||||
|
||||
public string Name => Profile.getName();
|
||||
public string Name => ProfileDescription.getName();
|
||||
|
||||
public ReactiveCommand<Unit, Unit> OpenHelp { get; }
|
||||
|
||||
public Profile Profile { get; }
|
||||
|
||||
public ProfileDescription ProfileDescription { get; }
|
||||
|
||||
public string Thumbnail => ProfileDescription.getThumbnail();
|
||||
|
||||
private static bool IsDefaultProfile(ProfileDescription profile, Host host)
|
||||
{
|
||||
var protocol = host.getProtocol();
|
||||
return protocol.getProvider().Equals(profile.getProvider())
|
||||
&& protocol.getIdentifier().Equals(profile.getIdentifier());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -52,7 +52,6 @@ namespace Ch.Cyberduck.Core.Refresh.ViewModels.Preferences.Pages
|
||||
LoadProfiles.SelectMany(Observable.Return(false)).Subscribe(loadActive);
|
||||
|
||||
var profiles = LoadProfiles.Select(s => s.AsObservableChangeSet()).Switch()
|
||||
.Filter(x => x.getProfile().isPresent())
|
||||
.Filter(this.WhenAnyValue(v => v.FilterText)
|
||||
.Throttle(TimeSpan.FromMilliseconds(500))
|
||||
.DistinctUntilChanged()
|
||||
@@ -61,7 +60,7 @@ namespace Ch.Cyberduck.Core.Refresh.ViewModels.Preferences.Pages
|
||||
.AsObservableList();
|
||||
|
||||
profiles.Connect()
|
||||
.Sort(SortExpressionComparer<ProfileViewModel>.Ascending(x => x.Profile))
|
||||
.Sort(SortExpressionComparer<ProfileViewModel>.Ascending(x => x.Description))
|
||||
.ObserveOnDispatcher()
|
||||
.Bind(out this.profiles)
|
||||
.Subscribe();
|
||||
@@ -81,7 +80,7 @@ namespace Ch.Cyberduck.Core.Refresh.ViewModels.Preferences.Pages
|
||||
}
|
||||
else
|
||||
{
|
||||
protocols.unregister(p.Sender.Profile);
|
||||
protocols.unregister(p.Sender.ProfileDescription.getIdentifier(), p.Sender.ProfileDescription.getProvider());
|
||||
}
|
||||
profileListObserver.RaiseProfilesChanged();
|
||||
});
|
||||
@@ -107,7 +106,7 @@ namespace Ch.Cyberduck.Core.Refresh.ViewModels.Preferences.Pages
|
||||
{
|
||||
private readonly TaskCompletionSource<IEnumerable<ProfileDescription>> completionSource;
|
||||
|
||||
public InternalProfilesSynchronizeWorker(ProtocolFactory protocolFactory, TaskCompletionSource<IEnumerable<ProfileDescription>> completionSource) : base(protocolFactory, ProfilesFinder.Visitor.Prefetch)
|
||||
public InternalProfilesSynchronizeWorker(ProtocolFactory protocols, TaskCompletionSource<IEnumerable<ProfileDescription>> completionSource) : base(protocols, ProfilesFinder.Visitor.Noop)
|
||||
{
|
||||
this.completionSource = completionSource;
|
||||
}
|
||||
|
||||
@@ -19,23 +19,17 @@ package ch.cyberduck.core;
|
||||
* dkocher@cyberduck.ch
|
||||
*/
|
||||
|
||||
import ch.cyberduck.core.exception.AccessDeniedException;
|
||||
import ch.cyberduck.core.features.Location;
|
||||
import ch.cyberduck.core.local.TemporaryFileServiceFactory;
|
||||
import ch.cyberduck.core.preferences.PreferencesFactory;
|
||||
import ch.cyberduck.core.serializer.Deserializer;
|
||||
import ch.cyberduck.core.serializer.Serializer;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.text.StringSubstitutor;
|
||||
import org.apache.commons.text.lookup.StringLookupFactory;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@@ -135,14 +129,9 @@ public class Profile implements Protocol {
|
||||
public static final String DEPRECATED_KEY = "Deprecated";
|
||||
public static final String HELP_KEY = "Help";
|
||||
|
||||
private Local disk;
|
||||
private Local icon;
|
||||
|
||||
public Profile(final Protocol parent, final Deserializer<?> dict) {
|
||||
this.parent = parent;
|
||||
this.dict = dict;
|
||||
this.disk = this.write(this.value(DISK_KEY));
|
||||
this.icon = this.write(this.value(ICON_KEY));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -191,17 +180,7 @@ public class Profile implements Protocol {
|
||||
if(this.isBundled()) {
|
||||
return true;
|
||||
}
|
||||
final String protocol = parent.getIdentifier();
|
||||
final String vendor = this.value(VENDOR_KEY);
|
||||
if(StringUtils.isNotBlank(protocol) && StringUtils.isNotBlank(vendor)) {
|
||||
final String property = PreferencesFactory.get().getProperty(StringUtils.lowerCase(String.format("profiles.%s.%s.enabled", protocol, vendor)));
|
||||
if(null == property) {
|
||||
// Not previously configured. Assume enabled
|
||||
return true;
|
||||
}
|
||||
return Boolean.parseBoolean(property);
|
||||
}
|
||||
return false;
|
||||
return ProtocolFactory.get().isEnabled(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -376,29 +355,20 @@ public class Profile implements Protocol {
|
||||
|
||||
@Override
|
||||
public String disk() {
|
||||
if(null == disk) {
|
||||
final String v = this.value(DISK_KEY);
|
||||
if(StringUtils.isBlank(v)) {
|
||||
return parent.disk();
|
||||
}
|
||||
if(!disk.exists()) {
|
||||
this.disk = this.write(this.value(DISK_KEY));
|
||||
}
|
||||
// Temporary file
|
||||
return disk.getAbsolute();
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String icon() {
|
||||
if(null == icon) {
|
||||
if(null == disk) {
|
||||
return parent.icon();
|
||||
}
|
||||
return this.disk();
|
||||
final String v = this.value(ICON_KEY);
|
||||
if(StringUtils.isBlank(v)) {
|
||||
return parent.icon();
|
||||
}
|
||||
if(!icon.exists()) {
|
||||
this.icon = this.write(this.value(ICON_KEY));
|
||||
}
|
||||
// Temporary file
|
||||
return icon.getAbsolute();
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -406,30 +376,6 @@ public class Profile implements Protocol {
|
||||
return parent.favicon();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write temporary file with data
|
||||
*
|
||||
* @param icon Base64 encoded image information
|
||||
* @return Path to file
|
||||
*/
|
||||
private Local write(final String icon) {
|
||||
if(StringUtils.isBlank(icon)) {
|
||||
return null;
|
||||
}
|
||||
final byte[] favicon = Base64.decodeBase64(icon);
|
||||
final Local file = TemporaryFileServiceFactory.get().create(new AlphanumericRandomStringService().random());
|
||||
try {
|
||||
try (final OutputStream out = file.getOutputStream(false)) {
|
||||
IOUtils.write(favicon, out);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
catch(IOException | AccessDeniedException e) {
|
||||
log.error("Error writing temporary file", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(final Credentials credentials, final LoginOptions options) {
|
||||
return parent.validate(credentials, options);
|
||||
@@ -806,7 +752,6 @@ public class Profile implements Protocol {
|
||||
sb.append("parent=").append(parent);
|
||||
sb.append(", vendor=").append(this.value(VENDOR_KEY));
|
||||
sb.append(", description=").append(this.value(DESCRIPTION_KEY));
|
||||
sb.append(", image=").append(disk);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -222,6 +222,8 @@ public interface Protocol extends FeatureFactory, Comparable<Protocol>, Serializ
|
||||
String getRegion();
|
||||
|
||||
/**
|
||||
* Either a filename or Base64 encoded image data
|
||||
*
|
||||
* @return A mounted disk icon to display
|
||||
*/
|
||||
String disk();
|
||||
|
||||
@@ -167,15 +167,35 @@ public final class ProtocolFactory {
|
||||
* @param profile Connection profile
|
||||
*/
|
||||
public void unregister(final Profile profile) {
|
||||
if(registered.remove(profile)) {
|
||||
this.unregister(profile.getIdentifier(), profile.getProvider());
|
||||
}
|
||||
|
||||
public void unregister(final String identifier, final String provider) {
|
||||
if(registered.removeIf(protocol -> protocol.getIdentifier().equals(identifier) && protocol.getProvider().equals(provider))) {
|
||||
preferences.setProperty(StringUtils.lowerCase(String.format("profiles.%s.%s.enabled",
|
||||
profile.getIdentifier(), profile.getProvider())), false);
|
||||
identifier, provider)), false);
|
||||
}
|
||||
else {
|
||||
log.warn("Failure removing protocol {}", profile);
|
||||
log.warn("Failure removing protocol {}:{}", identifier, provider);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled(final Profile profile) {
|
||||
return this.isEnabled(profile.getIdentifier(), profile.getProvider());
|
||||
}
|
||||
|
||||
public boolean isEnabled(String protocol, String vendor) {
|
||||
if(StringUtils.isNotBlank(protocol) && StringUtils.isNotBlank(vendor)) {
|
||||
final String property = preferences.getProperty(StringUtils.lowerCase(String.format("profiles.%s.%s.enabled", protocol, vendor)));
|
||||
if(null == property) {
|
||||
// Not previously configured. Assume enabled
|
||||
return true;
|
||||
}
|
||||
return Boolean.parseBoolean(property);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return List of enabled protocols
|
||||
*/
|
||||
|
||||
@@ -62,7 +62,9 @@ public class LocalProfilesFinder implements ProfilesFinder {
|
||||
if(directory.exists()) {
|
||||
log.debug("Load profiles from {}", directory);
|
||||
return directory.list().filter(new ProfileFilter()).toList().stream()
|
||||
.map(file -> visitor.visit(new LocalProfileDescription(protocols, parent, file))).collect(Collectors.toSet());
|
||||
.map(file -> visitor.visit(new LocalProfileDescription(protocols, parent, file)))
|
||||
.filter(d -> d.getProfile().isPresent())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@@ -53,12 +53,12 @@ public class ProfileDescription {
|
||||
|
||||
/**
|
||||
* @param protocols Registered protocols
|
||||
* @param parent Filter to apply for parent protocol reference in registered protocols
|
||||
* @param filter Filter to apply for parent protocol reference in registered protocols
|
||||
* @param checksum Checksum of connection profile
|
||||
* @param local File on disk
|
||||
*/
|
||||
public ProfileDescription(final ProtocolFactory protocols, final Predicate<Protocol> parent, final Checksum checksum, final Local local) {
|
||||
this(protocols, parent, new LazyInitializer<Checksum>() {
|
||||
public ProfileDescription(final ProtocolFactory protocols, final Predicate<Protocol> filter, final Checksum checksum, final Local local) {
|
||||
this(protocols, filter, new LazyInitializer<Checksum>() {
|
||||
@Override
|
||||
protected Checksum initialize() {
|
||||
return checksum;
|
||||
@@ -71,7 +71,7 @@ public class ProfileDescription {
|
||||
});
|
||||
}
|
||||
|
||||
public ProfileDescription(final ProtocolFactory protocols, final Predicate<Protocol> parent,
|
||||
public ProfileDescription(final ProtocolFactory protocols, final Predicate<Protocol> filter,
|
||||
final LazyInitializer<Checksum> checksum, final LazyInitializer<Local> local) {
|
||||
this.checksum = checksum;
|
||||
this.local = local;
|
||||
@@ -79,7 +79,7 @@ public class ProfileDescription {
|
||||
@Override
|
||||
protected Profile initialize() throws ConcurrentException {
|
||||
try {
|
||||
return new ProfilePlistReader(protocols, parent).read(local.get());
|
||||
return new ProfilePlistReader(protocols, filter).read(local.get());
|
||||
}
|
||||
catch(AccessDeniedException e) {
|
||||
log.warn("Failure {} reading profile {}", e, local.get());
|
||||
@@ -143,6 +143,36 @@ public class ProfileDescription {
|
||||
}
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
final Optional<Profile> profile = this.getProfile();
|
||||
return profile.map(Profile::getIdentifier).orElse(null);
|
||||
}
|
||||
|
||||
public String getProvider() {
|
||||
final Optional<Profile> profile = this.getProfile();
|
||||
return profile.map(Profile::getProvider).orElse(null);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
final Optional<Profile> profile = this.getProfile();
|
||||
return profile.map(Profile::getName).orElse(null);
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
final Optional<Profile> profile = this.getProfile();
|
||||
return profile.map(Profile::getDescription).orElse(null);
|
||||
}
|
||||
|
||||
public String getHelp() {
|
||||
final Optional<Profile> profile = this.getProfile();
|
||||
return profile.map(Profile::getHelp).orElse(null);
|
||||
}
|
||||
|
||||
public String getThumbnail() {
|
||||
final Optional<Profile> profile = this.getProfile();
|
||||
return profile.map(Profile::disk).orElse(null);
|
||||
}
|
||||
|
||||
public boolean isLatest() {
|
||||
return true;
|
||||
}
|
||||
@@ -151,6 +181,16 @@ public class ProfileDescription {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
final Optional<Profile> profile = this.getProfile();
|
||||
return profile.map(Profile::isEnabled).orElse(false);
|
||||
}
|
||||
|
||||
public boolean isBundled() {
|
||||
final Optional<Profile> profile = this.getProfile();
|
||||
return profile.map(Profile::isBundled).orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("ProfileDescription{");
|
||||
|
||||
+6
-6
@@ -35,7 +35,7 @@ public class ProtocolFactoryProfilesSynchronizer implements ProfilesSynchronizer
|
||||
|
||||
private final ProtocolFactory registry;
|
||||
private final LocalProfilesFinder local;
|
||||
private final RemoteProfilesFinder remote;
|
||||
private final RemoteIndexProfilesFinder remote;
|
||||
|
||||
public ProtocolFactoryProfilesSynchronizer(final Session<?> session) {
|
||||
this(ProtocolFactory.get(), session, LocalFactory.get(SupportDirectoryFinderFactory.get().find(),
|
||||
@@ -50,22 +50,22 @@ public class ProtocolFactoryProfilesSynchronizer implements ProfilesSynchronizer
|
||||
|
||||
public ProtocolFactoryProfilesSynchronizer(final ProtocolFactory registry, final LocalProfilesFinder local, final Session<?> session) {
|
||||
this(registry, local,
|
||||
// Find all profiles from repository
|
||||
new RemoteProfilesFinder(registry, session));
|
||||
// Find index of all profiles from repository
|
||||
new RemoteIndexProfilesFinder(registry, session));
|
||||
}
|
||||
|
||||
public ProtocolFactoryProfilesSynchronizer(final ProtocolFactory registry, final RemoteProfilesFinder remote) {
|
||||
public ProtocolFactoryProfilesSynchronizer(final ProtocolFactory registry, final RemoteIndexProfilesFinder 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) {
|
||||
public ProtocolFactoryProfilesSynchronizer(final ProtocolFactory registry, final Local directory, final RemoteIndexProfilesFinder 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) {
|
||||
public ProtocolFactoryProfilesSynchronizer(final ProtocolFactory registry, final LocalProfilesFinder local, final RemoteIndexProfilesFinder remote) {
|
||||
this.registry = registry;
|
||||
this.local = local;
|
||||
this.remote = remote;
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
package ch.cyberduck.core.profiles;
|
||||
|
||||
/*
|
||||
* Copyright (c) 2002-2026 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.ConnectionCallback;
|
||||
import ch.cyberduck.core.DefaultIOExceptionMappingService;
|
||||
import ch.cyberduck.core.DefaultPathAttributes;
|
||||
import ch.cyberduck.core.Local;
|
||||
import ch.cyberduck.core.LocalFactory;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.Protocol;
|
||||
import ch.cyberduck.core.ProtocolFactory;
|
||||
import ch.cyberduck.core.Session;
|
||||
import ch.cyberduck.core.exception.BackgroundException;
|
||||
import ch.cyberduck.core.features.Read;
|
||||
import ch.cyberduck.core.io.Checksum;
|
||||
import ch.cyberduck.core.preferences.TemporaryApplicationResourcesFinder;
|
||||
import ch.cyberduck.core.shared.DefaultPathHomeFeature;
|
||||
import ch.cyberduck.core.shared.DelegatingHomeFeature;
|
||||
import ch.cyberduck.core.transfer.TransferStatus;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.concurrent.ConcurrentException;
|
||||
import org.apache.commons.lang3.concurrent.LazyInitializer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
public class RemoteIndexProfilesFinder implements ProfilesFinder {
|
||||
private static final Logger log = LogManager.getLogger(RemoteIndexProfilesFinder.class);
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
private final ProtocolFactory protocols;
|
||||
private final Session<?> session;
|
||||
private final Local temporary = LocalFactory.get(new TemporaryApplicationResourcesFinder().find(), "profiles");
|
||||
|
||||
public RemoteIndexProfilesFinder(final Session<?> session) {
|
||||
this(ProtocolFactory.get(), session);
|
||||
}
|
||||
|
||||
public RemoteIndexProfilesFinder(final ProtocolFactory protocols, final Session<?> session) {
|
||||
this.protocols = protocols;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* {
|
||||
* "filename": "AWS PrivateLink for Amazon S3 (VPC endpoint).cyberduckprofile",
|
||||
* "protocol": "s3",
|
||||
* "vendor": "s3-privatelink",
|
||||
* "versions": [
|
||||
* {
|
||||
* "checksum": "255b117667ac1f0fd1ecfe25ae99440d",
|
||||
* "modified": "2025-12-02T09:09:07Z",
|
||||
* "version_id": "sMABDfcz3m0pwQeU85uI2.pW.JTGGT4T",
|
||||
* "latest": true
|
||||
* },
|
||||
* {
|
||||
* "checksum": "9a6fcc93e68a669952da5e47719af3c9",
|
||||
* "modified": "2022-09-15T12:55:09Z",
|
||||
* "version_id": ".iXSL9g6EWEIthW6gENMGgSileSI0XG8",
|
||||
* "latest": false
|
||||
* },
|
||||
* {
|
||||
* "checksum": "3fbf79c16d3187f135a5fbf32e0cbaf7",
|
||||
* "modified": "2021-11-30T11:54:30Z",
|
||||
* "version_id": "S71y2mc.dq8BXtF11g85Z9ZLvulhZDJk",
|
||||
* "latest": false
|
||||
* },
|
||||
* {
|
||||
* "checksum": "3fbf79c16d3187f135a5fbf32e0cbaf7",
|
||||
* "modified": "2021-10-27T06:00:55Z",
|
||||
* "version_id": "E.cXq21hr0xLkqtNMUZjBuZaj2HfA.n9",
|
||||
* "latest": false
|
||||
* },
|
||||
* {
|
||||
* "checksum": "7188dafb0c649fe123b70cbe5b7bfa40",
|
||||
* "modified": "2021-10-27T06:00:49Z",
|
||||
* "version_id": "nosIgW.rj3jrGn9jwdf_S.8fnKMO2ZW1",
|
||||
* "latest": false
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
@Override
|
||||
public Set<ProfileDescription> find(final Visitor visitor) throws BackgroundException {
|
||||
log.info("Fetch profiles from {}", session.getHost());
|
||||
final Set<ProfileDescription> profiles = new HashSet<>();
|
||||
final Path directory = new DelegatingHomeFeature(new DefaultPathHomeFeature(session.getHost())).find();
|
||||
try(final InputStream in = session.getFeature(Read.class).read(new Path(directory, "index.json", EnumSet.of(Path.Type.file)),
|
||||
new TransferStatus().setLength(TransferStatus.UNKNOWN_LENGTH), ConnectionCallback.noop)) {
|
||||
final ProfileMetadataList list = mapper.readValue(in, ProfileMetadataList.class);
|
||||
for(ProfileMetadata metadata : list.profiles) {
|
||||
for(ProfileMetadataVersion version : metadata.versions) {
|
||||
profiles.add(visitor.visit(new RemoteIndexProfileDescription(temporary, protocols, version, metadata, directory)));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(IOException e) {
|
||||
throw new DefaultIOExceptionMappingService().map(e);
|
||||
}
|
||||
return profiles;
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
private static class ProfileMetadataList {
|
||||
@JsonProperty("profiles")
|
||||
private ProfileMetadata[] profiles;
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
private static class ProfileMetadata {
|
||||
@JsonProperty("filename")
|
||||
private String filename;
|
||||
@JsonProperty("protocol")
|
||||
private String protocol;
|
||||
@JsonProperty("vendor")
|
||||
private String vendor;
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
@JsonProperty("help")
|
||||
private String help;
|
||||
@JsonProperty("thumbnail")
|
||||
private String thumbnail;
|
||||
@JsonProperty("versions")
|
||||
private ProfileMetadataVersion[] versions;
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
private static class ProfileMetadataVersion {
|
||||
@JsonProperty("checksum")
|
||||
private String checksum;
|
||||
@JsonProperty("modified")
|
||||
private String modified;
|
||||
@JsonProperty("version_id")
|
||||
private String version_id;
|
||||
@JsonProperty("latest")
|
||||
private Boolean latest;
|
||||
}
|
||||
|
||||
private final class RemoteIndexProfileDescription extends ProfileDescription {
|
||||
private final ProtocolFactory factory;
|
||||
private final ProfileMetadataVersion version;
|
||||
private final ProfileMetadata metadata;
|
||||
|
||||
public RemoteIndexProfileDescription(final Local temporary, final ProtocolFactory factory, final ProfileMetadataVersion version, final ProfileMetadata metadata, final Path directory) {
|
||||
super(factory, protocol -> true, new LazyInitializer<Checksum>() {
|
||||
@Override
|
||||
protected Checksum initialize() {
|
||||
return Checksum.parse(version.checksum);
|
||||
}
|
||||
}, new LazyInitializer<Local>() {
|
||||
@Override
|
||||
protected Local initialize() throws ConcurrentException {
|
||||
try {
|
||||
temporary.mkdir();
|
||||
final Local local = LocalFactory.get(temporary, metadata.filename);
|
||||
final Path file = new Path(directory, metadata.filename, EnumSet.of(Path.Type.file));
|
||||
final Read read = session.getFeature(Read.class);
|
||||
RemoteIndexProfilesFinder.log.info("Download profile {}", file);
|
||||
// Read latest version
|
||||
try(InputStream in = read.read(file.withAttributes(new DefaultPathAttributes(file.attributes())
|
||||
.setVersionId(version.version_id)), new TransferStatus().setLength(TransferStatus.UNKNOWN_LENGTH), ConnectionCallback.noop); OutputStream out = local.getOutputStream(false)) {
|
||||
IOUtils.copy(in, out);
|
||||
}
|
||||
return local;
|
||||
}
|
||||
catch(BackgroundException | IOException e) {
|
||||
throw new ConcurrentException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.factory = factory;
|
||||
this.version = version;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLatest() {
|
||||
return Boolean.TRUE.equals(version.latest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return factory.isEnabled(metadata.protocol, metadata.vendor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBundled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return metadata.protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvider() {
|
||||
return metadata.vendor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
final Protocol protocol = factory.forName(metadata.protocol);
|
||||
if(null == protocol) {
|
||||
return null;
|
||||
}
|
||||
return protocol.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
if(null == metadata.description) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
return metadata.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelp() {
|
||||
return metadata.help;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getThumbnail() {
|
||||
if(null == metadata.thumbnail) {
|
||||
final Protocol protocol = factory.forName(metadata.protocol);
|
||||
if(null == protocol) {
|
||||
return null;
|
||||
}
|
||||
return protocol.disk();
|
||||
}
|
||||
return metadata.thumbnail;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import ch.cyberduck.core.io.Checksum;
|
||||
import org.apache.commons.lang3.concurrent.LazyInitializer;
|
||||
|
||||
public final class RemoteProfileDescription extends ProfileDescription {
|
||||
|
||||
private final Path file;
|
||||
|
||||
/**
|
||||
@@ -47,7 +48,7 @@ public final class RemoteProfileDescription extends ProfileDescription {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("PathProfileDescription{");
|
||||
final StringBuilder sb = new StringBuilder("RemoteProfileDescription{");
|
||||
sb.append("file=").append(file);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
|
||||
@@ -24,7 +24,6 @@ import ch.cyberduck.core.ListService;
|
||||
import ch.cyberduck.core.Local;
|
||||
import ch.cyberduck.core.LocalFactory;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.ProgressListener;
|
||||
import ch.cyberduck.core.ProtocolFactory;
|
||||
import ch.cyberduck.core.Session;
|
||||
import ch.cyberduck.core.exception.BackgroundException;
|
||||
@@ -32,10 +31,7 @@ import ch.cyberduck.core.features.Read;
|
||||
import ch.cyberduck.core.preferences.TemporaryApplicationResourcesFinder;
|
||||
import ch.cyberduck.core.shared.DefaultPathHomeFeature;
|
||||
import ch.cyberduck.core.shared.DelegatingHomeFeature;
|
||||
import ch.cyberduck.core.transfer.TransferPathFilter;
|
||||
import ch.cyberduck.core.transfer.TransferStatus;
|
||||
import ch.cyberduck.core.transfer.download.CompareFilter;
|
||||
import ch.cyberduck.core.transfer.symlink.DisabledDownloadSymlinkResolver;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.concurrent.ConcurrentException;
|
||||
@@ -55,36 +51,26 @@ public class RemoteProfilesFinder implements ProfilesFinder {
|
||||
|
||||
private final ProtocolFactory protocols;
|
||||
private final Session<?> session;
|
||||
private final TransferPathFilter comparison;
|
||||
private final Filter<Path> filter;
|
||||
private final Local temporary;
|
||||
private final Local temporary = LocalFactory.get(new TemporaryApplicationResourcesFinder().find(), "profiles");
|
||||
|
||||
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), new ProfileFilter());
|
||||
this(protocols, session, new ProfileFilter());
|
||||
}
|
||||
|
||||
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) {
|
||||
public RemoteProfilesFinder(final ProtocolFactory protocols, final Session<?> session, final Filter<Path> filter) {
|
||||
this.protocols = protocols;
|
||||
this.session = session;
|
||||
this.comparison = comparison;
|
||||
this.filter = filter;
|
||||
this.temporary = LocalFactory.get(new TemporaryApplicationResourcesFinder().find(), "profiles");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ProfileDescription> find(final Visitor visitor) throws BackgroundException {
|
||||
log.info("Fetch profiles from {}", session.getHost());
|
||||
temporary.mkdir();
|
||||
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,
|
||||
@@ -92,18 +78,16 @@ public class RemoteProfilesFinder implements ProfilesFinder {
|
||||
@Override
|
||||
protected Local initialize() throws ConcurrentException {
|
||||
try {
|
||||
temporary.mkdir();
|
||||
final Local local = LocalFactory.get(temporary, file.getName());
|
||||
if(comparison.accept(file, local, new TransferStatus().setExists(true), ProgressListener.noop)) {
|
||||
final Read read = session.getFeature(Read.class);
|
||||
log.info("Download profile {}", file);
|
||||
// Read latest version
|
||||
try(InputStream in = read.read(file.withAttributes(new DefaultPathAttributes(file.attributes())
|
||||
// Read latest version
|
||||
.setVersionId(null)), new TransferStatus().setLength(TransferStatus.UNKNOWN_LENGTH), ConnectionCallback.noop); OutputStream out = local.getOutputStream(false)) {
|
||||
IOUtils.copy(in, out);
|
||||
}
|
||||
final Read read = session.getFeature(Read.class);
|
||||
log.info("Download profile {}", file);
|
||||
// Read latest version
|
||||
try(InputStream in = read.read(file.withAttributes(new DefaultPathAttributes(file.attributes())
|
||||
// Read latest version
|
||||
.setVersionId(null)), new TransferStatus().setLength(TransferStatus.UNKNOWN_LENGTH), ConnectionCallback.noop); OutputStream out = local.getOutputStream(false)) {
|
||||
IOUtils.copy(in, out);
|
||||
}
|
||||
// Skip download if previously cached
|
||||
return local;
|
||||
}
|
||||
catch(BackgroundException | IOException e) {
|
||||
|
||||
@@ -15,8 +15,6 @@ package ch.cyberduck.core.profiles;
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
import ch.cyberduck.core.Protocol;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
@@ -31,15 +29,10 @@ public class SearchProfilePredicate implements Predicate<ProfileDescription> {
|
||||
|
||||
@Override
|
||||
public boolean test(final ProfileDescription entry) {
|
||||
if(!entry.getProfile().isPresent()) {
|
||||
return false;
|
||||
}
|
||||
final Protocol protocol = entry.getProfile().get();
|
||||
for(String i : StringUtils.split(input, StringUtils.SPACE)) {
|
||||
if(StringUtils.containsIgnoreCase(protocol.getName(), i)
|
||||
|| StringUtils.containsIgnoreCase(protocol.getDescription(), i)
|
||||
|| StringUtils.containsIgnoreCase(protocol.getDefaultHostname(), i)
|
||||
|| StringUtils.containsIgnoreCase(protocol.getProvider(), i)) {
|
||||
if(StringUtils.containsIgnoreCase(entry.getName(), i)
|
||||
|| StringUtils.containsIgnoreCase(entry.getDescription(), i)
|
||||
|| StringUtils.containsIgnoreCase(entry.getProvider(), i)) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -33,7 +33,7 @@ public interface IconCache<I> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name Icon filename with extension
|
||||
* @param name Icon filename with extension or Base64 encoded image data
|
||||
* @param size Requested size
|
||||
* @return Cached image
|
||||
*/
|
||||
|
||||
@@ -16,10 +16,8 @@ package ch.cyberduck.core.profiles;
|
||||
*/
|
||||
|
||||
import ch.cyberduck.core.Local;
|
||||
import ch.cyberduck.core.Profile;
|
||||
import ch.cyberduck.core.ProtocolFactory;
|
||||
import ch.cyberduck.core.TestProtocol;
|
||||
import ch.cyberduck.core.serializer.impl.dd.ProfilePlistReader;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -32,7 +30,7 @@ public class LocalProfilesFinderTest {
|
||||
|
||||
@Test
|
||||
public void find() throws Exception {
|
||||
final ProfilePlistReader reader = new ProfilePlistReader(new ProtocolFactory(Collections.singleton(new TestProtocol() {
|
||||
final ProtocolFactory protocols = new ProtocolFactory(Collections.singleton(new TestProtocol() {
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.s3;
|
||||
@@ -42,11 +40,8 @@ public class LocalProfilesFinderTest {
|
||||
public boolean isEnabled() {
|
||||
return false;
|
||||
}
|
||||
})));
|
||||
final Profile profile = reader.read(
|
||||
new Local("src/test/resources/Test S3 (HTTP).cyberduckprofile")
|
||||
);
|
||||
final LocalProfilesFinder finder = new LocalProfilesFinder(ProtocolFactory.get(), new Local("src/test/resources/"));
|
||||
}));
|
||||
final LocalProfilesFinder finder = new LocalProfilesFinder(protocols, new Local("src/test/resources/"));
|
||||
final Set<ProfileDescription> stream = finder.find();
|
||||
assertFalse(stream.isEmpty());
|
||||
}
|
||||
|
||||
+40
-38
@@ -33,10 +33,8 @@ import ch.cyberduck.binding.foundation.NSObject;
|
||||
import ch.cyberduck.binding.foundation.NSString;
|
||||
import ch.cyberduck.core.BookmarkCollection;
|
||||
import ch.cyberduck.core.Local;
|
||||
import ch.cyberduck.core.Profile;
|
||||
import ch.cyberduck.core.Protocol;
|
||||
import ch.cyberduck.core.ProtocolFactory;
|
||||
import ch.cyberduck.core.ProviderHelpServiceFactory;
|
||||
import ch.cyberduck.core.exception.BackgroundException;
|
||||
import ch.cyberduck.core.local.BrowserLauncherFactory;
|
||||
import ch.cyberduck.core.profiles.LocalProfileDescription;
|
||||
@@ -46,6 +44,7 @@ import ch.cyberduck.core.profiles.ProfilesSynchronizeWorker;
|
||||
import ch.cyberduck.core.profiles.ProfilesWorkerBackgroundAction;
|
||||
import ch.cyberduck.core.profiles.SearchProfilePredicate;
|
||||
import ch.cyberduck.core.resources.IconCacheFactory;
|
||||
import ch.cyberduck.core.threading.AbstractBackgroundAction;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -58,7 +57,7 @@ import org.rococoa.cocoa.foundation.NSInteger;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -75,8 +74,8 @@ public class ProfilesPreferencesController extends BundleController {
|
||||
/**
|
||||
* Synchronized ist of available profiles
|
||||
*/
|
||||
private final Map<ProfileDescription, Profile> repository
|
||||
= Collections.synchronizedMap(new LinkedHashMap<>());
|
||||
private final Set<ProfileDescription> repository
|
||||
= Collections.synchronizedSet(new LinkedHashSet<>());
|
||||
|
||||
@Delegate
|
||||
private ProfilesTableDataSource profilesTableDataSource;
|
||||
@@ -133,12 +132,12 @@ public class ProfilesPreferencesController extends BundleController {
|
||||
private void reload() {
|
||||
final String input = searchField.stringValue();
|
||||
if(StringUtils.isBlank(input)) {
|
||||
this.profilesTableDataSource.withSource(toSorted(repository.keySet()));
|
||||
this.profilesTableDataSource.withSource(toSorted(repository));
|
||||
}
|
||||
else {
|
||||
// Setup search filter
|
||||
this.profilesTableDataSource.withSource(toSorted(
|
||||
repository.keySet().stream().filter(new SearchProfilePredicate(input)).collect(Collectors.toSet())));
|
||||
repository.stream().filter(new SearchProfilePredicate(input)).collect(Collectors.toSet())));
|
||||
}
|
||||
// Reload with current cache
|
||||
this.profilesTableView.reloadData();
|
||||
@@ -146,7 +145,7 @@ public class ProfilesPreferencesController extends BundleController {
|
||||
|
||||
public void setProfilesTableView(final NSOutlineView profilesTableView) {
|
||||
this.profilesTableView = profilesTableView;
|
||||
this.profilesTableDataSource = new ProfilesTableDataSource().withSource(toSorted(repository.keySet()));
|
||||
this.profilesTableDataSource = new ProfilesTableDataSource().withSource(toSorted(repository));
|
||||
this.profilesTableView.setDataSource(profilesTableDataSource.id());
|
||||
this.profilesTableDelegate = new ProfilesTableDelegate(profilesTableView.tableColumnWithIdentifier("Default"));
|
||||
this.profilesTableView.setDelegate(profilesTableDelegate.id());
|
||||
@@ -166,14 +165,10 @@ public class ProfilesPreferencesController extends BundleController {
|
||||
try {
|
||||
progressIndicator.startAnimation(null);
|
||||
this.background(new ProfilesWorkerBackgroundAction(controller,
|
||||
new ProfilesSynchronizeWorker(protocols, ProfilesFinder.Visitor.Prefetch) {
|
||||
new ProfilesSynchronizeWorker(protocols, ProfilesFinder.Visitor.Noop) {
|
||||
@Override
|
||||
public void cleanup(final Set<ProfileDescription> set) {
|
||||
for(ProfileDescription description : set) {
|
||||
if(description.getProfile().isPresent()) {
|
||||
repository.put(description, description.getProfile().get());
|
||||
}
|
||||
}
|
||||
repository.addAll(set);
|
||||
reload();
|
||||
progressIndicator.stopAnimation(null);
|
||||
}
|
||||
@@ -194,7 +189,7 @@ public class ProfilesPreferencesController extends BundleController {
|
||||
}
|
||||
|
||||
private ProfileDescription fromChecksum(final NSObject hash) {
|
||||
final Optional<ProfileDescription> found = repository.keySet().stream()
|
||||
final Optional<ProfileDescription> found = repository.stream()
|
||||
.filter(description -> description.getChecksum().hash.equals(hash.toString())).findFirst();
|
||||
return found.orElse(null);
|
||||
}
|
||||
@@ -225,7 +220,7 @@ public class ProfilesPreferencesController extends BundleController {
|
||||
if(null == description) {
|
||||
return null;
|
||||
}
|
||||
return repository.get(description).getDescription();
|
||||
return description.getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -284,7 +279,7 @@ public class ProfilesPreferencesController extends BundleController {
|
||||
if(null == description) {
|
||||
return false;
|
||||
}
|
||||
return !repository.get(description).isBundled();
|
||||
return true;
|
||||
}
|
||||
|
||||
public NSView outlineView_viewForTableColumn_item(final NSOutlineView outlineView, final NSTableColumn tableColumn, final NSObject item) {
|
||||
@@ -292,11 +287,10 @@ public class ProfilesPreferencesController extends BundleController {
|
||||
if(null == description) {
|
||||
return null;
|
||||
}
|
||||
final Profile profile = repository.get(description);
|
||||
if(controllers.containsKey(description)) {
|
||||
return controllers.get(description).getCellView();
|
||||
}
|
||||
final ProfileTableViewController controller = new ProfileTableViewController(description, profile);
|
||||
final ProfileTableViewController controller = new ProfileTableViewController(description);
|
||||
controllers.put(description, controller);
|
||||
return controller.getCellView();
|
||||
}
|
||||
@@ -380,7 +374,6 @@ public class ProfilesPreferencesController extends BundleController {
|
||||
|
||||
public final class ProfileTableViewController extends BundleController {
|
||||
private final ProfileDescription description;
|
||||
private final Profile profile;
|
||||
|
||||
@Outlet
|
||||
private NSTableCellView cellView;
|
||||
@@ -393,9 +386,8 @@ public class ProfilesPreferencesController extends BundleController {
|
||||
@Outlet
|
||||
private NSButton helpButton;
|
||||
|
||||
public ProfileTableViewController(final ProfileDescription description, final Profile profile) {
|
||||
public ProfileTableViewController(final ProfileDescription description) {
|
||||
this.description = description;
|
||||
this.profile = profile;
|
||||
this.loadBundle();
|
||||
}
|
||||
|
||||
@@ -409,14 +401,14 @@ public class ProfilesPreferencesController extends BundleController {
|
||||
|
||||
public void setImageView(final NSImageView imageView) {
|
||||
this.imageView = imageView;
|
||||
this.imageView.setImage(IconCacheFactory.<NSImage>get().iconNamed(profile.icon(), 32));
|
||||
this.imageView.setImage(IconCacheFactory.<NSImage>get().iconNamed(description.getThumbnail(), 32));
|
||||
}
|
||||
|
||||
public void setTextField(final NSTextField textField) {
|
||||
this.textField = textField;
|
||||
final NSMutableAttributedString description = NSMutableAttributedString.create(profile.getDescription(), PRIMARY_FONT_ATTRIBUTES);
|
||||
description.appendAttributedString(NSMutableAttributedString.create(String.format("\n%s", profile.getName()), SECONDARY_FONT_ATTRIBUTES));
|
||||
this.textField.setAttributedStringValue(description);
|
||||
final NSMutableAttributedString s = NSMutableAttributedString.create(description.getDescription(), PRIMARY_FONT_ATTRIBUTES);
|
||||
s.appendAttributedString(NSMutableAttributedString.create(String.format("\n%s", description.getName()), SECONDARY_FONT_ATTRIBUTES));
|
||||
this.textField.setAttributedStringValue(s);
|
||||
}
|
||||
|
||||
public void setCheckbox(final NSButton checkbox) {
|
||||
@@ -424,38 +416,49 @@ public class ProfilesPreferencesController extends BundleController {
|
||||
this.checkbox.setState(NSCell.NSOnState);
|
||||
this.checkbox.setTarget(this.id());
|
||||
this.checkbox.setAction(Foundation.selector("profileCheckboxClicked:"));
|
||||
this.checkbox.setEnabled(!profile.isBundled());
|
||||
if(BookmarkCollection.defaultCollection().stream().filter(host -> host.getProtocol().equals(profile)).findAny().isPresent()) {
|
||||
this.checkbox.setEnabled(!description.isBundled());
|
||||
if(BookmarkCollection.defaultCollection().stream().anyMatch(host -> host.getProtocol().getProvider().equals(description.getProvider())
|
||||
&& host.getProtocol().getIdentifier().equals(description.getIdentifier()))) {
|
||||
this.checkbox.setEnabled(false);
|
||||
}
|
||||
this.checkbox.setState(description.isInstalled() && profile.isEnabled() ? NSCell.NSOnState : NSCell.NSOffState);
|
||||
this.checkbox.setState(description.isInstalled() && description.isEnabled() ? NSCell.NSOnState : NSCell.NSOffState);
|
||||
}
|
||||
|
||||
public void setHelpButton(final NSButton helpButton) {
|
||||
this.helpButton = helpButton;
|
||||
this.helpButton.setTarget(this.id());
|
||||
this.helpButton.setAction(Foundation.selector("helpButtonClicked:"));
|
||||
this.helpButton.setEnabled(StringUtils.isNotBlank(description.getHelp()));
|
||||
}
|
||||
|
||||
@Action
|
||||
public void profileCheckboxClicked(final NSButton sender) {
|
||||
boolean enabled = sender.state() == NSCell.NSOnState;
|
||||
if(enabled) {
|
||||
final Optional<Local> file = description.getFile();
|
||||
// Update with last version from repository
|
||||
file.ifPresent(local -> repository.put(new LocalProfileDescription(protocols, ProtocolFactory.BUNDLED_PROFILE_PREDICATE,
|
||||
protocols.register(local)), profile));
|
||||
this.background(new AbstractBackgroundAction<Void>() {
|
||||
@Override
|
||||
public Void run() {
|
||||
// Download profile
|
||||
final Optional<Local> file = description.getFile();
|
||||
// Update with last version from repository
|
||||
file.ifPresent(local -> {
|
||||
repository.remove(description);
|
||||
repository.add(new LocalProfileDescription(protocols, ProtocolFactory.BUNDLED_PROFILE_PREDICATE,
|
||||
protocols.register(local)));
|
||||
});
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
final Optional<Profile> profile = description.getProfile();
|
||||
// Uninstall profile
|
||||
profile.ifPresent(protocols::unregister);
|
||||
protocols.unregister(description.getIdentifier(), description.getProvider());
|
||||
}
|
||||
}
|
||||
|
||||
@Action
|
||||
public void helpButtonClicked(final NSButton sender) {
|
||||
BrowserLauncherFactory.get().open(ProviderHelpServiceFactory.get().help(profile));
|
||||
BrowserLauncherFactory.get().open(description.getHelp());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -472,8 +475,7 @@ public class ProfilesPreferencesController extends BundleController {
|
||||
*/
|
||||
private static List<ProfileDescription> toSorted(final Set<ProfileDescription> profiles) {
|
||||
return profiles.stream()
|
||||
.filter(description -> description.getProfile().isPresent())
|
||||
.sorted(Comparator.comparing(o -> o.getProfile().get()))
|
||||
.sorted(Comparator.comparing(ProfileDescription::getDescription))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,55 +47,7 @@ import static org.junit.Assert.*;
|
||||
public class ProfilesSynchronizeWorkerTest {
|
||||
|
||||
@Test
|
||||
public void testRunCloudfrontEndpoint() throws Exception {
|
||||
// Registry in temporary folder
|
||||
final ProtocolFactory protocols = new ProtocolFactory(new HashSet<>(Collections.singletonList(new S3Protocol())));
|
||||
final Host host = new HostParser(protocols, new S3Protocol()).get("s3://djynunjb246r8.cloudfront.net").setCredentials(
|
||||
new Credentials(PreferencesFactory.get().getProperty("connection.login.anon.name")));
|
||||
final Session session = new S3Session(host, new DisabledX509TrustManager(), new DefaultX509KeyManager());
|
||||
session.open(new DisabledProxyFinder(), HostKeyCallback.noop, LoginCallback.noop, CancelCallback.noop);
|
||||
// Local directory with oudated profile
|
||||
final Local conflictprofile = LocalFactory.get(this.getClass().getResource("/test-conflict.cyberduckprofile").getPath());
|
||||
final Local localonlyprofile = LocalFactory.get(this.getClass().getResource("/test-localonly.cyberduckprofile").getPath());
|
||||
// Previous checksum b9afd8d6da91e7b520559fa9eaac54c1 found on server
|
||||
final Local outdatedprofile = LocalFactory.get(this.getClass().getResource("/test-outdated.cyberduckprofile").getPath());
|
||||
assertNotNull(new ProfilePlistReader(protocols).read(outdatedprofile));
|
||||
assertTrue(outdatedprofile.exists());
|
||||
final LocalProfileDescription conflictProfileDescription = new LocalProfileDescription(protocols, conflictprofile);
|
||||
assertTrue(conflictProfileDescription.getProfile().isPresent());
|
||||
final LocalProfileDescription localonlyProfileDescription = new LocalProfileDescription(protocols, localonlyprofile);
|
||||
assertTrue(localonlyProfileDescription.getProfile().isPresent());
|
||||
final LocalProfileDescription outdatedProfileDescription = new LocalProfileDescription(protocols, outdatedprofile);
|
||||
assertTrue(outdatedProfileDescription.getProfile().isPresent());
|
||||
final Local directory = outdatedprofile.getParent();
|
||||
final ProfilesSynchronizeWorker worker = new ProfilesSynchronizeWorker(protocols, directory, ProfilesFinder.Visitor.Noop);
|
||||
final Set<ProfileDescription> profiles = worker.run(session);
|
||||
assertFalse(profiles.isEmpty());
|
||||
profiles.forEach(d -> assertTrue(d.isLatest()));
|
||||
|
||||
assertFalse(profiles.contains(outdatedProfileDescription));
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.getProfile().get().getProvider().equals(outdatedProfileDescription.getProfile().get().getProvider())).findFirst().isPresent());
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.getProfile().get().getProvider().equals(outdatedProfileDescription.getProfile().get().getProvider())).findFirst().get().isInstalled());
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.getProfile().get().getProvider().equals(outdatedProfileDescription.getProfile().get().getProvider())).findFirst().get().isLatest());
|
||||
// Assert profile updated from remote
|
||||
assertNotEquals(outdatedProfileDescription, profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.getProfile().get().getProvider().equals(outdatedProfileDescription.getProfile().get().getProvider())).findFirst().get());
|
||||
assertNotEquals(outdatedProfileDescription.getChecksum(), profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.getProfile().get().getProvider().equals(outdatedProfileDescription.getProfile().get().getProvider())).findFirst().get().getChecksum());
|
||||
|
||||
assertTrue(profiles.contains(localonlyProfileDescription));
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(localonlyProfileDescription)).findFirst().isPresent());
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(localonlyProfileDescription)).findFirst().get().isInstalled());
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(localonlyProfileDescription)).findFirst().get().isLatest());
|
||||
assertEquals(localonlyProfileDescription, profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(localonlyProfileDescription)).findFirst().get());
|
||||
|
||||
assertTrue(profiles.contains(conflictProfileDescription));
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(conflictProfileDescription)).findFirst().isPresent());
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(conflictProfileDescription)).findFirst().get().isInstalled());
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(conflictProfileDescription)).findFirst().get().isLatest());
|
||||
assertEquals(conflictProfileDescription, profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(conflictProfileDescription)).findFirst().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunVirtualHostEndpoint() throws Exception {
|
||||
public void testRun() throws Exception {
|
||||
// Registry in temporary folder
|
||||
final ProtocolFactory protocols = new ProtocolFactory(new HashSet<>(Collections.singletonList(new S3Protocol())));
|
||||
final Host host = new HostParser(protocols, new S3Protocol()).get("s3:/profiles.cyberduck.io").setCredentials(
|
||||
@@ -122,7 +74,7 @@ public class ProfilesSynchronizeWorkerTest {
|
||||
profiles.forEach(d -> assertTrue(d.isLatest()));
|
||||
|
||||
assertFalse(profiles.contains(outdatedProfileDescription));
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.getProfile().get().getProvider().equals(outdatedProfileDescription.getProfile().get().getProvider())).findFirst().isPresent());
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).anyMatch(d -> d.getProfile().get().getProvider().equals(outdatedProfileDescription.getProfile().get().getProvider())));
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.getProfile().get().getProvider().equals(outdatedProfileDescription.getProfile().get().getProvider())).findFirst().get().isInstalled());
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.getProfile().get().getProvider().equals(outdatedProfileDescription.getProfile().get().getProvider())).findFirst().get().isLatest());
|
||||
// Assert profile updated from remote
|
||||
@@ -130,13 +82,13 @@ public class ProfilesSynchronizeWorkerTest {
|
||||
assertNotEquals(outdatedProfileDescription.getChecksum(), profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.getProfile().get().getProvider().equals(outdatedProfileDescription.getProfile().get().getProvider())).findFirst().get().getChecksum());
|
||||
|
||||
assertTrue(profiles.contains(localonlyProfileDescription));
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(localonlyProfileDescription)).findFirst().isPresent());
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).anyMatch(d -> d.equals(localonlyProfileDescription)));
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(localonlyProfileDescription)).findFirst().get().isInstalled());
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(localonlyProfileDescription)).findFirst().get().isLatest());
|
||||
assertEquals(localonlyProfileDescription, profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(localonlyProfileDescription)).findFirst().get());
|
||||
|
||||
assertTrue(profiles.contains(conflictProfileDescription));
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(conflictProfileDescription)).findFirst().isPresent());
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).anyMatch(d -> d.equals(conflictProfileDescription)));
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(conflictProfileDescription)).findFirst().get().isInstalled());
|
||||
assertTrue(profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(conflictProfileDescription)).findFirst().get().isLatest());
|
||||
assertEquals(conflictProfileDescription, profiles.stream().filter(d -> d.getProfile().isPresent()).filter(d -> d.equals(conflictProfileDescription)).findFirst().get());
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package ch.cyberduck.core.profiles;
|
||||
|
||||
/*
|
||||
* Copyright (c) 2002-2021 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.Credentials;
|
||||
import ch.cyberduck.core.HostKeyCallback;
|
||||
import ch.cyberduck.core.HostParser;
|
||||
import ch.cyberduck.core.LoginCallback;
|
||||
import ch.cyberduck.core.ProtocolFactory;
|
||||
import ch.cyberduck.core.Session;
|
||||
import ch.cyberduck.core.io.Checksum;
|
||||
import ch.cyberduck.core.preferences.PreferencesFactory;
|
||||
import ch.cyberduck.core.proxy.DisabledProxyFinder;
|
||||
import ch.cyberduck.core.s3.S3Protocol;
|
||||
import ch.cyberduck.core.s3.S3Session;
|
||||
import ch.cyberduck.core.ssl.DefaultX509KeyManager;
|
||||
import ch.cyberduck.core.ssl.DisabledX509TrustManager;
|
||||
import ch.cyberduck.core.threading.CancelCallback;
|
||||
import ch.cyberduck.test.IntegrationTest;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.categories.Category;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@Category(IntegrationTest.class)
|
||||
public class RemoteIndexProfilesFinderTest {
|
||||
|
||||
@Test
|
||||
public void testFind() throws Exception {
|
||||
final S3Protocol protocol = new S3Protocol() {
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
final ProtocolFactory protocols = new ProtocolFactory(Collections.singleton(protocol));
|
||||
final Session<?> session = new S3Session(new HostParser(protocols).get("s3:/profiles.cyberduck.io")
|
||||
.setCredentials(new Credentials(PreferencesFactory.get().getProperty("connection.login.anon.name"))), new DisabledX509TrustManager(), new DefaultX509KeyManager());
|
||||
session.open(new DisabledProxyFinder(), HostKeyCallback.noop, LoginCallback.noop, CancelCallback.noop);
|
||||
final RemoteIndexProfilesFinder finder = new RemoteIndexProfilesFinder(protocols, session);
|
||||
final Set<ProfileDescription> set = finder.find();
|
||||
assertFalse(set.isEmpty());
|
||||
// Check for versions of S3 (HTTP).cyberduckprofile
|
||||
assertFalse(set.stream().filter(ProfileDescription::isLatest).collect(Collectors.toSet()).isEmpty());
|
||||
assertFalse(set.stream().filter(description -> !description.isLatest()).collect(Collectors.toSet()).isEmpty());
|
||||
assertTrue(set.stream().anyMatch(description -> description.getChecksum().equals(Checksum.parse("b9afd8d6da91e7b520559fa9eaac54c1"))));
|
||||
assertTrue(set.stream().anyMatch(description -> description.getChecksum().equals(Checksum.parse("19ecbfe2d8f09644197c1ef53e207792"))));
|
||||
set.forEach(d -> {
|
||||
if(protocol.getIdentifier().equals(d.getIdentifier())) {
|
||||
assertNotNull(d.getName());
|
||||
}
|
||||
else {
|
||||
assertNull(d.getName());
|
||||
}
|
||||
assertTrue(new SearchProfilePredicate(StringUtils.EMPTY).test(d));
|
||||
});
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user