#!/usr/bin/env python
#
# NOTE: If you need this under a different license, just ask me!
# 
# Copyright (C) 2007  Christian Hergert <christian.hergert@gmail.com>
# 
# 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.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os

from twisted.internet import defer
from zope.interface import implements

def vfsopen(uri, mode = 'r'):
    """
    This method returns a file-like object for a uri using the async support
    in gnomevfs. This makes the common read/write methods return Deferreds
    instead of immediately.
    
    @uri: the uri of the file
    @mode: the file mode to open
    
    Returns: (a Deferred)
    """
    import gnomevfs
    from gnomevfs import async
    
    class AsyncGnomeVfsFile(object):
        """
        AsyncGnomeVfsFile is a wrapper file for gnomevfs that is asynchronous
        in nature while also using twisted deferreds.
        
        Under the hood, gnomevfs.async is used.
        """
        
        def __init__(self, uri):
            self.uri = uri
            
        def open(self, mode = 'r'):
            """
            This method will open the uri for the mode specified by @mode.
            
            @mode: any of 'a', 'r', 'w', 'rw', 'ra'
            
            Returns: (a Deferred)
            """
            self.mode = mode
            openMode = gnomevfs.OPEN_NONE
            
            if 'r' in self.mode:
                openMode |= gnomevfs.OPEN_READ
            if 'a' in self.mode:
                openMode |= gnomevfs.OPEN_WRITE
            if 'w' in self.mode:
                openMode |= gnomevfs.OPEN_WRITE
                openMode |= gnomevfs.OPEN_TRUNCATE
            
            df = defer.Deferred()
            self._handle = async.open(uri, self._open,
                                      open_mode = openMode,
                                      data = df)
            return df
            
        def _open(self, handle, error, data):
            if error:
                data.errback(error)
            else:
                data.callback(self)
            
        def _get_file_info(self, handle, results, data):
            assert len(results) == 1
            assert data
            
            _, error, fileInfo = results[0]
            
            if error:
                data.errback(error)
            else:
                data.callback(fileInfo)
        
        @defer.inlineCallbacks
        def read(self, size = -1):
            """
            Reads @size bytes from the gnomevfs file-handle.
            
            @size: the number of bytes to read
            
            Return: (a Deferred) callback(data)
            """
            assert hasattr(self, '_handle')
            
            if size < 0:
                df = defer.Deferred()
                async.get_file_info(self.uri, self._get_file_info, data = df)
                fileInfo = yield df
                size = fileInfo.size
                
            df = defer.Deferred()
            self._handle.read(size, self._read, data = df)
            data = yield df
            
            defer.returnValue(data)
            
        def _read(self, handle, buffer, error, bytesRead, data = None):
            assert hasattr(self, '_handle')
            assert data
            
            if error:
                data.errback(error)
            else:
                data.callback(buffer)
            
        def write(self, str):
            """
            Writes size bytes from the gnomevfs handle for this file.
            
            @str: the bytes to write to the file
            
            Returns: (a Deferred) callback(None)
            """
            assert hasattr(self, '_handle')
            
            df = defer.Deferred()
            self._handle.write(str, self._write, data = df)
            return df
            
        def _write(self, handle, bytesWritten, error, bytesToWrite, data):
            assert hasattr(self, '_handle')
            assert data
            
            if error:
                data.errback(error)
            elif bytesWritten != bytesToWrite:
                unwritten = bytesToWrite - bytesWritten
                data.errback(IOError('Error writing last %d bytes' % unwritten))
            else:
                data.callback(None)
            
        def flush(self):
            """
            GnomeVfs does not support flushing to disk. This method is here
            as a stub.
            """
            return defer.succeed(None)
        
        def close(self):
            """
            This method will close the handle for the file and free our
            resources.
            """
            assert hasattr(self, '_handle')
            
            df = defer.Deferred()
            self._handle.close(self._close, df)
            return df
        
        def _close(self, handle, error, data):
            assert data
            
            if error:
                data.errback(error)
            else:
                data.callback(None)
            
    asyncFile = AsyncGnomeVfsFile(uri)
    return asyncFile.open(mode)

if __name__ == '__main__':
	import sys
	from twisted.internet import gtk2reactor; gtk2reactor.install()
	from twisted.internet import reactor
	stop = lambda *_: reactor.stop()
	f = file('/tmp/gnomevfsfiletest.txt', 'w')
	f.write('blah blah blah blah')
	f.close()
	df = vfsopen('file:///tmp/gnomevfsfiletest.txt')
	df.addCallback(
		lambda s: s.read()).addCallback(
		lambda d: sys.stdout.write(d + '\n') or sys.stdout.flush()).addBoth(
		stop, stop)
	reactor.run()
