From ef90f1dca6273350c12e0ef4ab41705790881172 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?= <torkel.odegaard@gmail.com>
Date: Tue, 30 Dec 2014 10:37:00 +0100
Subject: [PATCH] Updated readme

---
 .../macaron-contrib/session/file.go           | 259 ++++++++++++++++++
 .../macaron-contrib/session/file_test.go      |  34 +++
 .../macaron-contrib/session/memory.go         | 212 ++++++++++++++
 .../macaron-contrib/session/memory_test.go    |  27 ++
 .../macaron-contrib/session/session_test.go   | 200 ++++++++++++++
 .../macaron-contrib/session/utils.go          |  69 +++++
 README.md                                     |  47 +---
 7 files changed, 803 insertions(+), 45 deletions(-)
 create mode 100644 Godeps/_workspace/src/github.com/macaron-contrib/session/file.go
 create mode 100644 Godeps/_workspace/src/github.com/macaron-contrib/session/file_test.go
 create mode 100644 Godeps/_workspace/src/github.com/macaron-contrib/session/memory.go
 create mode 100644 Godeps/_workspace/src/github.com/macaron-contrib/session/memory_test.go
 create mode 100644 Godeps/_workspace/src/github.com/macaron-contrib/session/session_test.go
 create mode 100644 Godeps/_workspace/src/github.com/macaron-contrib/session/utils.go

diff --git a/Godeps/_workspace/src/github.com/macaron-contrib/session/file.go b/Godeps/_workspace/src/github.com/macaron-contrib/session/file.go
new file mode 100644
index 00000000000..4dfb906ec7e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/macaron-contrib/session/file.go
@@ -0,0 +1,259 @@
+// Copyright 2013 Beego Authors
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"path"
+	"path/filepath"
+	"sync"
+	"time"
+
+	"github.com/Unknwon/com"
+)
+
+// FileSessionStore represents a file session store implementation.
+type FileSessionStore struct {
+	p    *FileProvider
+	sid  string
+	lock sync.RWMutex
+	data map[interface{}]interface{}
+}
+
+// NewFileSessionStore creates and returns a file session store.
+func NewFileSessionStore(p *FileProvider, sid string, kv map[interface{}]interface{}) *FileSessionStore {
+	return &FileSessionStore{
+		p:    p,
+		sid:  sid,
+		data: kv,
+	}
+}
+
+// Set sets value to given key in session.
+func (s *FileSessionStore) Set(key, val interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data[key] = val
+	return nil
+}
+
+// Get gets value by given key in session.
+func (s *FileSessionStore) Get(key interface{}) interface{} {
+	s.lock.RLock()
+	defer s.lock.RUnlock()
+
+	return s.data[key]
+}
+
+// Delete delete a key from session.
+func (s *FileSessionStore) Delete(key interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	delete(s.data, key)
+	return nil
+}
+
+// ID returns current session ID.
+func (s *FileSessionStore) ID() string {
+	return s.sid
+}
+
+// Release releases resource and save data to provider.
+func (s *FileSessionStore) Release() error {
+	data, err := EncodeGob(s.data)
+	if err != nil {
+		return err
+	}
+
+	return ioutil.WriteFile(s.p.filepath(s.sid), data, os.ModePerm)
+}
+
+// Flush deletes all session data.
+func (s *FileSessionStore) Flush() error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data = make(map[interface{}]interface{})
+	return nil
+}
+
+// FileProvider represents a file session provider implementation.
+type FileProvider struct {
+	lock        sync.RWMutex
+	maxlifetime int64
+	rootPath    string
+}
+
+// Init initializes file session provider with given root path.
+func (p *FileProvider) Init(maxlifetime int64, rootPath string) error {
+	p.maxlifetime = maxlifetime
+	p.rootPath = rootPath
+	return nil
+}
+
+func (p *FileProvider) filepath(sid string) string {
+	return path.Join(p.rootPath, string(sid[0]), string(sid[1]), sid)
+}
+
+// Read returns raw session store by session ID.
+func (p *FileProvider) Read(sid string) (_ RawStore, err error) {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	filename := p.filepath(sid)
+	if err = os.MkdirAll(path.Dir(filename), os.ModePerm); err != nil {
+		return nil, err
+	}
+
+	var f *os.File
+	if com.IsFile(filename) {
+		f, err = os.OpenFile(filename, os.O_RDWR, os.ModePerm)
+	} else {
+		f, err = os.Create(filename)
+	}
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	if err = os.Chtimes(filename, time.Now(), time.Now()); err != nil {
+		return nil, err
+	}
+
+	var kv map[interface{}]interface{}
+	data, err := ioutil.ReadAll(f)
+	if err != nil {
+		return nil, err
+	}
+	if len(data) == 0 {
+		kv = make(map[interface{}]interface{})
+	} else {
+		kv, err = DecodeGob(data)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return NewFileSessionStore(p, sid, kv), nil
+}
+
+// Exist returns true if session with given ID exists.
+func (p *FileProvider) Exist(sid string) bool {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	return com.IsFile(p.filepath(sid))
+}
+
+// Destory deletes a session by session ID.
+func (p *FileProvider) Destory(sid string) error {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	return os.Remove(p.filepath(sid))
+}
+
+func (p *FileProvider) regenerate(oldsid, sid string) (err error) {
+	filename := p.filepath(sid)
+	if com.IsExist(filename) {
+		return fmt.Errorf("new sid '%s' already exists", sid)
+	}
+
+	oldname := p.filepath(oldsid)
+	if !com.IsFile(oldname) {
+		data, err := EncodeGob(make(map[interface{}]interface{}))
+		if err != nil {
+			return err
+		}
+		if err = os.MkdirAll(path.Dir(oldname), os.ModePerm); err != nil {
+			return err
+		}
+		if err = ioutil.WriteFile(oldname, data, os.ModePerm); err != nil {
+			return err
+		}
+	}
+
+	if err = os.MkdirAll(path.Dir(filename), os.ModePerm); err != nil {
+		return err
+	}
+	if err = os.Rename(oldname, filename); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Regenerate regenerates a session store from old session ID to new one.
+func (p *FileProvider) Regenerate(oldsid, sid string) (_ RawStore, err error) {
+	p.lock.Lock()
+	if err := p.regenerate(oldsid, sid); err != nil {
+		p.lock.Unlock()
+		return nil, err
+	}
+	p.lock.Unlock()
+
+	return p.Read(sid)
+}
+
+// Count counts and returns number of sessions.
+func (p *FileProvider) Count() int {
+	count := 0
+	if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if !fi.IsDir() {
+			count++
+		}
+		return nil
+	}); err != nil {
+		log.Printf("error counting session files: %v", err)
+		return 0
+	}
+	return count
+}
+
+// GC calls GC to clean expired sessions.
+func (p *FileProvider) GC() {
+	if !com.IsExist(p.rootPath) {
+		return
+	}
+
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if !fi.IsDir() &&
+			(fi.ModTime().Unix()+p.maxlifetime) < time.Now().Unix() {
+			return os.Remove(path)
+		}
+		return nil
+	}); err != nil {
+		log.Printf("error garbage collecting session files: %v", err)
+	}
+}
+
+func init() {
+	Register("file", &FileProvider{})
+}
diff --git a/Godeps/_workspace/src/github.com/macaron-contrib/session/file_test.go b/Godeps/_workspace/src/github.com/macaron-contrib/session/file_test.go
new file mode 100644
index 00000000000..9c83555ab4e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/macaron-contrib/session/file_test.go
@@ -0,0 +1,34 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"os"
+	"path"
+	"testing"
+
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func Test_FileProvider(t *testing.T) {
+	Convey("Test file session provider", t, func() {
+		dir := path.Join(os.TempDir(), "data/sessions")
+		os.RemoveAll(dir)
+		testProvider(Options{
+			Provider:       "file",
+			ProviderConfig: dir,
+		})
+	})
+}
diff --git a/Godeps/_workspace/src/github.com/macaron-contrib/session/memory.go b/Godeps/_workspace/src/github.com/macaron-contrib/session/memory.go
new file mode 100644
index 00000000000..040b8e697dc
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/macaron-contrib/session/memory.go
@@ -0,0 +1,212 @@
+// Copyright 2013 Beego Authors
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"container/list"
+	"fmt"
+	"sync"
+	"time"
+)
+
+// MemSessionStore represents a in-memory session store implementation.
+type MemSessionStore struct {
+	sid        string
+	lock       sync.RWMutex
+	data       map[interface{}]interface{}
+	lastAccess time.Time
+}
+
+// NewMemSessionStore creates and returns a memory session store.
+func NewMemSessionStore(sid string) *MemSessionStore {
+	return &MemSessionStore{
+		sid:        sid,
+		data:       make(map[interface{}]interface{}),
+		lastAccess: time.Now(),
+	}
+}
+
+// Set sets value to given key in session.
+func (s *MemSessionStore) Set(key, val interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data[key] = val
+	return nil
+}
+
+// Get gets value by given key in session.
+func (s *MemSessionStore) Get(key interface{}) interface{} {
+	s.lock.RLock()
+	defer s.lock.RUnlock()
+
+	return s.data[key]
+}
+
+// Delete delete a key from session.
+func (s *MemSessionStore) Delete(key interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	delete(s.data, key)
+	return nil
+}
+
+// ID returns current session ID.
+func (s *MemSessionStore) ID() string {
+	return s.sid
+}
+
+// Release releases resource and save data to provider.
+func (_ *MemSessionStore) Release() error {
+	return nil
+}
+
+// Flush deletes all session data.
+func (s *MemSessionStore) Flush() error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data = make(map[interface{}]interface{})
+	return nil
+}
+
+// MemProvider represents a in-memory session provider implementation.
+type MemProvider struct {
+	lock        sync.RWMutex
+	maxLifetime int64
+	data        map[string]*list.Element
+	// A priority list whose lastAccess newer gets higer priority.
+	list *list.List
+}
+
+// Init initializes memory session provider.
+func (p *MemProvider) Init(maxLifetime int64, _ string) error {
+	p.maxLifetime = maxLifetime
+	return nil
+}
+
+// update expands time of session store by given ID.
+func (p *MemProvider) update(sid string) error {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	if e, ok := p.data[sid]; ok {
+		e.Value.(*MemSessionStore).lastAccess = time.Now()
+		p.list.MoveToFront(e)
+		return nil
+	}
+	return nil
+}
+
+// Read returns raw session store by session ID.
+func (p *MemProvider) Read(sid string) (_ RawStore, err error) {
+	p.lock.RLock()
+	e, ok := p.data[sid]
+	p.lock.RUnlock()
+
+	if ok {
+		if err = p.update(sid); err != nil {
+			return nil, err
+		}
+		return e.Value.(*MemSessionStore), nil
+	}
+
+	// Create a new session.
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	s := NewMemSessionStore(sid)
+	p.data[sid] = p.list.PushBack(s)
+	return s, nil
+}
+
+// Exist returns true if session with given ID exists.
+func (p *MemProvider) Exist(sid string) bool {
+	p.lock.RLock()
+	defer p.lock.RUnlock()
+
+	_, ok := p.data[sid]
+	return ok
+}
+
+// Destory deletes a session by session ID.
+func (p *MemProvider) Destory(sid string) error {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	e, ok := p.data[sid]
+	if !ok {
+		return nil
+	}
+
+	p.list.Remove(e)
+	delete(p.data, sid)
+	return nil
+}
+
+// Regenerate regenerates a session store from old session ID to new one.
+func (p *MemProvider) Regenerate(oldsid, sid string) (RawStore, error) {
+	if p.Exist(sid) {
+		return nil, fmt.Errorf("new sid '%s' already exists", sid)
+	}
+
+	s, err := p.Read(oldsid)
+	if err != nil {
+		return nil, err
+	}
+
+	if err = p.Destory(oldsid); err != nil {
+		return nil, err
+	}
+
+	s.(*MemSessionStore).sid = sid
+	p.data[sid] = p.list.PushBack(s)
+	return s, nil
+}
+
+// Count counts and returns number of sessions.
+func (p *MemProvider) Count() int {
+	return p.list.Len()
+}
+
+// GC calls GC to clean expired sessions.
+func (p *MemProvider) GC() {
+	p.lock.RLock()
+	for {
+		// No session in the list.
+		e := p.list.Back()
+		if e == nil {
+			break
+		}
+
+		if (e.Value.(*MemSessionStore).lastAccess.Unix() + p.maxLifetime) < time.Now().Unix() {
+			p.lock.RUnlock()
+			p.lock.Lock()
+			p.list.Remove(e)
+			delete(p.data, e.Value.(*MemSessionStore).sid)
+			p.lock.Unlock()
+			p.lock.RLock()
+		} else {
+			break
+		}
+	}
+	p.lock.RUnlock()
+}
+
+func init() {
+	Register("memory", &MemProvider{list: list.New(), data: make(map[string]*list.Element)})
+}
diff --git a/Godeps/_workspace/src/github.com/macaron-contrib/session/memory_test.go b/Godeps/_workspace/src/github.com/macaron-contrib/session/memory_test.go
new file mode 100644
index 00000000000..41659bb6a1e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/macaron-contrib/session/memory_test.go
@@ -0,0 +1,27 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"testing"
+
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func Test_MemProvider(t *testing.T) {
+	Convey("Test memory session provider", t, func() {
+		testProvider(Options{})
+	})
+}
diff --git a/Godeps/_workspace/src/github.com/macaron-contrib/session/session_test.go b/Godeps/_workspace/src/github.com/macaron-contrib/session/session_test.go
new file mode 100644
index 00000000000..327c1d0c6a5
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/macaron-contrib/session/session_test.go
@@ -0,0 +1,200 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+	"time"
+
+	"github.com/Unknwon/macaron"
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func Test_Version(t *testing.T) {
+	Convey("Check package version", t, func() {
+		So(Version(), ShouldEqual, _VERSION)
+	})
+}
+
+func Test_Sessioner(t *testing.T) {
+	Convey("Use session middleware", t, func() {
+		m := macaron.New()
+		m.Use(Sessioner())
+		m.Get("/", func() {})
+
+		resp := httptest.NewRecorder()
+		req, err := http.NewRequest("GET", "/", nil)
+		So(err, ShouldBeNil)
+		m.ServeHTTP(resp, req)
+	})
+
+	Convey("Register invalid provider that", t, func() {
+		Convey("Provider not exists", func() {
+			defer func() {
+				So(recover(), ShouldNotBeNil)
+			}()
+
+			m := macaron.New()
+			m.Use(Sessioner(Options{
+				Provider: "fake",
+			}))
+		})
+
+		Convey("Provider value is nil", func() {
+			defer func() {
+				So(recover(), ShouldNotBeNil)
+			}()
+
+			Register("fake", nil)
+		})
+
+		Convey("Register twice", func() {
+			defer func() {
+				So(recover(), ShouldNotBeNil)
+			}()
+
+			Register("memory", &MemProvider{})
+		})
+	})
+}
+
+func testProvider(opt Options) {
+	Convey("Basic operation", func() {
+		m := macaron.New()
+		m.Use(Sessioner(opt))
+
+		m.Get("/", func(ctx *macaron.Context, sess Store) {
+			sess.Set("uname", "unknwon")
+		})
+		m.Get("/reg", func(ctx *macaron.Context, sess Store) {
+			raw, err := sess.RegenerateId(ctx)
+			So(err, ShouldBeNil)
+			So(raw, ShouldNotBeNil)
+
+			uname := raw.Get("uname")
+			So(uname, ShouldNotBeNil)
+			So(uname, ShouldEqual, "unknwon")
+		})
+		m.Get("/get", func(ctx *macaron.Context, sess Store) {
+			sid := sess.ID()
+			So(sid, ShouldNotBeEmpty)
+
+			raw, err := sess.Read(sid)
+			So(err, ShouldBeNil)
+			So(raw, ShouldNotBeNil)
+
+			uname := sess.Get("uname")
+			So(uname, ShouldNotBeNil)
+			So(uname, ShouldEqual, "unknwon")
+
+			So(sess.Delete("uname"), ShouldBeNil)
+			So(sess.Get("uname"), ShouldBeNil)
+
+			So(sess.Destory(ctx), ShouldBeNil)
+		})
+
+		resp := httptest.NewRecorder()
+		req, err := http.NewRequest("GET", "/", nil)
+		So(err, ShouldBeNil)
+		m.ServeHTTP(resp, req)
+
+		cookie := resp.Header().Get("Set-Cookie")
+
+		resp = httptest.NewRecorder()
+		req, err = http.NewRequest("GET", "/reg", nil)
+		So(err, ShouldBeNil)
+		req.Header.Set("Cookie", cookie)
+		m.ServeHTTP(resp, req)
+
+		cookie = resp.Header().Get("Set-Cookie")
+
+		resp = httptest.NewRecorder()
+		req, err = http.NewRequest("GET", "/get", nil)
+		So(err, ShouldBeNil)
+		req.Header.Set("Cookie", cookie)
+		m.ServeHTTP(resp, req)
+	})
+
+	Convey("Regenrate empty session", func() {
+		m := macaron.New()
+		m.Use(Sessioner(opt))
+		m.Get("/", func(ctx *macaron.Context, sess Store) {
+			raw, err := sess.RegenerateId(ctx)
+			So(err, ShouldBeNil)
+			So(raw, ShouldNotBeNil)
+		})
+
+		resp := httptest.NewRecorder()
+		req, err := http.NewRequest("GET", "/", nil)
+		So(err, ShouldBeNil)
+		req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf486; Path=/;")
+		m.ServeHTTP(resp, req)
+	})
+
+	Convey("GC session", func() {
+		m := macaron.New()
+		opt2 := opt
+		opt2.Gclifetime = 1
+		m.Use(Sessioner(opt2))
+
+		m.Get("/", func(sess Store) {
+			sess.Set("uname", "unknwon")
+			So(sess.ID(), ShouldNotBeEmpty)
+			uname := sess.Get("uname")
+			So(uname, ShouldNotBeNil)
+			So(uname, ShouldEqual, "unknwon")
+
+			So(sess.Flush(), ShouldBeNil)
+			So(sess.Get("uname"), ShouldBeNil)
+
+			time.Sleep(2 * time.Second)
+			sess.GC()
+			So(sess.Count(), ShouldEqual, 0)
+		})
+
+		resp := httptest.NewRecorder()
+		req, err := http.NewRequest("GET", "/", nil)
+		So(err, ShouldBeNil)
+		m.ServeHTTP(resp, req)
+	})
+}
+
+func Test_Flash(t *testing.T) {
+	Convey("Test flash", t, func() {
+		m := macaron.New()
+		m.Use(Sessioner())
+		m.Get("/set", func(f *Flash) string {
+			f.Success("success")
+			f.Error("error")
+			f.Warning("warning")
+			f.Info("info")
+			return ""
+		})
+		m.Get("/get", func() {})
+
+		resp := httptest.NewRecorder()
+		req, err := http.NewRequest("GET", "/set", nil)
+		So(err, ShouldBeNil)
+		m.ServeHTTP(resp, req)
+
+		resp = httptest.NewRecorder()
+		req, err = http.NewRequest("GET", "/get", nil)
+		So(err, ShouldBeNil)
+		req.Header.Set("Cookie", "macaron_flash=error%3Derror%26info%3Dinfo%26success%3Dsuccess%26warning%3Dwarning; Path=/")
+		m.ServeHTTP(resp, req)
+	})
+}
diff --git a/Godeps/_workspace/src/github.com/macaron-contrib/session/utils.go b/Godeps/_workspace/src/github.com/macaron-contrib/session/utils.go
new file mode 100644
index 00000000000..a165de83070
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/macaron-contrib/session/utils.go
@@ -0,0 +1,69 @@
+// Copyright 2013 Beego Authors
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"bytes"
+	"crypto/rand"
+	"encoding/gob"
+	"io"
+
+	"github.com/Unknwon/com"
+)
+
+func init() {
+	gob.Register([]interface{}{})
+	gob.Register(map[int]interface{}{})
+	gob.Register(map[string]interface{}{})
+	gob.Register(map[interface{}]interface{}{})
+	gob.Register(map[string]string{})
+	gob.Register(map[int]string{})
+	gob.Register(map[int]int{})
+	gob.Register(map[int]int64{})
+}
+
+func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) {
+	for _, v := range obj {
+		gob.Register(v)
+	}
+	buf := bytes.NewBuffer(nil)
+	enc := gob.NewEncoder(buf)
+	err := enc.Encode(obj)
+	if err != nil {
+		return []byte(""), err
+	}
+	return buf.Bytes(), nil
+}
+
+func DecodeGob(encoded []byte) (map[interface{}]interface{}, error) {
+	buf := bytes.NewBuffer(encoded)
+	dec := gob.NewDecoder(buf)
+	var out map[interface{}]interface{}
+	err := dec.Decode(&out)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// generateRandomKey creates a random key with the given strength.
+func generateRandomKey(strength int) []byte {
+	k := make([]byte, strength)
+	if n, err := io.ReadFull(rand.Reader, k); n != strength || err != nil {
+		return com.RandomCreateBytes(strength)
+	}
+	return k
+}
diff --git a/README.md b/README.md
index 02cae2bb0e9..6fdb471d847 100644
--- a/README.md
+++ b/README.md
@@ -1,46 +1,3 @@
-grafana-pro
-===========
-
-This is very much a Work in Progess. The architecture and organisation has changed many times and I am not very happy
-with the current structure and architecture. I started with a lot of unit tests and drove the design though that in 
-the begining, but then having changed the code and structure (and web framework libs, and database) I sort of 
-abandoned them. When the architecture is getting more stable I will write & require more unit & integration tests. 
-
-# Architecture
-I have researched a lot of go projects, found very few with a nice modular & loosly coupled archictecture. Go projects 
-organize code very differently, some treat packages as global singletons (which reads nicely and is nice to work with 
-but seems like a bad practice). I do miss generics for doing strongly typed but loosley coupled command/query style
-internal buss messaging. 
-
-The biggest challange for architecture is making Grafana Pro very modular in a way that all/most components can 
-run inproc or in seperate process (usefull when running Grafana Pro in a large scale SaaS setup). Been investigating 
-using ZeroMQ or NanoMsg to handle module communication (Both ZeroMQ and NanoMsg can handle inproc & tcp which a number of 
-different topologies). Running Grafana Pro in a SaaS setup might warrant that the alerting stuff runs out of proc on 
-other servers feeding of a queue for example, but for simple on prem stuff it would be cool if that could be run in the 
-same process. I am also thinking that some stuff might need swapping out/in depending on the setup (plugin model or just
-interfaces with different implementations). 
-
-# Building
-* Just added a simple make file (the main binary package is in pkg/cmd/grafana-pro)
-* Need to change to godep or some go lang dependency management system
-
-# Data access
-Data access is very strange right now, instead of an interface I tried using public methods in the models package.
-These method pointers are assigned in when the sqlstore is initialized in pkg/store/sqlstore. This is probably a 
-bad idea. I am thinking about either moving to simple interfaces. But I do not want a giant interface for all 
-data acess. Would much prefe a simple generic command/query interface but golang lacks generics which makes this 
-painful. But a generic command/query interface could still be good as it would make it easier to have some commands or
-queries handled buy out of proc components (or inproc if we used someting like ZeroMQ for communication). Or it 
-could be overengineering thinking like this. 
-
-# Grafana frontend
-The grafana frontend is added as git submodule in order to easily sync with upstream grafana. 
-There should be a symbolic link from ./grafana/src to ./public . Need to add this to the Makefile. 
-
-# TODO
-* Add symbolik link between ./grafana/src ./public
-* Hash & Salt password
-* Unit tests for data access 
-* I switched recently from rethinkdb to sql (using a simple mini ORM lib called xorm, might need to switch to a more 
-popular ORM lib). 
+Work in progress Grafana 2.0 (with included Grafana backend)
 
+[![wercker status](https://app.wercker.com/status/0f109051cfaf2a6d94c0eebdc0dcaeae/s "wercker status")](https://app.wercker.com/project/bykey/0f109051cfaf2a6d94c0eebdc0dcaeae)