Пример #1
0
 def __init__(self, objectStore, outputQueue):
     BaseWorker.__init__(self)
     self.objectStore = objectStore
     self.outputQueue = outputQueue
     self.localStore = LocalProvider()
     c = config.Config()
     self.localSyncPath = c.get_home_folder()
     self.state = statestore.StateStore(c.username)
     self._isRunning = True
Пример #2
0
 def __init__(self, objectStore, outputQueue):
     BaseWorker.__init__(self)
     self.objectStore = objectStore
     self.outputQueue = outputQueue
     self.localStore = LocalProvider()
     c = config.Config()
     self.localSyncPath = c.get_home_folder()
     self.tempDownloadFolder = c.get_temporary_folder()
     self.state = statestore.StateStore(c.username)
     self.lock = threading.Lock()
     self.running = True
     self.trashFolder = c.get_trash_folder()
Пример #3
0
 def __init__(self, objectStore, outputQueue):
     BaseWorker.__init__(self)
     self.objectStore = objectStore
     self.outputQueue = outputQueue
     self.localStore = LocalProvider()
     c = config.Config()
     self.localSyncPath = c.get_home_folder()
     self.tempDownloadFolder = c.get_temporary_folder()
     self.state = statestore.StateStore(c.username)
     self.lock = threading.Lock()
     self.running = True
     self.trashFolder = c.get_trash_folder()
Пример #4
0
class Download(BaseWorker):
    def __init__(self, objectStore, outputQueue):
        BaseWorker.__init__(self)
        self.objectStore = objectStore
        self.outputQueue = outputQueue
        self.localStore = LocalProvider()
        c = config.Config()
        self.localSyncPath = c.get_home_folder()
        self.tempDownloadFolder = c.get_temporary_folder()
        self.state = statestore.StateStore(c.username)
        self.lock = threading.Lock()
        self.running = True
        self.trashFolder = c.get_trash_folder()

    def stop(self):
        logging.info("Download::stop")
        self.objectStore.stop()
        self.running = False

    def _get_working_message(self):
        return messages.Status("Looking for files to download")

    def perform(self):
        # get the current directory
        # logging.debug('Download::perform')
        self.outputQueue.put(self._get_working_message())
        files = self.objectStore.list_dir(None)
        for f in files:
            if not self.running:
                break
            # logging.debug('f.path = %r' % f.path)
            if f.isFolder:
                if f.name == self.trashFolder:
                    # we don't download the trash folder
                    continue
                else:
                    skipChildren = self.download_folder(f)
                    # if we deleted a bunch of stuff - it might
                    # mean our files list is out of wack
                    # so lets rather just break out - and restart
                    # next time round
                    if skipChildren:
                        logging.info("break")
                        break
            else:
                self.download_file(f)
        self.outputQueue.put(messages.Status("Local files up to date"))

    def download_file(self, f):
        localPath = self.get_local_path(f.path)
        if not os.path.exists(localPath):
            self._set_hadWorkToDo(True)
            # logging.debug('does not exist: %s' % localPath)
            if self.already_synced_file(f.path):
                # if we've already downloaded this file,
                # it means we have to delete it remotely!
                logging.info("delete remote version of %s" % localPath)
                self.delete_remote_file(f.path)
            else:
                # lets get the file
                head, tail = os.path.split(localPath)
                self.outputQueue.put(messages.Status("Downloading %s" % tail))
                tmpFile = self.get_tmp_filename()
                if os.path.exists(tmpFile):
                    # if a temporary file with the same name
                    # exists, delete it
                    os.remove(tmpFile)
                self.objectStore.download_object(f.path, tmpFile)
                os.rename(tmpFile, localPath)
                localMD = self.localStore.get_last_modified_date(localPath)
                self.state.markObjectAsSynced(f.path, f.hash, localMD)
                self.outputQueue.put(self._get_working_message())
        else:
            # the file already exists - do we overwrite it?
            syncInfo = self.state.getObjectSyncInfo(f.path)
            if syncInfo:
                localMD = self.localStore.get_last_modified_date(localPath)
                if syncInfo.dateModified != localMD:
                    # the dates differ! we need to calculate the hash!
                    localFileInfo = self.localStore.get_file_info(localPath)
                    if localFileInfo.hash != f.hash:
                        # hmm - ok, if the online one, has the same hash
                        # as I synced, then it means the local file
                        # has changed!
                        if syncInfo.hash == f.hash:
                            # online and synced have the same version!
                            # that means the local one has changed
                            # so we're not downloading anything
                            # the upload process should handle this
                            pass
                        else:
                            logging.warn("TODO: the files differ - which " "one do I use?")
                    else:
                        # all good - the files are the same
                        # we can update our local sync info
                        self.state.markObjectAsSynced(f.path, localFileInfo.hash, localMD)
                else:
                    # dates are the same, so we can assume the hash
                    # hasn't changed
                    if syncInfo.hash != f.hash:
                        # if the sync info is the same as the local file
                        # then it must mean the remote file has changed!
                        get_file_info = self.localStore.get_file_info
                        localFileInfo = get_file_info(localPath)
                        if localFileInfo.hash == syncInfo.hash:
                            self.replace_file(f, localPath)
                        else:
                            logging.info("remote hash: %r" % f.hash)
                            logging.info("local hash: %r" % localFileInfo.hash)
                            logging.info("sync hash: %r" % syncInfo.hash)
                            logging.warn("sync hash differs from local hash!")
                    else:
                        # sync hash is same as remote hash, and the file date
                        # hasn't changed. we assume this to mean, there have
                        # been no changes
                        pass
            else:
                # TODO: we need to do something here!
                # the file exists locally, and remotely - but we don't have any
                # record of having downloaded it
                localFileInfo = self.localStore.get_file_info(localPath)
                if localFileInfo.hash == f.hash:
                    localMD = self.localStore.get_last_modified_date(localPath)
                    self.state.markObjectAsSynced(f.path, localFileInfo.hash, localMD)
                else:
                    # we don't have any history of this file - and the hash
                    # from local differs from remote! WHAT DO WE DO!
                    logging.error("TODO: HASH differs! Which is which????: %r" % f.path)
            pass

    def replace_file(self, f, localPath):
        self._set_hadWorkToDo(True)
        head, tail = os.path.split(localPath)
        self.outputQueue.put(messages.Status("Downloading %s" % tail))
        tmpFile = self.get_tmp_filename()
        if os.path.exists(tmpFile):
            # if a temporary file with the same name exists, remove it
            os.remove(tmpFile)
        self.objectStore.download_object(f.path, tmpFile)
        send2trash(localPath)
        os.rename(tmpFile, localPath)
        localMD = self.localStore.get_last_modified_date(localPath)
        self.state.markObjectAsSynced(f.path, f.hash, localMD)
        self.outputQueue.put(self._get_working_message())

    def get_tmp_filename(self):
        return os.path.join(self.tempDownloadFolder, "tmpfile")

    def download_folder(self, folder):
        if not self.running:
            # return true, to indicate that children can be skipped
            return True
        # does the folder exist locally?
        # logging.debug('download_folder(%s)' % folder.path)
        localPath = self.get_local_path(folder.path)
        downloadFolderContents = True
        skipChildren = False
        if not os.path.exists(localPath):
            self._set_hadWorkToDo(True)
            # the path exists online, but NOT locally
            # we do one of two things, we either
            # a) delete it remotely
            #     if we know for a fact we've already downloaded this folder,
            #     then it not being here, can only mean we've deleted it
            # b) download it
            #     if we haven't marked this folder as being downloaded,
            #     then we get it now
            if self.already_downloaded_folder(folder.path):
                logging.info("we need to delete %r!" % folder.path)
                self.delete_remote_folder(folder.path)
                downloadFolderContents = False
                skipChildren = True
                logging.info("done deleting remote folder")
            else:
                # logging.info('creating: %r' % localPath)
                os.makedirs(localPath)
                localMD = self.localStore.get_last_modified_date(localPath)
                self.state.markObjectAsSynced(folder.path, None, localMD)
                # logging.info('done creating %r' % localPath)
        if downloadFolderContents:
            try:
                # logging.debug('downloading folder
                #              'contents for %s' % folder.path)
                files = self.objectStore.list_dir(folder.path)
                # logging.debug('got %r files' % len(files))
                for f in files:
                    if folder.path.strip("/") != f.path.strip("/"):
                        if f.isFolder:
                            skipChildren = self.download_folder(f)
                            if skipChildren:
                                break
                        else:
                            self.download_file(f)
            except:
                logging.error("failed to download %s" % folder.path)
                logging.error(traceback.format_exc())
        return skipChildren

    def get_local_path(self, remote_path):
        return os.path.join(self.localSyncPath, remote_path)

    def already_downloaded_folder(self, path):
        """ Establish if this folder was downloaded before
        typical use: the folder doesn't exist locally, but it
        does exist remotely - that would imply that if we'd already
        downloaded it, it can only be missing if it was deleted, and
        thusly, we delete it remotely.
        """
        alreadySynced = False
        syncInfo = self.state.getObjectSyncInfo(path)
        if syncInfo:
            # if we have sync info for this path - it means we've
            # already download
            # or uploaded it
            logging.info("we have sync info for %s" % path)
            alreadySynced = True
        else:
            # if we don't have sync info for this path
            # - it means we haven't downloaded it yet
            # logging.info('no sync info for %s' % path)
            pass
        return alreadySynced

    def already_synced_file(self, path):
        """ See: already_downloaded_folder
        """
        syncInfo = self.state.getObjectSyncInfo(path)
        if syncInfo:
            remoteFileInfo = self.objectStore.get_file_info(path)
            if remoteFileInfo.hash == syncInfo.hash:
                # the hash of the file we synced, is the
                # same as the one online.
                # this means, we've already synced this file!
                return True
            return False
        else:
            return False

    def delete_remote_folder(self, path):
        logging.info("delete_remote_folder(path = %r)" % path)
        # a folder has children - and we need to remove those!
        self._set_hadWorkToDo(True)
        children = self.objectStore.list_dir(path)
        # for child in children:
        #    logging.info('%s [child] %s' % (path, child.path))
        for child in children:
            if child.isFolder:
                # remove this child folder
                self.delete_remote_folder(child.path)
            else:
                # remove this child file
                self.delete_remote_file(child.path)
        logging.info("going to attempt to delete: %r" % path)
        self.delete_remote_file(path)

    def delete_remote_file(self, path):
        self._set_hadWorkToDo(True)
        logging.info("delete remote file: %s" % path)
        head, tail = os.path.split(path)
        self.outputQueue.put(messages.Status("Deleting %s" % tail))
        self.objectStore.delete_object(path, moveToTrash=True)
        self.state.removeObjectSyncRecord(path)
        self.outputQueue.put(self._get_working_message())
Пример #5
0
class Download(BaseWorker):
    def __init__(self, objectStore, outputQueue):
        BaseWorker.__init__(self)
        self.objectStore = objectStore
        self.outputQueue = outputQueue
        self.localStore = LocalProvider()
        c = config.Config()
        self.localSyncPath = c.get_home_folder()
        self.tempDownloadFolder = c.get_temporary_folder()
        self.state = statestore.StateStore(c.username)
        self.lock = threading.Lock()
        self.running = True
        self.trashFolder = c.get_trash_folder()

    def stop(self):
        logging.info('Download::stop')
        self.objectStore.stop()
        self.running = False

    def _get_working_message(self):
        return messages.Status('Looking for files to download')

    def perform(self):
        # get the current directory
        #logging.debug('Download::perform')
        self.outputQueue.put(self._get_working_message())
        files = self.objectStore.list_dir(None)
        for f in files:
            if not self.running:
                break
            #logging.debug('f.path = %r' % f.path)
            if f.isFolder:
                if f.name == self.trashFolder:
                    # we don't download the trash folder
                    continue
                else:
                    skipChildren = self.download_folder(f)
                    # if we deleted a bunch of stuff - it might
                    # mean our files list is out of wack
                    # so lets rather just break out - and restart
                    # next time round
                    if skipChildren:
                        logging.info('break')
                        break
            else:
                self.download_file(f)
        self.outputQueue.put(messages.Status('Local files up to date'))

    def download_file(self, f):
        localPath = self.get_local_path(f.path)
        if not os.path.exists(localPath):
            self._set_hadWorkToDo(True)
            #logging.debug('does not exist: %s' % localPath)
            if self.already_synced_file(f.path):
                # if we've already downloaded this file,
                # it means we have to delete it remotely!
                logging.info('delete remote version of %s' % localPath)
                self.delete_remote_file(f.path)
            else:
                # lets get the file
                head, tail = os.path.split(localPath)
                self.outputQueue.put(messages.Status('Downloading %s' % tail))
                tmpFile = self.get_tmp_filename()
                if os.path.exists(tmpFile):
                    # if a temporary file with the same name
                    # exists, delete it
                    os.remove(tmpFile)
                self.objectStore.download_object(f.path, tmpFile)
                os.rename(tmpFile, localPath)
                localMD = self.localStore.get_last_modified_date(localPath)
                self.state.markObjectAsSynced(f.path, f.hash, localMD)
                self.outputQueue.put(self._get_working_message())
        else:
            # the file already exists - do we overwrite it?
            syncInfo = self.state.getObjectSyncInfo(f.path)
            if syncInfo:
                localMD = self.localStore.get_last_modified_date(localPath)
                if syncInfo.dateModified != localMD:
                    # the dates differ! we need to calculate the hash!
                    localFileInfo = self.localStore.get_file_info(localPath)
                    if localFileInfo.hash != f.hash:
                        # hmm - ok, if the online one, has the same hash
                        # as I synced, then it means the local file
                        # has changed!
                        if syncInfo.hash == f.hash:
                            # online and synced have the same version!
                            # that means the local one has changed
                            # so we're not downloading anything
                            # the upload process should handle this
                            pass
                        else:
                            logging.warn('TODO: the files differ - which '
                                         'one do I use?')
                    else:
                        # all good - the files are the same
                        # we can update our local sync info
                        self.state.markObjectAsSynced(f.path,
                                                      localFileInfo.hash,
                                                      localMD)
                else:
                    # dates are the same, so we can assume the hash
                    # hasn't changed
                    if syncInfo.hash != f.hash:
                        # if the sync info is the same as the local file
                        # then it must mean the remote file has changed!
                        get_file_info = self.localStore.get_file_info
                        localFileInfo = get_file_info(localPath)
                        if localFileInfo.hash == syncInfo.hash:
                            self.replace_file(f, localPath)
                        else:
                            logging.info('remote hash: %r' % f.hash)
                            logging.info('local hash: %r' % localFileInfo.hash)
                            logging.info('sync hash: %r' % syncInfo.hash)
                            logging.warn('sync hash differs from local hash!')
                    else:
                        # sync hash is same as remote hash, and the file date
                        # hasn't changed. we assume this to mean, there have
                        # been no changes
                        pass
            else:
                # TODO: we need to do something here!
                # the file exists locally, and remotely - but we don't have any
                # record of having downloaded it
                localFileInfo = self.localStore.get_file_info(localPath)
                if localFileInfo.hash == f.hash:
                    localMD = self.localStore.get_last_modified_date(localPath)
                    self.state.markObjectAsSynced(f.path, localFileInfo.hash,
                                                  localMD)
                else:
                    # we don't have any history of this file - and the hash
                    # from local differs from remote! WHAT DO WE DO!
                    logging.error(
                        'TODO: HASH differs! Which is which????: %r' % f.path)
            pass

    def replace_file(self, f, localPath):
        self._set_hadWorkToDo(True)
        head, tail = os.path.split(localPath)
        self.outputQueue.put(messages.Status('Downloading %s' % tail))
        tmpFile = self.get_tmp_filename()
        if os.path.exists(tmpFile):
            # if a temporary file with the same name exists, remove it
            os.remove(tmpFile)
        self.objectStore.download_object(f.path, tmpFile)
        send2trash(localPath)
        os.rename(tmpFile, localPath)
        localMD = self.localStore.get_last_modified_date(localPath)
        self.state.markObjectAsSynced(f.path, f.hash, localMD)
        self.outputQueue.put(self._get_working_message())

    def get_tmp_filename(self):
        return os.path.join(self.tempDownloadFolder, 'tmpfile')

    def download_folder(self, folder):
        if not self.running:
            # return true, to indicate that children can be skipped
            return True
        # does the folder exist locally?
        #logging.debug('download_folder(%s)' % folder.path)
        localPath = self.get_local_path(folder.path)
        downloadFolderContents = True
        skipChildren = False
        if not os.path.exists(localPath):
            self._set_hadWorkToDo(True)
            # the path exists online, but NOT locally
            # we do one of two things, we either
            # a) delete it remotely
            #     if we know for a fact we've already downloaded this folder,
            #     then it not being here, can only mean we've deleted it
            # b) download it
            #     if we haven't marked this folder as being downloaded,
            #     then we get it now
            if self.already_downloaded_folder(folder.path):
                logging.info('we need to delete %r!' % folder.path)
                self.delete_remote_folder(folder.path)
                downloadFolderContents = False
                skipChildren = True
                logging.info('done deleting remote folder')
            else:
                #logging.info('creating: %r' % localPath)
                os.makedirs(localPath)
                localMD = self.localStore.get_last_modified_date(localPath)
                self.state.markObjectAsSynced(folder.path, None, localMD)
                #logging.info('done creating %r' % localPath)
        if downloadFolderContents:
            try:
                #logging.debug('downloading folder
                #              'contents for %s' % folder.path)
                files = self.objectStore.list_dir(folder.path)
                #logging.debug('got %r files' % len(files))
                for f in files:
                    if folder.path.strip('/') != f.path.strip('/'):
                        if f.isFolder:
                            skipChildren = self.download_folder(f)
                            if skipChildren:
                                break
                        else:
                            self.download_file(f)
            except:
                logging.error('failed to download %s' % folder.path)
                logging.error(traceback.format_exc())
        return skipChildren

    def get_local_path(self, remote_path):
        return os.path.join(self.localSyncPath, remote_path)

    def already_downloaded_folder(self, path):
        """ Establish if this folder was downloaded before
        typical use: the folder doesn't exist locally, but it
        does exist remotely - that would imply that if we'd already
        downloaded it, it can only be missing if it was deleted, and
        thusly, we delete it remotely.
        """
        alreadySynced = False
        syncInfo = self.state.getObjectSyncInfo(path)
        if syncInfo:
            # if we have sync info for this path - it means we've
            # already download
            # or uploaded it
            logging.info('we have sync info for %s' % path)
            alreadySynced = True
        else:
            # if we don't have sync info for this path
            # - it means we haven't downloaded it yet
            #logging.info('no sync info for %s' % path)
            pass
        return alreadySynced

    def already_synced_file(self, path):
        """ See: already_downloaded_folder
        """
        syncInfo = self.state.getObjectSyncInfo(path)
        if syncInfo:
            remoteFileInfo = self.objectStore.get_file_info(path)
            if remoteFileInfo.hash == syncInfo.hash:
                # the hash of the file we synced, is the
                # same as the one online.
                # this means, we've already synced this file!
                return True
            return False
        else:
            return False

    def delete_remote_folder(self, path):
        logging.info('delete_remote_folder(path = %r)' % path)
        # a folder has children - and we need to remove those!
        self._set_hadWorkToDo(True)
        children = self.objectStore.list_dir(path)
        #for child in children:
        #    logging.info('%s [child] %s' % (path, child.path))
        for child in children:
            if child.isFolder:
                # remove this child folder
                self.delete_remote_folder(child.path)
            else:
                # remove this child file
                self.delete_remote_file(child.path)
        logging.info('going to attempt to delete: %r' % path)
        self.delete_remote_file(path)

    def delete_remote_file(self, path):
        self._set_hadWorkToDo(True)
        logging.info('delete remote file: %s' % path)
        head, tail = os.path.split(path)
        self.outputQueue.put(messages.Status('Deleting %s' % tail))
        self.objectStore.delete_object(path, moveToTrash=True)
        self.state.removeObjectSyncRecord(path)
        self.outputQueue.put(self._get_working_message())
Пример #6
0
class Upload(BaseWorker):
    def __init__(self, objectStore, outputQueue):
        BaseWorker.__init__(self)
        self.objectStore = objectStore
        self.outputQueue = outputQueue
        self.localStore = LocalProvider()
        c = config.Config()
        self.localSyncPath = c.get_home_folder()
        self.state = statestore.StateStore(c.username)
        self._isRunning = True

    def stop(self):
        self.objectStore.stop()
        self._isRunning = False
        pass

    def _get_working_message(self):
        return messages.Status('Looking for files to upload')

    def perform(self):
        self.outputQueue.put(self._get_working_message())
        #logging.debug('Upload::perform')
        if not os.path.exists(self.localSyncPath):
            os.makedirs(self.localSyncPath)
        files = os.listdir(self.localSyncPath)
        for f in files:
            if not self._isRunning:
                break
            #logging.debug('upload: %s' % f)
            fullPath = os.path.join(self.localSyncPath, f)
            if os.path.isdir(fullPath):
                #logging.debug('is directory')
                self.upload_directory(f)
            elif os.path.isfile(fullPath):
                self.processFile(fullPath, f)
        self.outputQueue.put(messages.Status('Remote files up to date'))

    def remove_local_dir(self, localPath, remotePath):
        #logging.info('TODO: implement directory out of sync scenario')
        # TODO: check for environment, where local directory was deleted
        # but new files have been placed inside remote directory
        # TODO: check for environment, where remote directory was deleted
        # but new file have been placed inside local directory
        # we need to remove the local directory, and all it's contents
        files = os.listdir(localPath)
        for f in files:
            if not self._isRunning:
                break
            if isinstance(f, str):
                # TODO: this might be an issue on linux/mac
                # but in windows land, things are stored as latin-1
                # ... i think... maybe it needs to be utf8 instead?
                f = f.decode('latin-1')
            childLocalPath = os.path.join(localPath, f)
            logging.info('childLocalPath=%r' % childLocalPath)
            childRemotePath = '%s/%s' % (remotePath, f)
            if os.path.isdir(childLocalPath):
                self.remove_local_dir(childLocalPath, childRemotePath)
            else:
                self.deleteLocalFile(childLocalPath, childRemotePath)
                #logging.info('local=%s;remote=%s' % (childLocalPath,
                #                                     childRemotePath))
        #logging.info('local=%s;remote=%s' % (localPath, remotePath))
        logging.info('delete %r' % localPath)
        os.rmdir(localPath)
        self.state.removeObjectSyncRecord(remotePath)

    def upload_directory(self, remotePath):
        fullPath = os.path.join(self.localSyncPath, remotePath)
        files = os.listdir(fullPath)
        remoteDirInfo = self.objectStore.get_file_info(remotePath)
        if not remoteDirInfo:
            # now hold on - sometimes we have pseudo directories!
            tmp = self.objectStore.list_dir(remotePath)
            if len(tmp) > 0:
                # aaah - this is clearly a pseudo directory - because
                # we can see there are files in it!
                remoteDirInfo = True
                logging.info(('TODO: %r is a pseudoDirectory - create a real '
                             'directory object!')
                             % remotePath)
                #TODO: implement logic for creating a remote directory
        if not remoteDirInfo:
            self._set_hadWorkToDo(True)
            # the folder exists locally, but not remotely.
            # 1) if we've already synced it - it means it was deleted
            # remotely
            # 2) if we haven't synced it, it means we have to create it
            # remotely
            syncInfo = self.state.getObjectSyncInfo(remotePath)
            if syncInfo:
                # the folder has already been synced - so we delete it locally
                logging.info('%r does not exists remotely, so we remove %r' %
                             (remotePath, fullPath))
                self.remove_local_dir(fullPath, remotePath)
            else:
                #logging.info('creating remote folder %s' % remotePath)
                self.objectStore.create_folder(remotePath)
                localMD = self.localStore.get_last_modified_date(fullPath)
                self.state.markObjectAsSynced(remotePath,
                                              None,
                                              localMD)
        for f in files:
            if not self._isRunning:
                break
            # we want everything in unicode!
            if isinstance(f, str):
                # strings we encode to iso-8859-1 (on windows)
                f = f.decode('iso-8859-1')
                # then we push it into unicode
                f = unicode(f)
            newLocalPath = os.path.join(fullPath, f)
            newRemotePath = '%s/%s' % (remotePath, f)
            if os.path.isdir(newLocalPath):
                self.upload_directory(newRemotePath)
            elif os.path.isfile(newLocalPath):
                self.processFile(newLocalPath, newRemotePath)

    def processFile(self, localPath, remotePath):
        """ Depending on a number of factors - we do different
        things with files. Maybe we upload the local file,
        maybe we delete it. Maybe we delete the remote file!
        Maybe there's some kind of conflict and we need to rename
        the local file?
        """
        #logging.info('process %s to %s' % (localPath, remotePath))
        remoteFileInfo = self.objectStore.get_file_info(remotePath)
        if remoteFileInfo:
            cmpResult, syncInfo, dm = self.compareFile(localPath,
                                                       remotePath,
                                                       remoteFileInfo)
            if cmpResult:
                # the files are the same!
                # but wait - did we have syncinfo?
                if not syncInfo:
                    # we didn't have sync info!
                    # or it's been invalidated so we
                    # need to store it
                    logging.info('sync info for %s updated' % localPath)
                    self.state.markObjectAsSynced(remotePath,
                                                  remoteFileInfo.hash,
                                                  dm)
            else:
                localFileInfo = self.localStore.get_file_info(localPath)
                syncInfo = self.state.getObjectSyncInfo(remotePath)

                #logging.info('remote hash: %r' % remoteFileInfo.hash)
                #logging.info('local hash: %r' % localFileInfo.hash)

                if syncInfo:
                    #logging.info('sync hash: %r' % syncInfo.hash)
                    if remoteFileInfo.hash == syncInfo.hash:
                        # the remote file, and our sync record are the same
                        # that means the local version hash changed
                        self._set_hadWorkToDo(True)
                        self.objectStore.upload_object(localPath,
                                                       remotePath,
                                                       localFileInfo.hash)
                        self.state.markObjectAsSynced(remotePath,
                                                      localFileInfo.hash,
                                                      dm)
                    elif localFileInfo.hash == syncInfo.hash:
                        # the local file hasn't changed - so it must be the
                        # remote file! the download process should pick this up
                        pass
                    else:
                        logging.warn('not implemented!')
                    # the files are NOT the same - so either the local
                    # one is new, or the remote on is new
                    # this is a nasty nasty problem with no perfect solution!
                    # lots of thinking to be done here - but in the end
                    # it will be some kind of compromise
                    # we can however reduce the number of problems:
                    # 1) look at the hash we last uploaded
                    # 1.1) if the local hash and historic hash are the same
                    #      then it means that the remote file is newer,
                    #      download
                    # 1.2) if the historic hash is the same as the remote hash
                    #      then we know the local file has changed
                    # 1.3) if the historic hash differs from both the remote
                    #      hash
                    #      and the local hash, then we have no way of knowing
                    #      which
                    #      is newer - our only option is to rename the local
                    #      one
                else:
                    logging.error('not sync info for %r !' % remotePath)
        else:
            self._set_hadWorkToDo(True)
            # woah - the file isn't online!
            # do we upload the local file? or do we delete it???
            if self.fileHasBeenUploaded(localPath, remotePath):
                # we uploaded this file - but it's NOT online!!
                # this can only mean that it's been deleted online
                # so we need to delete it locally!
                self.deleteLocalFile(localPath, remotePath)
            else:
                # the file hasn't been uploaded before, so we upload it now
                self.uploadFile(localPath, remotePath)

    def deleteLocalFile(self, localPath, remotePath):
        #logging.warn('delete local file %s' % localPath)
        head, tail = os.path.split(localPath)
        self.outputQueue.put(messages.Status('Deleting %s' % tail))
        send2trash(localPath)
        self.state.removeObjectSyncRecord(remotePath)
        self.outputQueue.put(self._get_working_message())

    def uploadFile(self, localPath, remotePath):
        #logging.warn('upload local file %s' % localPath)
        head, tail = os.path.split(localPath)
        self.outputQueue.put(messages.Status('Uploading %s' % tail))
        # before we upload it - we calculate the hash
        localFileInfo = self.localStore.get_file_info(localPath)
        self.objectStore.upload_object(localPath,
                                       remotePath,
                                       localFileInfo.hash)
        localMD = self.localStore.get_last_modified_date(localPath)
        self.state.markObjectAsSynced(remotePath,
                                      localFileInfo.hash,
                                      localMD)
        self.outputQueue.put(self._get_working_message())

    def fileHasBeenUploaded(self, localPath, remotePath):
        syncInfo = self.state.getObjectSyncInfo(remotePath)
        if syncInfo:
            # we have info for the file - lets check that it's the same info!
            localMD = self.localStore.get_last_modified_date(localPath)
            if syncInfo.dateModified != localMD:
                # the modification date has changed - so it might not be the
                # same file we logged!
                localFileInfo = self.localStore.get_file_info(localPath)
                # if the hash of the local file, is the same as the one we
                # stored then the file hasn't changed since we synced, so
                # we have uploaded this file
                return localFileInfo.hash == syncInfo.hash
            else:
                # the file date hasn't modified, so we assume it hasn't changed
                # if it hasn't changed - it means we've uploaded it
                return True
        else:
            # we don't have any local sync info - so our assumption
            # is that this file has not been uploaded
            return False

    def compareFile(self, localFilePath, remoteFilePath, remoteFileInfo):
        """
        return (True if files are the same, local sync info (if valid/present),
                local last modified date)
        """
        # get sync info for the file
        syncInfo = self.state.getObjectSyncInfo(remoteFilePath)
        localFileInfo = None
        if syncInfo:
            # we have local sync info
            # if the sync modified date, and file modified date are the same
            # then we know for a fact the file is unchanged
            localMD = self.localStore.get_last_modified_date(localFilePath)
            if syncInfo.dateModified != localMD:
                # the dates differ! we need to calculate the hash
                localFileInfo = self.localStore.get_file_info(localFilePath)
                # invalidate the sync info!
                syncInfo = None
            else:
                # the dates are the same, so the hash from the syncInfo
                # should be good
                localFileInfo = BucketFile(remoteFilePath, None, None)
                localFileInfo.hash = syncInfo.hash
        else:
            # we don't have sync info! this means we HAVE to do a hash compare
            localFileInfo = self.localStore.get_file_info(localFilePath)
            localMD = self.localStore.get_last_modified_date(localFilePath)

        return (localFileInfo.hash == remoteFileInfo.hash,
                syncInfo,
                localMD)