def delete_immutable(name, data_key, privatekey, proxy=None, txid=None):
    """
    delete_immutable
    """

    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 record have this data?
    if not user_db.has_immutable_data(user, data_key):

        # already deleted
        return {'status': True}

    # remove hash from the user record and update it
    user_db.remove_immutable_data(user, data_key)

    user_json = user_db.serialize_user(user)

    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']

    # remove the data itself data
    delete_result = storage.delete_immutable_data(data_key, txid)
    if delete_result:

        result['status'] = True

    else:

        # be sure to give back the update transaction hash, so this call can be retried
        result['error'] = 'Failed to delete immutable data'

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

    return result
def remove_name_record(user, txid):
    """
    Delete JSON user record data from immutable storage providers, synchronously.

    Return (True, hash(user)) on success
    Return (False, hash(user)) on error
    """

    username = user_db.name(user)

    # serialize
    user_json = None
    try:
        user_json = user_db.serialize_user(user)
    except Exception, e:
        log.error("Failed to serialize '%s'" % user)
        return False
def update(name, user_json_or_hash, privatekey, txid=None, proxy=None, tx_only=False, public_key=None, subsidy_key=None):
    """
    update

    Optionally supply a txid.  The reason for doing so is to try to replicate user
    data to new storage systems, or to recover from a transient error encountered
    earlier.
    """

    # sanity check
    if privatekey is None and public_key is None:
        return {'error': 'Missing public and private key'}

    if proxy is None:
        proxy = get_default_proxy()

    user_record_hash = None
    user_data = None

    # 160-bit hex string?
    if len(user_json_or_hash) == 40 and len(user_json_or_hash.translate(None, "0123456789ABCDEFabcdef")) == 0:

        user_record_hash = user_json_or_hash.lower()
    else:

        # user record json.  hash it
        user_data = user_db.parse_user(user_json_or_hash)
        if user_data is None:
            return {'error': 'Invalid user record JSON'}

        user_record_hash = pybitcoin.hash.hex_hash160(user_db.serialize_user(user_data))

    # go get the current user record
    current_user_record = get_name_record( name, create_if_absent=True )
    if current_user_record is None:
        return {'error': 'No such user'}

    if current_user_record.has_key('error'):
        # some other error
        return current_user_record

    result = {}

    old_hash = pybitcoin.hash.hex_hash160(user_db.serialize_user(user_data))

    # only want transaction?
    if tx_only:

        if privatekey is None and public_key is not None and subsidy_key is not None:
            return proxy.update_tx_subsidized( name, user_record_hash, public_key, subsidy_key )

        else:
            return proxy.update_tx( name, user_record_hash, privatekey )


    # no transaction: go put one
    if txid is None:

        if tx_only:
            result = proxy.update_tx( name, user_record_hash, privatekey )
            return result

        else:
            result = proxy.update(name, user_record_hash, privatekey)

        if 'error' in result:
            # failed
            return result

        if 'transaction_hash' not in result:
            # failed
            result['error'] = "No transaction hash given"
            return result

        txid = result['transaction_hash']

    else:

        # embed the txid into the result nevertheless
        result['transaction_hash'] = txid

    # store new user data
    rc = True
    new_data_hash = None
    if user_data is not None:
        rc, new_data_hash = store_name_record(user_data, txid)

    else:
        # was already a hash
        new_data_hash = user_json_or_hash

    if not rc:
        result['error'] = "Failed to store updated user record."
        return result

    result['status'] = True
    result['value_hash'] = new_data_hash
    result['transaction_hash'] = txid

    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 put_immutable(name, data, privatekey, txid=None, proxy=None):
    """
    put_immutable

    Optionally include a txid from the user record update, in order to retry a failed
    data replication (in which case, this txid corresponds to the succeeded name
    update operation).  This is to avoid needing to pay for each replication retry.
    """

    if proxy is None:
        global default_proxy
        proxy = default_proxy

    # need to put the transaction ID into the data record we put
    user = get_name_record(name, create_if_absent=True)
    if 'error' in user:

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

    data_hash = storage.get_data_hash( data )
    rc = user_db.add_immutable_data(user, data_hash)
    if not rc:
        return {'error': 'Invalid hash'}

    user_json = user_db.serialize_user(user)
    if user_json is None:
        raise Exception("BUG: failed to serialize user record")

    value_hash = None

    if txid is None:

        # haven't updated the user record yet.  Do so now.
        # put the new user record hash
        update_result = update(name, user_json, privatekey, proxy=proxy)
        if 'error' in update_result:

            # failed to replicate user record
            # NOTE: result will have the txid in it; pass it as txid to try again!
            return update_result

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

    result = {
        'data_hash': data_hash,
        'transaction_hash': txid
    }

    # propagate update() data
    if value_hash is not None:
        result['value_hash'] = value_hash

    # replicate the data
    rc = storage.put_immutable_data(data, txid)
    if not rc:
        result['error'] = 'Failed to store immutable data'
        return result

    else:
        result['status'] = True
        return result