~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/persistent-cookiejar/serialize.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2015 The Go Authors. All rights reserved.
 
2
// Use of this source code is governed by a BSD-style
 
3
// license that can be found in the LICENSE file.
 
4
 
 
5
package cookiejar
 
6
 
 
7
import (
 
8
        "encoding/json"
 
9
        "io"
 
10
        "log"
 
11
        "os"
 
12
        "sort"
 
13
        "time"
 
14
 
 
15
        filelock "github.com/juju/go4/lock"
 
16
        "gopkg.in/errgo.v1"
 
17
)
 
18
 
 
19
// Save saves the cookies to the persistent cookie file.
 
20
// Before the file is written, it reads any cookies that
 
21
// have been stored from it and merges them into j.
 
22
func (j *Jar) Save() error {
 
23
        return j.save(time.Now())
 
24
}
 
25
 
 
26
// save is like Save but takes the current time as a parameter.
 
27
func (j *Jar) save(now time.Time) error {
 
28
        locked, err := lockFile(lockFileName(j.filename))
 
29
        if err != nil {
 
30
                return errgo.Mask(err)
 
31
        }
 
32
        defer locked.Close()
 
33
        f, err := os.OpenFile(j.filename, os.O_RDWR|os.O_CREATE, 0600)
 
34
        if err != nil {
 
35
                return errgo.Mask(err)
 
36
        }
 
37
        defer f.Close()
 
38
        // TODO optimization: if the file hasn't changed since we
 
39
        // loaded it, don't bother with the merge step.
 
40
 
 
41
        j.mu.Lock()
 
42
        defer j.mu.Unlock()
 
43
        if err := j.mergeFrom(f); err != nil {
 
44
                // The cookie file is probably corrupt.
 
45
                log.Printf("cannot read cookie file to merge it; ignoring it: %v", err)
 
46
        }
 
47
        j.deleteExpired(now)
 
48
        if err := f.Truncate(0); err != nil {
 
49
                return errgo.Notef(err, "cannot truncate file")
 
50
        }
 
51
        if _, err := f.Seek(0, 0); err != nil {
 
52
                return errgo.Mask(err)
 
53
        }
 
54
        return j.writeTo(f)
 
55
}
 
56
 
 
57
// load loads the cookies from j.filename. If the file does not exist,
 
58
// no error will be returned and no cookies will be loaded.
 
59
func (j *Jar) load() error {
 
60
        locked, err := lockFile(lockFileName(j.filename))
 
61
        if err != nil {
 
62
                return errgo.Mask(err)
 
63
        }
 
64
        defer locked.Close()
 
65
        f, err := os.Open(j.filename)
 
66
        if err != nil {
 
67
                if os.IsNotExist(err) {
 
68
                        return nil
 
69
                }
 
70
                return err
 
71
        }
 
72
        defer f.Close()
 
73
        if err := j.mergeFrom(f); err != nil {
 
74
                return errgo.Mask(err)
 
75
        }
 
76
        return nil
 
77
}
 
78
 
 
79
// mergeFrom reads all the cookies from r and stores them in the Jar.
 
80
func (j *Jar) mergeFrom(r io.Reader) error {
 
81
        decoder := json.NewDecoder(r)
 
82
        // Cope with old cookiejar format by just discarding
 
83
        // cookies, but still return an error if it's invalid JSON.
 
84
        var data json.RawMessage
 
85
        if err := decoder.Decode(&data); err != nil {
 
86
                if err == io.EOF {
 
87
                        // Empty file.
 
88
                        return nil
 
89
                }
 
90
                return err
 
91
        }
 
92
        var entries []entry
 
93
        if err := json.Unmarshal(data, &entries); err != nil {
 
94
                log.Printf("warning: discarding cookies in invalid format (error: %v)", err)
 
95
                return nil
 
96
        }
 
97
        j.merge(entries)
 
98
        return nil
 
99
}
 
100
 
 
101
// writeTo writes all the cookies in the jar to w
 
102
// as a JSON array.
 
103
func (j *Jar) writeTo(w io.Writer) error {
 
104
        encoder := json.NewEncoder(w)
 
105
        entries := j.allPersistentEntries()
 
106
        if err := encoder.Encode(entries); err != nil {
 
107
                return err
 
108
        }
 
109
        return nil
 
110
}
 
111
 
 
112
// allPersistentEntries returns all the entries in the jar, sorted by primarly by canonical host
 
113
// name and secondarily by path length.
 
114
func (j *Jar) allPersistentEntries() []entry {
 
115
        var entries []entry
 
116
        for _, submap := range j.entries {
 
117
                for _, e := range submap {
 
118
                        if e.Persistent {
 
119
                                entries = append(entries, e)
 
120
                        }
 
121
                }
 
122
        }
 
123
        sort.Sort(byCanonicalHost{entries})
 
124
        return entries
 
125
}
 
126
 
 
127
// lockFileName returns the name of the lock file associated with
 
128
// the given path.
 
129
func lockFileName(path string) string {
 
130
        return path + ".lock"
 
131
}
 
132
 
 
133
const maxRetryDuration = 1 * time.Second
 
134
 
 
135
func lockFile(path string) (io.Closer, error) {
 
136
        retry := 100 * time.Microsecond
 
137
        startTime := time.Now()
 
138
        for {
 
139
                locker, err := filelock.Lock(path)
 
140
                if err == nil {
 
141
                        return locker, nil
 
142
                }
 
143
                total := time.Since(startTime)
 
144
                if total > maxRetryDuration {
 
145
                        return nil, errgo.Notef(err, "file locked for too long; giving up")
 
146
                }
 
147
                // Always have at least one try at the end of the interval.
 
148
                if remain := maxRetryDuration - total; retry > remain {
 
149
                        retry = remain
 
150
                }
 
151
                time.Sleep(retry)
 
152
                retry *= 2
 
153
        }
 
154
}