def get_mutable(name, data_id, ver_min=None, ver_max=None, ver_check=None, conf=None):
    """
    get_mutable
    """

    if conf is None:
        conf = config.get_config()

    user = get_name_record(name)
    if 'error' in user:

        # no user data
        return {'error': "Unable to load user record: %s" % user['error']}

    # find the mutable data ID
    data_route = user_db.get_mutable_data_route(user, data_id)
    if data_route is None:

        # no data
        return {'error': 'No such route'}

    # go and fetch the data
    data = storage.get_mutable_data(data_route, ver_min=ver_min,
                                    ver_max=ver_max,
                                    ver_check=ver_check)
    if data is None:

        # no data
        return {'error': 'No mutable data found'}

    # what's the expected version of this data?
    expected_version = load_mutable_data_version( conf, name, data_id, try_remote=False )
    if expected_version is not None:
        if expected_version > data['ver']:
            return {'error': 'Stale data', 'ver_minimum': expected_version, 'ver_received': data['ver']}

    elif ver_check is None:
        # we don't have a local version, and the caller didn't check it.
        log.warning("Unconfirmed version for data '%s'" % data_id)
        data['warning'] = "Unconfirmed version"

    # remember latest version
    if data['ver'] > expected_version:
        store_mutable_data_version( conf, data_id, data['ver'] )

    # include the route
    data['route'] = data_route
    return data
def delete_mutable(name, data_id, privatekey, proxy=default_proxy, txid=None,
                   route=None):
    """
    delete_mutable
    """

    if proxy is None:
        proxy = get_default_proxy()

    result = {}
    user = get_name_record(name)
    if 'error' in user:

        # no user data
        return {'error': "Unable to load user record: %s" % user['error']}

    # does the user have a route to this data?
    if not user_db.has_mutable_data_route(user, data_id) and txid is None:

        # nope--we're good
        return {'status': True}

    # blow away the data
    storage_rc = storage.delete_mutable_data(data_id, privatekey)
    if not storage_rc:
        result['error'] = "Failed to delete mutable data"
        return result

    # remove the route from the user record
    user_db.remove_mutable_data_route(user, data_id)
    user_json = user_db.serialize_user(user)

    # update the user record
    update_status = update(name, user_json, privatekey, txid=txid,
                           proxy=proxy)

    if 'error' in update_status:

        # failed; caller should try again
        return update_status

    if txid is None:
        txid = update_status['transaction_hash']

    # blow away the route
    if route is None:
        route = user_db.get_mutable_data_route(user, data_id)

    route_hash = storage.get_mutable_data_route_hash(route)
    storage_rc = storage.delete_immutable_data(route_hash, txid)
    if not storage_rc:

        result['error'] = "Failed to delete immutable data route"
        result['route'] = route

    else:
        result['status'] = True
        result['transaction_hash'] = txid
        result['value_hash'] = update_status['value_hash']

    # uncache local version
    delete_mutable_data_version( config.get_config(), data_id )

    return result
def put_mutable(name, data_id, data_text, privatekey, proxy=None, create=True,
                txid=None, ver=None, make_ver=None, conf=None ):
    """
    put_mutable

    ** Consistency **

    ver, if given, is the version to include in the data.
    make_ver, if given, is a callback that takes the data_id, data_text, and current version as arguments, and generates the version to be included in the data record uploaded.
    If ver is not given, but make_ver is, then make_ver will be used to generate the version.
    If neither ver nor make_ver are given, the mutable data (if it already exists) is fetched, and the version is calculated as the larget known version + 1.

    ** Durability **

    Replication is best-effort.  If one storage provider driver succeeds, the put_mutable succeeds.  If they all fail, then put_mutable fails.
    More complex behavior can be had by creating a "meta-driver" that calls existing drivers' methods in the desired manner.
    """

    if proxy is None:
        proxy = get_default_proxy()

    result = {}
    user = get_name_record(name, create_if_absent=create)
    if 'error' in user:

        return {'error': "Unable to load user record: %s" % user['error']}

    route = None
    exists = user_db.has_mutable_data_route(user, data_id)
    old_hash = None
    cur_hash = None
    new_ver = ver

    if ver is None:

        if exists:
            # mutable record already exists.
            # generate one automatically.
            # use the existing locally-stored version,
            # and fall back to using the last-known version
            # from the existing mutable data record.
            new_ver = load_mutable_data_version( config.get_config(), name, data_id, try_remote=True )
            if new_ver is None:
                # data exists, but we couldn't figure out the version
                return {'error': "Unable to determine version"}

        if make_ver is not None:
            # generate version
            new_ver = make_ver( data_id, data_text, new_ver )

        else:
            # no version known, and no way to generate it.
            # by default, start at 1.  we'll manage it ourselves.
            if new_ver is None:
                new_ver = 1
            else:
                new_ver += 1


    # do we have a route for this data yet?
    if not exists:

        if not create:
            # won't create; expect it to exist
            return {'error': 'No such route'}

        # need to put one
        urls = storage.make_mutable_urls(data_id)
        if len(urls) == 0:
            return {"error": "No routes constructed"}

        writer_pubkey = pybitcointools.privkey_to_pubkey(privatekey)

        route = storage.mutable_data_route(data_id, urls,
                                           writer_pubkey=writer_pubkey)

        user_db.add_mutable_data_route(user, route)

        user_json = user_db.serialize_user(user)

        # update the user record with the new route
        update_result = update(name, user_json, privatekey, txid=txid, proxy=proxy)
        if 'error' in update_result:

            # update failed; caller should try again
            return update_result

        txid = update_result['transaction_hash']
        cur_hash = update_result['value_hash']

    else:

        route = user_db.get_mutable_data_route(user, data_id)
        if route is None:

            return {"error": "No such route"}

    # generate the data
    data = storage.mutable_data(data_id, data_text, new_ver, privkey=privatekey)
    if data is None:
        return {"error": "Failed to generate data record"}

    # serialize...
    data_json = parsing.json_stable_serialize(data)

    # replicate...
    store_rc = storage.put_mutable_data( data, privatekey )
    if not store_rc:
        result['error'] = "Failed to store mutable data"

    else:
        result['status'] = True

    result['transaction_hash'] = txid

    if cur_hash:
        # propagate
        result['value_hash'] = cur_hash

    # cache new version
    store_mutable_data_version( conf, data_id, new_ver )

    return result
def delete_mutable(name,
                   data_id,
                   privatekey,
                   proxy=default_proxy,
                   txid=None,
                   route=None):
    """
    delete_mutable
    """

    if proxy is None:
        proxy = get_default_proxy()

    result = {}
    user = get_name_record(name)
    if 'error' in user:

        # no user data
        return {'error': "Unable to load user record: %s" % user['error']}

    # does the user have a route to this data?
    if not user_db.has_mutable_data_route(user, data_id) and txid is None:

        # nope--we're good
        return {'status': True}

    # blow away the data
    storage_rc = storage.delete_mutable_data(data_id, privatekey)
    if not storage_rc:
        result['error'] = "Failed to delete mutable data"
        return result

    # remove the route from the user record
    user_db.remove_mutable_data_route(user, data_id)
    user_json = user_db.serialize_user(user)

    # update the user record
    update_status = update(name, user_json, privatekey, txid=txid, proxy=proxy)

    if 'error' in update_status:

        # failed; caller should try again
        return update_status

    if txid is None:
        txid = update_status['transaction_hash']

    # blow away the route
    if route is None:
        route = user_db.get_mutable_data_route(user, data_id)

    route_hash = storage.get_mutable_data_route_hash(route)
    storage_rc = storage.delete_immutable_data(route_hash, txid)
    if not storage_rc:

        result['error'] = "Failed to delete immutable data route"
        result['route'] = route

    else:
        result['status'] = True
        result['transaction_hash'] = txid
        result['value_hash'] = update_status['value_hash']

    # uncache local version
    delete_mutable_data_version(config.get_config(), data_id)

    return result
def put_mutable(name,
                data_id,
                data_text,
                privatekey,
                proxy=None,
                create=True,
                txid=None,
                ver=None,
                make_ver=None,
                conf=None):
    """
    put_mutable

    ** Consistency **

    ver, if given, is the version to include in the data.
    make_ver, if given, is a callback that takes the data_id, data_text, and current version as arguments, and generates the version to be included in the data record uploaded.
    If ver is not given, but make_ver is, then make_ver will be used to generate the version.
    If neither ver nor make_ver are given, the mutable data (if it already exists) is fetched, and the version is calculated as the larget known version + 1.

    ** Durability **

    Replication is best-effort.  If one storage provider driver succeeds, the put_mutable succeeds.  If they all fail, then put_mutable fails.
    More complex behavior can be had by creating a "meta-driver" that calls existing drivers' methods in the desired manner.
    """

    if proxy is None:
        proxy = get_default_proxy()

    result = {}
    user = get_name_record(name, create_if_absent=create)
    if 'error' in user:

        return {'error': "Unable to load user record: %s" % user['error']}

    route = None
    exists = user_db.has_mutable_data_route(user, data_id)
    old_hash = None
    cur_hash = None
    new_ver = ver

    if ver is None:

        if exists:
            # mutable record already exists.
            # generate one automatically.
            # use the existing locally-stored version,
            # and fall back to using the last-known version
            # from the existing mutable data record.
            new_ver = load_mutable_data_version(config.get_config(),
                                                name,
                                                data_id,
                                                try_remote=True)
            if new_ver is None:
                # data exists, but we couldn't figure out the version
                return {'error': "Unable to determine version"}

        if make_ver is not None:
            # generate version
            new_ver = make_ver(data_id, data_text, new_ver)

        else:
            # no version known, and no way to generate it.
            # by default, start at 1.  we'll manage it ourselves.
            if new_ver is None:
                new_ver = 1
            else:
                new_ver += 1

    # do we have a route for this data yet?
    if not exists:

        if not create:
            # won't create; expect it to exist
            return {'error': 'No such route'}

        # need to put one
        urls = storage.make_mutable_urls(data_id)
        if len(urls) == 0:
            return {"error": "No routes constructed"}

        writer_pubkey = pybitcointools.privkey_to_pubkey(privatekey)

        route = storage.mutable_data_route(data_id,
                                           urls,
                                           writer_pubkey=writer_pubkey)

        user_db.add_mutable_data_route(user, route)

        user_json = user_db.serialize_user(user)

        # update the user record with the new route
        update_result = update(name,
                               user_json,
                               privatekey,
                               txid=txid,
                               proxy=proxy)
        if 'error' in update_result:

            # update failed; caller should try again
            return update_result

        txid = update_result['transaction_hash']
        cur_hash = update_result['value_hash']

    else:

        route = user_db.get_mutable_data_route(user, data_id)
        if route is None:

            return {"error": "No such route"}

    # generate the data
    data = storage.mutable_data(data_id,
                                data_text,
                                new_ver,
                                privkey=privatekey)
    if data is None:
        return {"error": "Failed to generate data record"}

    # serialize...
    data_json = parsing.json_stable_serialize(data)

    # replicate...
    store_rc = storage.put_mutable_data(data, privatekey)
    if not store_rc:
        result['error'] = "Failed to store mutable data"

    else:
        result['status'] = True

    result['transaction_hash'] = txid

    if cur_hash:
        # propagate
        result['value_hash'] = cur_hash

    # cache new version
    store_mutable_data_version(conf, data_id, new_ver)

    return result
def get_mutable(name,
                data_id,
                ver_min=None,
                ver_max=None,
                ver_check=None,
                conf=None):
    """
    get_mutable
    """

    if conf is None:
        conf = config.get_config()

    user = get_name_record(name)
    if 'error' in user:

        # no user data
        return {'error': "Unable to load user record: %s" % user['error']}

    # find the mutable data ID
    data_route = user_db.get_mutable_data_route(user, data_id)
    if data_route is None:

        # no data
        return {'error': 'No such route'}

    # go and fetch the data
    data = storage.get_mutable_data(data_route,
                                    ver_min=ver_min,
                                    ver_max=ver_max,
                                    ver_check=ver_check)
    if data is None:

        # no data
        return {'error': 'No mutable data found'}

    # what's the expected version of this data?
    expected_version = load_mutable_data_version(conf,
                                                 name,
                                                 data_id,
                                                 try_remote=False)
    if expected_version is not None:
        if expected_version > data['ver']:
            return {
                'error': 'Stale data',
                'ver_minimum': expected_version,
                'ver_received': data['ver']
            }

    elif ver_check is None:
        # we don't have a local version, and the caller didn't check it.
        log.warning("Unconfirmed version for data '%s'" % data_id)
        data['warning'] = "Unconfirmed version"

    # remember latest version
    if data['ver'] > expected_version:
        store_mutable_data_version(conf, data_id, data['ver'])

    # include the route
    data['route'] = data_route
    return data