mirror of
https://github.com/Codeux-Software/Textual.git
synced 2026-06-16 13:24:32 +00:00
881 lines
26 KiB
Objective-C
Executable File
881 lines
26 KiB
Objective-C
Executable File
/* *********************************************************************
|
|
* _____ _ _
|
|
* |_ _|____ _| |_ _ _ __ _| |
|
|
* | |/ _ \ \/ / __| | | |/ _` | |
|
|
* | | __/> <| |_| |_| | (_| | |
|
|
* |_|\___/_/\_\\__|\__,_|\__,_|_|
|
|
*
|
|
* Copyright (c) 2008 - 2010 Satoshi Nakagawa <psychs AT limechat DOT net>
|
|
* Copyright (c) 2010 - 2020 Codeux Software, LLC & respective contributors.
|
|
* Please see Acknowledgements.pdf for additional information.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of Textual, "Codeux Software, LLC", nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
*********************************************************************** */
|
|
|
|
#import "NSObjectHelperPrivate.h"
|
|
#import "TPCPathInfo.h"
|
|
#import "TPCPreferencesLocal.h"
|
|
#import "TPCPreferencesUserDefaults.h"
|
|
#import "TLOInternetAddressLookup.h"
|
|
#import "TLOLocalization.h"
|
|
#import "TLOTimer.h"
|
|
#import "IRCWorld.h"
|
|
#import "TVCBasicTableView.h"
|
|
#import "TDCFileTransferDialogTableCellPrivate.h"
|
|
#import "TDCFileTransferDialogTransferControllerPrivate.h"
|
|
#import "TDCFileTransferDialogInternal.h"
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
/* Refuse to have more than X number of items incoming at any given time. */
|
|
#define _addReceiverHardLimit 120
|
|
|
|
@interface TDCFileTransferDialog () <NSMenuItemValidation>
|
|
@property (nonatomic, weak) IBOutlet NSButton *clearButton;
|
|
@property (nonatomic, weak) IBOutlet NSSegmentedCell *navigationControllerCell;
|
|
@property (nonatomic, weak, readwrite) IBOutlet TVCBasicTableView *fileTransferTable;
|
|
@property (nonatomic, strong) IBOutlet NSArrayController *fileTransfersController;
|
|
@property (nonatomic, strong, nullable) TLOInternetAddressLookup *IPAddressRequest;
|
|
@property (readonly) TDCFileTransferDialogSelection navigationSelection;
|
|
@property (nonatomic, strong) TLOTimer *maintenanceTimer;
|
|
@property (nonatomic, copy, nullable) NSURL *downloadDestinationURLPrivate;
|
|
|
|
- (IBAction)hideWindow:(id)sender;
|
|
|
|
- (IBAction)clear:(id)sender;
|
|
|
|
- (IBAction)startTransferOfFile:(id)sender;
|
|
- (IBAction)stopTransferOfFile:(id)sender;
|
|
- (IBAction)removeTransferFromList:(id)sender;
|
|
- (IBAction)openReceivedFile:(id)sender;
|
|
- (IBAction)revealReceivedFileInFinder:(id)sender;
|
|
|
|
- (IBAction)navigationSelectionDidChange:(id)sender;
|
|
@end
|
|
|
|
@implementation TDCFileTransferDialog
|
|
|
|
- (instancetype)init
|
|
{
|
|
if ((self = [super init])) {
|
|
[self prepareInitialState];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)prepareInitialState
|
|
{
|
|
[RZMainBundle() loadNibNamed:@"TDCFileTransferDialog" owner:self topLevelObjects:nil];
|
|
|
|
self.maintenanceTimer =
|
|
[TLOTimer timerWithActionBlock:^(TLOTimer *sender) {
|
|
[self onMaintenanceTimer];
|
|
} onQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
|
|
|
|
[RZNotificationCenter() addObserver:self selector:@selector(clientWillBeDestroyed:) name:IRCWorldWillDestroyClientNotification object:nil];
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[RZNotificationCenter() removeObserver:self];
|
|
|
|
[self.maintenanceTimer stop];
|
|
self.maintenanceTimer = nil;
|
|
}
|
|
|
|
- (void)show
|
|
{
|
|
[self show:YES restorePosition:YES];
|
|
}
|
|
|
|
- (void)show:(BOOL)makeKeyWindow
|
|
{
|
|
[self show:makeKeyWindow restorePosition:YES];
|
|
}
|
|
|
|
- (void)show:(BOOL)makeKeyWindow restorePosition:(BOOL)restorePosition
|
|
{
|
|
if (makeKeyWindow) {
|
|
[self.window makeKeyAndOrderFront:nil];
|
|
} else {
|
|
[self.window orderFront:nil];
|
|
}
|
|
|
|
if (restorePosition) {
|
|
[self.window restoreWindowStateForClass:self.class];
|
|
}
|
|
}
|
|
|
|
- (nullable TDCFileTransferDialogTransferController *)fileTransferMatchingPort:(uint16_t)port
|
|
{
|
|
TDCFileTransferDialogTransferController *fileTransfer =
|
|
[self fileTransferMatchingCondition:^BOOL(TDCFileTransferDialogTransferController *controller) {
|
|
return (controller.hostPort == port);
|
|
}];
|
|
|
|
return fileTransfer;
|
|
}
|
|
|
|
- (nullable TDCFileTransferDialogTransferController *)fileTransferWithUniqueIdentifier:(NSString *)identifier
|
|
{
|
|
NSParameterAssert(identifier != nil);
|
|
|
|
TDCFileTransferDialogTransferController *fileTransfer =
|
|
[self fileTransferMatchingCondition:^BOOL(TDCFileTransferDialogTransferController *controller) {
|
|
return [identifier isEqualToString:controller.uniqueIdentifier];
|
|
}];
|
|
|
|
return fileTransfer;
|
|
}
|
|
|
|
- (BOOL)fileTransferExistsWithToken:(NSString *)transferToken
|
|
{
|
|
NSParameterAssert(transferToken != nil);
|
|
|
|
TDCFileTransferDialogTransferController *fileTransfer =
|
|
[self fileTransferMatchingCondition:^BOOL(TDCFileTransferDialogTransferController *controller) {
|
|
return [transferToken isEqualToString:controller.transferToken];
|
|
}];
|
|
|
|
return (fileTransfer != nil);
|
|
}
|
|
|
|
- (nullable TDCFileTransferDialogTransferController *)fileTransferSenderMatchingToken:(NSString *)transferToken
|
|
{
|
|
NSParameterAssert(transferToken != nil);
|
|
|
|
TDCFileTransferDialogTransferController *fileTransfer =
|
|
[self fileTransferMatchingCondition:^BOOL(TDCFileTransferDialogTransferController *controller) {
|
|
return ([transferToken isEqualToString:controller.transferToken] && controller.isSender);
|
|
}];
|
|
|
|
return fileTransfer;
|
|
}
|
|
|
|
- (nullable TDCFileTransferDialogTransferController *)fileTransferReceiverMatchingToken:(NSString *)transferToken
|
|
{
|
|
NSParameterAssert(transferToken != nil);
|
|
|
|
TDCFileTransferDialogTransferController *fileTransfer =
|
|
[self fileTransferMatchingCondition:^BOOL(TDCFileTransferDialogTransferController *controller) {
|
|
return ([transferToken isEqualToString:controller.transferToken] && controller.isSender == NO);
|
|
}];
|
|
|
|
return fileTransfer;
|
|
}
|
|
|
|
- (void)prepareForApplicationTermination
|
|
{
|
|
LogToConsoleTerminationProgress("Stopping access to download destination bookmark");
|
|
|
|
if (self.downloadDestinationURLPrivate) {
|
|
[self.downloadDestinationURLPrivate stopAccessingSecurityScopedResource];
|
|
}
|
|
|
|
LogToConsoleTerminationProgress("Closing file transfer window");
|
|
|
|
[self close];
|
|
|
|
LogToConsoleTerminationProgress("Preparing all file transfers for destruction");
|
|
|
|
[self enumerateFileTransfers:^(TDCFileTransferDialogTransferController *fileTransfer, BOOL *stop) {
|
|
[fileTransfer prepareForPermanentDestruction];
|
|
}];
|
|
}
|
|
|
|
- (nullable NSString *)addReceiverForClient:(IRCClient *)client nickname:(NSString *)nickname address:(NSString *)hostAddress port:(uint16_t)hostPort filename:(NSString *)filename filesize:(uint64_t)totalFilesize token:(nullable NSString *)transferToken
|
|
{
|
|
NSParameterAssert(client != nil);
|
|
NSParameterAssert(nickname != nil);
|
|
NSParameterAssert(hostAddress != nil);
|
|
NSParameterAssert(filename != nil);
|
|
|
|
/* A hard limit exists to prevent a bad person continuously sending file transfers
|
|
which appear in the file transfer, exhausting resources. */
|
|
if ([self receiverCount] > _addReceiverHardLimit) {
|
|
LogToConsoleError("Max receiver count of %{public}i exceeded", _addReceiverHardLimit);
|
|
|
|
return nil;
|
|
}
|
|
|
|
TDCFileTransferDialogTransferController *controller = [TDCFileTransferDialogTransferController receiverForClient:client nickname:nickname address:hostAddress port:hostPort filename:filename filesize:totalFilesize token:transferToken];
|
|
|
|
if (controller == nil) {
|
|
return nil;
|
|
}
|
|
|
|
[self show:NO restorePosition:NO];
|
|
|
|
[self addFileTransfer:controller];
|
|
|
|
NSString *savePath = self.downloadDestinationURLPrivate.path;
|
|
|
|
if ([TPCPreferences fileTransferRequestReplyAction] == TXFileTransferRequestReplyAutomaticallyDownload) {
|
|
if (savePath == nil) {
|
|
savePath = [TPCPathInfo userDownloads];
|
|
}
|
|
|
|
[controller openWithPath:savePath];
|
|
}
|
|
|
|
return controller.uniqueIdentifier;
|
|
}
|
|
|
|
- (nullable NSString *)addSenderForClient:(IRCClient *)client nickname:(NSString *)nickname path:(NSString *)path autoOpen:(BOOL)autoOpen
|
|
{
|
|
NSParameterAssert(client != nil);
|
|
NSParameterAssert(nickname != nil);
|
|
NSParameterAssert(path != nil);
|
|
|
|
TDCFileTransferDialogTransferController *controller = [TDCFileTransferDialogTransferController senderForClient:client nickname:nickname path:path];
|
|
|
|
if (controller == nil) {
|
|
return nil;
|
|
}
|
|
|
|
[self show:YES restorePosition:NO];
|
|
|
|
[self addFileTransfer:controller];
|
|
|
|
if (autoOpen) {
|
|
[controller open];
|
|
}
|
|
|
|
return controller.uniqueIdentifier;
|
|
}
|
|
|
|
- (void)updateClearButton
|
|
{
|
|
NSArray *stoppedFileTransfers = [self stoppedFileTransfers];
|
|
|
|
self.clearButton.enabled = (stoppedFileTransfers.count > 0);
|
|
}
|
|
|
|
- (void)addFileTransfer:(TDCFileTransferDialogTransferController *)controller
|
|
{
|
|
/* Resetting the predicate the each time a controller is added is stupid,
|
|
but this is a low frequency task that we can forgive. */
|
|
NSPredicate *filterPredicate = self.fileTransfersController.filterPredicate;
|
|
|
|
self.fileTransfersController.filterPredicate = nil;
|
|
|
|
[self.fileTransfersController insertObject:controller atArrangedObjectIndex:0];
|
|
|
|
self.fileTransfersController.filterPredicate = filterPredicate;
|
|
}
|
|
|
|
- (void)removeFileTransfersMatchingClient:(IRCClient *)client
|
|
{
|
|
NSParameterAssert(client != nil);
|
|
|
|
NSArray *fileTransfers = [self fileTransfersMatchingClient:client];
|
|
|
|
if (fileTransfers.count == 0) {
|
|
return;
|
|
}
|
|
|
|
[fileTransfers makeObjectsPerformSelector:@selector(prepareForPermanentDestruction)];
|
|
|
|
[self.fileTransfersController removeObjects:fileTransfers];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Notifications
|
|
|
|
- (void)clientWillBeDestroyed:(NSNotification *)notification
|
|
{
|
|
[self removeFileTransfersMatchingClient:notification.object];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Actions
|
|
|
|
- (BOOL)validateMenuItem:(NSMenuItem *)item
|
|
{
|
|
NSInteger tag = item.tag;
|
|
|
|
NSArray *selectedFileTransfers = [self selectedFileTransfers];
|
|
|
|
if (selectedFileTransfers.count == 0) {
|
|
return NO;
|
|
}
|
|
|
|
/* Begin actual validation. */
|
|
switch (tag) {
|
|
case 3001: // Start Download
|
|
{
|
|
for (TDCFileTransferDialogTransferController *fileTransfer in selectedFileTransfers) {
|
|
TDCFileTransferDialogTransferStatus transferStatus = fileTransfer.transferStatus;
|
|
|
|
if (transferStatus == TDCFileTransferDialogTransferStatusStopped ||
|
|
transferStatus == TDCFileTransferDialogTransferStatusRecoverableError)
|
|
{
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
case 3003: // Stop Download
|
|
{
|
|
for (TDCFileTransferDialogTransferController *fileTransfer in selectedFileTransfers) {
|
|
TDCFileTransferDialogTransferStatus transferStatus = fileTransfer.transferStatus;
|
|
|
|
if (transferStatus == TDCFileTransferDialogTransferStatusConnecting ||
|
|
transferStatus == TDCFileTransferDialogTransferStatusReceiving ||
|
|
transferStatus == TDCFileTransferDialogTransferStatusIsListeningAsSender ||
|
|
transferStatus == TDCFileTransferDialogTransferStatusIsListeningAsReceiver ||
|
|
transferStatus == TDCFileTransferDialogTransferStatusSending ||
|
|
transferStatus == TDCFileTransferDialogTransferStatusMappingListeningPort ||
|
|
transferStatus == TDCFileTransferDialogTransferStatusWaitingForLocalIPAddress ||
|
|
transferStatus == TDCFileTransferDialogTransferStatusWaitingForReceiverToAccept ||
|
|
transferStatus == TDCFileTransferDialogTransferStatusWaitingForResumeAccept)
|
|
{
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
case 3004: // Remove Item
|
|
{
|
|
return YES;
|
|
}
|
|
case 3005: // Open File
|
|
{
|
|
for (TDCFileTransferDialogTransferController *fileTransfer in selectedFileTransfers) {
|
|
TDCFileTransferDialogTransferStatus transferStatus = fileTransfer.transferStatus;
|
|
|
|
if (fileTransfer.isSender != NO) {
|
|
continue;
|
|
}
|
|
|
|
if (transferStatus == TDCFileTransferDialogTransferStatusComplete) {
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
case 3006: // Reveal In Finder
|
|
{
|
|
for (TDCFileTransferDialogTransferController *fileTransfer in selectedFileTransfers) {
|
|
TDCFileTransferDialogTransferStatus transferStatus = fileTransfer.transferStatus;
|
|
|
|
if (fileTransfer.isSender != NO) {
|
|
continue;
|
|
}
|
|
|
|
if (transferStatus == TDCFileTransferDialogTransferStatusComplete) {
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
return NO; // Default validation to NO.
|
|
}
|
|
|
|
- (void)clear:(id)sender
|
|
{
|
|
NSArray *stoppedFileTransfers = [self stoppedFileTransfers];
|
|
|
|
[stoppedFileTransfers makeObjectsPerformSelector:@selector(prepareForPermanentDestruction)];
|
|
|
|
[self.fileTransfersController removeObjects:stoppedFileTransfers];
|
|
|
|
[self updateClearButton];
|
|
}
|
|
|
|
- (void)startTransferOfFile:(id)sender
|
|
{
|
|
NSString *savePath = self.downloadDestinationURLPrivate.path;
|
|
|
|
NSMutableArray<TDCFileTransferDialogTransferController *> *fileTransfersPending = [NSMutableArray array];
|
|
|
|
/* Open all file transfers who are senders or have a path */
|
|
[self enumerateSelectedFileTransfers:^(TDCFileTransferDialogTransferController *fileTransfer, NSUInteger index, BOOL *stop) {
|
|
TDCFileTransferDialogTransferStatus transferStatus = fileTransfer.transferStatus;
|
|
|
|
if (transferStatus != TDCFileTransferDialogTransferStatusStopped &&
|
|
transferStatus != TDCFileTransferDialogTransferStatusRecoverableError)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (fileTransfer.isSender || fileTransfer.path != nil) {
|
|
[fileTransfer open];
|
|
|
|
return;
|
|
} else if (fileTransfer.path == nil && savePath != nil) {
|
|
[fileTransfer openWithPath:savePath];
|
|
|
|
return;
|
|
}
|
|
|
|
[fileTransfersPending addObject:fileTransfer];
|
|
}];
|
|
|
|
/* If there are file transfers that weren't opened because of a missing
|
|
path, then we now prompt the user for where they want to save the files. */
|
|
if (fileTransfersPending.count == 0) {
|
|
return;
|
|
}
|
|
|
|
NSOpenPanel *openDialog = [NSOpenPanel openPanel];
|
|
|
|
openDialog.directoryURL = [TPCPathInfo userDownloadsURL];
|
|
|
|
openDialog.allowsMultipleSelection = NO;
|
|
openDialog.canChooseDirectories = YES;
|
|
openDialog.canChooseFiles = NO;
|
|
openDialog.canCreateDirectories = YES;
|
|
openDialog.resolvesAliases = YES;
|
|
|
|
openDialog.message = TXTLS(@"TDCFileTransferDialog[dcm-w7]");
|
|
|
|
openDialog.prompt = TXTLS(@"Prompts[xne-79]");
|
|
|
|
[openDialog beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) {
|
|
if (result != NSModalResponseOK) {
|
|
return;
|
|
}
|
|
|
|
NSString *path = openDialog.URL.path;
|
|
|
|
for (TDCFileTransferDialogTransferController *fileTransfer in fileTransfersPending) {
|
|
[fileTransfer openWithPath:path];
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)stopTransferOfFile:(id)sender
|
|
{
|
|
[self enumerateSelectedFileTransfers:^(TDCFileTransferDialogTransferController *fileTransfer, NSUInteger index, BOOL *stop) {
|
|
[fileTransfer closeAndPostNotification:NO];
|
|
}];
|
|
}
|
|
|
|
- (void)removeTransferFromList:(id)sender
|
|
{
|
|
NSArray *selectedFileTransfers = [self selectedFileTransfers];
|
|
|
|
for (TDCFileTransferDialogTransferController *fileTransfer in selectedFileTransfers) {
|
|
[fileTransfer prepareForPermanentDestruction];
|
|
}
|
|
|
|
[self.fileTransfersController removeObjects:selectedFileTransfers];
|
|
}
|
|
|
|
- (void)openReceivedFile:(id)sender
|
|
{
|
|
[self enumerateSelectedFileTransfers:^(TDCFileTransferDialogTransferController *fileTransfer, NSUInteger index, BOOL *stop) {
|
|
if (fileTransfer.isSender != NO) {
|
|
return;
|
|
}
|
|
|
|
[RZWorkspace() openURL:fileTransfer.fileURL];
|
|
}];
|
|
}
|
|
|
|
- (void)revealReceivedFileInFinder:(id)sender
|
|
{
|
|
[self enumerateSelectedFileTransfers:^(TDCFileTransferDialogTransferController *fileTransfer, NSUInteger index, BOOL *stop) {
|
|
if (fileTransfer.isSender != NO) {
|
|
return;
|
|
}
|
|
|
|
[RZWorkspace() selectFile:fileTransfer.filePath inFileViewerRootedAtPath:@""];
|
|
}];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Timer
|
|
|
|
- (void)updateMaintenanceTimerOnMainThread
|
|
{
|
|
NSArray *activeFileTransfers = [self activeFileTransfers];
|
|
|
|
if (self.maintenanceTimer.timerIsActive) {
|
|
if (activeFileTransfers.count == 0) {
|
|
[self.maintenanceTimer stop];
|
|
}
|
|
} else {
|
|
if (activeFileTransfers.count > 0) {
|
|
[self.maintenanceTimer start:1.0 onRepeat:YES];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)updateMaintenanceTimer
|
|
{
|
|
XRPerformBlockSynchronouslyOnMainQueue(^{
|
|
[self updateMaintenanceTimerOnMainThread];
|
|
});
|
|
}
|
|
|
|
- (void)onMaintenanceTimer
|
|
{
|
|
NSArray *activeFileTransfers = [self activeFileTransfers];
|
|
|
|
for (TDCFileTransferDialogTransferController *fileTransfer in activeFileTransfers) {
|
|
[fileTransfer onMaintenanceTimer];
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Table View Delegate
|
|
|
|
- (nullable NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
|
|
{
|
|
TDCFileTransferDialogTransferController *fileTransfer = self.fileTransfersController.arrangedObjects[row];
|
|
|
|
TDCFileTransferDialogTableCell *newView =
|
|
(TDCFileTransferDialogTableCell *)[tableView makeViewWithIdentifier:@"GroupView" owner:self];
|
|
|
|
fileTransfer.transferTableCell = newView;
|
|
|
|
return newView;
|
|
}
|
|
|
|
- (void)tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
|
|
{
|
|
TDCFileTransferDialogTableCell *tableCell = [tableView viewAtColumn:0 row:row makeIfNecessary:NO];
|
|
|
|
[tableCell prepareInitialState];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Network Information
|
|
|
|
- (nullable NSString *)IPAddress
|
|
{
|
|
if ([TPCPreferences fileTransferIPAddressDetectionMethod] == TXFileTransferIPAddressMethodManual) {
|
|
NSString *userAddress = [TPCPreferences fileTransferManuallyEnteredIPAddress];
|
|
|
|
if (userAddress.length == 0) {
|
|
return nil;
|
|
}
|
|
|
|
return userAddress;
|
|
}
|
|
|
|
return self->_IPAddress;
|
|
}
|
|
|
|
- (void)clearIPAddress
|
|
{
|
|
self.IPAddress = nil;
|
|
|
|
[self.IPAddressRequest cancelLookup];
|
|
self.IPAddressRequest = nil;
|
|
}
|
|
|
|
- (void)requestIPAddress
|
|
{
|
|
if (self.IPAddressRequest != nil) {
|
|
return;
|
|
}
|
|
|
|
TLOInternetAddressLookup *lookupRequest = [[TLOInternetAddressLookup alloc] initWithDelegate:(id)self];
|
|
|
|
[lookupRequest performLookup];
|
|
|
|
self.IPAddressRequest = lookupRequest;
|
|
}
|
|
|
|
- (void)internetAddressLookupReturnedAddress:(NSString *)address
|
|
{
|
|
self.IPAddress = address;
|
|
|
|
[self enumerateFileTransferSenders:^(TDCFileTransferDialogTransferController *fileTransfer, BOOL *stop) {
|
|
if (fileTransfer.transferStatus != TDCFileTransferDialogTransferStatusWaitingForLocalIPAddress) {
|
|
return;
|
|
}
|
|
|
|
[fileTransfer noteIPAddressLookupSucceeded];
|
|
}];
|
|
|
|
self.IPAddressRequest = nil;
|
|
}
|
|
|
|
- (void)internetAddressLookupFailed
|
|
{
|
|
[self enumerateFileTransferSenders:^(TDCFileTransferDialogTransferController *fileTransfer, BOOL *stop) {
|
|
if (fileTransfer.transferStatus != TDCFileTransferDialogTransferStatusWaitingForLocalIPAddress) {
|
|
return;
|
|
}
|
|
|
|
[fileTransfer noteIPAddressLookupFailed];
|
|
}];
|
|
|
|
self.IPAddressRequest = nil;
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Navigation
|
|
|
|
- (TDCFileTransferDialogSelection)navigationSelection
|
|
{
|
|
return self.navigationControllerCell.selectedSegment;
|
|
}
|
|
|
|
- (void)navigationSelectionDidChange:(id)sender
|
|
{
|
|
TDCFileTransferDialogSelection selection = self.navigationSelection;
|
|
|
|
NSPredicate *filterPredicate = nil;
|
|
|
|
if (selection == TDCFileTransferDialogSelectionSending) {
|
|
filterPredicate = [NSPredicate predicateWithFormat:@"isSender == YES"];
|
|
} else if (selection == TDCFileTransferDialogSelectionReceiving) {
|
|
filterPredicate = [NSPredicate predicateWithFormat:@"isSender == NO"];
|
|
}
|
|
|
|
self.fileTransfersController.filterPredicate = filterPredicate;
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Transfer Search
|
|
|
|
- (NSUInteger)receiverCount
|
|
{
|
|
__block NSUInteger receiverCount = 0;
|
|
|
|
[self enumerateFileTransferReceivers:^(TDCFileTransferDialogTransferController *fileTransfer, BOOL *stop) {
|
|
receiverCount += 1;
|
|
}];
|
|
|
|
return receiverCount;
|
|
}
|
|
|
|
- (NSArray<TDCFileTransferDialogTransferController *> *)stoppedFileTransfers
|
|
{
|
|
return [self fileTransfersMatchingCondition:^BOOL(TDCFileTransferDialogTransferController *fileTransfer) {
|
|
TDCFileTransferDialogTransferStatus transferStatus = fileTransfer.transferStatus;
|
|
|
|
if (transferStatus != TDCFileTransferDialogTransferStatusComplete &&
|
|
transferStatus != TDCFileTransferDialogTransferStatusStopped &&
|
|
transferStatus != TDCFileTransferDialogTransferStatusFatalError &&
|
|
transferStatus != TDCFileTransferDialogTransferStatusRecoverableError)
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
return YES;
|
|
}];
|
|
}
|
|
|
|
- (NSArray<TDCFileTransferDialogTransferController *> *)activeFileTransfers
|
|
{
|
|
return [self fileTransfersMatchingCondition:^BOOL(TDCFileTransferDialogTransferController *fileTransfer) {
|
|
TDCFileTransferDialogTransferStatus transferStatus = fileTransfer.transferStatus;
|
|
|
|
if (transferStatus != TDCFileTransferDialogTransferStatusReceiving &&
|
|
transferStatus != TDCFileTransferDialogTransferStatusSending)
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
return YES;
|
|
}];
|
|
}
|
|
|
|
- (NSArray<TDCFileTransferDialogTransferController *> *)fileTransfersMatchingCondition:(BOOL (NS_NOESCAPE ^)(TDCFileTransferDialogTransferController *fileTransfer))matchCondition
|
|
{
|
|
NSMutableArray<TDCFileTransferDialogTransferController *> *fileTransfers = [NSMutableArray array];
|
|
|
|
[self enumerateFileTransfers:^(TDCFileTransferDialogTransferController *fileTransfer, BOOL *stop) {
|
|
if (matchCondition(fileTransfer) == NO) {
|
|
return;
|
|
}
|
|
|
|
[fileTransfers addObject:fileTransfer];
|
|
}];
|
|
|
|
return [fileTransfers copy];
|
|
}
|
|
|
|
- (nullable TDCFileTransferDialogTransferController *)fileTransferMatchingCondition:(BOOL (NS_NOESCAPE ^)(TDCFileTransferDialogTransferController *fileTransfer))matchCondition
|
|
{
|
|
__block TDCFileTransferDialogTransferController *fileTransferMatched = nil;
|
|
|
|
[self enumerateFileTransfers:^(TDCFileTransferDialogTransferController *fileTransfer, BOOL *stop) {
|
|
if (matchCondition(fileTransfer) == NO) {
|
|
return;
|
|
}
|
|
|
|
fileTransferMatched = fileTransfer;
|
|
|
|
*stop = YES;
|
|
}];
|
|
|
|
return fileTransferMatched;
|
|
}
|
|
|
|
- (NSArray<TDCFileTransferDialogTransferController *> *)selectedFileTransfers
|
|
{
|
|
NSMutableArray<TDCFileTransferDialogTransferController *> *selectedFileTransfers = [NSMutableArray array];
|
|
|
|
[self enumerateSelectedFileTransfers:^(TDCFileTransferDialogTransferController *fileTransfer, NSUInteger index, BOOL *stop) {
|
|
[selectedFileTransfers addObject:fileTransfer];
|
|
}];
|
|
|
|
return [selectedFileTransfers copy];
|
|
}
|
|
|
|
- (NSArray<TDCFileTransferDialogTransferController *> *)fileTransfersMatchingClient:(IRCClient *)client
|
|
{
|
|
NSParameterAssert(client != nil);
|
|
|
|
return [self fileTransfersMatchingCondition:^BOOL(TDCFileTransferDialogTransferController * _Nonnull fileTransfer) {
|
|
return (fileTransfer.client == client);
|
|
}];
|
|
}
|
|
|
|
- (void)enumerateSelectedFileTransfers:(void (NS_NOESCAPE ^)(TDCFileTransferDialogTransferController *fileTransfer, NSUInteger index, BOOL *stop))enumerationBlock
|
|
{
|
|
NSIndexSet *selectedRows = self.fileTransferTable.selectedRowIndexes;
|
|
|
|
[selectedRows enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) {
|
|
TDCFileTransferDialogTransferController *fileTransfer = self.fileTransfersController.arrangedObjects[index];
|
|
|
|
enumerationBlock(fileTransfer, index, stop);
|
|
}];
|
|
}
|
|
|
|
- (void)enumerateFileTransfers:(void (NS_NOESCAPE ^)(TDCFileTransferDialogTransferController *fileTransfer, BOOL *stop))enumerationBlock
|
|
{
|
|
[self _enumerateFileTransfers:enumerationBlock limitScope:NO limitScopeToSenders:NO];
|
|
}
|
|
|
|
- (void)enumerateFileTransferReceivers:(void (NS_NOESCAPE ^)(TDCFileTransferDialogTransferController *fileTransfer, BOOL *stop))enumerationBlock
|
|
{
|
|
[self _enumerateFileTransfers:enumerationBlock limitScope:YES limitScopeToSenders:NO];
|
|
}
|
|
|
|
- (void)enumerateFileTransferSenders:(void (NS_NOESCAPE ^)(TDCFileTransferDialogTransferController *fileTransfer, BOOL *stop))enumerationBlock
|
|
{
|
|
[self _enumerateFileTransfers:enumerationBlock limitScope:YES limitScopeToSenders:YES];
|
|
}
|
|
|
|
- (void)_enumerateFileTransfers:(void (NS_NOESCAPE ^)(TDCFileTransferDialogTransferController *fileTransfer, BOOL *stop))enumerationBlock limitScope:(BOOL)limitScope limitScopeToSenders:(BOOL)limitScopeToSenders
|
|
{
|
|
NSParameterAssert(enumerationBlock != nil);
|
|
|
|
for (TDCFileTransferDialogTransferController *fileTransfer in self.fileTransfersController.arrangedObjects) {
|
|
if (limitScope && limitScopeToSenders != fileTransfer.isSender) {
|
|
continue;
|
|
}
|
|
|
|
BOOL stop = NO;
|
|
|
|
enumerationBlock(fileTransfer, &stop);
|
|
|
|
if (stop) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Window Delegate
|
|
|
|
- (void)windowWillClose:(NSNotification *)note
|
|
{
|
|
[self.window saveWindowStateForClass:self.class];
|
|
}
|
|
|
|
- (void)hideWindow:(id)sender
|
|
{
|
|
[self close];
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
#pragma mark Destination Folder
|
|
|
|
@implementation TDCFileTransferDialog (TDCFileTransferDialogDownloadDestinationExtension)
|
|
|
|
- (nullable NSURL *)downloadDestinationURL
|
|
{
|
|
return self.downloadDestinationURLPrivate;
|
|
}
|
|
|
|
- (void)startUsingDownloadDestinationURL
|
|
{
|
|
NSData *bookmark = [RZUserDefaults() dataForKey:@"File Transfers -> File Transfer Download Folder Bookmark"];
|
|
|
|
if (bookmark == nil) {
|
|
return;
|
|
}
|
|
|
|
BOOL resolvedBookmarkIsStale = YES;
|
|
|
|
NSError *resolvedBookmarkError = nil;
|
|
|
|
NSURL *resolvedBookmark =
|
|
[NSURL URLByResolvingBookmarkData:bookmark
|
|
options:NSURLBookmarkResolutionWithSecurityScope
|
|
relativeToURL:nil
|
|
bookmarkDataIsStale:&resolvedBookmarkIsStale
|
|
error:&resolvedBookmarkError];
|
|
|
|
if (resolvedBookmark == nil) {
|
|
LogToConsoleError("Error creating bookmark for URL: %{public}@",
|
|
resolvedBookmarkError.localizedDescription);
|
|
|
|
return;
|
|
}
|
|
|
|
self.downloadDestinationURLPrivate = resolvedBookmark;
|
|
|
|
if ([self.downloadDestinationURLPrivate startAccessingSecurityScopedResource] == NO) {
|
|
LogToConsoleError("Failed to access bookmark");
|
|
}
|
|
}
|
|
|
|
- (void)setDownloadDestinationURL:(nullable NSData *)downloadDestinationURL
|
|
{
|
|
if ( self.downloadDestinationURLPrivate) {
|
|
[self.downloadDestinationURLPrivate stopAccessingSecurityScopedResource];
|
|
self.downloadDestinationURLPrivate = nil;
|
|
}
|
|
|
|
[RZUserDefaults() setObject:downloadDestinationURL forKey:@"File Transfers -> File Transfer Download Folder Bookmark"];
|
|
|
|
[self startUsingDownloadDestinationURL];
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|