Search code examples
objective-cfile-copying

Block per block file copying


Is there a good way to copy some file or directory(with files and subdirectories) block per block in Mac OS with objective c?


Solution

  • You can copy a file per blocks by creating a NSInputStream and a NSOutputStream and scheduling them in a NSRunLoop. Then, when you get bytes from the input stream, you write them in a buffer and when the output stream is ready, you copy the contents of the buffer into it.

    @synthesize numberOfBytesTransferred = _numberOfBytesTransferred;
    
    static const NSUInteger blockSize = 65536; // should be adjusted
    
    - (void)startWithSourcePath:(NSString *)srcPath
                destinationPath:(NSString *)dstPath
              completionHandler:(void (^)(NSUInteger, NSError *))completionHandler
    {
        _buffer = malloc(blockSize);
        _numberOfBytesTransferred = _bufferLength = _bufferOffset = 0;
        _completionHandler = [completionHandler copy];
        _srcStream = [[NSInputStream alloc] initWithFileAtPath:srcPath];
        _dstStream = [[NSOutputStream alloc] initToFileAtPath:dstPath append:NO];
        _srcStream.delegate = self;
        _dstStream.delegate = self;
        [_srcStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
        [_dstStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
        [_srcStream open];
        [_dstStream open];
    }
    
    - (void)processStreams
    {    
        if ( _srcStream.hasBytesAvailable && ! _bufferLength )
            _bufferLength = [_srcStream read:_buffer maxLength:blockSize];
    
        if ( _dstStream.hasSpaceAvailable && _bufferLength ) {
            NSInteger length = [_dstStream write:_buffer + _bufferOffset maxLength:_bufferLength];
            _bufferOffset += length;
            _bufferLength -= length;
        }
    
        if ( _bufferOffset && !_bufferLength ) {
            [self willChangeValueForKey:@"numberOfBytesTransferred"];
            _numberOfBytesTransferred += _bufferOffset;
            _bufferOffset = 0;
            [self didChangeValueForKey:@"numberOfBytesTransferred"];
        }
    
        if ( _dstStream.hasSpaceAvailable && NSStreamStatusAtEnd == _srcStream.streamStatus ) {
            [_srcStream close];
            [_dstStream close];
            _completionHandler(_numberOfBytesTransferred, nil);
        }
    }
    
    - (void)cancel
    {
        [_srcStream close];
        [_dstStream close];
    }
    
    - (void)pause
    {
        _paused = YES;
    }
    
    - (void)resume
    {
        _paused = NO;
        [self processStreams];
    }
    
    - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
    {
        if ( NSStreamEventErrorOccurred == eventCode ) {
            [_srcStream close];
            [_dstStream close];
            _completionHandler(_numberOfBytesTransferred, stream.streamError);
            return;
        }
    
        if ( ! _paused )
            [self processStreams];
    }
    

    Copying the contents of a directory will require to enumerate through the contents of the directory. You can create an instance of NSDirectoryEnumerator with -[NSFileManager enumeratorAtPath:].

    Once you have the enumerator, you call nextObject then get the file attributes:

    • If the file is a directory, you create a new directory in the destination directory
    • If the file is a regular file, you start a file copy task and wait until the completion handler is called.