Compare changes on the client to changes on the server and update local files
    which have changed on the server.

def update(session_token, testing = False):
    """ Compare changes on the client to changes on the server and update local files
    which have changed on the server. """

    conflict_comparison_file_dest = cpjoin(config['data_dir'], '.shttpfs', 'conflict_files')
    conflict_resolution_path = cpjoin(config['data_dir'], '.shttpfs', 'conflict_resolution.json')
    conflict_resolutions = file_or_default(conflict_resolution_path, [], json.loads)

    # Make sure that all conflicts have been resolved, reject if not
    if not all(len(c['4_resolution']) == 1 for c in conflict_resolutions): conflict_resolutions = []

    # Send the changes and the revision of the most recent update to the server to find changes
    manifest, client_changes = find_local_changes()

    req_result, headers = server_connection.request("find_changed", {
        "session_token"        : session_token,
        'repository'           : config['repository'],
        "previous_revision"    : manifest['have_revision'],
        }, {
            "client_changes"       : json.dumps(client_changes),
            "conflict_resolutions" : json.dumps(conflict_resolutions)})

    if headers['status'] != 'ok':
        if headers['msg'] == 'Please resolve conflicts':
            raise SystemExit('Server error: Please resolve conflicts in .shttpfs/conflict_resolution.json')
        else:
            raise SystemExit('Server error')

    result = json.loads(req_result)
    changes = result['sorted_changes']

    # Are there any changes?
    if all(v == [] for k,v in changes.iteritems()):
        print 'Nothing to update'
        return

    # Pull and delete from remote to local
    if changes['client_pull_files'] != []:
        # Filter out pull ignore files
        filtered_pull_files = []
        for fle in changes['client_pull_files']:
            if not next((True for flter in config['pull_ignore_filters'] if fnmatch.fnmatch(fle['path'], flter)), False):
                filtered_pull_files.append(fle)
            else: # log ignored items to give the opportunity to pull them in the future
                with open(cpjoin(working_copy_base_path, '.shttpfs', 'pull_ignored_items'), 'a') as pull_ignore_log:
                    pull_ignore_log.write(json.dumps((result['head'], fle)))
                    pull_ignore_log.flush()

        if filtered_pull_files != []:
            print 'Pulling files from server...'

        #----------
        for fle in filtered_pull_files:
            print 'Pulling file: ' + fle['path']

            req_result, headers = server_connection.request("pull_file", {
                'session_token' : session_token,
                'repository'    : config['repository'],
                'path'          : fle['path']}, gen = True)

            if headers['status'] != 'ok':
                raise SystemExit('Failed to pull file')
            else:
                make_dirs_if_dont_exist(data_store.get_full_file_path(cpjoin(*fle['path'].split('/')[:-1]) + '/'))
                data_store.fs_put(fle['path'], req_result)

    # Files which have been deleted on server and need deleting on client
    if changes['to_delete_on_client'] != []:
        print 'Removing files deleted on the server...'

        for fle in changes['to_delete_on_client']:
            print 'Deleting file: ' + fle['path']

            try: data_store.fs_delete(fle['path'])
            except OSError: print 'Warning: remote deleted file does not exist locally.'

            # Delete the folder if it is now empty
            try: os.removedirs(os.path.dirname(data_store.get_full_file_path(fle['path'])))
            except OSError as e:
                if e.errno not in [errno.ENOTEMPTY, errno.ENOENT]: raise

    # Files which are in conflict
    if changes['conflict_files'] != []:
        print "There are conflicts!\n"

        out = []; server_versions = []
        for fle in changes['conflict_files']:
            fle['resolution'] = ['local', 'remote']
            print 'Path:          ' + fle['file_info']['path']
            print 'Client status: ' + fle['client_status']
            print 'Server status: ' + fle['server_status']
            print
            out.append({'1_path'          : fle['file_info']['path'],
                        '2_client_status' : fle['client_status'],
                        '3_server_status' : fle['server_status'],
                        '4_resolution'    : ['client', 'server']})
            if fle['server_status'] == 'Changed': server_versions.append(fle['file_info'])

        #===============
        if server_versions != []:
            choice = None
            if not testing:
                while True:
                    print 'Download server versions for comparison? (Y/N)'
                    choice = raw_input()
                    if choice.lower() in ['y', 'n']: break
            else: choice = 'y'

            errors = []
            if choice == 'y':
                for fle in server_versions:
                    print 'Pulling file: ' + fle['path']

                    result, headers = server_connection.request("pull_file", {
                        'session_token' : session_token,
                        'repository'    : config['repository'],
                        'path'          : fle['path']}, gen = True)

                    if headers['status'] != 'ok':
                        errors.append(fle['path'])

                    else:
                        make_dirs_if_dont_exist(cpjoin(conflict_comparison_file_dest, *fle['path'].split('/')[:-1]) + '/')
                        result(cpjoin(conflict_comparison_file_dest, fle['path']))

                print 'Server versions of conflicting files written to .shttpfs/conflict_files\n'

            pprint(errors)

        # ====================

        file_put_contents(conflict_resolution_path, json.dumps(out, indent=4, sort_keys=True))
        raise SystemExit("Conflict resolution file written to .shttpfs/conflict_resolution.json\n" +
                         "Please edit this file removing 'client', or 'server' to choose which version to retain.")

    # Update the latest revision in the manifest only if there are no conflicts
    else:
        data_store.begin()
        manifest = data_store.read_local_manifest()
        manifest['have_revision'] = result['head']
        data_store.write_local_manifest(manifest)
        data_store.commit()

        #delete the conflicts resolution file and recursively delete any conflict files downloaded for comparison
        ignore(os.remove, conflict_resolution_path)
        ignore(shutil.rmtree, conflict_comparison_file_dest)

        if changes['to_delete_on_server'] != [] or changes['client_push_files'] != []:
            print 'There are local changes to commit'
        else:
            print 'Update OK'