<commit_before>package state

//
// DefinitionSelect postgres specific query for definitions
//
const DefinitionSelect = `
select td.definition_id                    as definitionid,
       td.adaptive_resource_allocation     as adaptiveresourceallocation,
       td.image                            as image,
       td.group_name                       as groupname,
       td.alias                            as alias,
       td.memory                           as memory,
       coalesce(td.command, '')            as command,
       coalesce(td.task_type, '')          as tasktype,
       env::TEXT                           as env,
       td.cpu                              as cpu,
       td.gpu                              as gpu,
       array_to_json('{""}'::TEXT[])::TEXT as tags,
       array_to_json('{}'::INT[])::TEXT    as ports
from (select * from task_def) td
`

//
// ListDefinitionsSQL postgres specific query for listing definitions
//
const ListDefinitionsSQL = DefinitionSelect + "\n%s %s limit $1 offset $2"

//
// GetDefinitionSQL postgres specific query for getting a single definition
//
const GetDefinitionSQL = DefinitionSelect + "\nwhere definition_id = $1"

//
// GetDefinitionByAliasSQL get definition by alias
//
const GetDefinitionByAliasSQL = DefinitionSelect + "\nwhere alias = $1"

const TaskResourcesSelectCommandSQL = `
SELECT cast((percentile_disc(0.99) within GROUP (ORDER BY A.max_memory_used)) * 1.75 as int) as memory,
       cast((percentile_disc(0.99) within GROUP (ORDER BY A.max_cpu_used)) * 1.25  as int)  as cpu
FROM (SELECT CASE WHEN exit_code = 137 THEN memory * 2 ELSE max_memory_used END as max_memory_used, cpu as max_cpu_used
      FROM TASK
      WHERE
           queued_at >= CURRENT_TIMESTAMP - INTERVAL '7 days'
           AND exit_code = 137
           AND engine = 'eks'
           AND definition_id = $1
           AND command_hash = (SELECT command_hash FROM task WHERE run_id = $2)
      LIMIT 30) A

`
const TaskExecutionRuntimeCommandSQL = `
SELECT percentile_disc(0.95) within GROUP (ORDER BY A.minutes) as minutes
FROM (SELECT EXTRACT(epoch from finished_at - started_at) / 60 as minutes
      FROM TASK
      WHERE definition_id = $1
        AND exit_code = 0
        AND engine = 'eks'
        AND queued_at >= CURRENT_TIMESTAMP - INTERVAL '7 days'
        AND command_hash = (SELECT command_hash FROM task WHERE run_id = $2)
      LIMIT 30) A
`

const ListFailingNodesSQL = `
WITH control_plane_errors AS
         (
             SELECT instance_dns_name
             FROM TASK
             WHERE (exit_code = 128 OR
                    pod_events @> '[{"reason": "Failed"}]' OR
                    pod_events @> '[{"reason": "FailedSync"}]' OR
                    pod_events @> '[{"reason": "OutOfmemory"}]' OR
                    pod_events @> '[{"reason": "FailedCreatePodSandBox"}]' OR
                    (exit_code = 1 AND exit_reason is null))
               AND engine = 'eks'
               AND queued_at >= NOW() - INTERVAL '12 HOURS'
             GROUP BY 1
         ),
     timeouts AS
         (
             SELECT instance_dns_name, count(*) as c
             FROM TASK
             WHERE exit_reason like 'Task terminated by - %'
               AND engine = 'eks'
               AND queued_at >= NOW() - INTERVAL '12 HOURS'
             GROUP BY 1
         )
SELECT
   instance_dns_name
FROM
   control_plane_errors
UNION ALL
SELECT
   instance_dns_name
FROM
   timeouts
WHERE
   c > 5
`

const PodReAttemptRate = `
SELECT (multiple_attempts / (CASE WHEN single_attempts = 0 THEN 1 ELSE single_attempts END)) AS attempts
FROM (
      SELECT COUNT(CASE WHEN attempt_count = 1 THEN 1 END) * 1.0 AS single_attempts,
             COUNT(CASE WHEN attempt_count != 1 THEN 1 END) * 1.0 AS multiple_attempts
      FROM task
      WHERE engine = 'eks' AND
            queued_at >= NOW() - INTERVAL '30 MINUTES' AND
            node_lifecycle = 'spot') A
`

//
// RunSelect postgres specific query for runs
//
const RunSelect = `
select t.run_id                          as runid,
       coalesce(t.definition_id, '')     as definitionid,
       coalesce(t.alias, '')             as alias,
       coalesce(t.image, '')             as image,
       coalesce(t.cluster_name, '')      as clustername,
       t.exit_code                       as exitcode,
       t.exit_reason                     as exitreason,
       coalesce(t.status, '')            as status,
       queued_at                         as queuedat,
       started_at                        as startedat,
       finished_at                       as finishedat,
       coalesce(t.instance_id, '')       as instanceid,
       coalesce(t.instance_dns_name, '') as instancednsname,
       coalesce(t.group_name, '')        as groupname,
       coalesce(t.task_type, '')         as tasktype,
       env::TEXT                         as env,
       command,
       memory,
       cpu,
       gpu,
       engine,
       ephemeral_storage                 as ephemeralstorage,
       node_lifecycle                    as nodelifecycle,
       pod_name                          as podname,
       namespace,
       max_cpu_used                      as maxcpuused,
       max_memory_used                   as maxmemoryused,
       pod_events::TEXT                  as podevents,
       command_hash                      as commandhash,
       cloudtrail_notifications::TEXT    as cloudtrailnotifications,
       coalesce(executable_id, '')       as executableid,
       coalesce(executable_type, '')     as executabletype,
       execution_request_custom::TEXT    as executionrequestcustom,
       cpu_limit                         as cpulimit,
       memory_limit                      as memorylimit,
       attempt_count                     as attemptcount,
       spawned_runs::TEXT                as spawnedruns,
       run_exceptions::TEXT              as runexceptions,
       active_deadline_seconds           as activedeadlineseconds,
       spark_extension::TEXT             as sparkextension,
       metrics_uri                       as metricsuri,
       description                       as description
from task t
`

//
// ListRunsSQL postgres specific query for listing runs
//
const ListRunsSQL = RunSelect + "\n%s %s limit $1 offset $2"

//
// GetRunSQL postgres specific query for getting a single run
//
const GetRunSQL = RunSelect + "\nwhere run_id = $1"

const GetRunSQLByEMRJobId = RunSelect + "\nwhere spark_extension->>'emr_job_id' = $1"

//
// GetRunSQLForUpdate postgres specific query for getting a single run
// for update
//
const GetRunSQLForUpdate = GetRunSQL + " for update"

//
// GroupsSelect postgres specific query for getting existing definition
// group_names
//
const GroupsSelect = `
select distinct group_name from task_def
`

//
// TagsSelect postgres specific query for getting existing definition tags
//
const TagsSelect = `
select distinct text from tags
`

//
// ListGroupsSQL postgres specific query for listing definition group_names
//
const ListGroupsSQL = GroupsSelect + "\n%s order by group_name asc limit $1 offset $2"

//
// ListTagsSQL postgres specific query for listing definition tags
//
const ListTagsSQL = TagsSelect + "\n%s order by text asc limit $1 offset $2"

//
// WorkerSelect postgres specific query for workers
//
const WorkerSelect = `
  select
    worker_type        as workertype,
    count_per_instance as countperinstance,
    engine
  from worker
`

//
// ListWorkersSQL postgres specific query for listing workers
//
const ListWorkersSQL = WorkerSelect

const GetWorkerEngine = WorkerSelect + "\nwhere engine = $1"

//
// GetWorkerSQL postgres specific query for retrieving data for a specific
// worker type.
//
const GetWorkerSQL = WorkerSelect + "\nwhere worker_type = $1 and engine = $2"

//
// GetWorkerSQLForUpdate postgres specific query for retrieving data for a specific
// worker type; locks the row.
//
const GetWorkerSQLForUpdate = GetWorkerSQL + " for update"

// TemplateSelect selects a template
const TemplateSelect = `
SELECT
  template_id as templateid,
  template_name as templatename,
  version,
  schema,
  command_template as commandtemplate,
  adaptive_resource_allocation as adaptiveresourceallocation,
  image,
  memory,
  env::TEXT as env,
  privileged,
  cpu,
  gpu,
  defaults,
  coalesce(avatar_uri, '') as avataruri
FROM template
`

// ListTemplatesSQL postgres specific query for listing templates
const ListTemplatesSQL = TemplateSelect + "\n%s limit $1 offset $2"

// GetTemplateByIDSQL postgres specific query for getting a single template
const GetTemplateByIDSQL = TemplateSelect + "\nwhere template_id = $1"

// ListTemplatesLatestOnlySQL lists the latest version of each distinct
// template name.
const ListTemplatesLatestOnlySQL = `
  SELECT DISTINCT ON (template_name)
    template_id as templateid,
    template_name as templatename,
    version,
    schema,
    command_template as commandtemplate,
    adaptive_resource_allocation as adaptiveresourceallocation,
    image,
    memory,
    env::TEXT as env,
    privileged,
    cpu,
    gpu,
    defaults,
    coalesce(avatar_uri, '') as avataruri
  FROM template
  ORDER BY template_name, version DESC, template_id
  LIMIT $1 OFFSET $2
`

// GetTemplateLatestOnlySQL get the latest version of a specific template name.
const GetTemplateLatestOnlySQL = TemplateSelect + "\nWHERE template_name = $1 ORDER BY version DESC LIMIT 1;"
const GetTemplateByVersionSQL = TemplateSelect + "\nWHERE template_name = $1 AND version = $2 ORDER BY version DESC LIMIT 1;"
<commit_msg>handle edge case for OOM<commit_after>package state

//
// DefinitionSelect postgres specific query for definitions
//
const DefinitionSelect = `
select td.definition_id                    as definitionid,
       td.adaptive_resource_allocation     as adaptiveresourceallocation,
       td.image                            as image,
       td.group_name                       as groupname,
       td.alias                            as alias,
       td.memory                           as memory,
       coalesce(td.command, '')            as command,
       coalesce(td.task_type, '')          as tasktype,
       env::TEXT                           as env,
       td.cpu                              as cpu,
       td.gpu                              as gpu,
       array_to_json('{""}'::TEXT[])::TEXT as tags,
       array_to_json('{}'::INT[])::TEXT    as ports
from (select * from task_def) td
`

//
// ListDefinitionsSQL postgres specific query for listing definitions
//
const ListDefinitionsSQL = DefinitionSelect + "\n%s %s limit $1 offset $2"

//
// GetDefinitionSQL postgres specific query for getting a single definition
//
const GetDefinitionSQL = DefinitionSelect + "\nwhere definition_id = $1"

//
// GetDefinitionByAliasSQL get definition by alias
//
const GetDefinitionByAliasSQL = DefinitionSelect + "\nwhere alias = $1"

const TaskResourcesSelectCommandSQL = `
SELECT cast((percentile_disc(0.99) within GROUP (ORDER BY A.max_memory_used)) * 1.75 as int) as memory,
       cast((percentile_disc(0.99) within GROUP (ORDER BY A.max_cpu_used)) * 1.25  as int)  as cpu
FROM (SELECT CASE WHEN (exit_code = 137 or exit_reason = 'OOMKilled') THEN memory * 2 ELSE max_memory_used END as max_memory_used, cpu as max_cpu_used
      FROM TASK
      WHERE
           queued_at >= CURRENT_TIMESTAMP - INTERVAL '7 days'
           AND (exit_code = 137 or exit_reason = 'OOMKilled')
           AND engine = 'eks'
           AND definition_id = $1
           AND command_hash = (SELECT command_hash FROM task WHERE run_id = $2)
      LIMIT 30) A

`
const TaskExecutionRuntimeCommandSQL = `
SELECT percentile_disc(0.95) within GROUP (ORDER BY A.minutes) as minutes
FROM (SELECT EXTRACT(epoch from finished_at - started_at) / 60 as minutes
      FROM TASK
      WHERE definition_id = $1
        AND exit_code = 0
        AND engine = 'eks'
        AND queued_at >= CURRENT_TIMESTAMP - INTERVAL '7 days'
        AND command_hash = (SELECT command_hash FROM task WHERE run_id = $2)
      LIMIT 30) A
`

const ListFailingNodesSQL = `
WITH control_plane_errors AS
         (
             SELECT instance_dns_name
             FROM TASK
             WHERE (exit_code = 128 OR
                    pod_events @> '[{"reason": "Failed"}]' OR
                    pod_events @> '[{"reason": "FailedSync"}]' OR
                    pod_events @> '[{"reason": "OutOfmemory"}]' OR
                    pod_events @> '[{"reason": "FailedCreatePodSandBox"}]' OR
                    (exit_code = 1 AND exit_reason is null))
               AND engine = 'eks'
               AND queued_at >= NOW() - INTERVAL '12 HOURS'
             GROUP BY 1
         ),
     timeouts AS
         (
             SELECT instance_dns_name, count(*) as c
             FROM TASK
             WHERE exit_reason like 'Task terminated by - %'
               AND engine = 'eks'
               AND queued_at >= NOW() - INTERVAL '12 HOURS'
             GROUP BY 1
         )
SELECT
   instance_dns_name
FROM
   control_plane_errors
UNION ALL
SELECT
   instance_dns_name
FROM
   timeouts
WHERE
   c > 5
`

const PodReAttemptRate = `
SELECT (multiple_attempts / (CASE WHEN single_attempts = 0 THEN 1 ELSE single_attempts END)) AS attempts
FROM (
      SELECT COUNT(CASE WHEN attempt_count = 1 THEN 1 END) * 1.0 AS single_attempts,
             COUNT(CASE WHEN attempt_count != 1 THEN 1 END) * 1.0 AS multiple_attempts
      FROM task
      WHERE engine = 'eks' AND
            queued_at >= NOW() - INTERVAL '30 MINUTES' AND
            node_lifecycle = 'spot') A
`

//
// RunSelect postgres specific query for runs
//
const RunSelect = `
select t.run_id                          as runid,
       coalesce(t.definition_id, '')     as definitionid,
       coalesce(t.alias, '')             as alias,
       coalesce(t.image, '')             as image,
       coalesce(t.cluster_name, '')      as clustername,
       t.exit_code                       as exitcode,
       t.exit_reason                     as exitreason,
       coalesce(t.status, '')            as status,
       queued_at                         as queuedat,
       started_at                        as startedat,
       finished_at                       as finishedat,
       coalesce(t.instance_id, '')       as instanceid,
       coalesce(t.instance_dns_name, '') as instancednsname,
       coalesce(t.group_name, '')        as groupname,
       coalesce(t.task_type, '')         as tasktype,
       env::TEXT                         as env,
       command,
       memory,
       cpu,
       gpu,
       engine,
       ephemeral_storage                 as ephemeralstorage,
       node_lifecycle                    as nodelifecycle,
       pod_name                          as podname,
       namespace,
       max_cpu_used                      as maxcpuused,
       max_memory_used                   as maxmemoryused,
       pod_events::TEXT                  as podevents,
       command_hash                      as commandhash,
       cloudtrail_notifications::TEXT    as cloudtrailnotifications,
       coalesce(executable_id, '')       as executableid,
       coalesce(executable_type, '')     as executabletype,
       execution_request_custom::TEXT    as executionrequestcustom,
       cpu_limit                         as cpulimit,
       memory_limit                      as memorylimit,
       attempt_count                     as attemptcount,
       spawned_runs::TEXT                as spawnedruns,
       run_exceptions::TEXT              as runexceptions,
       active_deadline_seconds           as activedeadlineseconds,
       spark_extension::TEXT             as sparkextension,
       metrics_uri                       as metricsuri,
       description                       as description
from task t
`

//
// ListRunsSQL postgres specific query for listing runs
//
const ListRunsSQL = RunSelect + "\n%s %s limit $1 offset $2"

//
// GetRunSQL postgres specific query for getting a single run
//
const GetRunSQL = RunSelect + "\nwhere run_id = $1"

const GetRunSQLByEMRJobId = RunSelect + "\nwhere spark_extension->>'emr_job_id' = $1"

//
// GetRunSQLForUpdate postgres specific query for getting a single run
// for update
//
const GetRunSQLForUpdate = GetRunSQL + " for update"

//
// GroupsSelect postgres specific query for getting existing definition
// group_names
//
const GroupsSelect = `
select distinct group_name from task_def
`

//
// TagsSelect postgres specific query for getting existing definition tags
//
const TagsSelect = `
select distinct text from tags
`

//
// ListGroupsSQL postgres specific query for listing definition group_names
//
const ListGroupsSQL = GroupsSelect + "\n%s order by group_name asc limit $1 offset $2"

//
// ListTagsSQL postgres specific query for listing definition tags
//
const ListTagsSQL = TagsSelect + "\n%s order by text asc limit $1 offset $2"

//
// WorkerSelect postgres specific query for workers
//
const WorkerSelect = `
  select
    worker_type        as workertype,
    count_per_instance as countperinstance,
    engine
  from worker
`

//
// ListWorkersSQL postgres specific query for listing workers
//
const ListWorkersSQL = WorkerSelect

const GetWorkerEngine = WorkerSelect + "\nwhere engine = $1"

//
// GetWorkerSQL postgres specific query for retrieving data for a specific
// worker type.
//
const GetWorkerSQL = WorkerSelect + "\nwhere worker_type = $1 and engine = $2"

//
// GetWorkerSQLForUpdate postgres specific query for retrieving data for a specific
// worker type; locks the row.
//
const GetWorkerSQLForUpdate = GetWorkerSQL + " for update"

// TemplateSelect selects a template
const TemplateSelect = `
SELECT
  template_id as templateid,
  template_name as templatename,
  version,
  schema,
  command_template as commandtemplate,
  adaptive_resource_allocation as adaptiveresourceallocation,
  image,
  memory,
  env::TEXT as env,
  privileged,
  cpu,
  gpu,
  defaults,
  coalesce(avatar_uri, '') as avataruri
FROM template
`

// ListTemplatesSQL postgres specific query for listing templates
const ListTemplatesSQL = TemplateSelect + "\n%s limit $1 offset $2"

// GetTemplateByIDSQL postgres specific query for getting a single template
const GetTemplateByIDSQL = TemplateSelect + "\nwhere template_id = $1"

// ListTemplatesLatestOnlySQL lists the latest version of each distinct
// template name.
const ListTemplatesLatestOnlySQL = `
  SELECT DISTINCT ON (template_name)
    template_id as templateid,
    template_name as templatename,
    version,
    schema,
    command_template as commandtemplate,
    adaptive_resource_allocation as adaptiveresourceallocation,
    image,
    memory,
    env::TEXT as env,
    privileged,
    cpu,
    gpu,
    defaults,
    coalesce(avatar_uri, '') as avataruri
  FROM template
  ORDER BY template_name, version DESC, template_id
  LIMIT $1 OFFSET $2
`

// GetTemplateLatestOnlySQL get the latest version of a specific template name.
const GetTemplateLatestOnlySQL = TemplateSelect + "\nWHERE template_name = $1 ORDER BY version DESC LIMIT 1;"
const GetTemplateByVersionSQL = TemplateSelect + "\nWHERE template_name = $1 AND version = $2 ORDER BY version DESC LIMIT 1;"
<|endoftext|>


<commit_before>package transition

import (
	"fmt"
	"strings"

	"github.com/jinzhu/gorm"
	"github.com/qor/qor/audited"
)

type StateChangeLog struct {
	gorm.Model
	ReferTable string
	ReferId    string
	From       string
	To         string
	Note       string `sql:"size:1024"`
	audited.AuditedModel
}

// GenerateReferenceKey generate reference key used for change log
func GenerateReferenceKey(model interface{}, db *gorm.DB) string {
	var (
		scope         = db.NewScope(model)
		primaryValues []string
	)

	for _, field := range scope.PrimaryFields() {
		primaryValues = append(primaryValues, fmt.Sprint(field.Field.Interface()))
	}

	return strings.Join(primaryValues, "::")
}

// GetChangeLogs get state change logs
func GetStateChangeLogs(model interface{}, db *gorm.DB) []StateChangeLog {
	var (
		changelogs []StateChangeLog
		scope      = db.NewScope(model)
	)

	db.Where("refer_table = ? AND refer_id = ?", scope.TableName(), GenerateReferenceKey(model, db)).Find(&changelogs)

	return changelogs
}
<commit_msg>Extract audited out from qor<commit_after>package transition

import (
	"fmt"
	"strings"

	"github.com/jinzhu/gorm"
	"github.com/qor/audited"
)

type StateChangeLog struct {
	gorm.Model
	ReferTable string
	ReferId    string
	From       string
	To         string
	Note       string `sql:"size:1024"`
	audited.AuditedModel
}

// GenerateReferenceKey generate reference key used for change log
func GenerateReferenceKey(model interface{}, db *gorm.DB) string {
	var (
		scope         = db.NewScope(model)
		primaryValues []string
	)

	for _, field := range scope.PrimaryFields() {
		primaryValues = append(primaryValues, fmt.Sprint(field.Field.Interface()))
	}

	return strings.Join(primaryValues, "::")
}

// GetChangeLogs get state change logs
func GetStateChangeLogs(model interface{}, db *gorm.DB) []StateChangeLog {
	var (
		changelogs []StateChangeLog
		scope      = db.NewScope(model)
	)

	db.Where("refer_table = ? AND refer_id = ?", scope.TableName(), GenerateReferenceKey(model, db)).Find(&changelogs)

	return changelogs
}
<|endoftext|>


<commit_before>package dao

import (
	"encoding/json"
	"errors"
	"fmt"
	resourcedmaster_storage "github.com/resourced/resourced-master/storage"
	"strconv"
	"strings"
	"time"
)

// NewApplication is constructor for Application struct.
func NewApplication(store resourcedmaster_storage.Storer, name string) (*Application, error) {
	a := &Application{}
	a.store = store
	a.Name = name
	a.Enabled = true
	a.Id = strconv.FormatInt(time.Now().UnixNano(), 10)

	return a, nil
}

// SaveApplicationByAccessToken saves application data in JSON format with accessToken as key.
func SaveApplicationByAccessToken(store resourcedmaster_storage.Storer, accessToken string, data []byte) error {
	return store.Update("/applications/access-token/"+accessToken, data)
}

// GetApplicationByAccessToken returns Application struct with name as key.
func GetApplicationByAccessToken(store resourcedmaster_storage.Storer, accessToken string) (*Application, error) {
	jsonBytes, err := store.Get("/applications/access-token/" + accessToken)
	if err != nil {
		return nil, err
	}

	a := &Application{}

	err = json.Unmarshal(jsonBytes, a)
	if err != nil {
		return nil, err
	}

	a.store = store

	return a, nil
}

// DeleteApplicationByAccessToken returns error.
func DeleteApplicationByAccessToken(store resourcedmaster_storage.Storer, accessToken string) error {
	return store.Delete(fmt.Sprintf("/applications/access-token/%v", accessToken))
}

// GetApplicationById returns Application struct with name as key.
func GetApplicationById(store resourcedmaster_storage.Storer, id string) (*Application, error) {
	jsonBytes, err := store.Get(fmt.Sprintf("/applications/id/%v/record", id))
	if err != nil {
		return nil, err
	}

	a := &Application{}

	err = json.Unmarshal(jsonBytes, a)
	if err != nil {
		return nil, err
	}

	a.store = store

	return a, nil
}

// AllApplications returns a slice of all Application structs.
func AllApplications(store resourcedmaster_storage.Storer) ([]*Application, error) {
	idList, err := store.List("/applications/id")
	if err != nil {
		return nil, err
	}

	applications := make([]*Application, 0)

	for _, keyWithoutFullpath := range idList {
		keyInChunk := strings.Split(keyWithoutFullpath, "/")
		if len(keyInChunk) < 1 {
			continue
		}

		id := keyInChunk[0]
		a, err := GetApplicationById(store, id)
		if err == nil {
			applications = append(applications, a)
		}
	}

	return applications, nil
}

// SaveApplicationReaderWriterJson saves application reader data in JSON format with id and path as keys.
func SaveApplicationReaderWriterJson(store resourcedmaster_storage.Storer, id string, readerOrWriter, path string, data []byte) error {
	return store.Update(fmt.Sprintf("applications/id/%v/%vs/%v", id, readerOrWriter, path), data)
}

// DeleteApplicationReaderWriterJson returns error.
func DeleteApplicationReaderWriterJson(store resourcedmaster_storage.Storer, id string, readerOrWriter, path string) error {
	return store.Delete(fmt.Sprintf("applications/id/%v/%vs/%v", id, readerOrWriter, path))
}

// GetApplicationReaderWriterJson returns json bytes.
func GetApplicationReaderWriterJson(store resourcedmaster_storage.Storer, id string, readerOrWriter, path string) ([]byte, error) {
	return store.Get(fmt.Sprintf("applications/id/%v/%vs/%v", id, readerOrWriter, path))
}

// SaveApplicationReaderWriterByHostJson saves application reader data in JSON format with id, host and path as keys.
func SaveApplicationReaderWriterByHostJson(store resourcedmaster_storage.Storer, id string, host, readerOrWriter, path string, data []byte) error {
	return store.Update(fmt.Sprintf("applications/id/%v/hosts/%v/%vs/%v", id, host, readerOrWriter, path), data)
}

// DeleteApplicationReaderWriterByHostJson returns error.
func DeleteApplicationReaderWriterByHostJson(store resourcedmaster_storage.Storer, id string, host, readerOrWriter, path string) error {
	return store.Delete(fmt.Sprintf("applications/id/%v/hosts/%v/%vs/%v", id, host, readerOrWriter, path))
}

// GetApplicationReaderWriterByHostJson returns json bytes.
func GetApplicationReaderWriterByHostJson(store resourcedmaster_storage.Storer, id string, host, readerOrWriter, path string) ([]byte, error) {
	return store.Get(fmt.Sprintf("applications/id/%v/hosts/%v/%vs/%v", id, host, readerOrWriter, path))
}

// AllReaderWriterByHost returns a slice of all reader/writer JSON data with id and host as keys.
func AllReaderWriterByHost(store resourcedmaster_storage.Storer, id string, host, readerOrWriter string) ([][]byte, error) {
	paths, err := store.List(fmt.Sprintf("applications/id/%v/hosts/%v/%vs", id, host, readerOrWriter))
	if err != nil {
		return nil, err
	}

	sliceJsonData := make([][]byte, 0)

	for _, path := range paths {
		jsonData, err := GetApplicationReaderWriterByHostJson(store, id, host, readerOrWriter, path)
		if err == nil {
			sliceJsonData = append(sliceJsonData, jsonData)
		}
	}

	return sliceJsonData, nil
}

type Application struct {
	Id      string
	Name    string
	Enabled bool
	store   resourcedmaster_storage.Storer
}

// validateBeforeSave checks various conditions before saving.
func (a *Application) validateBeforeSave() error {
	if a.Id == "" {
		return errors.New("Id must not be empty.")
	}
	if a.Name == "" {
		return errors.New("Name must not be empty.")
	}
	return nil
}

// Save application in JSON format.
func (a *Application) Save() error {
	err := a.validateBeforeSave()
	if err != nil {
		return err
	}

	jsonBytes, err := json.Marshal(a)
	if err != nil {
		return err
	}

	err = CommonSaveById(a.store, "applications", a.Id, jsonBytes)
	if err != nil {
		return err
	}

	fmt.Printf("jsonBytes 2: %v\n", string(jsonBytes))

	return nil
}

func (a *Application) Delete() error {
	return CommonDeleteById(a.store, "applications", a.Id)
}

func (a *Application) SaveReaderWriterJson(readerOrWriter, path string, data []byte) error {
	return SaveApplicationReaderWriterJson(a.store, a.Id, readerOrWriter, path, data)
}

func (a *Application) DeleteReaderWriterJson(readerOrWriter, path string) error {
	return DeleteApplicationReaderWriterJson(a.store, a.Id, readerOrWriter, path)
}

func (a *Application) GetReaderWriterJson(readerOrWriter, path string) ([]byte, error) {
	return GetApplicationReaderWriterJson(a.store, a.Id, readerOrWriter, path)
}

func (a *Application) SaveReaderWriterByHostJson(readerOrWriter, host, path string, data []byte) error {
	return SaveApplicationReaderWriterByHostJson(a.store, a.Id, host, readerOrWriter, path, data)
}

func (a *Application) DeleteReaderWriterByHostJson(readerOrWriter, host, path string) error {
	return DeleteApplicationReaderWriterByHostJson(a.store, a.Id, host, readerOrWriter, path)
}

func (a *Application) GetReaderWriterByHostJson(readerOrWriter, host, path string) ([]byte, error) {
	return GetApplicationReaderWriterByHostJson(a.store, a.Id, host, readerOrWriter, path)
}
<commit_msg>remove useless printf.<commit_after>package dao

import (
	"encoding/json"
	"errors"
	"fmt"
	resourcedmaster_storage "github.com/resourced/resourced-master/storage"
	"strconv"
	"strings"
	"time"
)

// NewApplication is constructor for Application struct.
func NewApplication(store resourcedmaster_storage.Storer, name string) (*Application, error) {
	a := &Application{}
	a.store = store
	a.Name = name
	a.Enabled = true
	a.Id = strconv.FormatInt(time.Now().UnixNano(), 10)

	return a, nil
}

// SaveApplicationByAccessToken saves application data in JSON format with accessToken as key.
func SaveApplicationByAccessToken(store resourcedmaster_storage.Storer, accessToken string, data []byte) error {
	return store.Update("/applications/access-token/"+accessToken, data)
}

// GetApplicationByAccessToken returns Application struct with name as key.
func GetApplicationByAccessToken(store resourcedmaster_storage.Storer, accessToken string) (*Application, error) {
	jsonBytes, err := store.Get("/applications/access-token/" + accessToken)
	if err != nil {
		return nil, err
	}

	a := &Application{}

	err = json.Unmarshal(jsonBytes, a)
	if err != nil {
		return nil, err
	}

	a.store = store

	return a, nil
}

// DeleteApplicationByAccessToken returns error.
func DeleteApplicationByAccessToken(store resourcedmaster_storage.Storer, accessToken string) error {
	return store.Delete(fmt.Sprintf("/applications/access-token/%v", accessToken))
}

// GetApplicationById returns Application struct with name as key.
func GetApplicationById(store resourcedmaster_storage.Storer, id string) (*Application, error) {
	jsonBytes, err := store.Get(fmt.Sprintf("/applications/id/%v/record", id))
	if err != nil {
		return nil, err
	}

	a := &Application{}

	err = json.Unmarshal(jsonBytes, a)
	if err != nil {
		return nil, err
	}

	a.store = store

	return a, nil
}

// AllApplications returns a slice of all Application structs.
func AllApplications(store resourcedmaster_storage.Storer) ([]*Application, error) {
	idList, err := store.List("/applications/id")
	if err != nil {
		return nil, err
	}

	applications := make([]*Application, 0)

	for _, keyWithoutFullpath := range idList {
		keyInChunk := strings.Split(keyWithoutFullpath, "/")
		if len(keyInChunk) < 1 {
			continue
		}

		id := keyInChunk[0]
		a, err := GetApplicationById(store, id)
		if err == nil {
			applications = append(applications, a)
		}
	}

	return applications, nil
}

// SaveApplicationReaderWriterJson saves application reader data in JSON format with id and path as keys.
func SaveApplicationReaderWriterJson(store resourcedmaster_storage.Storer, id string, readerOrWriter, path string, data []byte) error {
	return store.Update(fmt.Sprintf("applications/id/%v/%vs/%v", id, readerOrWriter, path), data)
}

// DeleteApplicationReaderWriterJson returns error.
func DeleteApplicationReaderWriterJson(store resourcedmaster_storage.Storer, id string, readerOrWriter, path string) error {
	return store.Delete(fmt.Sprintf("applications/id/%v/%vs/%v", id, readerOrWriter, path))
}

// GetApplicationReaderWriterJson returns json bytes.
func GetApplicationReaderWriterJson(store resourcedmaster_storage.Storer, id string, readerOrWriter, path string) ([]byte, error) {
	return store.Get(fmt.Sprintf("applications/id/%v/%vs/%v", id, readerOrWriter, path))
}

// SaveApplicationReaderWriterByHostJson saves application reader data in JSON format with id, host and path as keys.
func SaveApplicationReaderWriterByHostJson(store resourcedmaster_storage.Storer, id string, host, readerOrWriter, path string, data []byte) error {
	return store.Update(fmt.Sprintf("applications/id/%v/hosts/%v/%vs/%v", id, host, readerOrWriter, path), data)
}

// DeleteApplicationReaderWriterByHostJson returns error.
func DeleteApplicationReaderWriterByHostJson(store resourcedmaster_storage.Storer, id string, host, readerOrWriter, path string) error {
	return store.Delete(fmt.Sprintf("applications/id/%v/hosts/%v/%vs/%v", id, host, readerOrWriter, path))
}

// GetApplicationReaderWriterByHostJson returns json bytes.
func GetApplicationReaderWriterByHostJson(store resourcedmaster_storage.Storer, id string, host, readerOrWriter, path string) ([]byte, error) {
	return store.Get(fmt.Sprintf("applications/id/%v/hosts/%v/%vs/%v", id, host, readerOrWriter, path))
}

// AllReaderWriterByHost returns a slice of all reader/writer JSON data with id and host as keys.
func AllReaderWriterByHost(store resourcedmaster_storage.Storer, id string, host, readerOrWriter string) ([][]byte, error) {
	paths, err := store.List(fmt.Sprintf("applications/id/%v/hosts/%v/%vs", id, host, readerOrWriter))
	if err != nil {
		return nil, err
	}

	sliceJsonData := make([][]byte, 0)

	for _, path := range paths {
		jsonData, err := GetApplicationReaderWriterByHostJson(store, id, host, readerOrWriter, path)
		if err == nil {
			sliceJsonData = append(sliceJsonData, jsonData)
		}
	}

	return sliceJsonData, nil
}

type Application struct {
	Id      string
	Name    string
	Enabled bool
	store   resourcedmaster_storage.Storer
}

// validateBeforeSave checks various conditions before saving.
func (a *Application) validateBeforeSave() error {
	if a.Id == "" {
		return errors.New("Id must not be empty.")
	}
	if a.Name == "" {
		return errors.New("Name must not be empty.")
	}
	return nil
}

// Save application in JSON format.
func (a *Application) Save() error {
	err := a.validateBeforeSave()
	if err != nil {
		return err
	}

	jsonBytes, err := json.Marshal(a)
	if err != nil {
		return err
	}

	err = CommonSaveById(a.store, "applications", a.Id, jsonBytes)
	if err != nil {
		return err
	}

	return nil
}

func (a *Application) Delete() error {
	return CommonDeleteById(a.store, "applications", a.Id)
}

func (a *Application) SaveReaderWriterJson(readerOrWriter, path string, data []byte) error {
	return SaveApplicationReaderWriterJson(a.store, a.Id, readerOrWriter, path, data)
}

func (a *Application) DeleteReaderWriterJson(readerOrWriter, path string) error {
	return DeleteApplicationReaderWriterJson(a.store, a.Id, readerOrWriter, path)
}

func (a *Application) GetReaderWriterJson(readerOrWriter, path string) ([]byte, error) {
	return GetApplicationReaderWriterJson(a.store, a.Id, readerOrWriter, path)
}

func (a *Application) SaveReaderWriterByHostJson(readerOrWriter, host, path string, data []byte) error {
	return SaveApplicationReaderWriterByHostJson(a.store, a.Id, host, readerOrWriter, path, data)
}

func (a *Application) DeleteReaderWriterByHostJson(readerOrWriter, host, path string) error {
	return DeleteApplicationReaderWriterByHostJson(a.store, a.Id, host, readerOrWriter, path)
}

func (a *Application) GetReaderWriterByHostJson(readerOrWriter, host, path string) ([]byte, error) {
	return GetApplicationReaderWriterByHostJson(a.store, a.Id, host, readerOrWriter, path)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"

	"github.com/gonuts/commander"
	"github.com/gonuts/flag"
	//gocfg "github.com/sbinet/go-config/config"
	_ "github.com/mana-fwk/git-tools/utils"
)

func hwaf_make_cmd_pkg_rm() *commander.Command {
	cmd := &commander.Command{
		Run:       hwaf_run_cmd_pkg_rm,
		UsageLine: "rm [options] <local-pkg-name> [<pkg2> [...]]",
		Short:     "remove a package from the current workarea",
		Long: `
rm removes a package from the current workarea.

ex:
 $ hwaf pkg rm ./src/foo/pkg
 $ hwaf pkg rm Control/AthenaKernel
 $ hwaf pkg rm Control/AthenaKernel Control/AthenaServices
`,
		Flag: *flag.NewFlagSet("hwaf-pkg-rm", flag.ExitOnError),
	}
	cmd.Flag.Bool("q", true, "only print error and warning messages, all other output will be suppressed")

	return cmd
}

func hwaf_run_cmd_pkg_rm(cmd *commander.Command, args []string) {
	var err error
	n := "hwaf-pkg-" + cmd.Name()
	switch len(args) {
	case 0:
		err = fmt.Errorf("%s: you need to give (at least) one package name to remove", n)
		handle_err(err)
	}

	quiet := cmd.Flag.Lookup("q").Value.Get().(bool)

	cfg, err := g_ctx.LocalCfg()
	handle_err(err)

	srcdir := "src"
	if cfg.HasOption("hwaf-cfg", "cmtpkgs") {
		srcdir, err = cfg.String("hwaf-cfg", "cmtpkgs")
		handle_err(err)
	}

	do_rm := func(pkgname string) error {
		var err error

		pkgname = os.ExpandEnv(pkgname)
		pkgname = filepath.Clean(pkgname)
		if !quiet {
			fmt.Printf("%s: remove package [%s]...\n", n, pkgname)
		}

		pkg := pkgname
		if !g_ctx.PkgDb.HasPkg(pkg) {
			pkg = filepath.Join(srcdir, pkgname)
		}
		if !g_ctx.PkgDb.HasPkg(pkg) {
			return fmt.Errorf("%s: no such package [%s] in db", n, pkg)
		}

		// people may have already removed it via a simple 'rm -rf foo'...
		//
		// if !path_exists(pkg) {
		// 	pkg = filepath.Join(srcdir, pkgname)
		// 	if !path_exists(pkg) {
		// 		err = fmt.Errorf("%s: no such package [%s]", n, pkgname)
		// 		handle_err(err)
		// 	}
		// }

		if !g_ctx.PkgDb.HasPkg(pkg) {
			return fmt.Errorf("%s: no such package [%s] in db", n, pkg)
		}

		vcspkg, err := g_ctx.PkgDb.GetPkg(pkg)
		if err != nil {
			return err
		}

		switch vcspkg.Type {

		case "git":
			rmcmd := []string{"rm-submodule"}
			if !quiet {
				rmcmd = append(rmcmd, "--verbose")
			}
			rmcmd = append(rmcmd, pkg)
			git := exec.Command("git", rmcmd...)
			if !quiet {
				git.Stdin = os.Stdin
				git.Stdout = os.Stdout
				git.Stderr = os.Stderr
			}
			err = git.Run()
			if err != nil {
				return err
			}

		case "svn":
			if path_exists(vcspkg.Path) {
				err = os.RemoveAll(vcspkg.Path)
				if err != nil {
					return err
				}
				path := vcspkg.Path
				// clean-up dir if empty...
				for {
					path = filepath.Dir(path)
					if path == srcdir {
						break
					}
					content, err := filepath.Glob(filepath.Join(path, "*"))
					if err != nil {
						return err
					}
					if len(content) > 0 {
						break
					}
					err = os.RemoveAll(path)
					if err != nil {
						return err
					}
				}
			}

		case "local":
			// local packages are tracked from workarea-git-repo
			git := exec.Command("git", "rm", vcspkg.Path)
			if !quiet {
				git.Stdin = os.Stdin
				git.Stdout = os.Stdout
				git.Stderr = os.Stderr
			}
			err = git.Run()
			if err != nil {
				return err
			}
			git = exec.Command(
				"git", "commit", "-m",
				fmt.Sprintf("removed local package [%s]", vcspkg.Path),
			)
			err = git.Run()
			if err != nil {
				return err
			}
			if path_exists(vcspkg.Path) {
				err = os.RemoveAll(vcspkg.Path)
				if err != nil {
					return err
				}
				path := vcspkg.Path
				// clean-up dir if empty...
				for {
					path = filepath.Dir(path)
					if path == srcdir {
						break
					}
					content, err := filepath.Glob(filepath.Join(path, "*"))
					if err != nil {
						return err
					}
					if len(content) > 0 {
						break
					}
					err = os.RemoveAll(path)
					if err != nil {
						return err
					}
				}
			}

		default:
			return fmt.Errorf("%s: VCS of type [%s] is not handled", n, vcspkg.Type)
		}

		err = g_ctx.PkgDb.Remove(pkg)
		if err != nil {
			return err
		}

		if !quiet {
			fmt.Printf("%s: remove package [%s]... [ok]\n", n, pkgname)
		}
		return nil
	}

	for _, pkgname := range args {
		err := do_rm(pkgname)
		handle_err(err)
	}
}

// EOF
<commit_msg>pkg-rm: use recursive git rm<commit_after>package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"

	"github.com/gonuts/commander"
	"github.com/gonuts/flag"
	//gocfg "github.com/sbinet/go-config/config"
	_ "github.com/mana-fwk/git-tools/utils"
)

func hwaf_make_cmd_pkg_rm() *commander.Command {
	cmd := &commander.Command{
		Run:       hwaf_run_cmd_pkg_rm,
		UsageLine: "rm [options] <local-pkg-name> [<pkg2> [...]]",
		Short:     "remove a package from the current workarea",
		Long: `
rm removes a package from the current workarea.

ex:
 $ hwaf pkg rm ./src/foo/pkg
 $ hwaf pkg rm Control/AthenaKernel
 $ hwaf pkg rm Control/AthenaKernel Control/AthenaServices
`,
		Flag: *flag.NewFlagSet("hwaf-pkg-rm", flag.ExitOnError),
	}
	cmd.Flag.Bool("q", true, "only print error and warning messages, all other output will be suppressed")

	return cmd
}

func hwaf_run_cmd_pkg_rm(cmd *commander.Command, args []string) {
	var err error
	n := "hwaf-pkg-" + cmd.Name()
	switch len(args) {
	case 0:
		err = fmt.Errorf("%s: you need to give (at least) one package name to remove", n)
		handle_err(err)
	}

	quiet := cmd.Flag.Lookup("q").Value.Get().(bool)

	cfg, err := g_ctx.LocalCfg()
	handle_err(err)

	srcdir := "src"
	if cfg.HasOption("hwaf-cfg", "cmtpkgs") {
		srcdir, err = cfg.String("hwaf-cfg", "cmtpkgs")
		handle_err(err)
	}

	do_rm := func(pkgname string) error {
		var err error

		pkgname = os.ExpandEnv(pkgname)
		pkgname = filepath.Clean(pkgname)
		if !quiet {
			fmt.Printf("%s: remove package [%s]...\n", n, pkgname)
		}

		pkg := pkgname
		if !g_ctx.PkgDb.HasPkg(pkg) {
			pkg = filepath.Join(srcdir, pkgname)
		}
		if !g_ctx.PkgDb.HasPkg(pkg) {
			return fmt.Errorf("%s: no such package [%s] in db", n, pkg)
		}

		// people may have already removed it via a simple 'rm -rf foo'...
		//
		// if !path_exists(pkg) {
		// 	pkg = filepath.Join(srcdir, pkgname)
		// 	if !path_exists(pkg) {
		// 		err = fmt.Errorf("%s: no such package [%s]", n, pkgname)
		// 		handle_err(err)
		// 	}
		// }

		if !g_ctx.PkgDb.HasPkg(pkg) {
			return fmt.Errorf("%s: no such package [%s] in db", n, pkg)
		}

		vcspkg, err := g_ctx.PkgDb.GetPkg(pkg)
		if err != nil {
			return err
		}

		switch vcspkg.Type {

		case "git":
			rmcmd := []string{"rm-submodule"}
			if !quiet {
				rmcmd = append(rmcmd, "--verbose")
			}
			rmcmd = append(rmcmd, pkg)
			git := exec.Command("git", rmcmd...)
			if !quiet {
				git.Stdin = os.Stdin
				git.Stdout = os.Stdout
				git.Stderr = os.Stderr
			}
			err = git.Run()
			if err != nil {
				return err
			}

		case "svn":
			if path_exists(vcspkg.Path) {
				err = os.RemoveAll(vcspkg.Path)
				if err != nil {
					return err
				}
				path := vcspkg.Path
				// clean-up dir if empty...
				for {
					path = filepath.Dir(path)
					if path == srcdir {
						break
					}
					content, err := filepath.Glob(filepath.Join(path, "*"))
					if err != nil {
						return err
					}
					if len(content) > 0 {
						break
					}
					err = os.RemoveAll(path)
					if err != nil {
						return err
					}
				}
			}

		case "local":
			// local packages are tracked from workarea-git-repo
			git := exec.Command("git", "rm", "-r", vcspkg.Path)
			if !quiet {
				git.Stdin = os.Stdin
				git.Stdout = os.Stdout
				git.Stderr = os.Stderr
			}
			err = git.Run()
			if err != nil {
				return err
			}
			git = exec.Command(
				"git", "commit", "-m",
				fmt.Sprintf("removed local package [%s]", vcspkg.Path),
			)
			err = git.Run()
			if err != nil {
				return err
			}
			if path_exists(vcspkg.Path) {
				err = os.RemoveAll(vcspkg.Path)
				if err != nil {
					return err
				}
				path := vcspkg.Path
				// clean-up dir if empty...
				for {
					path = filepath.Dir(path)
					if path == srcdir {
						break
					}
					content, err := filepath.Glob(filepath.Join(path, "*"))
					if err != nil {
						return err
					}
					if len(content) > 0 {
						break
					}
					err = os.RemoveAll(path)
					if err != nil {
						return err
					}
				}
			}

		default:
			return fmt.Errorf("%s: VCS of type [%s] is not handled", n, vcspkg.Type)
		}

		err = g_ctx.PkgDb.Remove(pkg)
		if err != nil {
			return err
		}

		if !quiet {
			fmt.Printf("%s: remove package [%s]... [ok]\n", n, pkgname)
		}
		return nil
	}

	for _, pkgname := range args {
		err := do_rm(pkgname)
		handle_err(err)
	}
}

// EOF
<|endoftext|>


<commit_before>// Commands from https://redis.io/commands#pubsub

package miniredis

import (
	"fmt"
	"strings"

	"github.com/alicebob/miniredis/v2/server"
)

// commandsPubsub handles all PUB/SUB operations.
func commandsPubsub(m *Miniredis) {
	m.srv.Register("SUBSCRIBE", m.cmdSubscribe)
	m.srv.Register("UNSUBSCRIBE", m.cmdUnsubscribe)
	m.srv.Register("PSUBSCRIBE", m.cmdPsubscribe)
	m.srv.Register("PUNSUBSCRIBE", m.cmdPunsubscribe)
	m.srv.Register("PUBLISH", m.cmdPublish)
	m.srv.Register("PUBSUB", m.cmdPubSub)
}

// SUBSCRIBE
func (m *Miniredis) cmdSubscribe(c *server.Peer, cmd string, args []string) {
	if len(args) < 1 {
		setDirty(c)
		c.WriteError(errWrongNumber(cmd))
		return
	}
	if !m.handleAuth(c) {
		return
	}

	withTx(m, c, func(c *server.Peer, ctx *connCtx) {
		sub := m.subscribedState(c)
		for _, channel := range args {
			n := sub.Subscribe(channel)
			c.Block(func(c *server.Writer) {
				c.WriteLen(3)
				c.WriteBulk("subscribe")
				c.WriteBulk(channel)
				c.WriteInt(n)
			})
		}
	})
}

// UNSUBSCRIBE
func (m *Miniredis) cmdUnsubscribe(c *server.Peer, cmd string, args []string) {
	if !m.handleAuth(c) {
		return
	}

	channels := args

	withTx(m, c, func(c *server.Peer, ctx *connCtx) {
		sub := m.subscribedState(c)

		if len(channels) == 0 {
			channels = sub.Channels()
		}

		// there is no de-duplication
		for _, channel := range channels {
			n := sub.Unsubscribe(channel)
			c.Block(func(c *server.Writer) {
				c.WriteLen(3)
				c.WriteBulk("unsubscribe")
				c.WriteBulk(channel)
				c.WriteInt(n)
			})
		}

		if sub.Count() == 0 {
			endSubscriber(m, c)
		}
	})
}

// PSUBSCRIBE
func (m *Miniredis) cmdPsubscribe(c *server.Peer, cmd string, args []string) {
	if len(args) < 1 {
		setDirty(c)
		c.WriteError(errWrongNumber(cmd))
		return
	}
	if !m.handleAuth(c) {
		return
	}

	withTx(m, c, func(c *server.Peer, ctx *connCtx) {
		sub := m.subscribedState(c)
		for _, pat := range args {
			n := sub.Psubscribe(pat)
			c.Block(func(w *server.Writer) {
				w.WriteLen(3)
				w.WriteBulk("psubscribe")
				w.WriteBulk(pat)
				w.WriteInt(n)
			})
			c.Flush()
		}
	})
}

// PUNSUBSCRIBE
func (m *Miniredis) cmdPunsubscribe(c *server.Peer, cmd string, args []string) {
	if !m.handleAuth(c) {
		return
	}

	patterns := args

	withTx(m, c, func(c *server.Peer, ctx *connCtx) {
		sub := m.subscribedState(c)

		if len(patterns) == 0 {
			patterns = sub.Patterns()
		}

		// there is no de-duplication
		for _, pat := range patterns {
			n := sub.Punsubscribe(pat)
			c.Block(func(c *server.Writer) {
				c.WriteLen(3)
				c.WriteBulk("punsubscribe")
				c.WriteBulk(pat)
				c.WriteInt(n)
			})
		}

		if sub.Count() == 0 {
			endSubscriber(m, c)
		}
	})
}

// PUBLISH
func (m *Miniredis) cmdPublish(c *server.Peer, cmd string, args []string) {
	if len(args) != 2 {
		setDirty(c)
		c.WriteError(errWrongNumber(cmd))
		return
	}
	if !m.handleAuth(c) {
		return
	}
	if m.checkPubsub(c) {
		return
	}

	channel, mesg := args[0], args[1]

	withTx(m, c, func(c *server.Peer, ctx *connCtx) {
		c.WriteInt(m.publish(channel, mesg))
	})
}

// PUBSUB
func (m *Miniredis) cmdPubSub(c *server.Peer, cmd string, args []string) {
	if len(args) < 1 {
		setDirty(c)
		c.WriteError(errWrongNumber(cmd))
		return
	}

	if m.checkPubsub(c) {
		return
	}

	subcommand := strings.ToUpper(args[0])
	subargs := args[1:]
	var argsOk bool

	switch subcommand {
	case "CHANNELS":
		argsOk = len(subargs) < 2
	case "NUMSUB":
		argsOk = true
	case "NUMPAT":
		argsOk = len(subargs) == 0
	default:
		argsOk = false
	}

	if !argsOk {
		setDirty(c)
		c.WriteError(fmt.Sprintf(msgFPubsubUsage, subcommand))
		return
	}

	if !m.handleAuth(c) {
		return
	}

	withTx(m, c, func(c *server.Peer, ctx *connCtx) {
		switch subcommand {
		case "CHANNELS":
			pat := ""
			if len(subargs) == 1 {
				pat = subargs[0]
			}

			channels := activeChannels(m.allSubscribers(), pat)

			c.WriteLen(len(channels))
			for _, channel := range channels {
				c.WriteBulk(channel)
			}

		case "NUMSUB":
			subs := m.allSubscribers()
			c.WriteLen(len(subargs) * 2)
			for _, channel := range subargs {
				c.WriteBulk(channel)
				c.WriteInt(countSubs(subs, channel))
			}
		case "NUMPAT":
			c.WriteInt(countPsubs(m.allSubscribers()))
		}
	})
}
<commit_msg>less shadowing<commit_after>// Commands from https://redis.io/commands#pubsub

package miniredis

import (
	"fmt"
	"strings"

	"github.com/alicebob/miniredis/v2/server"
)

// commandsPubsub handles all PUB/SUB operations.
func commandsPubsub(m *Miniredis) {
	m.srv.Register("SUBSCRIBE", m.cmdSubscribe)
	m.srv.Register("UNSUBSCRIBE", m.cmdUnsubscribe)
	m.srv.Register("PSUBSCRIBE", m.cmdPsubscribe)
	m.srv.Register("PUNSUBSCRIBE", m.cmdPunsubscribe)
	m.srv.Register("PUBLISH", m.cmdPublish)
	m.srv.Register("PUBSUB", m.cmdPubSub)
}

// SUBSCRIBE
func (m *Miniredis) cmdSubscribe(c *server.Peer, cmd string, args []string) {
	if len(args) < 1 {
		setDirty(c)
		c.WriteError(errWrongNumber(cmd))
		return
	}
	if !m.handleAuth(c) {
		return
	}

	withTx(m, c, func(c *server.Peer, ctx *connCtx) {
		sub := m.subscribedState(c)
		for _, channel := range args {
			n := sub.Subscribe(channel)
			c.Block(func(w *server.Writer) {
				w.WriteLen(3)
				w.WriteBulk("subscribe")
				w.WriteBulk(channel)
				w.WriteInt(n)
			})
		}
	})
}

// UNSUBSCRIBE
func (m *Miniredis) cmdUnsubscribe(c *server.Peer, cmd string, args []string) {
	if !m.handleAuth(c) {
		return
	}

	channels := args

	withTx(m, c, func(c *server.Peer, ctx *connCtx) {
		sub := m.subscribedState(c)

		if len(channels) == 0 {
			channels = sub.Channels()
		}

		// there is no de-duplication
		for _, channel := range channels {
			n := sub.Unsubscribe(channel)
			c.Block(func(w *server.Writer) {
				w.WriteLen(3)
				w.WriteBulk("unsubscribe")
				w.WriteBulk(channel)
				w.WriteInt(n)
			})
		}

		if sub.Count() == 0 {
			endSubscriber(m, c)
		}
	})
}

// PSUBSCRIBE
func (m *Miniredis) cmdPsubscribe(c *server.Peer, cmd string, args []string) {
	if len(args) < 1 {
		setDirty(c)
		c.WriteError(errWrongNumber(cmd))
		return
	}
	if !m.handleAuth(c) {
		return
	}

	withTx(m, c, func(c *server.Peer, ctx *connCtx) {
		sub := m.subscribedState(c)
		for _, pat := range args {
			n := sub.Psubscribe(pat)
			c.Block(func(w *server.Writer) {
				w.WriteLen(3)
				w.WriteBulk("psubscribe")
				w.WriteBulk(pat)
				w.WriteInt(n)
			})
		}
	})
}

// PUNSUBSCRIBE
func (m *Miniredis) cmdPunsubscribe(c *server.Peer, cmd string, args []string) {
	if !m.handleAuth(c) {
		return
	}

	patterns := args

	withTx(m, c, func(c *server.Peer, ctx *connCtx) {
		sub := m.subscribedState(c)

		if len(patterns) == 0 {
			patterns = sub.Patterns()
		}

		// there is no de-duplication
		for _, pat := range patterns {
			n := sub.Punsubscribe(pat)
			c.Block(func(w *server.Writer) {
				w.WriteLen(3)
				w.WriteBulk("punsubscribe")
				w.WriteBulk(pat)
				w.WriteInt(n)
			})
		}

		if sub.Count() == 0 {
			endSubscriber(m, c)
		}
	})
}

// PUBLISH
func (m *Miniredis) cmdPublish(c *server.Peer, cmd string, args []string) {
	if len(args) != 2 {
		setDirty(c)
		c.WriteError(errWrongNumber(cmd))
		return
	}
	if !m.handleAuth(c) {
		return
	}
	if m.checkPubsub(c) {
		return
	}

	channel, mesg := args[0], args[1]

	withTx(m, c, func(c *server.Peer, ctx *connCtx) {
		c.WriteInt(m.publish(channel, mesg))
	})
}

// PUBSUB
func (m *Miniredis) cmdPubSub(c *server.Peer, cmd string, args []string) {
	if len(args) < 1 {
		setDirty(c)
		c.WriteError(errWrongNumber(cmd))
		return
	}

	if m.checkPubsub(c) {
		return
	}

	subcommand := strings.ToUpper(args[0])
	subargs := args[1:]
	var argsOk bool

	switch subcommand {
	case "CHANNELS":
		argsOk = len(subargs) < 2
	case "NUMSUB":
		argsOk = true
	case "NUMPAT":
		argsOk = len(subargs) == 0
	default:
		argsOk = false
	}

	if !argsOk {
		setDirty(c)
		c.WriteError(fmt.Sprintf(msgFPubsubUsage, subcommand))
		return
	}

	if !m.handleAuth(c) {
		return
	}

	withTx(m, c, func(c *server.Peer, ctx *connCtx) {
		switch subcommand {
		case "CHANNELS":
			pat := ""
			if len(subargs) == 1 {
				pat = subargs[0]
			}

			channels := activeChannels(m.allSubscribers(), pat)

			c.WriteLen(len(channels))
			for _, channel := range channels {
				c.WriteBulk(channel)
			}

		case "NUMSUB":
			subs := m.allSubscribers()
			c.WriteLen(len(subargs) * 2)
			for _, channel := range subargs {
				c.WriteBulk(channel)
				c.WriteInt(countSubs(subs, channel))
			}
		case "NUMPAT":
			c.WriteInt(countPsubs(m.allSubscribers()))
		}
	})
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
)

// `direnv stdlib`
var CmdStdlib = &Cmd{
	Name: "stdlib",
	Desc: "Displays the stdlib available in the .envrc execution context",
	Fn: func(env Env, args []string) (err error) {
		var config *Config
		if config, err = LoadConfig(env); err != nil {
			return
		}

		fmt.Printf(STDLIB, config.SelfPath)
		return
	},
}

const STDLIB = `# These are the commands available in an .envrc context
set -e
direnv="%s"

# Usage: has <command>
#
# Returns 0 if the <command> is available. Returns 1 otherwise. It can be a
# binary in the PATH or a shell function.
#
# Example:
#
#    if has curl; then
#      echo "Yes we do"
#    fi
#
has() {
	type "$1" &>/dev/null
}

# Usage: expand_path <rel_path> [<relative_to>]
#
# Outputs the absolute path of <rel_path> relaitve to <relative_to> or the
# current directory.
#
# Example:
#
#    cd /usr/local/games
#    expand_path ../foo
#    # output: /usr/local/foo
#
expand_path() {
	"$direnv" expand_path "$@"
}

# Usage: dotenv [<dotenv>]
#
# Loads a ".env" file into the current environment
#
dotenv() {
	eval "$("$direnv" dotenv bash "$@")"
}

# Usage: user_rel_path <abs_path>
#
# Transforms an absolute path <abs_path> into a user-relative path if
# possible.
#
# Example:
#
#    echo $HOME
#    # output: /home/user
#    user_rel_path /home/user/my/project
#    # output: ~/my/project
#    user_rel_path /usr/local/lib
#    # output: /usr/local/lib
#
user_rel_path() {
	local path="${1#-}"

	if [ -z "$path" ]; then return; fi

	if [ -n "$HOME" ]; then
		local rel_path="${path#$HOME}"
		if [ "$rel_path" != "$path" ]; then
			path="~${rel_path}"
		fi
	fi

	echo "$path"
}

# Usage: find_up <filename>
#
# Outputs the path of <filename> when searched from the current directory up to
# /. Returns 1 if the file has not been found.
#
# Example:
#
#    cd /usr/local/my
#    mkdir -p project/foo
#    touch bar
#    cd project/foo
#    find_up bar
#    # output: /usr/local/my/bar
#
find_up() {
	(
		cd "$(pwd -P 2>/dev/null)"
		while true; do
			if [ -f "$1" ]; then
				echo "$PWD/$1"
				return 0
			fi
			if [ "$PWD" = "/" ] || [ "$PWD" = "//" ]; then
				return 1
			fi
			cd ..
		done
	)
}

# Usage: source_env <file_or_dir_path>
#
# Loads another ".envrc" either by specifying its path or filename.
source_env() {
	local rcfile="$1"
	local rcpath="${1/#\~/$HOME}"
	if ! [ -f "$rcpath" ]; then
		rcfile="$rcfile/.envrc"
		rcpath="$rcpath/.envrc"
	fi
	echo "direnv: loading $rcfile"
	pushd "$(dirname "$rcpath")" > /dev/null
	. "./$(basename "$rcpath")"
	popd > /dev/null
}

# Usage: source_up [<filename>]
#
# Loads another ".envrc" if found with the find_up command.
#
source_up() {
	local file="$1"
	if [ -z "$file" ]; then
		file=".envrc"
	fi
	local path="$(cd .. && find_up "$file")"
	if [ -n "$path" ]; then
		source_env "$(user_rel_path "$path")"
	fi
}

# Usage: direnv_load <command-generating-dump-output>
#   e.g: direnv_load opam-env exec -- direnv dump
#
# Applies the environment generated by running <argv> as a
# command. This is useful for adopting the environment of a child
# process - cause that process to run "direnv dump" and then wrap
# the results with direnv_load.
#
direnv_load() {
	exports="$("$direnv" apply_dump <("$@"))"
	if test "$?" -ne 0; then
		exit 1
	fi
	eval "$exports"
}

# Usage: PATH_add <path>
#
# Prepends the expanded <path> to the PATH environment variable. It prevents a
# common mistake where PATH is replaced by only the new <path>.
#
# Example:
#
#    pwd
#    # output: /home/user/my/project
#    PATH_add bin
#    echo $PATH
#    # output: /home/user/my/project/bin:/usr/bin:/bin
#
PATH_add() {
	export PATH="$(expand_path "$1"):$PATH"
}

# Usage: path_add <varname> <path>
#
# Works like PATH_add except that it's for an arbitrary <varname>.
path_add() {
	local old_paths="${!1}"
	local path="$(expand_path "$2")"

	if [ -z "$old_paths" ]; then
		old_paths="$path"
	else
		old_paths="$path:$old_paths"
	fi

	export $1="$old_paths"
}

# Usage: load_prefix <prefix_path>
#
# Expands some common path variables for the given <prefix_path> prefix. This is
# useful if you installed something in the <prefix_path> using
# $(./configure --prefix=<prefix_path> && make install) and want to use it in
# the project.
#
# Variables set:
#
#    CPATH
#    LD_LIBRARY_PATH
#    LIBRARY_PATH
#    MANPATH
#    PATH
#    PKG_CONFIG_PATH
#
# Example:
#
#    ./configure --prefix=$HOME/rubies/ruby-1.9.3
#    make && make install
#    # Then in the .envrc
#    load_prefix ~/rubies/ruby-1.9.3
#
load_prefix() {
	local path="$(expand_path "$1")"
	path_add CPATH "$path/include"
	path_add LD_LIBRARY_PATH "$path/lib"
	path_add LIBRARY_PATH "$path/lib"
	path_add MANPATH "$path/man"
	path_add MANPATH "$path/share/man"
	path_add PATH "$path/bin"
	path_add PKG_CONFIG_PATH "$path/lib/pkgconfig"
}

# Usage: layout <type>
#
# A semantic dispatch used to describe common project layouts.
#
layout() {
	eval "layout_$1"
}

# Usage: layout ruby
#
# Sets the GEM_HOME environment variable to "$PWD/.direnv/ruby/RUBY_VERSION".
# This forces the installation of any gems into the project's sub-folder.
# If you're using bundler it will create wrapper programs that can be invoked
# directly instead of using the $(bundle exec) prefix.
#
layout_ruby() {
	local ruby_version="$(ruby -e"puts (defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby') + '-' + RUBY_VERSION")"

	export GEM_HOME="$PWD/.direnv/${ruby_version}"
	export BUNDLE_BIN="$PWD/.direnv/bin"

	PATH_add ".direnv/${ruby_version}/bin"
	PATH_add ".direnv/bin"
}

# Usage: layout python
#
# Creates and loads a virtualenv environment under "$PWD/.direnv/virtualenv".
# This forces the installation of any egg into the project's sub-folder.
#
layout_python() {
	if ! [ -d .direnv/virtualenv ]; then
		virtualenv --no-site-packages --distribute .direnv/virtualenv
		virtualenv --relocatable .direnv/virtualenv
	fi
	source .direnv/virtualenv/bin/activate
}

# Usage: layout node
#
# Adds "$PWD/node_modules/.bin" to the PATH environment variable.
layout_node() {
	PATH_add node_modules/.bin
}

# Usage: layout go
#
# Sets the GOPATH environment variable to the current directory.
#
layout_go() {
	path_add GOPATH "$PWD"
}

# Usage: use <program_name> [<version>]
#
# A semantic command dispatch intended for loading external dependencies into
# the environment.
#
# Example:
#
#    use_ruby() {
#      echo "Ruby $1"
#    }
#    use ruby 1.9.3
#    # output: Ruby 1.9.3
#
use() {
	local cmd="$1"
	echo "Using $@"
	shift
	use_$cmd "$@"
}

# Usage: use rbenv
#
# Loads rbenv which add the ruby wrappers available on the PATH.
#
use_rbenv() {
	eval "$(rbenv init -)"
}

# Usage: rvm [...]
#
# Should work just like in the shell if you have rvm installed.#
#
rvm() {
	unset rvm
	if [ -n "${rvm_scripts_path:-}" ]; then
		source "${rvm_scripts_path}/rvm"
	elif [ -n "${rvm_path:-}" ]; then
		source "${rvm_path}/scripts/rvm"
	else
		source "$HOME/.rvm/scripts/rvm"
	fi
	rvm "$@"
}

## Load the global ~/.direnvrc if present
if [ -f "$HOME/.direnvrc" ]; then
	source_env "~/.direnvrc" >&2
fi
`
<commit_msg>stdlib: `layout go` now also adds ./bin in the PATH<commit_after>package main

import (
	"fmt"
)

// `direnv stdlib`
var CmdStdlib = &Cmd{
	Name: "stdlib",
	Desc: "Displays the stdlib available in the .envrc execution context",
	Fn: func(env Env, args []string) (err error) {
		var config *Config
		if config, err = LoadConfig(env); err != nil {
			return
		}

		fmt.Printf(STDLIB, config.SelfPath)
		return
	},
}

const STDLIB = `# These are the commands available in an .envrc context
set -e
direnv="%s"

# Usage: has <command>
#
# Returns 0 if the <command> is available. Returns 1 otherwise. It can be a
# binary in the PATH or a shell function.
#
# Example:
#
#    if has curl; then
#      echo "Yes we do"
#    fi
#
has() {
	type "$1" &>/dev/null
}

# Usage: expand_path <rel_path> [<relative_to>]
#
# Outputs the absolute path of <rel_path> relaitve to <relative_to> or the
# current directory.
#
# Example:
#
#    cd /usr/local/games
#    expand_path ../foo
#    # output: /usr/local/foo
#
expand_path() {
	"$direnv" expand_path "$@"
}

# Usage: dotenv [<dotenv>]
#
# Loads a ".env" file into the current environment
#
dotenv() {
	eval "$("$direnv" dotenv bash "$@")"
}

# Usage: user_rel_path <abs_path>
#
# Transforms an absolute path <abs_path> into a user-relative path if
# possible.
#
# Example:
#
#    echo $HOME
#    # output: /home/user
#    user_rel_path /home/user/my/project
#    # output: ~/my/project
#    user_rel_path /usr/local/lib
#    # output: /usr/local/lib
#
user_rel_path() {
	local path="${1#-}"

	if [ -z "$path" ]; then return; fi

	if [ -n "$HOME" ]; then
		local rel_path="${path#$HOME}"
		if [ "$rel_path" != "$path" ]; then
			path="~${rel_path}"
		fi
	fi

	echo "$path"
}

# Usage: find_up <filename>
#
# Outputs the path of <filename> when searched from the current directory up to
# /. Returns 1 if the file has not been found.
#
# Example:
#
#    cd /usr/local/my
#    mkdir -p project/foo
#    touch bar
#    cd project/foo
#    find_up bar
#    # output: /usr/local/my/bar
#
find_up() {
	(
		cd "$(pwd -P 2>/dev/null)"
		while true; do
			if [ -f "$1" ]; then
				echo "$PWD/$1"
				return 0
			fi
			if [ "$PWD" = "/" ] || [ "$PWD" = "//" ]; then
				return 1
			fi
			cd ..
		done
	)
}

# Usage: source_env <file_or_dir_path>
#
# Loads another ".envrc" either by specifying its path or filename.
source_env() {
	local rcfile="$1"
	local rcpath="${1/#\~/$HOME}"
	if ! [ -f "$rcpath" ]; then
		rcfile="$rcfile/.envrc"
		rcpath="$rcpath/.envrc"
	fi
	echo "direnv: loading $rcfile"
	pushd "$(dirname "$rcpath")" > /dev/null
	. "./$(basename "$rcpath")"
	popd > /dev/null
}

# Usage: source_up [<filename>]
#
# Loads another ".envrc" if found with the find_up command.
#
source_up() {
	local file="$1"
	if [ -z "$file" ]; then
		file=".envrc"
	fi
	local path="$(cd .. && find_up "$file")"
	if [ -n "$path" ]; then
		source_env "$(user_rel_path "$path")"
	fi
}

# Usage: direnv_load <command-generating-dump-output>
#   e.g: direnv_load opam-env exec -- direnv dump
#
# Applies the environment generated by running <argv> as a
# command. This is useful for adopting the environment of a child
# process - cause that process to run "direnv dump" and then wrap
# the results with direnv_load.
#
direnv_load() {
	exports="$("$direnv" apply_dump <("$@"))"
	if test "$?" -ne 0; then
		exit 1
	fi
	eval "$exports"
}

# Usage: PATH_add <path>
#
# Prepends the expanded <path> to the PATH environment variable. It prevents a
# common mistake where PATH is replaced by only the new <path>.
#
# Example:
#
#    pwd
#    # output: /home/user/my/project
#    PATH_add bin
#    echo $PATH
#    # output: /home/user/my/project/bin:/usr/bin:/bin
#
PATH_add() {
	export PATH="$(expand_path "$1"):$PATH"
}

# Usage: path_add <varname> <path>
#
# Works like PATH_add except that it's for an arbitrary <varname>.
path_add() {
	local old_paths="${!1}"
	local path="$(expand_path "$2")"

	if [ -z "$old_paths" ]; then
		old_paths="$path"
	else
		old_paths="$path:$old_paths"
	fi

	export $1="$old_paths"
}

# Usage: load_prefix <prefix_path>
#
# Expands some common path variables for the given <prefix_path> prefix. This is
# useful if you installed something in the <prefix_path> using
# $(./configure --prefix=<prefix_path> && make install) and want to use it in
# the project.
#
# Variables set:
#
#    CPATH
#    LD_LIBRARY_PATH
#    LIBRARY_PATH
#    MANPATH
#    PATH
#    PKG_CONFIG_PATH
#
# Example:
#
#    ./configure --prefix=$HOME/rubies/ruby-1.9.3
#    make && make install
#    # Then in the .envrc
#    load_prefix ~/rubies/ruby-1.9.3
#
load_prefix() {
	local path="$(expand_path "$1")"
	path_add CPATH "$path/include"
	path_add LD_LIBRARY_PATH "$path/lib"
	path_add LIBRARY_PATH "$path/lib"
	path_add MANPATH "$path/man"
	path_add MANPATH "$path/share/man"
	path_add PATH "$path/bin"
	path_add PKG_CONFIG_PATH "$path/lib/pkgconfig"
}

# Usage: layout <type>
#
# A semantic dispatch used to describe common project layouts.
#
layout() {
	eval "layout_$1"
}

# Usage: layout ruby
#
# Sets the GEM_HOME environment variable to "$PWD/.direnv/ruby/RUBY_VERSION".
# This forces the installation of any gems into the project's sub-folder.
# If you're using bundler it will create wrapper programs that can be invoked
# directly instead of using the $(bundle exec) prefix.
#
layout_ruby() {
	local ruby_version="$(ruby -e"puts (defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby') + '-' + RUBY_VERSION")"

	export GEM_HOME="$PWD/.direnv/${ruby_version}"
	export BUNDLE_BIN="$PWD/.direnv/bin"

	PATH_add ".direnv/${ruby_version}/bin"
	PATH_add ".direnv/bin"
}

# Usage: layout python
#
# Creates and loads a virtualenv environment under "$PWD/.direnv/virtualenv".
# This forces the installation of any egg into the project's sub-folder.
#
layout_python() {
	if ! [ -d .direnv/virtualenv ]; then
		virtualenv --no-site-packages --distribute .direnv/virtualenv
		virtualenv --relocatable .direnv/virtualenv
	fi
	source .direnv/virtualenv/bin/activate
}

# Usage: layout node
#
# Adds "$PWD/node_modules/.bin" to the PATH environment variable.
layout_node() {
	PATH_add node_modules/.bin
}

# Usage: layout go
#
# Sets the GOPATH environment variable to the current directory.
#
layout_go() {
	path_add GOPATH "$PWD"
	PATH_add bin
}

# Usage: use <program_name> [<version>]
#
# A semantic command dispatch intended for loading external dependencies into
# the environment.
#
# Example:
#
#    use_ruby() {
#      echo "Ruby $1"
#    }
#    use ruby 1.9.3
#    # output: Ruby 1.9.3
#
use() {
	local cmd="$1"
	echo "Using $@"
	shift
	use_$cmd "$@"
}

# Usage: use rbenv
#
# Loads rbenv which add the ruby wrappers available on the PATH.
#
use_rbenv() {
	eval "$(rbenv init -)"
}

# Usage: rvm [...]
#
# Should work just like in the shell if you have rvm installed.#
#
rvm() {
	unset rvm
	if [ -n "${rvm_scripts_path:-}" ]; then
		source "${rvm_scripts_path}/rvm"
	elif [ -n "${rvm_path:-}" ]; then
		source "${rvm_path}/scripts/rvm"
	else
		source "$HOME/.rvm/scripts/rvm"
	fi
	rvm "$@"
}

## Load the global ~/.direnvrc if present
if [ -f "$HOME/.direnvrc" ]; then
	source_env "~/.direnvrc" >&2
fi
`
<|endoftext|>


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

package v4

import (
	"archive/zip"
	"encoding/json"
	"net/http"
	"net/url"

	"github.com/juju/errgo"
	"gopkg.in/juju/charm.v3"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"

	"github.com/juju/charmstore/internal/charmstore"
	"github.com/juju/charmstore/internal/mongodoc"
	"github.com/juju/charmstore/internal/router"
	"github.com/juju/charmstore/params"
)

type handler struct {
	*router.Router
	store *charmstore.Store
}

// New returns a new instance of the v4 API handler.
func New(store *charmstore.Store) http.Handler {
	h := &handler{
		store: store,
	}
	h.Router = router.New(&router.Handlers{
		Global: map[string]http.Handler{
			"stats/counter/":     router.HandleJSON(h.serveStatsCounter),
			"stats/":             router.NotFoundHandler(),
			"search":             http.HandlerFunc(h.serveSearch),
			"search/interesting": http.HandlerFunc(h.serveSearchInteresting),
			"debug":              http.HandlerFunc(h.serveDebug),
		},
		Id: map[string]router.IdHandler{
			"resources": h.serveResources,
			"archive":   h.serveArchive,
			"archive/":  h.serveArchiveFile,
			"expand-id": h.serveExpandId,
		},
		Meta: map[string]router.BulkIncludeHandler{
			"charm-metadata":      h.entityHandler(h.metaCharmMetadata, "charmmeta"),
			"bundle-metadata":     h.entityHandler(h.metaBundleMetadata, "bundledata"),
			"charm-config":        h.entityHandler(h.metaCharmConfig, "charmconfig"),
			"charm-actions":       h.entityHandler(h.metaCharmActions, "charmactions"),
			"archive-size":        h.entityHandler(h.metaArchiveSize, "size"),
			"manifest":            h.entityHandler(h.metaManifest, "blobname"),
			"archive-upload-time": h.entityHandler(h.metaArchiveUploadTime, "uploadtime"),
			"charm-related":       h.entityHandler(h.metaCharmRelated, "charmprovidedinterfaces", "charmrequiredinterfaces"),

			// endpoints not yet implemented - use SingleIncludeHandler for the time being.
			"color":              router.SingleIncludeHandler(h.metaColor),
			"bundles-containing": router.SingleIncludeHandler(h.metaBundlesContaining),
			"revision-info":      router.SingleIncludeHandler(h.metaRevisionInfo),
			"extra-info":         router.SingleIncludeHandler(h.metaExtraInfo),
			"extra-info/":        router.SingleIncludeHandler(h.metaExtraInfoWithKey),
		},
	}, h.resolveURL)
	return h
}

func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	h.Router.ServeHTTP(w, req)
}

// ResolveURL resolves the series and revision of the given URL
// if either is unspecified by filling them out with information retrieved
// from the store.
func ResolveURL(store *charmstore.Store, url *charm.Reference) error {
	if url.Series != "" && url.Revision != -1 {
		return nil
	}
	urls, err := store.ExpandURL(url)
	if err != nil {
		return errgo.Notef(err, "cannot expand URL")
	}
	if len(urls) == 0 {
		return resolveURLError(url)
	}
	*url = *selectPreferredURL(urls)
	return nil
}

func resolveURLError(url *charm.Reference) error {
	return errgo.WithCausef(nil, params.ErrNotFound, "no matching charm or bundle for %q", url)
}

func (h *handler) resolveURL(url *charm.Reference) error {
	return ResolveURL(h.store, url)
}

type entityHandlerFunc func(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error)

// entityHandler returns a handler that calls f with a *mongodoc.Entity that
// contains at least the given fields.
func (h *handler) entityHandler(f entityHandlerFunc, fields ...string) router.BulkIncludeHandler {
	handle := func(doc interface{}, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
		edoc := doc.(*mongodoc.Entity)
		val, err := f(edoc, id, path, method, flags)
		return val, errgo.Mask(err, errgo.Any)
	}
	type entityHandlerKey struct{}
	return router.FieldIncludeHandler(
		entityHandlerKey{},
		h.entityQuery,
		fields,
		handle,
	)
}

func (h *handler) entityQuery(id *charm.Reference, selector map[string]int) (interface{}, error) {
	var val mongodoc.Entity
	err := h.store.DB.Entities().
		Find(bson.D{{"_id", id}}).
		Select(selector).
		One(&val)
	if err == mgo.ErrNotFound {
		return nil, params.ErrNotFound
	}
	if err != nil {
		return nil, errgo.Mask(err)
	}
	return &val, nil
}

var ltsReleases = map[string]bool{
	"lucid":   true,
	"precise": true,
	"trusty":  true,
}

func selectPreferredURL(urls []*charm.Reference) *charm.Reference {
	best := urls[0]
	for _, url := range urls {
		if preferredURL(url, best) {
			best = url
		}
	}
	return best
}

// preferredURL reports whether url0 is preferred over url1.
func preferredURL(url0, url1 *charm.Reference) bool {
	if url0.Series == url1.Series {
		return url0.Revision > url1.Revision
	}
	if url0.Series == "bundle" || url1.Series == "bundle" {
		// One of the URLs refers to a bundle. Choose
		// a charm by preference.
		return url0.Series != "bundle"
	}
	if ltsReleases[url0.Series] == ltsReleases[url1.Series] {
		return url0.Series > url1.Series
	}
	return ltsReleases[url0.Series]
}

var errNotImplemented = errgo.Newf("method not implemented")

// GET search[?text=text][&autocomplete=1][&filter=value…][&limit=limit][&include=meta]
// http://tinyurl.com/qzobc69
func (h *handler) serveSearch(w http.ResponseWriter, req *http.Request) {
	router.WriteError(w, errNotImplemented)
}

// GET search/interesting[?limit=limit][&include=meta]
// http://tinyurl.com/ntmdrg8
func (h *handler) serveSearchInteresting(w http.ResponseWriter, req *http.Request) {
	router.WriteError(w, errNotImplemented)
}

// GET /debug
// http://tinyurl.com/m63xhz8
func (h *handler) serveDebug(w http.ResponseWriter, req *http.Request) {
	router.WriteError(w, errNotImplemented)
}

// POST id/resources/name.stream
// http://tinyurl.com/pnmwvy4
//
// GET  id/resources/name.stream[-revision]/arch/filename
// http://tinyurl.com/pydbn3u
//
// PUT id/resources/[~user/]series/name.stream-revision/arch?sha256=hash
// http://tinyurl.com/k8l8kdg
func (h *handler) serveResources(charmId *charm.Reference, w http.ResponseWriter, req *http.Request) error {
	return errNotImplemented
}

// GET id/expand-id
// https://docs.google.com/a/canonical.com/document/d/1TgRA7jW_mmXoKH3JiwBbtPvQu7WiM6XMrz1wSrhTMXw/edit#bookmark=id.4xdnvxphb2si
func (h *handler) serveExpandId(id *charm.Reference, w http.ResponseWriter, req *http.Request) error {
	// Mutate the given id so that it represents a base URL.
	id.Revision = -1
	id.Series = ""

	// Retrieve all the entities with the same base URL.
	var docs []mongodoc.Entity
	if err := h.store.DB.Entities().Find(bson.D{{"baseurl", id}}).Select(bson.D{{"_id", 1}}).All(&docs); err != nil {
		return errgo.Notef(err, "cannot retrieve the entities")
	}

	// A not found error should have been already returned by the router in the
	// case a partial id is provided. Here we do the same for the case when
	// a fully qualified URL is provided, but no matching entities are found.
	if len(docs) == 0 {
		return resolveURLError(id)
	}

	// Collect all the expanded identifiers for each entity.
	response := make([]params.ExpandedId, 0, len(docs))
	for _, doc := range docs {
		response = append(response, params.ExpandedId{Id: doc.URL.String()})
	}

	// Write the response in JSON format.
	encoder := json.NewEncoder(w)
	if err := encoder.Encode(response); err != nil {
		return errgo.Notef(err, "cannot serialize the response")
	}
	return nil
}

func badRequestf(underlying error, f string, a ...interface{}) error {
	err := errgo.WithCausef(underlying, params.ErrBadRequest, f, a...)
	err.(*errgo.Err).SetLocation(1)
	return err
}

// GET id/meta/charm-metadata
// http://tinyurl.com/poeoulw
func (h *handler) metaCharmMetadata(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.CharmMeta, nil
}

// GET id/meta/bundle-metadata
// http://tinyurl.com/ozshbtb
func (h *handler) metaBundleMetadata(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.BundleData, nil
}

// GET id/meta/manifest
// http://tinyurl.com/p3xdcto
func (h *handler) metaManifest(entity *mongodoc.Entity, id *charm.Reference, path, method string, flags url.Values) (interface{}, error) {
	r, size, err := h.store.BlobStore.Open(entity.BlobName)
	if err != nil {
		return nil, errgo.Notef(err, "cannot open archive data for %s", id)
	}
	defer r.Close()
	zipReader, err := zip.NewReader(&readerAtSeeker{r}, size)
	if err != nil {
		return nil, errgo.Notef(err, "cannot read archive data for %s", id)
	}
	// Collect the files.
	manifest := make([]params.ManifestFile, 0, len(zipReader.File))
	for _, file := range zipReader.File {
		fileInfo := file.FileInfo()
		if fileInfo.IsDir() {
			continue
		}
		manifest = append(manifest, params.ManifestFile{
			Name: file.Name,
			Size: fileInfo.Size(),
		})
	}
	return manifest, nil
}

// GET id/meta/charm-actions
// http://tinyurl.com/kfd2h34
func (h *handler) metaCharmActions(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.CharmActions, nil
}

// GET id/meta/charm-config
// http://tinyurl.com/oxxyujx
func (h *handler) metaCharmConfig(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.CharmConfig, nil
}

// GET id/meta/color
// http://tinyurl.com/o2t3j4p
func (h *handler) metaColor(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/archive-size
// http://tinyurl.com/m8b9geq
func (h *handler) metaArchiveSize(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return &params.ArchiveSizeResponse{
		Size: entity.Size,
	}, nil
}

// GET id/meta/stats/
// http://tinyurl.com/lvyp2l5
func (h *handler) metaStats(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/bundles-containing[?include=meta[&include=meta…]]
// http://tinyurl.com/oqc386r
func (h *handler) metaBundlesContaining(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/revision-info
// http://tinyurl.com/q6xos7f
func (h *handler) metaRevisionInfo(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/extra-info
// http://tinyurl.com/keos7wd
func (h *handler) metaExtraInfo(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/extra-info/key
// http://tinyurl.com/polrbn7
func (h *handler) metaExtraInfoWithKey(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/archive-upload-time
// http://tinyurl.com/nmujuqk
func (h *handler) metaArchiveUploadTime(entity *mongodoc.Entity, id *charm.Reference, path, method string, flags url.Values) (interface{}, error) {
	return &params.ArchiveUploadTimeResponse{
		UploadTime: entity.UploadTime.UTC(),
	}, nil
}
<commit_msg>Use the router helper to write JSON data in the response.<commit_after>// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.

package v4

import (
	"archive/zip"
	"net/http"
	"net/url"

	"github.com/juju/errgo"
	"gopkg.in/juju/charm.v3"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"

	"github.com/juju/charmstore/internal/charmstore"
	"github.com/juju/charmstore/internal/mongodoc"
	"github.com/juju/charmstore/internal/router"
	"github.com/juju/charmstore/params"
)

type handler struct {
	*router.Router
	store *charmstore.Store
}

// New returns a new instance of the v4 API handler.
func New(store *charmstore.Store) http.Handler {
	h := &handler{
		store: store,
	}
	h.Router = router.New(&router.Handlers{
		Global: map[string]http.Handler{
			"stats/counter/":     router.HandleJSON(h.serveStatsCounter),
			"stats/":             router.NotFoundHandler(),
			"search":             http.HandlerFunc(h.serveSearch),
			"search/interesting": http.HandlerFunc(h.serveSearchInteresting),
			"debug":              http.HandlerFunc(h.serveDebug),
		},
		Id: map[string]router.IdHandler{
			"resources": h.serveResources,
			"archive":   h.serveArchive,
			"archive/":  h.serveArchiveFile,
			"expand-id": h.serveExpandId,
		},
		Meta: map[string]router.BulkIncludeHandler{
			"charm-metadata":      h.entityHandler(h.metaCharmMetadata, "charmmeta"),
			"bundle-metadata":     h.entityHandler(h.metaBundleMetadata, "bundledata"),
			"charm-config":        h.entityHandler(h.metaCharmConfig, "charmconfig"),
			"charm-actions":       h.entityHandler(h.metaCharmActions, "charmactions"),
			"archive-size":        h.entityHandler(h.metaArchiveSize, "size"),
			"manifest":            h.entityHandler(h.metaManifest, "blobname"),
			"archive-upload-time": h.entityHandler(h.metaArchiveUploadTime, "uploadtime"),
			"charm-related":       h.entityHandler(h.metaCharmRelated, "charmprovidedinterfaces", "charmrequiredinterfaces"),

			// endpoints not yet implemented - use SingleIncludeHandler for the time being.
			"color":              router.SingleIncludeHandler(h.metaColor),
			"bundles-containing": router.SingleIncludeHandler(h.metaBundlesContaining),
			"revision-info":      router.SingleIncludeHandler(h.metaRevisionInfo),
			"extra-info":         router.SingleIncludeHandler(h.metaExtraInfo),
			"extra-info/":        router.SingleIncludeHandler(h.metaExtraInfoWithKey),
		},
	}, h.resolveURL)
	return h
}

func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	h.Router.ServeHTTP(w, req)
}

// ResolveURL resolves the series and revision of the given URL
// if either is unspecified by filling them out with information retrieved
// from the store.
func ResolveURL(store *charmstore.Store, url *charm.Reference) error {
	if url.Series != "" && url.Revision != -1 {
		return nil
	}
	urls, err := store.ExpandURL(url)
	if err != nil {
		return errgo.Notef(err, "cannot expand URL")
	}
	if len(urls) == 0 {
		return resolveURLError(url)
	}
	*url = *selectPreferredURL(urls)
	return nil
}

func resolveURLError(url *charm.Reference) error {
	return errgo.WithCausef(nil, params.ErrNotFound, "no matching charm or bundle for %q", url)
}

func (h *handler) resolveURL(url *charm.Reference) error {
	return ResolveURL(h.store, url)
}

type entityHandlerFunc func(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error)

// entityHandler returns a handler that calls f with a *mongodoc.Entity that
// contains at least the given fields.
func (h *handler) entityHandler(f entityHandlerFunc, fields ...string) router.BulkIncludeHandler {
	handle := func(doc interface{}, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
		edoc := doc.(*mongodoc.Entity)
		val, err := f(edoc, id, path, method, flags)
		return val, errgo.Mask(err, errgo.Any)
	}
	type entityHandlerKey struct{}
	return router.FieldIncludeHandler(
		entityHandlerKey{},
		h.entityQuery,
		fields,
		handle,
	)
}

func (h *handler) entityQuery(id *charm.Reference, selector map[string]int) (interface{}, error) {
	var val mongodoc.Entity
	err := h.store.DB.Entities().
		Find(bson.D{{"_id", id}}).
		Select(selector).
		One(&val)
	if err == mgo.ErrNotFound {
		return nil, params.ErrNotFound
	}
	if err != nil {
		return nil, errgo.Mask(err)
	}
	return &val, nil
}

var ltsReleases = map[string]bool{
	"lucid":   true,
	"precise": true,
	"trusty":  true,
}

func selectPreferredURL(urls []*charm.Reference) *charm.Reference {
	best := urls[0]
	for _, url := range urls {
		if preferredURL(url, best) {
			best = url
		}
	}
	return best
}

// preferredURL reports whether url0 is preferred over url1.
func preferredURL(url0, url1 *charm.Reference) bool {
	if url0.Series == url1.Series {
		return url0.Revision > url1.Revision
	}
	if url0.Series == "bundle" || url1.Series == "bundle" {
		// One of the URLs refers to a bundle. Choose
		// a charm by preference.
		return url0.Series != "bundle"
	}
	if ltsReleases[url0.Series] == ltsReleases[url1.Series] {
		return url0.Series > url1.Series
	}
	return ltsReleases[url0.Series]
}

var errNotImplemented = errgo.Newf("method not implemented")

// GET search[?text=text][&autocomplete=1][&filter=value…][&limit=limit][&include=meta]
// http://tinyurl.com/qzobc69
func (h *handler) serveSearch(w http.ResponseWriter, req *http.Request) {
	router.WriteError(w, errNotImplemented)
}

// GET search/interesting[?limit=limit][&include=meta]
// http://tinyurl.com/ntmdrg8
func (h *handler) serveSearchInteresting(w http.ResponseWriter, req *http.Request) {
	router.WriteError(w, errNotImplemented)
}

// GET /debug
// http://tinyurl.com/m63xhz8
func (h *handler) serveDebug(w http.ResponseWriter, req *http.Request) {
	router.WriteError(w, errNotImplemented)
}

// POST id/resources/name.stream
// http://tinyurl.com/pnmwvy4
//
// GET  id/resources/name.stream[-revision]/arch/filename
// http://tinyurl.com/pydbn3u
//
// PUT id/resources/[~user/]series/name.stream-revision/arch?sha256=hash
// http://tinyurl.com/k8l8kdg
func (h *handler) serveResources(charmId *charm.Reference, w http.ResponseWriter, req *http.Request) error {
	return errNotImplemented
}

// GET id/expand-id
// https://docs.google.com/a/canonical.com/document/d/1TgRA7jW_mmXoKH3JiwBbtPvQu7WiM6XMrz1wSrhTMXw/edit#bookmark=id.4xdnvxphb2si
func (h *handler) serveExpandId(id *charm.Reference, w http.ResponseWriter, req *http.Request) error {
	// Mutate the given id so that it represents a base URL.
	id.Revision = -1
	id.Series = ""

	// Retrieve all the entities with the same base URL.
	var docs []mongodoc.Entity
	if err := h.store.DB.Entities().Find(bson.D{{"baseurl", id}}).Select(bson.D{{"_id", 1}}).All(&docs); err != nil {
		return errgo.Notef(err, "cannot get ids")
	}

	// A not found error should have been already returned by the router in the
	// case a partial id is provided. Here we do the same for the case when
	// a fully qualified URL is provided, but no matching entities are found.
	if len(docs) == 0 {
		return resolveURLError(id)
	}

	// Collect all the expanded identifiers for each entity.
	response := make([]params.ExpandedId, 0, len(docs))
	for _, doc := range docs {
		response = append(response, params.ExpandedId{Id: doc.URL.String()})
	}

	// Write the response in JSON format.
	return router.WriteJSON(w, http.StatusOK, response)
}

func badRequestf(underlying error, f string, a ...interface{}) error {
	err := errgo.WithCausef(underlying, params.ErrBadRequest, f, a...)
	err.(*errgo.Err).SetLocation(1)
	return err
}

// GET id/meta/charm-metadata
// http://tinyurl.com/poeoulw
func (h *handler) metaCharmMetadata(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.CharmMeta, nil
}

// GET id/meta/bundle-metadata
// http://tinyurl.com/ozshbtb
func (h *handler) metaBundleMetadata(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.BundleData, nil
}

// GET id/meta/manifest
// http://tinyurl.com/p3xdcto
func (h *handler) metaManifest(entity *mongodoc.Entity, id *charm.Reference, path, method string, flags url.Values) (interface{}, error) {
	r, size, err := h.store.BlobStore.Open(entity.BlobName)
	if err != nil {
		return nil, errgo.Notef(err, "cannot open archive data for %s", id)
	}
	defer r.Close()
	zipReader, err := zip.NewReader(&readerAtSeeker{r}, size)
	if err != nil {
		return nil, errgo.Notef(err, "cannot read archive data for %s", id)
	}
	// Collect the files.
	manifest := make([]params.ManifestFile, 0, len(zipReader.File))
	for _, file := range zipReader.File {
		fileInfo := file.FileInfo()
		if fileInfo.IsDir() {
			continue
		}
		manifest = append(manifest, params.ManifestFile{
			Name: file.Name,
			Size: fileInfo.Size(),
		})
	}
	return manifest, nil
}

// GET id/meta/charm-actions
// http://tinyurl.com/kfd2h34
func (h *handler) metaCharmActions(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.CharmActions, nil
}

// GET id/meta/charm-config
// http://tinyurl.com/oxxyujx
func (h *handler) metaCharmConfig(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return entity.CharmConfig, nil
}

// GET id/meta/color
// http://tinyurl.com/o2t3j4p
func (h *handler) metaColor(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/archive-size
// http://tinyurl.com/m8b9geq
func (h *handler) metaArchiveSize(entity *mongodoc.Entity, id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return &params.ArchiveSizeResponse{
		Size: entity.Size,
	}, nil
}

// GET id/meta/stats/
// http://tinyurl.com/lvyp2l5
func (h *handler) metaStats(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/bundles-containing[?include=meta[&include=meta…]]
// http://tinyurl.com/oqc386r
func (h *handler) metaBundlesContaining(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/revision-info
// http://tinyurl.com/q6xos7f
func (h *handler) metaRevisionInfo(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/extra-info
// http://tinyurl.com/keos7wd
func (h *handler) metaExtraInfo(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/extra-info/key
// http://tinyurl.com/polrbn7
func (h *handler) metaExtraInfoWithKey(id *charm.Reference, path string, method string, flags url.Values) (interface{}, error) {
	return nil, errNotImplemented
}

// GET id/meta/archive-upload-time
// http://tinyurl.com/nmujuqk
func (h *handler) metaArchiveUploadTime(entity *mongodoc.Entity, id *charm.Reference, path, method string, flags url.Values) (interface{}, error) {
	return &params.ArchiveUploadTimeResponse{
		UploadTime: entity.UploadTime.UTC(),
	}, nil
}
<|endoftext|>