<commit_before>// Copyright (c) 2015 <NAME>. All rights reserved.
//
// Use of this source code is governed by the ISC
// license that can be found in the LICENSE file.

package acd

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"reflect"

	"github.com/google/go-querystring/query"
)

// NodesService provides access to the nodes in the Amazon Cloud Drive API.
//
// See: https://developer.amazon.com/public/apis/experience/cloud-drive/content/nodes
type NodesService struct {
	client *Client
}

// Gets the root folder of the Amazon Cloud Drive.
func (s *NodesService) GetRoot() (*Folder, *http.Response, error) {
	opts := &NodeListOptions{Filters: "kind:FOLDER AND isRoot:true"}

	roots, resp, err := s.GetNodes(opts)
	if err != nil {
		return nil, resp, err
	}

	if len(roots) < 1 {
		return nil, resp, errors.New("No root found")
	}

	return &Folder{roots[0]}, resp, nil
}

// Gets the list of all nodes.
func (s *NodesService) GetAllNodes(opts *NodeListOptions) ([]*Node, *http.Response, error) {
	return s.listAllNodes("nodes", opts)
}

// Gets a list of nodes, up until the limit (either default or the one set in opts).
func (s *NodesService) GetNodes(opts *NodeListOptions) ([]*Node, *http.Response, error) {
	return s.listNodes("nodes", opts)
}

func (s *NodesService) listAllNodes(url string, opts *NodeListOptions) ([]*Node, *http.Response, error) {
	// Need opts to maintain state (NodeListOptions.reachedEnd)
	if opts == nil {
		opts = &NodeListOptions{}
	}

	result := make([]*Node, 0, 200)

	for {
		nodes, resp, err := s.listNodes(url, opts)
		if err != nil {
			return result, resp, err
		}
		if nodes == nil {
			break
		}

		result = append(result, nodes...)
	}

	return result, nil, nil
}

func (s *NodesService) listNodes(url string, opts *NodeListOptions) ([]*Node, *http.Response, error) {
	if opts != nil && opts.reachedEnd {
		return nil, nil, nil
	}

	url, err := addOptions(url, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url, nil)
	if err != nil {
		return nil, nil, err
	}

	nodeList := &nodeListInternal{}
	resp, err := s.client.Do(req, nodeList)
	if err != nil {
		return nil, resp, err
	}

	if opts != nil {
		if nodeList.NextToken != nil {
			opts.StartToken = *nodeList.NextToken
		} else {
			opts.reachedEnd = true
		}
	}

	nodes := nodeList.Data
	for _, node := range nodes {
		node.service = s
	}

	return nodes, resp, nil
}

type nodeListInternal struct {
	Count     *uint64 `json:"count"`
	NextToken *string `json:"nextToken"`
	Data      []*Node `json:"data"`
}

// Node represents a digital asset on the Amazon Cloud Drive, including files
// and folders, in a parent-child relationship. A node contains only metadata
// (e.g. folder) or it contains metadata and content (e.g. file).
type Node struct {
	Id                *string `json:"id"`
	Name              *string `json:"name"`
	Kind              *string `json:"kind"`
	ContentProperties *struct {
		Size *uint64 `json:"size"`
	} `json:"contentProperties"`

	service *NodesService
}

// IsFile returns whether the node represents a file.
func (n *Node) IsFile() bool {
	return n.Kind != nil && *n.Kind == "FILE"
}

// IsFolder returns whether the node represents a folder.
func (n *Node) IsFolder() bool {
	return n.Kind != nil && *n.Kind == "FOLDER"
}

// Typed returns the Node typed as either File or Folder.
func (n *Node) Typed() interface{} {
	if n.IsFile() {
		return &File{n}
	}

	if n.IsFolder() {
		return &Folder{n}
	}

	return n
}

// GetMetadata return a pretty-printed JSON string of the node's metadata
func (n *Node) GetMetadata() (string, error) {
	url := fmt.Sprintf("nodes/%s", *n.Id)
	req, err := n.service.client.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	buf := &bytes.Buffer{}
	_, err = n.service.client.Do(req, buf)
	if err != nil {
		return "", err
	}

	md := &bytes.Buffer{}
	err = json.Indent(md, buf.Bytes(), "", "    ")
	if err != nil {
		return "", err
	}

	return md.String(), nil
}

// File represents a file on the Amazon Cloud Drive.
type File struct {
	*Node
}

// Folder represents a folder on the Amazon Cloud Drive.
type Folder struct {
	*Node
}

// Gets the list of all children.
func (f *Folder) GetAllChildren(opts *NodeListOptions) ([]*Node, *http.Response, error) {
	url := fmt.Sprintf("nodes/%s/children", *f.Id)
	return f.service.listAllNodes(url, opts)
}

// Gets a list of children, up until the limit (either default or the one set in opts).
func (f *Folder) GetChildren(opts *NodeListOptions) ([]*Node, *http.Response, error) {
	url := fmt.Sprintf("nodes/%s/children", *f.Id)
	return f.service.listNodes(url, opts)
}

// Gets the subfolder by name. It is an error if not exactly one subfolder is found.
func (f *Folder) GetFolder(name string) (*Folder, *http.Response, error) {
	n, resp, err := f.GetNode(name)
	if err != nil {
		return nil, resp, err
	}

	res, ok := n.Typed().(*Folder)
	if !ok {
		err := errors.New(fmt.Sprintf("Node '%s' is not a folder", name))
		return nil, resp, err
	}

	return res, resp, nil
}

// Gets the file by name. It is an error if not exactly one file is found.
func (f *Folder) GetFile(name string) (*File, *http.Response, error) {
	n, resp, err := f.GetNode(name)
	if err != nil {
		return nil, resp, err
	}

	res, ok := n.Typed().(*File)
	if !ok {
		err := errors.New(fmt.Sprintf("Node '%s' is not a file", name))
		return nil, resp, err
	}

	return res, resp, nil
}

// Gets the node by name. It is an error if not exactly one node is found.
func (f *Folder) GetNode(name string) (*Node, *http.Response, error) {
	filter := "parents:" + *f.Id + " AND name:" + name
	opts := &NodeListOptions{Filters: filter}

	nodes, resp, err := f.service.GetNodes(opts)
	if err != nil {
		return nil, resp, err
	}

	if len(nodes) < 1 {
		err := errors.New(fmt.Sprintf("No node '%s' found", name))
		return nil, resp, err
	}
	if len(nodes) > 1 {
		err := errors.New(fmt.Sprintf("Too many nodes '%s' found (%v)", name, len(nodes)))
		return nil, resp, err
	}<commit_msg>Fix getting node by name that contains whitespace<commit_after>// Copyright (c) 2015 <NAME>. All rights reserved.
//
// Use of this source code is governed by the ISC
// license that can be found in the LICENSE file.

package acd

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"reflect"

	"github.com/google/go-querystring/query"
)

// NodesService provides access to the nodes in the Amazon Cloud Drive API.
//
// See: https://developer.amazon.com/public/apis/experience/cloud-drive/content/nodes
type NodesService struct {
	client *Client
}

// Gets the root folder of the Amazon Cloud Drive.
func (s *NodesService) GetRoot() (*Folder, *http.Response, error) {
	opts := &NodeListOptions{Filters: "kind:FOLDER AND isRoot:true"}

	roots, resp, err := s.GetNodes(opts)
	if err != nil {
		return nil, resp, err
	}

	if len(roots) < 1 {
		return nil, resp, errors.New("No root found")
	}

	return &Folder{roots[0]}, resp, nil
}

// Gets the list of all nodes.
func (s *NodesService) GetAllNodes(opts *NodeListOptions) ([]*Node, *http.Response, error) {
	return s.listAllNodes("nodes", opts)
}

// Gets a list of nodes, up until the limit (either default or the one set in opts).
func (s *NodesService) GetNodes(opts *NodeListOptions) ([]*Node, *http.Response, error) {
	return s.listNodes("nodes", opts)
}

func (s *NodesService) listAllNodes(url string, opts *NodeListOptions) ([]*Node, *http.Response, error) {
	// Need opts to maintain state (NodeListOptions.reachedEnd)
	if opts == nil {
		opts = &NodeListOptions{}
	}

	result := make([]*Node, 0, 200)

	for {
		nodes, resp, err := s.listNodes(url, opts)
		if err != nil {
			return result, resp, err
		}
		if nodes == nil {
			break
		}

		result = append(result, nodes...)
	}

	return result, nil, nil
}

func (s *NodesService) listNodes(url string, opts *NodeListOptions) ([]*Node, *http.Response, error) {
	if opts != nil && opts.reachedEnd {
		return nil, nil, nil
	}

	url, err := addOptions(url, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url, nil)
	if err != nil {
		return nil, nil, err
	}

	nodeList := &nodeListInternal{}
	resp, err := s.client.Do(req, nodeList)
	if err != nil {
		return nil, resp, err
	}

	if opts != nil {
		if nodeList.NextToken != nil {
			opts.StartToken = *nodeList.NextToken
		} else {
			opts.reachedEnd = true
		}
	}

	nodes := nodeList.Data
	for _, node := range nodes {
		node.service = s
	}

	return nodes, resp, nil
}

type nodeListInternal struct {
	Count     *uint64 `json:"count"`
	NextToken *string `json:"nextToken"`
	Data      []*Node `json:"data"`
}

// Node represents a digital asset on the Amazon Cloud Drive, including files
// and folders, in a parent-child relationship. A node contains only metadata
// (e.g. folder) or it contains metadata and content (e.g. file).
type Node struct {
	Id                *string `json:"id"`
	Name              *string `json:"name"`
	Kind              *string `json:"kind"`
	ContentProperties *struct {
		Size *uint64 `json:"size"`
	} `json:"contentProperties"`

	service *NodesService
}

// IsFile returns whether the node represents a file.
func (n *Node) IsFile() bool {
	return n.Kind != nil && *n.Kind == "FILE"
}

// IsFolder returns whether the node represents a folder.
func (n *Node) IsFolder() bool {
	return n.Kind != nil && *n.Kind == "FOLDER"
}

// Typed returns the Node typed as either File or Folder.
func (n *Node) Typed() interface{} {
	if n.IsFile() {
		return &File{n}
	}

	if n.IsFolder() {
		return &Folder{n}
	}

	return n
}

// GetMetadata return a pretty-printed JSON string of the node's metadata
func (n *Node) GetMetadata() (string, error) {
	url := fmt.Sprintf("nodes/%s", *n.Id)
	req, err := n.service.client.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	buf := &bytes.Buffer{}
	_, err = n.service.client.Do(req, buf)
	if err != nil {
		return "", err
	}

	md := &bytes.Buffer{}
	err = json.Indent(md, buf.Bytes(), "", "    ")
	if err != nil {
		return "", err
	}

	return md.String(), nil
}

// File represents a file on the Amazon Cloud Drive.
type File struct {
	*Node
}

// Folder represents a folder on the Amazon Cloud Drive.
type Folder struct {
	*Node
}

// Gets the list of all children.
func (f *Folder) GetAllChildren(opts *NodeListOptions) ([]*Node, *http.Response, error) {
	url := fmt.Sprintf("nodes/%s/children", *f.Id)
	return f.service.listAllNodes(url, opts)
}

// Gets a list of children, up until the limit (either default or the one set in opts).
func (f *Folder) GetChildren(opts *NodeListOptions) ([]*Node, *http.Response, error) {
	url := fmt.Sprintf("nodes/%s/children", *f.Id)
	return f.service.listNodes(url, opts)
}

// Gets the subfolder by name. It is an error if not exactly one subfolder is found.
func (f *Folder) GetFolder(name string) (*Folder, *http.Response, error) {
	n, resp, err := f.GetNode(name)
	if err != nil {
		return nil, resp, err
	}

	res, ok := n.Typed().(*Folder)
	if !ok {
		err := errors.New(fmt.Sprintf("Node '%s' is not a folder", name))
		return nil, resp, err
	}

	return res, resp, nil
}

// Gets the file by name. It is an error if not exactly one file is found.
func (f *Folder) GetFile(name string) (*File, *http.Response, error) {
	n, resp, err := f.GetNode(name)
	if err != nil {
		return nil, resp, err
	}

	res, ok := n.Typed().(*File)
	if !ok {
		err := errors.New(fmt.Sprintf("Node '%s' is not a file", name))
		return nil, resp, err
	}

	return res, resp, nil
}

// Gets the node by name. It is an error if not exactly one node is found.
func (f *Folder) GetNode(name string) (*Node, *http.Response, error) {
	filter := fmt.Sprintf("parents:\"%v\" AND name:\"%s\"", *f.Id, name)
	opts := &NodeListOptions{Filters: filter}

	nodes, resp, err := f.service.GetNodes(opts)
	if err != nil {
		return nil, resp, err
	}

	if len(nodes) < 1 {
		err := errors.New(fmt.Sprintf("No node '%s' found", name))
		return nil, resp, err
	}
	if len(nodes) > 1 {
		err := errors.New(fmt.Sprintf("Too many nodes '%s' found (%v)", name, len(nodes)))
		return nil, resp, err
	}
{'fraction_non_alphanumeric': 0.1508905013192612, 'fraction_numerical': 0.002638522427440633, 'mean_word_length': 3.4510091743119267, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 7, 'https://': 2, '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': '386926', 'n_tokens_mistral': 4345, 'n_tokens_neox': 4153, 'n_words': 1433}