func (c *Client) readPlaylist() (pls []Attrs, err os.Error) { pls = []Attrs{} for { line, err := c.text.ReadLine() if err != nil { return nil, err } if line == "OK" { break } if strings.HasPrefix(line, "file:") { // new song entry begins pls = append(pls, Attrs{}) } if len(pls) == 0 { return nil, textproto.ProtocolError("unexpected: " + line) } z := strings.Index(line, ": ") if z < 0 { return nil, textproto.ProtocolError("can't parse line: " + line) } key := line[0:z] pls[len(pls)-1][key] = line[z+2:] } return pls, nil }
func (c *Client) readAttrsList(startKey string) (attrs []Attrs, err error) { attrs = []Attrs{} startKey += ": " for { line, err := c.text.ReadLine() if err != nil { return nil, err } if line == "OK" { break } if strings.HasPrefix(line, startKey) { // new entry begins attrs = append(attrs, Attrs{}) } if len(attrs) == 0 { return nil, textproto.ProtocolError("unexpected: " + line) } i := strings.Index(line, ": ") if i < 0 { return nil, textproto.ProtocolError("can't parse line: " + line) } attrs[len(attrs)-1][line[0:i]] = line[i+2:] } return attrs, nil }
// Fields returns the fields in s. // Fields are space separated unquoted words // or quoted with single or double quote. func fields(s string) ([]string, error) { var v []string i := 0 for { for i < len(s) && (s[i] == ' ' || s[i] == '\t') { i++ } if i >= len(s) { break } if s[i] == '"' || s[i] == '\'' { q := s[i] // quoted string var j int for j = i + 1; ; j++ { if j >= len(s) { return nil, textproto.ProtocolError("malformed quoted string") } if s[j] == '\\' { j++ continue } if s[j] == q { j++ break } } v = append(v, unquote(s[i+1:j-1])) i = j } else { // atom var j int for j = i; j < len(s); j++ { if s[j] == ' ' || s[j] == '\t' || s[j] == '\\' || s[j] == '"' || s[j] == '\'' { break } } v = append(v, s[i:j]) i = j } if i < len(s) { c := s[i] if c != ' ' && c != '\t' { return nil, textproto.ProtocolError("quotes not on word boundaries") } } } return v, nil }
// ListAllInfo returns attributes for songs in the library. Information about // any song that is either inside or matches the passed in uri is returned. // To get information about every song in the library, pass in "/". func (c *Client) ListAllInfo(uri string) ([]Attrs, error) { id, err := c.cmd("listallinfo %s ", quote(uri)) if err != nil { return nil, err } c.text.StartResponse(id) defer c.text.EndResponse(id) attrs := []Attrs{} inEntry := false for { line, err := c.text.ReadLine() if err != nil { return nil, err } if line == "OK" { break } else if strings.HasPrefix(line, "file: ") { // new entry begins attrs = append(attrs, Attrs{}) inEntry = true } else if strings.HasPrefix(line, "directory: ") { inEntry = false } if inEntry { i := strings.Index(line, ": ") if i < 0 { return nil, textproto.ProtocolError("can't parse line: " + line) } attrs[len(attrs)-1][line[0:i]] = line[i+2:] } } return attrs, nil }
// ListInfo lists the contents of the directory URI using MPD's lsinfo command. func (c *Client) ListInfo(uri string) ([]Attrs, error) { id, err := c.cmd("lsinfo %s", quote(uri)) if err != nil { return nil, err } c.text.StartResponse(id) defer c.text.EndResponse(id) attrs := []Attrs{} for { line, err := c.text.ReadLine() if err != nil { return nil, err } if line == "OK" { break } if strings.HasPrefix(line, "file: ") || strings.HasPrefix(line, "directory: ") || strings.HasPrefix(line, "playlist: ") { attrs = append(attrs, Attrs{}) } i := strings.Index(line, ": ") if i < 0 { return nil, textproto.ProtocolError("can't parse line: " + line) } attrs[len(attrs)-1][strings.ToLower(line[0:i])] = line[i+2:] } return attrs, nil }
// List searches the database for your query. You can use something simple like // `artist` for your search, or something like `artist album <Album Name>` if // you want the artist that has an album with a specified album name. func (c *Client) List(typ ...string) ([]string, error) { id, err := c.cmd("list " + quoteArgs(typ)) if err != nil { return nil, err } c.text.StartResponse(id) defer c.text.EndResponse(id) var ret []string for { line, err := c.text.ReadLine() if err != nil { return nil, err } i := strings.Index(line, ": ") if i > 0 { ret = append(ret, line[i+2:]) } else if line == "OK" { break } else { return nil, textproto.ProtocolError("can't parse line: " + line) } } return ret, nil }
// ReadCodeLine reads a response code line of the form // code message // where code is a three-digit status code and the message // extends to the rest of the line. An example of such a line is: // 220 plan9.bell-labs.com ESMTP // // If the prefix of the status does not match the digits in expectCode, // ReadCodeLine returns with err set to &Error{code, message}. // For example, if expectCode is 31, an error will be returned if // the status is not in the range [310,319]. // // If the response is multi-line, ReadCodeLine returns an error. // // An expectCode <= 0 disables the check of the status code. // func (r *Reader) ReadCodeLine(expectCode int) (code int, message string, err error) { code, continued, message, err := r.readCodeLine(expectCode) if err == nil && continued { err = textproto.ProtocolError("unexpected multi-line response: " + message) } return }
// AddID adds the file/directory uri to playlist and returns the identity // id of the song added. If pos is positive, the song is added to position // pos. func (c *Client) AddID(uri string, pos int) (int, error) { var id uint var err error if pos >= 0 { id, err = c.cmd("addid %s %d", quote(uri), pos) } else { id, err = c.cmd("addid %s", quote(uri)) } if err != nil { return -1, err } c.text.StartResponse(id) defer c.text.EndResponse(id) attrs, err := c.readAttrs("OK") if err != nil { return -1, err } tok, ok := attrs["Id"] if !ok { return -1, textproto.ProtocolError("addid did not return Id") } return strconv.Atoi(tok) }
// Dicts returns a list of the dictionaries available on the server. func (c *Client) Dicts() ([]Dict, error) { id, err := c.text.Cmd("SHOW DB") if err != nil { return nil, err } c.text.StartResponse(id) defer c.text.EndResponse(id) _, _, err = c.text.ReadCodeLine(110) if err != nil { return nil, err } lines, err := c.text.ReadDotLines() if err != nil { return nil, err } _, _, err = c.text.ReadCodeLine(250) dicts := make([]Dict, len(lines)) for i := range dicts { d := &dicts[i] a, _ := fields(lines[i]) if len(a) < 2 { return nil, textproto.ProtocolError("invalid dictionary: " + lines[i]) } d.Name = a[0] d.Desc = a[1] } return dicts, err }
// Define requests the definition of the given word. // The argument dict names the dictionary to use, // the Name field of a Dict returned by Dicts. // // The special dictionary name "*" means to look in all the // server's dictionaries. // The special dictionary name "!" means to look in all the // server's dictionaries in turn, stopping after finding the word // in one of them. func (c *Client) Define(dict, word string) ([]*Defn, error) { id, err := c.text.Cmd("DEFINE %s %q", dict, word) if err != nil { return nil, err } c.text.StartResponse(id) defer c.text.EndResponse(id) _, line, err := c.text.ReadCodeLine(150) if err != nil { return nil, err } a, _ := fields(line) if len(a) < 1 { return nil, textproto.ProtocolError("malformed response: " + line) } n, err := strconv.Atoi(a[0]) if err != nil { return nil, textproto.ProtocolError("invalid definition count: " + a[0]) } def := make([]*Defn, n) for i := 0; i < n; i++ { _, line, err = c.text.ReadCodeLine(151) if err != nil { return nil, err } a, _ := fields(line) if len(a) < 3 { // skip it, to keep protocol in sync i-- n-- def = def[0:n] continue } d := &Defn{Word: a[0], Dict: Dict{a[1], a[2]}} d.Text, err = c.text.ReadDotBytes() if err != nil { return nil, err } def[i] = d } _, _, err = c.text.ReadCodeLine(250) return def, err }
func parseCodeLine(line string, expectCode int) (code int, continued bool, message string, err error) { if len(line) < 4 || line[3] != ' ' && line[3] != '-' { err = textproto.ProtocolError("short response: " + line) return } continued = line[3] == '-' code, err = strconv.Atoi(line[0:3]) if err != nil || code < 100 { err = textproto.ProtocolError("invalid response code: " + line) return } message = line[4:] if 1 <= expectCode && expectCode < 10 && code/100 != expectCode || 10 <= expectCode && expectCode < 100 && code/10 != expectCode || 100 <= expectCode && expectCode < 1000 && code != expectCode { err = &textproto.Error{Code: code, Msg: message} } return }
func (c *Client) readOKLine() (err os.Error) { line, err := c.text.ReadLine() if err != nil { return } if line == "OK" { return nil } return textproto.ProtocolError("unexpected response: " + line) }
func (c *Client) readOKLine(terminator string) (err error) { line, err := c.text.ReadLine() if err != nil { return } if line == terminator { return nil } return textproto.ProtocolError("unexpected response: " + line) }
// Dial connects to MPD listening on address addr (e.g. "127.0.0.1:6600") // on network network (e.g. "tcp"). func Dial(network, addr string) (c *Client, err error) { text, err := textproto.Dial(network, addr) if err != nil { return nil, err } line, err := text.ReadLine() if err != nil { return nil, err } if line[0:6] != "OK MPD" { return nil, textproto.ProtocolError("no greeting") } return &Client{text: text}, nil }
func Dial(address, password string) (c *Client, err error) { c = new(Client) if c.conn, err = textproto.Dial(TCP, address); err != nil { return } var version string if version, err = c.conn.ReadLine(); err != nil { return nil, err } if version[0:6] != "OK MPD" { return nil, textproto.ProtocolError("Handshake Error") } return }
func (c *Client) readList(key string) (list []string, err error) { list = []string{} key += ": " for { line, err := c.text.ReadLine() if err != nil { return nil, err } if line == "OK" { break } if !strings.HasPrefix(line, key) { return nil, textproto.ProtocolError("unexpected: " + line) } list = append(list, line[len(key):]) } return }
func (c *Client) readAttrs(terminator string) (attrs Attrs, err error) { attrs = make(Attrs) for { line, err := c.text.ReadLine() if err != nil { return nil, err } if line == terminator { break } z := strings.Index(line, ": ") if z < 0 { return nil, textproto.ProtocolError("can't parse line: " + line) } key := line[0:z] attrs[key] = line[z+2:] } return }
func (c *Client) readValues() (values Values, err error) { values = make(Values) for { line, err := c.conn.ReadLine() if err != nil { return nil, err } if line == "OK" { break } z := strings.Index(line, ":") if z < 0 { return nil, textproto.ProtocolError("Can't parse line: " + line) } key := line[0:z] values[key] = line[z+2:] } return }
// Update updates MPD's database: find new files, remove deleted files, update // modified files. uri is a particular directory or file to update. If it is an // empty string, everything is updated. // // The returned jobID identifies the update job, enqueued by MPD. func (c *Client) Update(uri string) (jobID int, err error) { id, err := c.cmd("update %s", quote(uri)) if err != nil { return } c.text.StartResponse(id) defer c.text.EndResponse(id) line, err := c.text.ReadLine() if err != nil { return } if !strings.HasPrefix(line, "updating_db: ") { return 0, textproto.ProtocolError("unexpected response: " + line) } jobID, err = strconv.Atoi(line[13:]) if err != nil { return } return jobID, c.readOKLine("OK") }
// AddId adds the file/directory uri to playlist and returns the identity // id of the song added. If pos is positive, the song is added to position // pos. func (c *Client) AddId(uri string, pos int) (int, os.Error) { var id uint var err os.Error if pos >= 0 { id, err = c.text.Cmd("addid %q %d", uri, pos) } id, err = c.text.Cmd("addid %q", uri) if err != nil { return -1, err } c.text.StartResponse(id) defer c.text.EndResponse(id) attrs, err := c.readAttrs() if err != nil { return -1, err } tok, ok := attrs["Id"] if !ok { return -1, textproto.ProtocolError("addid did not return Id") } return strconv.Atoi(tok) }
func newProtocolError(fmt string, args ...interface{}) textproto.ProtocolError { return textproto.ProtocolError(gofmt.Sprintf(fmt, args...)) }
// ReadMIMEHeader reads a MIME-style header from r. // The header is a sequence of possibly continued Key: Value lines // ending in a blank line. // The returned map m maps CanonicalMIMEHeaderKey(key) to a // sequence of values in the same order encountered in the input. // // For example, consider this input: // // My-Key: Value 1 // Long-Key: Even // Longer Value // My-Key: Value 2 // // Given that input, ReadMIMEHeader returns the map: // // map[string][]string{ // "My-Key": {"Value 1", "Value 2"}, // "Long-Key": {"Even Longer Value"}, // } // func (r *Reader) ReadMIMEHeader() (textproto.MIMEHeader, []string, error) { // Avoid lots of small slice allocations later by allocating one // large one ahead of time which we'll cut up into smaller // slices. If this isn't big enough later, we allocate small ones. var strs []string hint := r.upcomingHeaderNewlines() if hint > 0 { strs = make([]string, hint) } var rawHeaders = []string{} m := make(textproto.MIMEHeader, hint) for { kv, err := r.readContinuedLineSlice() if len(kv) == 0 { return m, rawHeaders, err } rawHeaders = append(rawHeaders, string(kv)) // Key ends at first colon; should not have spaces but // they appear in the wild, violating specs, so we // remove them if present. i := bytes.IndexByte(kv, ':') if i < 0 { return m, rawHeaders, textproto.ProtocolError("malformed MIME header line: " + string(kv)) } endKey := i for endKey > 0 && kv[endKey-1] == ' ' { endKey-- } key := canonicalMIMEHeaderKey(kv[:endKey]) // As per RFC 7230 field-name is a token, tokens consist of one or more chars. // We could return a ProtocolError here, but better to be liberal in what we // accept, so if we get an empty key, skip it. if key == "" { continue } // Skip initial spaces in value. i++ // skip colon for i < len(kv) && (kv[i] == ' ' || kv[i] == '\t') { i++ } value := string(kv[i:]) vv := m[key] if vv == nil && len(strs) > 0 { // More than likely this will be a single-element key. // Most headers aren't multi-valued. // Set the capacity on strs[0] to 1, so any future append // won't extend the slice into the other strings. vv, strs = strs[:1:1], strs[1:] vv[0] = value m[key] = vv } else { m[key] = append(vv, value) } if err != nil { return m, rawHeaders, err } } }