5 // Created by Nathan Oates on Sun Aug 01 2004.
6 // Copyright (c) 2004-7 Nathan Oates nathan@noates.com All rights reserved.
9 /* This file was modified by Kalle Olavi Niemitalo on 2007-10-18. */
11 // includes code based on uproar, license noted below:
13 // Copyright (c) 2001 Kasima Tharnpipitchai <me@kasima.org>
17 * This source code is free software; you can redistribute it and/or
18 * modify it under the terms of the GNU Public License as published
19 * by the Free Software Foundation; either version 2 of the License,
20 * or (at your option) any later version.
22 * This source code is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
25 * Please refer to the GNU Public License for more details.
27 * You should have received a copy of the GNU Public License along with
28 * this source code; if not, write to:
29 * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
32 /* Much of the code that initializes and closes the device is from Apple
33 * and is released under the license below.
37 * © Copyright 2001 Apple Computer, Inc. All rights reserved.
39 * IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. (“Apple”) in
40 * consideration of your agreement to the following terms, and your use, installation,
41 * modification or redistribution of this Apple software constitutes acceptance of these
42 * terms. If you do not agree with these terms, please do not use, install, modify or
43 * redistribute this Apple software.
45 * In consideration of your agreement to abide by the following terms, and subject to these
46 * terms, Apple grants you a personal, non exclusive license, under Apple’s copyrights in this
47 * original Apple software (the “Apple Software”), to use, reproduce, modify and redistribute
48 * the Apple Software, with or without modifications, in source and/or binary forms; provided
49 * that if you redistribute the Apple Software in its entirety and without modifications, you
50 * must retain this notice and the following text and disclaimers in all such redistributions
51 * of the Apple Software. Neither the name, trademarks, service marks or logos of Apple
52 * Computer, Inc. may be used to endorse or promote products derived from the Apple Software
53 * without specific prior written permission from Apple. Except as expressly stated in this
54 * notice, no other rights or licenses, express or implied, are granted by Apple herein,
55 * including but not limited to any patent rights that may be infringed by your derivative
56 * works or by other works in which the Apple Software may be incorporated.
58 * The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES,
59 * EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-
60 * INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE
61 * SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
63 * IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL
64 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
65 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE,
66 * REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND
67 * WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
68 * OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
76 #include <mach/mach.h>
77 #include <IOKit/IOCFPlugIn.h>
78 #include <CoreFoundation/CFNumber.h>
80 #import "DataHandler.h"
81 #import "TFUSBController.h"
82 #import "TFDataFormat.h"
83 #import "UIElements.h"
85 static void hexDump(UInt8 *buf, int len);
86 static int doSend(IOUSBDeviceInterface197 **dev,
87 IOUSBInterfaceInterface197 **intf, UInt8 *outBuf, UInt32 len, int type);
88 static int doRecv(IOUSBDeviceInterface197 **dev,
89 IOUSBInterfaceInterface197 **intf, UInt8 *inBuf, UInt32 dataLen, int type);
90 static int dealWithInterface(io_service_t usbInterfaceRef,
91 USBDeviceContext *device);
92 static int dealWithDevice(io_service_t usbDeviceRef, USBDeviceContext *device);
93 static int initDevice(USBDeviceContext *device);
97 static int kBlockSize;
98 static int connectedSpeed;
100 @interface TFUSBController (PrivateMethods)
103 - (NSData*) sendCommand:(NSData*)fullyPackagedCommand
104 toDevice:(USBDeviceContext*)device expectResponse:(BOOL) getResponse
105 careForReturn:(BOOL)careFactor;
107 // Protocol message sequences
108 - (id) getFileListForPath:(NSString*)path;
109 - (void) turnTurboOn:(BOOL)turnOn;
110 - (void) renameFile:(NSString*)oldName withName:(NSString*)newName atPath:(NSString*)currentPath;
111 - (void) makeFolder:(NSString*)newName atPath:(NSString*)currentPath;
112 - (void) checkUSB:(USBDeviceContext*)device;
115 - (UIElements*) uiElements;
116 - (void) updateProgress:(NSDictionary*)inDict;
117 - (NSString*) elapsedTime:(NSTimeInterval)totalSeconds;
120 - (BOOL) hasPriorityTransfer;
121 - (void) getNextTransfer:(NSDictionary**)transfer queue:(NSMutableArray**)queue;
122 - (void) removeTransfer:(NSDictionary*)transfer fromQueue:(NSMutableArray*)queue;
126 #define TopfieldVendorID 4571
130 #define TF5kProdID 4096
135 // ---------------------------------------------------------------------------
136 #pragma mark Functions outside TFUSBController
137 // ---------------------------------------------------------------------------
140 hexDump(UInt8 *buf, int len)
142 int row, col, maxrows;
145 if (len % 16) maxrows++;
146 for (row=0; row< maxrows; row++) {
147 for (col=0; col<16; col++) {
148 if (!(col%2)) printf(" ");
149 printf("%02x", buf[row*16 + col] & 0xff);
152 for (col=0; col<16; col++) {
153 if ((buf[row*16 + col]>32) && (buf[row*16 + col]<126)) {
154 printf("%c", buf[row*16 + col]);
156 else { printf("."); }
163 doSend(IOUSBDeviceInterface197 **dev, IOUSBInterfaceInterface197 **intf,
164 UInt8 *outBuf, UInt32 len, int type)
170 printf(("sending:\n"));
171 hexDump(outBuf, len);
173 sendLen = ((len/kBlockSize))*kBlockSize;
174 if (len % kBlockSize)
175 sendLen += kBlockSize;
176 if ((sendLen % 0x200) == 0)
177 sendLen += kBlockSize;
179 err = (*intf)->WritePipeTO(intf, 1, outBuf, sendLen, 1000, 20000);
181 printf("write err: %08x\n", err);
188 doRecv(IOUSBDeviceInterface197 **dev, IOUSBInterfaceInterface197 **intf,
189 UInt8 *inBuf, UInt32 dataLen, int type)
194 if (dataLen > kMaxXferSize) return 1;
196 len = (dataLen/kBlockSize) * kBlockSize;
197 if (dataLen % kBlockSize)
200 err = (*intf)->ReadPipeTO(intf, 2, (void *)inBuf, &len, 1000, 20000);
202 printf("read err 2: %08x\n", err);
203 printf("resetting\n");
204 (*intf)->ClearPipeStallBothEnds(intf, 2); // ignore return value
209 printf(("receiving: \n"));
216 dealWithInterface(io_service_t usbInterfaceRef, USBDeviceContext *device)
219 IOCFPlugInInterface **iodev; // requires <IOKit/IOCFPlugIn.h>
220 IOUSBInterfaceInterface197 **intf;
221 IOUSBDeviceInterface197 **dev;
223 UInt8 numPipes, confNum, dSpeed;
226 err = IOCreatePlugInInterfaceForService(usbInterfaceRef, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &iodev, &score);
229 printf("dealWithInterface: unable to create plugin. ret = %08x, iodev = %p\n", err, iodev);
230 return kUproarDeviceErr;
232 err = (*iodev)->QueryInterface(iodev, CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID), (LPVOID)&(device->intf));
233 (*iodev)->Release(iodev); // done with this
237 printf("dealWithInterface: unable to create a device interface. ret = %08x, intf = %p\n", err, intf);
238 return kUproarDeviceErr;
242 err = (*intf)->USBInterfaceOpen(intf);
245 printf("dealWithInterface: unable to open interface. ret = %08x\n", err);
246 return kUproarDeviceErr;
248 err = (*intf)->GetNumEndpoints(intf, &numPipes);
251 printf("dealWithInterface: unable to get number of endpoints. ret = %08x\n", err);
252 return kUproarDeviceErr;
255 printf("dealWithInterface: found %d pipes\n", numPipes);
257 err = (*intf)->GetConfigurationValue(intf, &confNum);
258 err = (*dev)->GetDeviceSpeed(dev, &dSpeed);
264 printf("confnum: %08x, dspeed: %08x, blockS:%i\n", confNum, dSpeed, kBlockSize);
265 connectedSpeed = dSpeed;
266 return kUproarSuccess;
271 dealWithDevice(io_service_t usbDeviceRef, USBDeviceContext *device)
274 IOCFPlugInInterface **iodev; // requires <IOKit/IOCFPlugIn.h>
275 IOUSBDeviceInterface197 **dev;
278 IOUSBConfigurationDescriptorPtr confDesc;
279 IOUSBFindInterfaceRequest interfaceRequest;
280 io_iterator_t iterator;
281 io_service_t usbInterfaceRef;
285 err = IOCreatePlugInInterfaceForService(usbDeviceRef, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &iodev, &score);
288 printf("dealWithDevice: unable to create plugin. ret = %08x, iodev = %p\n", err, iodev);
289 return kUproarDeviceErr;
291 err = (*iodev)->QueryInterface(iodev, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID)&(device->dev));
292 (*iodev)->Release(iodev);
297 printf("dealWithDevice: unable to create a device interface. ret = %08x, dev = %p\n", err, dev);
298 return kUproarDeviceErr;
300 err = (*dev)->USBDeviceOpen(dev);
303 printf("dealWithDevice: unable to open device. ret = %08x\n", err);
304 return kUproarDeviceErr;
306 err = (*dev)->GetNumberOfConfigurations(dev, &numConf);
309 printf("dealWithDevice: unable to obtain the number of configurations. ret = %08x\n", err);
310 return kUproarDeviceErr;
312 printf("dealWithDevice: found %d configurations\n", numConf);
313 err = (*dev)->GetConfigurationDescriptorPtr(dev, 0, &confDesc); // get the first config desc (index 0)
316 printf("dealWithDevice:unable to get config descriptor for index 0\n");
317 return kUproarDeviceErr;
319 err = (*dev)->SetConfiguration(dev, confDesc->bConfigurationValue);
322 printf("dealWithDevice: unable to set the configuration\n");
323 return kUproarDeviceErr;
326 interfaceRequest.bInterfaceClass = kIOUSBFindInterfaceDontCare; // requested class
327 interfaceRequest.bInterfaceSubClass = kIOUSBFindInterfaceDontCare; // requested subclass
328 interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; // requested protocol
329 interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare; // requested alt setting
331 err = (*dev)->CreateInterfaceIterator(dev, &interfaceRequest, &iterator);
334 printf("dealWithDevice: unable to create interface iterator\n");
335 return kUproarDeviceErr;
338 while (usbInterfaceRef = IOIteratorNext(iterator))
340 printf("found interface: %#jx\n", (uintmax_t)usbInterfaceRef);
341 err = dealWithInterface(usbInterfaceRef, device);
342 IOObjectRelease(usbInterfaceRef); // no longer need this reference
346 IOObjectRelease(iterator);
348 if ((!found) || (err))
349 return kUproarDeviceErr;
351 return kUproarSuccess;
355 initDevice(USBDeviceContext *device)
357 mach_port_t masterPort = 0;
359 CFMutableDictionaryRef matchingDictionary = 0; // requires <IOKit/IOKitLib.h>
360 short idVendor = TopfieldVendorID;
361 short idProduct = TF5kProdID;
362 CFNumberRef numberRef;
363 io_iterator_t iterator = 0;
364 io_service_t usbDeviceRef;
367 err = IOMasterPort(bootstrap_port, &masterPort);
371 printf("Anchortest: could not create master port, err = %08x\n", err);
374 matchingDictionary = IOServiceMatching(kIOUSBDeviceClassName); // requires <IOKit/usb/IOUSBLib.h>
375 if (!matchingDictionary)
377 printf("Anchortest: could not create matching dictionary\n");
380 numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &idVendor);
383 printf("Anchortest: could not create CFNumberRef for vendor\n");
386 CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBVendorName), numberRef);
387 CFRelease(numberRef);
389 numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &idProduct);
392 printf("Anchortest: could not create CFNumberRef for product\n");
395 CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBProductName), numberRef);
396 CFRelease(numberRef);
399 err = IOServiceGetMatchingServices(masterPort, matchingDictionary, &iterator);
400 matchingDictionary = 0; // this was consumed by the above call
402 while (usbDeviceRef = IOIteratorNext(iterator))
404 printf("Found device %#jx\n", (uintmax_t)usbDeviceRef);
405 err = dealWithDevice(usbDeviceRef, device);
406 IOObjectRelease(usbDeviceRef); // no longer need this reference
411 IOObjectRelease(iterator);
413 mach_port_deallocate(mach_task_self(), masterPort);
414 if ((!found) || (err))
415 return kUproarDeviceErr;
417 return kUproarSuccess;
420 @implementation TFUSBController (PrivateMethods)
422 // ---------------------------------------------------------------------------
423 #pragma mark Low-level USB (PrivateMethods)
424 // ---------------------------------------------------------------------------
426 - (NSData*)sendCommand:(NSData*)fullyPackagedCommand
427 toDevice:(USBDeviceContext*)device expectResponse:(BOOL) getResponse
428 careForReturn:(BOOL) careFactor
431 int cmdLength = [fullyPackagedCommand length];
432 unsigned char outBuffer[cmdLength];
433 memset(outBuffer, 0, cmdLength);
434 // NSLog(@"send: %@", [fullyPackagedCommand description]);
435 [fullyPackagedCommand getBytes:outBuffer];
437 err = doSend(device->dev, device->intf, outBuffer, cmdLength, 2);
440 NSLog(@"sendError: %08x\n");
444 if (! getResponse) return nil;
446 int inLen = 0xFFFF; // i think this is biggest needed?
447 unsigned char inBuf[inLen];
448 memset(inBuf, 0, inLen);
449 err = doRecv(device->dev, device->intf, inBuf, inLen, 2);
452 NSLog(@"inError: %08x\n", err);
456 if (! careFactor) return nil;
457 NSMutableData* data = [NSMutableData dataWithBytes:inBuf length:inLen];
458 data = [dataFormat swap:data];
459 inLen = inBuf[1]*256 + inBuf[0]; // work out how long the response really is. NB data is flipped, but inBuf still isn't
460 return [data subdataWithRange:(NSRange) {0,inLen}];
463 // ---------------------------------------------------------------------------
464 #pragma mark Protocol message sequences (PrivateMethods)
465 // ---------------------------------------------------------------------------
467 - (id) getFileListForPath:(NSString*)path {
468 if (myContext == nil)
471 NSData* hddListCmd = [dataFormat prepareCmdHddDirWithPath:path];
472 if (hddListCmd == nil)
475 [self checkUSB:myContext]; // sends cancel and waits for response
476 int contiguousErrors = 0;
477 [[dh fileList] removeAllObjects];
478 NSData* response = [self sendCommand:hddListCmd toDevice:myContext
479 expectResponse:YES careForReturn:YES];
480 while (response != nil && contiguousErrors++ < 5) {
481 TopfieldUSBEcode ecode = USB_OK;
482 if (![dataFormat isCommunicationBlockValid:response error:NULL ecode:&ecode]) {
483 response = [self sendCommand:[dataFormat prepareFailWithECode:ecode]
484 toDevice:myContext expectResponse:YES careForReturn:YES];
485 } else switch ([dataFormat cmdFromCommunicationBlock:response]) {
487 // [statusField setStringValue:NSLocalizedString(@"LAST_ERROR", @"Error on last command.")];
488 [[dh fileList] removeAllObjects];
489 response = [self sendCommand:hddListCmd toDevice:myContext
490 expectResponse:YES careForReturn:YES];
493 contiguousErrors = 0;
495 // Swapping sometimes adds a byte of padding. The following uses
496 // only complete 144-byte structures and so ignores such padding.
497 for (i=0; 8+(i+1)*114 <= [response length]; i++) {
498 NSData* typeFile = [response subdataWithRange:(NSRange) {8+i*114,114}];
499 NSMutableDictionary* tfFile = [dh newTFFileFromSwappedHexData:typeFile];
500 if (![[tfFile objectForKey:@"name"] isEqualToString:@".."]) {
501 [dh convertRawDataToUseful:tfFile];
502 [[dh fileList] addObject:tfFile];
506 response = [self sendCommand:[dataFormat prepareSuccess]
507 toDevice:myContext expectResponse:YES careForReturn:YES];
509 case USB_DataHddDirEnd:
510 contiguousErrors = 0;
514 [self checkUSB:myContext]; // cancel whatever is going on
515 [[dh fileList] removeAllObjects];
516 response = [self sendCommand:hddListCmd toDevice:myContext
517 expectResponse:YES careForReturn:YES];
522 [tableView reloadData];
523 [[self uiElements] tableView:tableView didClickTableColumn:[[self uiElements]selectedColumn]];
524 [[self uiElements] tableView:tableView didClickTableColumn:[[self uiElements]selectedColumn]]; //twice so get the same sort as before
528 - (void) turnTurboOn:(BOOL)turnOn {
529 if (![[[self uiElements] isConnected] intValue]) return;
530 [self checkUSB:myContext];
531 NSData* turboCommand = [dataFormat prepareCmdTurboWithMode:(turnOn ? 1 : 0)];
532 [self sendCommand:turboCommand toDevice:myContext expectResponse:YES careForReturn:NO];
535 - (void) renameFile:(NSString*)oldName withName:(NSString*)newName
536 atPath:(NSString*)currentPath
538 NSLog(@"%@,%@,%@", oldName, newName, currentPath);
539 [self checkUSB:myContext];
540 NSString* oldFname = [dataFormat fnameForFile:oldName atPath:currentPath];
541 NSString* newFname = [dataFormat fnameForFile:newName atPath:currentPath];
542 NSData* fileRenCmd = [dataFormat prepareCmdHddRenameFromFname:oldFname
544 [self sendCommand:fileRenCmd toDevice:myContext expectResponse:YES careForReturn:NO];
547 - (void) makeFolder:(NSString*)newName atPath:(NSString*)currentPath {
548 [self checkUSB:myContext];
549 NSString* fname = [dataFormat fnameForFile:newName atPath:currentPath];
550 NSData* newFoldCmd = [dataFormat prepareCmdHddCreateDirWithFname:fname];
551 NSData* usbCancel = [dataFormat prepareCancel];
552 [self sendCommand:usbCancel toDevice:myContext expectResponse:YES careForReturn:NO];
553 [self sendCommand:newFoldCmd toDevice:myContext expectResponse:YES careForReturn:NO];
556 - (void) checkUSB:(USBDeviceContext*)device {
557 NSData* usbCancel = [dataFormat prepareCancel];
559 for (retry = 0; retry < 8; ++retry) {
560 NSData* block = [self sendCommand:usbCancel toDevice:device
561 expectResponse:YES careForReturn:YES];
562 NSError* error = nil;
563 if (![dataFormat isCommunicationBlockValid:block
564 error:&error ecode:NULL]) {
565 NSLog(@"bad response to cancel: %@", [error localizedDescription]);
568 TopfieldUSBCmd cmd = [dataFormat cmdFromCommunicationBlock:block];
569 if (cmd != USB_Success) {
570 NSLog(@"response to cancel was %#x rather than success", (unsigned) cmd);
575 // tell someone that no longer connected here??
578 // ---------------------------------------------------------------------------
579 #pragma mark User interface (PrivateMethods)
580 // ---------------------------------------------------------------------------
582 - (UIElements*) uiElements {
583 return [NSApp delegate];
586 - (void) updateProgress:(NSDictionary*)inDict {
587 NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
588 double offset = [[inDict objectForKey:@"offset"] doubleValue];
589 double size = [[inDict objectForKey:@"size"] doubleValue];
590 NSDate* startTime = [inDict objectForKey:@"startTime"];
591 [progressBar setDoubleValue:((double)offset/size*100)];
592 [progressBar displayIfNeeded];
593 [progressTime setStringValue:[self elapsedTime:[[NSDate date] timeIntervalSinceDate:startTime]]];
594 [progressTime displayIfNeeded];
598 - (NSString*) elapsedTime:(NSTimeInterval)totalSeconds {
601 char sp = 20; //padding
602 int hours = (totalSeconds / 3600); // returns number of whole hours fitted in totalSecs
605 int minutes = ((totalSeconds / 60) - hours*60); // Whole minutes
608 int seconds = ((long) totalSeconds % 60); // Here we can use modulo to get num secs NOT fitting in whole minutes (60 secs)
611 return [NSString stringWithFormat:@"%c%i:%c%i:%c%i", hp, hours, mp, minutes, sp, seconds];
614 // ---------------------------------------------------------------------------
615 #pragma mark Queue management (PrivateMethods)
616 // ---------------------------------------------------------------------------
618 - (BOOL) hasPriorityTransfer {
620 @synchronized (self) {
621 ret = ([priorityTransferQueue count] > 0);
626 - (void) getNextTransfer:(NSDictionary**)transferOut
627 queue:(NSMutableArray**)queueOut
629 NSDictionary* transfer = nil;
630 NSMutableArray* queue = nil;
632 @synchronized (self) {
633 if ([priorityTransferQueue count] > 0) {
634 queue = priorityTransferQueue;
635 transfer = [queue objectAtIndex:0];
636 } else if ([transferQueue count] > 0) {
637 queue = transferQueue;
638 transfer = [queue objectAtIndex:0];
641 // The queue exists as long as the TFUSBController does
642 // so it need not be retained and autoreleased here.
644 [[transfer retain] autorelease];
647 if (transferOut != NULL)
648 *transferOut = transfer;
649 if (queueOut != NULL)
653 - (void) removeTransfer:(NSDictionary*)transfer
654 fromQueue:(NSMutableArray*)queue
656 @synchronized (self) {
657 [queue removeObjectIdenticalTo:transfer];
663 @implementation TFUSBController
665 // ---------------------------------------------------------------------------
666 #pragma mark Low-level USB
667 // ---------------------------------------------------------------------------
669 - (void) closeDevice:(USBDeviceContext *)device
671 IOUSBInterfaceInterface197 **intf;
672 IOUSBDeviceInterface197 **dev;
678 err = (*intf)->USBInterfaceClose(intf);
681 printf("dealWithInterface: unable to close interface. ret = %08x\n", err);
683 err = (*intf)->Release(intf);
686 printf("dealWithInterface: unable to release interface. ret = %08x\n", err);
689 err = (*dev)->USBDeviceClose(dev);
692 printf("dealWithDevice: error closing device - %08x\n", err);
693 (*dev)->Release(dev);
695 err = (*dev)->Release(dev);
698 printf("dealWithDevice: error releasing device - %08x\n", err);
702 - (USBDeviceContext*) initializeUSB {
703 USBDeviceContext *device;
706 device = malloc(sizeof(USBDeviceContext));
707 err = initDevice(device);
709 printf("Could not connect to Topfield\n");
713 printf("Connected to Topfield\n\n");
718 // ---------------------------------------------------------------------------
719 #pragma mark Protocol message sequences
720 // ---------------------------------------------------------------------------
722 - (int) getFile:(NSDictionary*)fileInfo forPath:(NSString*)currentPath
723 toSaveTo:(NSString*)savePath beginAtOffset:(unsigned long long) offset
724 withLooping:(BOOL)looping existingTime:(NSTimeInterval)existingTime {
725 // [progressBar setDoubleValue:0];
726 // [progressTime setDoubleValue:0];
727 NSString* nameOnToppy = [fileInfo objectForKey:@"name"];
728 NSNumber* fileSize = [fileInfo objectForKey:@"fileSize"];
730 [[[self uiElements] currentlyField] setStringValue:
731 [NSLocalizedString(@"DOWNLOADING", @"Downloading: ")
732 stringByAppendingString:nameOnToppy]];
733 [[[self uiElements] connectLight] setImage:[NSImage imageNamed:@"blink.tiff"]];
734 [[[self uiElements] currentlyField] displayIfNeeded];
736 //prepackage commands to send
737 NSData* fileSendCmd = [dataFormat
738 prepareCmdHddFileSendWithDirection:USB_FileToHost
739 fname:[dataFormat fnameForFile:nameOnToppy atPath:currentPath]
741 NSData* usbSuccess = [dataFormat prepareSuccess];
742 NSData* usbCancel = [dataFormat prepareCancel];
746 [self turnTurboOn:YES];
748 [self checkUSB:myContext]; //turbo has a check itself
750 NSData* receivedBlock = [self sendCommand:fileSendCmd toDevice:myContext
751 expectResponse:YES careForReturn:YES];
752 NSError* error = nil;
753 if (![dataFormat isCommunicationBlockValid:receivedBlock
754 error:&error ecode:NULL]) {
755 NSLog(@"Malformed response from Toppy after %@: %@",
756 [dataFormat nameOfCmd:USB_CmdHddFileSend],
757 [error localizedDescription]);
758 [self turnTurboOn:NO];
761 TopfieldUSBCmd receivedCmd = [dataFormat
762 cmdFromCommunicationBlock:receivedBlock];
763 if (receivedCmd == USB_Fail) {
764 TopfieldUSBEcode ecode = [dataFormat ecodeFromFail:receivedBlock];
765 NSLog(@"Download fails. Toppy reports error %@",
766 [dataFormat nameOfEcode:ecode]);
767 // TODO: retry if USB_Err_CRC?
768 [self turnTurboOn:NO];
769 } else if (receivedCmd != USB_DataHddFileStart) {
770 NSLog(@"Unexpected response from Toppy during download. "
771 "Expected %@ but received %@",
772 [dataFormat nameOfCmd:USB_DataHddFileStart],
773 [dataFormat nameOfCmd:receivedCmd]);
774 [self turnTurboOn:NO];
779 NSDate* startTime = [NSDate date];
780 startTime = [startTime addTimeInterval:(0-existingTime)];
781 // acknowledge the USB_DataHddFileStart
782 receivedBlock = [self sendCommand:usbSuccess toDevice:myContext
783 expectResponse:YES careForReturn:YES];
784 if (![dataFormat isCommunicationBlockValid:receivedBlock
785 error:&error ecode:NULL]) {
786 NSLog(@"Malformed block from Toppy after %@: %@",
787 [dataFormat nameOfCmd:USB_Success],
788 [error localizedDescription]);
789 [self turnTurboOn:NO];
792 receivedCmd = [dataFormat cmdFromCommunicationBlock:receivedBlock];
793 if (receivedCmd != USB_DataHddFileData) {
794 NSLog(@"Unexpected response from Toppy during download. "
795 "Expected %@ but received %@",
796 [dataFormat nameOfCmd:USB_DataHddFileData],
797 [dataFormat nameOfCmd:receivedCmd]);
798 [self turnTurboOn:NO];
801 if ([receivedBlock length] < 16) {
802 NSLog(@"Incorrect Data length in %@",
803 [dataFormat nameOfCmd:receivedCmd]);
804 [self turnTurboOn:NO];
807 SInt64 receivedOffset = [dataFormat offsetFromDataHddFileData:receivedBlock];
808 if (receivedOffset != offset) {
809 NSLog(@"File offset skipped from %lld to %lld!",
810 offset, receivedOffset);
811 [self turnTurboOn:NO];
815 // clean up data and prepare path to save it
816 NSData* finalData = [receivedBlock subdataWithRange:(NSRange) {16,[receivedBlock length]-16}];
818 // first initialize a file of the right name, as NSFileHandle requires an existing file to work on
820 [[NSData dataWithBytes:"\0" length:1] writeToFile:savePath atomically:NO]; // write 0x00 to initialize (overwritten later)
821 NSFileHandle* outFile = [NSFileHandle fileHandleForWritingAtPath:savePath];
822 [outFile seekToFileOffset:offset];
823 [outFile writeData:finalData];
824 BOOL returnSuccess = YES;
825 if (looping) { // loop for multiple data sends (ie files > 64k)
827 if ([fileSize isGreaterThan:[NSNumber numberWithDouble:1048576]]) {
829 NSLog(@"large file detected - low GUI update rate");
832 // Because this loop usually runs for a long time, use a separate
833 // NSAutoreleasePool for each iteration. Keep the current pool
834 // in a variable outside the loop so that it can be released when
835 // the loop is left with a break statement. (@finally is another
836 // alternative, but it would require extra code in order to avoid
837 // releasing exception objects too early.)
838 NSAutoreleasePool* pool = nil;
839 int dataBlocksReceived = 0;
840 TopfieldUSBEcode sendEcode = USB_OK;
845 pool = [[NSAutoreleasePool alloc] init];
847 if (![[[self uiElements] isConnected] boolValue]) {
848 NSLog(@"Aborting the transfer because the Toppy has been disconnected.");
852 if ([self hasPriorityTransfer]) {
853 NSLog(@"Suspending the transfer to make way for a priority transfer.");
854 [self addTransfer:[NSDictionary dictionaryWithObjectsAndKeys:
855 fileInfo,@"filename", currentPath,@"path", savePath,@"savePath",
856 [NSNumber numberWithUnsignedLongLong:[outFile offsetInFile]],@"offset",
857 @"download",@"transferType", [NSNumber numberWithBool:YES],@"looping",
858 [NSNumber numberWithInt:[[NSDate date] timeIntervalSinceDate:startTime]],@"existingTime",
860 atIndex:1]; // nb adding to index 1 as the current transfer lies at 0 and will be deleted soon
861 [self sendCommand:usbCancel toDevice:myContext
862 expectResponse:YES careForReturn:NO];
868 if (sendEcode == USB_OK)
869 sendBlock = usbSuccess;
871 sendBlock = [dataFormat prepareFailWithECode:sendEcode];
872 receivedBlock = [self sendCommand:sendBlock toDevice:myContext
873 expectResponse:YES careForReturn:YES];
875 if (![dataFormat isCommunicationBlockValid:receivedBlock
876 error:&error ecode:&sendEcode]) {
877 NSLog(@"Malformed block from Toppy after %@: %@",
878 [dataFormat nameOfCmd:USB_Success],
879 [error localizedDescription]);
882 receivedCmd = [dataFormat cmdFromCommunicationBlock:receivedBlock];
883 if (receivedCmd == USB_DataHddFileData) {
884 if ([receivedBlock length] < 16) {
885 NSLog(@"Received %@ is too short, only %d bytes",
886 [dataFormat nameOfCmd:receivedCmd],
887 (int) [receivedBlock length]);
888 sendEcode = USB_Err_Size;
891 SInt64 receivedOffset = [dataFormat
892 offsetFromDataHddFileData:receivedBlock];
893 if (receivedOffset != [outFile offsetInFile]) {
894 NSLog(@"File offset skipped from %lld to %lld!",
895 [outFile offsetInFile], receivedOffset);
899 NSData* fileData = [receivedBlock
900 subdataWithRange:(NSRange) {16, [receivedBlock length] - 16}];
901 [outFile writeData:fileData];
903 dataBlocksReceived++;
904 if (dataBlocksReceived < 8 || dataBlocksReceived % updateRate == 0) {
905 [self performSelectorOnMainThread:@selector(updateProgress:)
906 withObject:[NSDictionary dictionaryWithObjectsAndKeys:
907 [NSNumber numberWithDouble:(double)[outFile offsetInFile]], @"offset",
908 fileSize, @"size", startTime, @"startTime", nil]
910 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];
913 } else if (receivedCmd == USB_DataHddFileEnd) {
914 [self sendCommand:usbSuccess toDevice:myContext
915 expectResponse:NO careForReturn:NO];
919 NSLog(@"Unexpected response from Toppy during download. "
920 "Expected %@ but received %@",
921 [dataFormat nameOfCmd:USB_DataHddFileData],
922 [dataFormat nameOfCmd:receivedCmd]);
923 [self sendCommand:usbCancel toDevice:myContext
924 expectResponse:YES careForReturn:NO];
935 [outFile synchronizeFile];
937 //now add the right modification date to the file
938 [[NSFileManager defaultManager]
939 changeFileAttributes:[NSDictionary
940 dictionaryWithObject:[fileInfo objectForKey:@"date"]
941 forKey:@"NSFileModificationDate"]
943 [self turnTurboOn:NO];
944 if (looping) [[self uiElements] finishTransfer];
945 return returnSuccess ? 1 : 0;
948 - (void) uploadFile:(NSString*)fileToUpload ofSize:(long long)size
949 fromPath:(NSString*)curPath withAttributes:(NSData*)typeFile
950 atOffset:(unsigned long long)offset
951 existingTime:(NSTimeInterval)existingTime
953 NSLog(@"upload: %@,%@,%qu", fileToUpload, curPath, offset);
954 USBDeviceContext* dev = myContext;
955 // prepare Send command
956 NSMutableArray* array = [NSMutableArray arrayWithArray:[fileToUpload componentsSeparatedByString:@"/"]];
957 NSMutableString* fname = [NSMutableString stringWithString:[array lastObject]];
958 char dir = USB_FileToDevice;
959 NSMutableData* build = [NSMutableData dataWithBytes:&dir length:1];
960 short int nsize = [fname length]+[curPath length]+2;// one for slash, one for padding 0x00
961 const UInt16 nsize_bigendian = EndianU16_NtoB(nsize);
962 [build appendData:[NSData dataWithBytes:&nsize_bigendian length:2]];
963 NSData* d = [curPath dataUsingEncoding:NSISOLatin1StringEncoding];
964 [build appendData:d];
965 dir = 0x5c; // 0x5c = "/"
966 [build appendData:[NSData dataWithBytes:&dir length:1]];
967 [build appendData:[fname dataUsingEncoding:NSISOLatin1StringEncoding]];// may need to pad to 95...
968 if ([fname length] < 95)
969 [build increaseLengthBy:95-[fname length]];
971 [build appendData:[NSData dataWithBytes:&dir length:1]];
972 UInt64 offset_bigendian = EndianU64_NtoB(offset);
973 [build appendData:[NSData dataWithBytes:&offset_bigendian length:8]];
975 // prepackage commands to send
976 NSData* fileSendCmd = [dataFormat prepareCommand:USB_CmdHddFileSend
978 NSData* fileStartCmd = [dataFormat prepareCommand:USB_DataHddFileStart
980 NSData* fileEndCmd = [dataFormat prepareDataHddFileEnd];
983 NSDate* startTime = [NSDate date];
984 startTime = [startTime addTimeInterval:(0-existingTime)];
987 [self turnTurboOn:YES];
990 // now the proper commands
991 NSData *data = [self sendCommand:fileSendCmd toDevice:dev expectResponse:YES careForReturn:YES];
992 data = [self sendCommand:fileStartCmd toDevice:dev expectResponse:YES careForReturn:YES];
993 const UInt32 rW_bigendian = EndianU32_NtoB(USB_Success);
994 NSData* responseWanted = [NSData dataWithBytes:&rW_bigendian length:4];
995 NSData* responseToCheck = nil;
997 NSFileHandle* fileHandle = [NSFileHandle fileHandleForReadingAtPath:fileToUpload];
998 [fileHandle seekToFileOffset:offset];
1000 if ([self hasPriorityTransfer]) {
1001 //break out and create a new transfer to continue it
1002 NSLog(@"pausing upload");
1003 [self addTransfer:[NSDictionary dictionaryWithObjectsAndKeys:
1004 fileToUpload,@"filename",
1005 [NSNumber numberWithUnsignedLongLong:size],@"fileSize",
1006 curPath,@"path",typeFile,@"attributes",@"upload",@"transferType",
1007 [NSNumber numberWithUnsignedLongLong:offset],@"offset",
1008 [NSNumber numberWithInt:[[NSDate date] timeIntervalSinceDate:startTime]],@"existingTime",
1010 atIndex:1]; //nb use index 1 as current transfer is at 0 and will be deleted
1011 [self turnTurboOn:NO];
1014 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1015 offset_bigendian = EndianU64_NtoB(offset);
1016 NSMutableData* fileData = [NSMutableData dataWithBytes:&offset_bigendian length:8];
1017 [fileData appendData:[fileHandle readDataOfLength:rate]];
1019 NSData* fileDataCmd = [dataFormat prepareCommand:USB_DataHddFileData
1021 data = [self sendCommand:fileDataCmd toDevice:dev expectResponse:YES careForReturn:YES];
1022 if ([data length] >= 8)
1023 responseToCheck = [data subdataWithRange:(NSRange) {4,4}];
1024 else responseToCheck = nil;
1025 if (responseToCheck == nil || ![responseWanted isEqualToData:responseToCheck])
1027 [NSThread detachNewThreadSelector:@selector(updateProgress:) toTarget:self
1028 withObject:[NSDictionary dictionaryWithObjectsAndKeys:
1029 [NSNumber numberWithDouble:(double)offset], @"offset",
1030 [NSNumber numberWithDouble:(double)size], @"size",
1031 startTime, @"startTime", nil]];
1034 } while (offset < size && [[[self uiElements] isConnected] intValue]);
1035 if ([[[self uiElements] isConnected] intValue])
1036 data = [self sendCommand:fileEndCmd toDevice:dev expectResponse:YES careForReturn:YES];
1037 [fileHandle closeFile];
1038 [[self uiElements] goToPath:[[self uiElements]currentPath]];
1039 [[self uiElements] tableView:tableView didClickTableColumn:[[self uiElements]selectedColumn]];
1040 [[self uiElements] tableView:tableView didClickTableColumn:[[self uiElements]selectedColumn]]; //twice so get the same sort as before
1041 [self turnTurboOn:NO];
1042 [[self uiElements] finishTransfer];
1045 - (void) deleteFile:(NSDictionary*)fileInfo fromPath:(NSString*)currentPath {
1046 [self checkUSB:myContext];
1047 NSString* fname = [dataFormat fnameForFile:[fileInfo objectForKey:@"name"]
1048 atPath:currentPath];
1049 NSData* fileDelCmd = [dataFormat prepareCmdHddDelWithFname:fname];
1050 [self sendCommand:fileDelCmd toDevice:myContext expectResponse:YES careForReturn:NO];
1053 // ---------------------------------------------------------------------------
1054 #pragma mark Queue management
1055 // ---------------------------------------------------------------------------
1057 - (void) addPriorityTransfer:(id)newTransfer {
1058 @synchronized (self) {
1059 [priorityTransferQueue addObject:newTransfer];
1060 NSLog(@"%i,p:%i-%@",[transferQueue count],[priorityTransferQueue count],[newTransfer objectForKey:@"transferType"]);
1064 - (void) addTransfer:(id)newTransfer atIndex:(int)front { //-1 for at end
1065 @synchronized (self) {
1066 if (front>=0) [transferQueue insertObject:newTransfer atIndex:front];
1067 else [transferQueue addObject:newTransfer];
1068 NSLog(@"%i,p:%i",[transferQueue count],[priorityTransferQueue count]);
1072 - (void) clearQueues {
1073 @synchronized (self) {
1074 [priorityTransferQueue removeAllObjects];
1075 [transferQueue removeAllObjects];
1076 [pausedQueue removeAllObjects];
1080 - (BOOL) hasCurrentTransfer {
1082 @synchronized (self) {
1083 ret = ([transferQueue count] > 0);
1088 - (id) currentTransferInfo {
1090 @synchronized (self) {
1091 if ([transferQueue count] > 0) {
1092 transfer = [transferQueue objectAtIndex:0];
1093 // Make a copy of the object so that it can be safely manipulated
1094 // in a different thread from the original.
1095 transfer = [[transfer copy] autorelease];
1101 - (id) firstPausedTransferInfo {
1103 @synchronized (self) {
1104 if ([pausedQueue count] > 0) {
1105 transfer = [pausedQueue objectAtIndex:0];
1106 // Make a copy of the object so that it can be safely manipulated
1107 // in a different thread from the original.
1108 transfer = [[transfer copy] autorelease];
1114 - (void) transfer:(id)sender {
1115 while ([[[self uiElements] isConnected] intValue]) {
1116 NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
1117 NSDictionary* currentTransfer = nil;
1118 NSMutableArray* queue = nil;
1119 [self getNextTransfer:¤tTransfer queue:&queue];
1120 if (currentTransfer != nil) {
1121 NSString* transferType = [currentTransfer objectForKey:@"transferType"];
1122 if ([transferType isEqualToString:@"fileList"]) {
1123 [self getFileListForPath:[currentTransfer objectForKey:@"path"]];
1125 else if ([transferType isEqualToString:@"turbo"]) {
1126 // [self turnTurboOn:[[currentTransfer objectForKey:@"turboOn"] boolValue]];
1128 else if ([transferType isEqualToString:@"rename"]) {
1129 [self renameFile:[currentTransfer objectForKey:@"oldName"]
1130 withName:[currentTransfer objectForKey:@"newName"]
1131 atPath:[currentTransfer objectForKey:@"path"]];
1133 else if ([transferType isEqualToString:@"newFolder"]) {
1134 [self makeFolder:[currentTransfer objectForKey:@"newName"] atPath:[currentTransfer objectForKey:@"path"]];
1136 else if ([transferType isEqualToString:@"delete"]) {
1137 [self deleteFile:[currentTransfer objectForKey:@"file"] fromPath:[currentTransfer objectForKey:@"path"]];
1139 else if ([transferType isEqualToString:@"pause"]) {
1140 @synchronized (self) {
1141 if ([transferQueue count] != 0) {
1142 NSArray* toPause = [transferQueue filteredArrayUsingPredicate:
1143 [NSPredicate predicateWithFormat:@"(filename = %@)",
1144 [currentTransfer objectForKey:@"filename"]]];
1145 if ([toPause count] > 1) NSLog(@"multiple pauses?");
1147 [pausedQueue addObjectsFromArray:toPause];
1148 [transferQueue removeObjectsInArray:toPause];
1153 else if ([transferType isEqualToString:@"resume"]) {
1154 @synchronized (self) {
1155 if ([pausedQueue count] != 0) {
1156 NSArray* toResume = [pausedQueue filteredArrayUsingPredicate:
1157 [NSPredicate predicateWithFormat:@"(filename = %@)",
1158 [currentTransfer objectForKey:@"filename"]]];
1159 if ([toResume count] > 1) NSLog(@"multiple resumes?");
1161 [transferQueue addObjectsFromArray:toResume];
1162 [pausedQueue removeObjectsInArray:toResume];
1167 else if ([transferType isEqualToString:@"download"]) {
1168 [self getFile:[currentTransfer objectForKey:@"filename"]
1169 forPath:[currentTransfer objectForKey:@"path"]
1170 toSaveTo:[currentTransfer objectForKey:@"savePath"]
1171 beginAtOffset:[[currentTransfer objectForKey:@"offset"] unsignedLongLongValue]
1172 withLooping:[[currentTransfer objectForKey:@"looping"] boolValue]
1173 existingTime:[[currentTransfer objectForKey:@"existingTime"] intValue]];
1175 else if ([transferType isEqualToString:@"upload"]) {
1176 [self uploadFile:[currentTransfer objectForKey:@"filename"]
1177 ofSize:[[currentTransfer objectForKey:@"fileSize"] unsignedLongLongValue]
1178 fromPath:[currentTransfer objectForKey:@"path"]
1179 withAttributes:[currentTransfer objectForKey:@"attributes"]
1180 atOffset:[[currentTransfer objectForKey:@"offset"] unsignedLongLongValue]
1181 existingTime:[[currentTransfer objectForKey:@"existingTime"] intValue]];
1184 NSLog(@"Unrecognized transfer type: %@", transferType);
1186 [self removeTransfer:currentTransfer fromQueue:queue];
1193 // ---------------------------------------------------------------------------
1194 #pragma mark User interface
1195 // ---------------------------------------------------------------------------
1197 - (void) setProgressBar:(NSProgressIndicator*)bar
1198 time:(NSTextField*)timeField
1199 turbo:(NSButton*)turbo
1202 progressTime = timeField;
1206 - (void) setDH:(id)newDH tableView:(id)tv {
1211 // ---------------------------------------------------------------------------
1212 #pragma mark Miscellaneous
1213 // ---------------------------------------------------------------------------
1216 if (self = [super init]) {
1219 transferQueue = [[NSMutableArray arrayWithCapacity:1] retain];
1220 pausedQueue = [[NSMutableArray arrayWithCapacity:1] retain];
1221 priorityTransferQueue = [[NSMutableArray arrayWithCapacity:1] retain];
1222 dataFormat = [TFDataFormat new];
1228 [dataFormat release];
1233 return connectedSpeed;
1236 - (void) setDebug:(int)mode {
1238 NSLog(@"Debug level: %i", debug);
1241 - (void) setRate:(int)newRate {
1243 NSLog(@"New rate set: %i", rate);