<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package state

import (
	"fmt"
	"io"
	"path"
	"time"

	"github.com/juju/errors"
	"github.com/juju/utils"
	charmresource "gopkg.in/juju/charm.v6-unstable/resource"

	"github.com/juju/juju/resource"
)

type resourcePersistence interface {
	// ListResources returns the resource data for the given service ID.
	// None of the resources will be pending.
	ListResources(serviceID string) (resource.ServiceResources, error)

	// ListModelResources returns the resource data for the given
	// service ID. None of the resources will be pending.
	ListModelResources(serviceID string) ([]resource.ModelResource, error)

	// StageResource adds the resource in a separate staging area
	// if the resource isn't already staged. If the resource already
	// exists then it is treated as unavailable as long as the new one
	// is staged.
	//
	// A separate staging area is necessary because we are dealing with
	// the DB and storage at the same time for the same resource in some
	// operations (e.g. SetResource).  Resources are staged in the DB,
	// added to storage, and then finalized in the DB.
	StageResource(resource.ModelResource) error

	// UnstageResource ensures that the resource is removed
	// from the staging area. If it isn't in the staging area
	// then this is a noop.
	UnstageResource(id, serviceID string) error

	// SetResource stores the resource info. If the resource
	// is already staged then it is unstaged.
	SetResource(resource.ModelResource) error

	// SetUnitResource stores the resource info for a unit.
	SetUnitResource(unitID string, args resource.ModelResource) error
}

type resourceStorage interface {
	// PutAndCheckHash stores the content of the reader into the storage.
	PutAndCheckHash(path string, r io.Reader, length int64, hash string) error

	// Remove removes the identified data from the storage.
	Remove(path string) error

	// Get returns a reader for the resource at path. The size of the
	// data is also returned.
	Get(path string) (io.ReadCloser, int64, error)
}

type resourceState struct {
	persist resourcePersistence
	storage resourceStorage

	newPendingID     func() (string, error)
	currentTimestamp func() time.Time
}

// ListResources returns the resource data for the given service ID.
func (st resourceState) ListResources(serviceID string) (resource.ServiceResources, error) {
	resources, err := st.persist.ListResources(serviceID)
	if err != nil {
		return resource.ServiceResources{}, errors.Trace(err)
	}

	return resources, nil
}

// GetResource returns the resource data for the identified resource.
func (st resourceState) GetResource(serviceID, name string) (resource.Resource, error) {
	res, err := st.getResource(serviceID, name)
	if err != nil {
		return res.Resource, errors.Trace(err)
	}
	return res.Resource, nil
}

func (st resourceState) getResource(serviceID, name string) (resource.ModelResource, error) {
	var res resource.ModelResource

	resources, err := st.persist.ListModelResources(serviceID)
	if err != nil {
		return res, errors.Trace(err)
	}

	for _, res := range resources {
		if res.Resource.Name == name {
			return res, nil
		}
	}
	return res, errors.NotFoundf("resource %q", name)
}

// TODO(ericsnow) Separate setting the metadata from storing the blob?

// SetResource stores the resource in the Juju model.
func (st resourceState) SetResource(serviceID, userID string, chRes charmresource.Resource, r io.Reader) (resource.Resource, error) {
	logger.Tracef("adding resource %q for service %q", chRes.Name, serviceID)
	pendingID := ""
	res, err := st.setResource(pendingID, serviceID, userID, chRes, r)
	if err != nil {
		return res, errors.Trace(err)
	}
	return res, nil
}

func (st resourceState) setResource(pendingID, serviceID, userID string, chRes charmresource.Resource, r io.Reader) (resource.Resource, error) {
	res := resource.Resource{
		Resource: chRes,
	}
	if r != nil {
		res.Username = userID
		res.Timestamp = st.currentTimestamp()
	}

	if err := res.Validate(); err != nil {
		return res, errors.Annotate(err, "bad resource metadata")
	}

	id := newResourceID(pendingID, serviceID, res)

	args := resource.ModelResource{
		ID:        id,
		PendingID: pendingID,
		ServiceID: serviceID,
		Resource:  res,
	}
	if r == nil {
		err := st.setResourceInfo(args)
		return res, errors.Trace(err)
	}

	// We use a staging approach for adding the resource metadata
	// to the model. This is necessary because the resource data
	// is stored separately and adding to both should be an atomic
	// operation.

	path := storagePath(res.Name, serviceID, pendingID)
	args.StoragePath = path
	if err := st.persist.StageResource(args); err != nil {<commit_msg>Add a TODO.<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package state

import (
	"fmt"
	"io"
	"path"
	"time"

	"github.com/juju/errors"
	"github.com/juju/utils"
	charmresource "gopkg.in/juju/charm.v6-unstable/resource"

	"github.com/juju/juju/resource"
)

type resourcePersistence interface {
	// ListResources returns the resource data for the given service ID.
	// None of the resources will be pending.
	ListResources(serviceID string) (resource.ServiceResources, error)

	// ListModelResources returns the resource data for the given
	// service ID. None of the resources will be pending.
	ListModelResources(serviceID string) ([]resource.ModelResource, error)

	// StageResource adds the resource in a separate staging area
	// if the resource isn't already staged. If the resource already
	// exists then it is treated as unavailable as long as the new one
	// is staged.
	//
	// A separate staging area is necessary because we are dealing with
	// the DB and storage at the same time for the same resource in some
	// operations (e.g. SetResource).  Resources are staged in the DB,
	// added to storage, and then finalized in the DB.
	StageResource(resource.ModelResource) error

	// UnstageResource ensures that the resource is removed
	// from the staging area. If it isn't in the staging area
	// then this is a noop.
	UnstageResource(id, serviceID string) error

	// SetResource stores the resource info. If the resource
	// is already staged then it is unstaged.
	SetResource(resource.ModelResource) error

	// SetUnitResource stores the resource info for a unit.
	SetUnitResource(unitID string, args resource.ModelResource) error
}

type resourceStorage interface {
	// PutAndCheckHash stores the content of the reader into the storage.
	PutAndCheckHash(path string, r io.Reader, length int64, hash string) error

	// Remove removes the identified data from the storage.
	Remove(path string) error

	// Get returns a reader for the resource at path. The size of the
	// data is also returned.
	Get(path string) (io.ReadCloser, int64, error)
}

type resourceState struct {
	persist resourcePersistence
	storage resourceStorage

	newPendingID     func() (string, error)
	currentTimestamp func() time.Time
}

// ListResources returns the resource data for the given service ID.
func (st resourceState) ListResources(serviceID string) (resource.ServiceResources, error) {
	resources, err := st.persist.ListResources(serviceID)
	if err != nil {
		return resource.ServiceResources{}, errors.Trace(err)
	}

	return resources, nil
}

// GetResource returns the resource data for the identified resource.
func (st resourceState) GetResource(serviceID, name string) (resource.Resource, error) {
	res, err := st.getResource(serviceID, name)
	if err != nil {
		return res.Resource, errors.Trace(err)
	}
	return res.Resource, nil
}

func (st resourceState) getResource(serviceID, name string) (resource.ModelResource, error) {
	var res resource.ModelResource

	resources, err := st.persist.ListModelResources(serviceID)
	if err != nil {
		return res, errors.Trace(err)
	}

	for _, res := range resources {
		if res.Resource.Name == name {
			return res, nil
		}
	}
	return res, errors.NotFoundf("resource %q", name)
}

// TODO(ericsnow) Separate setting the metadata from storing the blob?

// SetResource stores the resource in the Juju model.
func (st resourceState) SetResource(serviceID, userID string, chRes charmresource.Resource, r io.Reader) (resource.Resource, error) {
	logger.Tracef("adding resource %q for service %q", chRes.Name, serviceID)
	pendingID := ""
	res, err := st.setResource(pendingID, serviceID, userID, chRes, r)
	if err != nil {
		return res, errors.Trace(err)
	}
	return res, nil
}

func (st resourceState) setResource(pendingID, serviceID, userID string, chRes charmresource.Resource, r io.Reader) (resource.Resource, error) {
	res := resource.Resource{
		Resource: chRes,
	}
	if r != nil {
		// TODO(ericsnow) Validate the user ID (or use a tag).
		res.Username = userID
		res.Timestamp = st.currentTimestamp()
	}

	if err := res.Validate(); err != nil {
		return res, errors.Annotate(err, "bad resource metadata")
	}

	id := newResourceID(pendingID, serviceID, res)

	args := resource.ModelResource{
		ID:        id,
		PendingID: pendingID,
		ServiceID: serviceID,
		Resource:  res,
	}
	if r == nil {
		err := st.setResourceInfo(args)
		return res, errors.Trace(err)
	}

	// We use a staging approach for adding the resource metadata
	// to the model. This is necessary because the resource data
	// is stored separately and adding to both should be an atomic
	// operation.

	path := storagePath(res.Name, serviceID, pendingID)
	args.StoragePath = path
	if err := st.persist.StageResource(args); err != nil {
{'fraction_non_alphanumeric': 0.09485917704952361, 'fraction_numerical': 0.0020940215684221546, 'mean_word_length': 4.045958795562599, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1652586', 'n_tokens_mistral': 2921, 'n_tokens_neox': 2798, 'n_words': 1059}