summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md3
-rw-r--r--Godeps/Godeps.json5
-rw-r--r--Godeps/_workspace/src/code.google.com/p/log4go/.hgtags4
-rw-r--r--Godeps/_workspace/src/code.google.com/p/log4go/log4go_test.go534
-rw-r--r--Godeps/_workspace/src/code.google.com/p/log4go/termlog.go45
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/.gitignore2
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/LICENSE (renamed from Godeps/_workspace/src/code.google.com/p/log4go/LICENSE)0
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/README (renamed from Godeps/_workspace/src/code.google.com/p/log4go/README)0
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/config.go (renamed from Godeps/_workspace/src/code.google.com/p/log4go/config.go)4
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/examples/ConsoleLogWriter_Manual.go (renamed from Godeps/_workspace/src/code.google.com/p/log4go/examples/ConsoleLogWriter_Manual.go)1
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/examples/FileLogWriter_Manual.go (renamed from Godeps/_workspace/src/code.google.com/p/log4go/examples/FileLogWriter_Manual.go)0
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/examples/SimpleNetLogServer.go (renamed from Godeps/_workspace/src/code.google.com/p/log4go/examples/SimpleNetLogServer.go)0
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/examples/SocketLogWriter_Manual.go (renamed from Godeps/_workspace/src/code.google.com/p/log4go/examples/SocketLogWriter_Manual.go)0
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/examples/XMLConfigurationExample.go (renamed from Godeps/_workspace/src/code.google.com/p/log4go/examples/XMLConfigurationExample.go)0
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/examples/example.xml (renamed from Godeps/_workspace/src/code.google.com/p/log4go/examples/example.xml)0
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/filelog.go (renamed from Godeps/_workspace/src/code.google.com/p/log4go/filelog.go)53
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/log4go.go (renamed from Godeps/_workspace/src/code.google.com/p/log4go/log4go.go)32
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/pattlog.go (renamed from Godeps/_workspace/src/code.google.com/p/log4go/pattlog.go)8
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/socklog.go (renamed from Godeps/_workspace/src/code.google.com/p/log4go/socklog.go)0
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/termlog.go49
-rw-r--r--Godeps/_workspace/src/github.com/alecthomas/log4go/wrapper.go (renamed from Godeps/_workspace/src/code.google.com/p/log4go/wrapper.go)10
-rw-r--r--Makefile10
-rw-r--r--api/admin.go2
-rw-r--r--api/api.go2
-rw-r--r--api/channel.go23
-rw-r--r--api/channel_benchmark_test.go2
-rw-r--r--api/channel_test.go48
-rw-r--r--api/command.go2
-rw-r--r--api/context.go2
-rw-r--r--api/file.go2
-rw-r--r--api/import.go2
-rw-r--r--api/oauth.go2
-rw-r--r--api/post.go2
-rw-r--r--api/preference.go2
-rw-r--r--api/server.go2
-rw-r--r--api/slackimport.go2
-rw-r--r--api/team.go2
-rw-r--r--api/user.go4
-rw-r--r--api/web_conn.go2
-rw-r--r--api/web_hub.go2
-rw-r--r--api/web_socket.go2
-rw-r--r--api/web_team_hub.go2
-rw-r--r--api/webhook.go2
-rw-r--r--config/config.json8
-rw-r--r--doc/developer/tests/test-attachments.md27
-rw-r--r--doc/developer/tests/test-markdown-basics.md14
-rw-r--r--doc/install/Upgrade-Guide.md2
-rw-r--r--docker/1.4/Dockerfile49
-rw-r--r--docker/1.4/Dockerrun.aws.zipbin0 -> 1043 bytes
-rw-r--r--docker/1.4/Dockerrun.aws/.ebextensions/01_files.config14
-rwxr-xr-xdocker/1.4/Dockerrun.aws/Dockerrun.aws.json13
-rw-r--r--docker/1.4/README.md23
-rw-r--r--docker/1.4/config_docker.json111
-rwxr-xr-xdocker/1.4/docker-entry.sh111
-rw-r--r--manualtesting/manual_testing.go2
-rw-r--r--manualtesting/test_autolink.go2
-rw-r--r--mattermost.go2
-rw-r--r--model/channel.go4
-rw-r--r--model/client.go6
-rw-r--r--model/version.go1
-rw-r--r--store/sql_channel_store.go9
-rw-r--r--store/sql_preference_store.go2
-rw-r--r--store/sql_session_store.go2
-rw-r--r--store/sql_store.go2
-rw-r--r--store/store.go2
-rw-r--r--utils/config.go6
-rw-r--r--utils/mail.go2
-rw-r--r--web/react/components/admin_console/email_settings.jsx4
-rw-r--r--web/react/components/channel_invite_modal.jsx31
-rw-r--r--web/react/components/channel_members_modal.jsx49
-rw-r--r--web/react/components/file_attachment.jsx2
-rw-r--r--web/react/components/file_upload.jsx29
-rw-r--r--web/react/components/invite_member_modal.jsx10
-rw-r--r--web/react/components/post_info.jsx2
-rw-r--r--web/react/components/posts_view.jsx7
-rw-r--r--web/react/utils/async_client.jsx3
-rw-r--r--web/react/utils/client.jsx11
-rw-r--r--web/react/utils/utils.jsx15
-rw-r--r--web/sass-files/sass/partials/_post.scss27
-rw-r--r--web/sass-files/sass/partials/_post_right.scss1
-rw-r--r--web/templates/head.html2
-rw-r--r--web/web.go6
82 files changed, 728 insertions, 756 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 42b485742..5849b30b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -112,9 +112,12 @@ The following options are not present in the System Console, and can be modified
- Searching for a phrase in quotations returns more than just the phrase on Mattermost installations with a Postgres database
- Deleted/Archived channels are not removed from the "More" menu of the person that deleted/archived the channel until after refresh
- Search results don't highlight searches for @username, non-latin characters, or terms inside Markdown code blocks
+- Searching for a username or hashtag containing a dot returns a search where the dot is replaced with the "or" operator
- Hashtags less than three characters long are not searchable
- After deactivating a team member, the person remains in the channel counter
- Certain symbols (<,>,-,+,=,%,^,#,*,|) directly before or after a hashtag cause the message to not show up in a hashtag search
+- Security tab > Active Sessions reports iOS devices as "unknown"
+- Getting a permalink for the second message or later consecutively sent in a group by the same author displaces the copy link popover or causes an error
#### Contributors
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index bd2392f90..d9c4a61bd 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -3,9 +3,8 @@
"GoVersion": "go1.5.1",
"Deps": [
{
- "ImportPath": "code.google.com/p/log4go",
- "Comment": "go.weekly.2012-02-22-1",
- "Rev": "c3294304d93f48a37d3bed1d382882a9c2989f99"
+ "ImportPath": "github.com/alecthomas/log4go",
+ "Rev": "8e9057c3b25c409a34c0b9737cdc82cbcafeabce"
},
{
"ImportPath": "github.com/braintree/manners",
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/.hgtags b/Godeps/_workspace/src/code.google.com/p/log4go/.hgtags
deleted file mode 100644
index 72a2eea2c..000000000
--- a/Godeps/_workspace/src/code.google.com/p/log4go/.hgtags
+++ /dev/null
@@ -1,4 +0,0 @@
-4fbe6aadba231e838a449d340e43bdaab0bf85bd go.weekly.2012-02-07
-56168fd53249d639c25c74ced881fffb20d27be9 go.weekly.2012-02-22
-56168fd53249d639c25c74ced881fffb20d27be9 go.weekly.2012-02-22
-5c22fbd77d91f54d76cdbdee05318699754c44cc go.weekly.2012-02-22
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/log4go_test.go b/Godeps/_workspace/src/code.google.com/p/log4go/log4go_test.go
deleted file mode 100644
index 90c629977..000000000
--- a/Godeps/_workspace/src/code.google.com/p/log4go/log4go_test.go
+++ /dev/null
@@ -1,534 +0,0 @@
-// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved.
-
-package log4go
-
-import (
- "crypto/md5"
- "encoding/hex"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "runtime"
- "testing"
- "time"
-)
-
-const testLogFile = "_logtest.log"
-
-var now time.Time = time.Unix(0, 1234567890123456789).In(time.UTC)
-
-func newLogRecord(lvl level, src string, msg string) *LogRecord {
- return &LogRecord{
- Level: lvl,
- Source: src,
- Created: now,
- Message: msg,
- }
-}
-
-func TestELog(t *testing.T) {
- fmt.Printf("Testing %s\n", L4G_VERSION)
- lr := newLogRecord(CRITICAL, "source", "message")
- if lr.Level != CRITICAL {
- t.Errorf("Incorrect level: %d should be %d", lr.Level, CRITICAL)
- }
- if lr.Source != "source" {
- t.Errorf("Incorrect source: %s should be %s", lr.Source, "source")
- }
- if lr.Message != "message" {
- t.Errorf("Incorrect message: %s should be %s", lr.Source, "message")
- }
-}
-
-var formatTests = []struct {
- Test string
- Record *LogRecord
- Formats map[string]string
-}{
- {
- Test: "Standard formats",
- Record: &LogRecord{
- Level: ERROR,
- Source: "source",
- Message: "message",
- Created: now,
- },
- Formats: map[string]string{
- // TODO(kevlar): How can I do this so it'll work outside of PST?
- FORMAT_DEFAULT: "[2009/02/13 23:31:30 UTC] [EROR] (source) message\n",
- FORMAT_SHORT: "[23:31 02/13/09] [EROR] message\n",
- FORMAT_ABBREV: "[EROR] message\n",
- },
- },
-}
-
-func TestFormatLogRecord(t *testing.T) {
- for _, test := range formatTests {
- name := test.Test
- for fmt, want := range test.Formats {
- if got := FormatLogRecord(fmt, test.Record); got != want {
- t.Errorf("%s - %s:", name, fmt)
- t.Errorf(" got %q", got)
- t.Errorf(" want %q", want)
- }
- }
- }
-}
-
-var logRecordWriteTests = []struct {
- Test string
- Record *LogRecord
- Console string
-}{
- {
- Test: "Normal message",
- Record: &LogRecord{
- Level: CRITICAL,
- Source: "source",
- Message: "message",
- Created: now,
- },
- Console: "[02/13/09 23:31:30] [CRIT] message\n",
- },
-}
-
-func TestConsoleLogWriter(t *testing.T) {
- console := make(ConsoleLogWriter)
-
- r, w := io.Pipe()
- go console.run(w)
- defer console.Close()
-
- buf := make([]byte, 1024)
-
- for _, test := range logRecordWriteTests {
- name := test.Test
-
- console.LogWrite(test.Record)
- n, _ := r.Read(buf)
-
- if got, want := string(buf[:n]), test.Console; got != want {
- t.Errorf("%s: got %q", name, got)
- t.Errorf("%s: want %q", name, want)
- }
- }
-}
-
-func TestFileLogWriter(t *testing.T) {
- defer func(buflen int) {
- LogBufferLength = buflen
- }(LogBufferLength)
- LogBufferLength = 0
-
- w := NewFileLogWriter(testLogFile, false)
- if w == nil {
- t.Fatalf("Invalid return: w should not be nil")
- }
- defer os.Remove(testLogFile)
-
- w.LogWrite(newLogRecord(CRITICAL, "source", "message"))
- w.Close()
- runtime.Gosched()
-
- if contents, err := ioutil.ReadFile(testLogFile); err != nil {
- t.Errorf("read(%q): %s", testLogFile, err)
- } else if len(contents) != 50 {
- t.Errorf("malformed filelog: %q (%d bytes)", string(contents), len(contents))
- }
-}
-
-func TestXMLLogWriter(t *testing.T) {
- defer func(buflen int) {
- LogBufferLength = buflen
- }(LogBufferLength)
- LogBufferLength = 0
-
- w := NewXMLLogWriter(testLogFile, false)
- if w == nil {
- t.Fatalf("Invalid return: w should not be nil")
- }
- defer os.Remove(testLogFile)
-
- w.LogWrite(newLogRecord(CRITICAL, "source", "message"))
- w.Close()
- runtime.Gosched()
-
- if contents, err := ioutil.ReadFile(testLogFile); err != nil {
- t.Errorf("read(%q): %s", testLogFile, err)
- } else if len(contents) != 185 {
- t.Errorf("malformed xmllog: %q (%d bytes)", string(contents), len(contents))
- }
-}
-
-func TestLogger(t *testing.T) {
- sl := NewDefaultLogger(WARNING)
- if sl == nil {
- t.Fatalf("NewDefaultLogger should never return nil")
- }
- if lw, exist := sl["stdout"]; lw == nil || exist != true {
- t.Fatalf("NewDefaultLogger produced invalid logger (DNE or nil)")
- }
- if sl["stdout"].Level != WARNING {
- t.Fatalf("NewDefaultLogger produced invalid logger (incorrect level)")
- }
- if len(sl) != 1 {
- t.Fatalf("NewDefaultLogger produced invalid logger (incorrect map count)")
- }
-
- //func (l *Logger) AddFilter(name string, level int, writer LogWriter) {}
- l := make(Logger)
- l.AddFilter("stdout", DEBUG, NewConsoleLogWriter())
- if lw, exist := l["stdout"]; lw == nil || exist != true {
- t.Fatalf("AddFilter produced invalid logger (DNE or nil)")
- }
- if l["stdout"].Level != DEBUG {
- t.Fatalf("AddFilter produced invalid logger (incorrect level)")
- }
- if len(l) != 1 {
- t.Fatalf("AddFilter produced invalid logger (incorrect map count)")
- }
-
- //func (l *Logger) Warn(format string, args ...interface{}) error {}
- if err := l.Warn("%s %d %#v", "Warning:", 1, []int{}); err.Error() != "Warning: 1 []int{}" {
- t.Errorf("Warn returned invalid error: %s", err)
- }
-
- //func (l *Logger) Error(format string, args ...interface{}) error {}
- if err := l.Error("%s %d %#v", "Error:", 10, []string{}); err.Error() != "Error: 10 []string{}" {
- t.Errorf("Error returned invalid error: %s", err)
- }
-
- //func (l *Logger) Critical(format string, args ...interface{}) error {}
- if err := l.Critical("%s %d %#v", "Critical:", 100, []int64{}); err.Error() != "Critical: 100 []int64{}" {
- t.Errorf("Critical returned invalid error: %s", err)
- }
-
- // Already tested or basically untestable
- //func (l *Logger) Log(level int, source, message string) {}
- //func (l *Logger) Logf(level int, format string, args ...interface{}) {}
- //func (l *Logger) intLogf(level int, format string, args ...interface{}) string {}
- //func (l *Logger) Finest(format string, args ...interface{}) {}
- //func (l *Logger) Fine(format string, args ...interface{}) {}
- //func (l *Logger) Debug(format string, args ...interface{}) {}
- //func (l *Logger) Trace(format string, args ...interface{}) {}
- //func (l *Logger) Info(format string, args ...interface{}) {}
-}
-
-func TestLogOutput(t *testing.T) {
- const (
- expected = "fdf3e51e444da56b4cb400f30bc47424"
- )
-
- // Unbuffered output
- defer func(buflen int) {
- LogBufferLength = buflen
- }(LogBufferLength)
- LogBufferLength = 0
-
- l := make(Logger)
-
- // Delete and open the output log without a timestamp (for a constant md5sum)
- l.AddFilter("file", FINEST, NewFileLogWriter(testLogFile, false).SetFormat("[%L] %M"))
- defer os.Remove(testLogFile)
-
- // Send some log messages
- l.Log(CRITICAL, "testsrc1", fmt.Sprintf("This message is level %d", int(CRITICAL)))
- l.Logf(ERROR, "This message is level %v", ERROR)
- l.Logf(WARNING, "This message is level %s", WARNING)
- l.Logc(INFO, func() string { return "This message is level INFO" })
- l.Trace("This message is level %d", int(TRACE))
- l.Debug("This message is level %s", DEBUG)
- l.Fine(func() string { return fmt.Sprintf("This message is level %v", FINE) })
- l.Finest("This message is level %v", FINEST)
- l.Finest(FINEST, "is also this message's level")
-
- l.Close()
-
- contents, err := ioutil.ReadFile(testLogFile)
- if err != nil {
- t.Fatalf("Could not read output log: %s", err)
- }
-
- sum := md5.New()
- sum.Write(contents)
- if sumstr := hex.EncodeToString(sum.Sum(nil)); sumstr != expected {
- t.Errorf("--- Log Contents:\n%s---", string(contents))
- t.Fatalf("Checksum does not match: %s (expecting %s)", sumstr, expected)
- }
-}
-
-func TestCountMallocs(t *testing.T) {
- const N = 1
- var m runtime.MemStats
- getMallocs := func() uint64 {
- runtime.ReadMemStats(&m)
- return m.Mallocs
- }
-
- // Console logger
- sl := NewDefaultLogger(INFO)
- mallocs := 0 - getMallocs()
- for i := 0; i < N; i++ {
- sl.Log(WARNING, "here", "This is a WARNING message")
- }
- mallocs += getMallocs()
- fmt.Printf("mallocs per sl.Log((WARNING, \"here\", \"This is a log message\"): %d\n", mallocs/N)
-
- // Console logger formatted
- mallocs = 0 - getMallocs()
- for i := 0; i < N; i++ {
- sl.Logf(WARNING, "%s is a log message with level %d", "This", WARNING)
- }
- mallocs += getMallocs()
- fmt.Printf("mallocs per sl.Logf(WARNING, \"%%s is a log message with level %%d\", \"This\", WARNING): %d\n", mallocs/N)
-
- // Console logger (not logged)
- sl = NewDefaultLogger(INFO)
- mallocs = 0 - getMallocs()
- for i := 0; i < N; i++ {
- sl.Log(DEBUG, "here", "This is a DEBUG log message")
- }
- mallocs += getMallocs()
- fmt.Printf("mallocs per unlogged sl.Log((WARNING, \"here\", \"This is a log message\"): %d\n", mallocs/N)
-
- // Console logger formatted (not logged)
- mallocs = 0 - getMallocs()
- for i := 0; i < N; i++ {
- sl.Logf(DEBUG, "%s is a log message with level %d", "This", DEBUG)
- }
- mallocs += getMallocs()
- fmt.Printf("mallocs per unlogged sl.Logf(WARNING, \"%%s is a log message with level %%d\", \"This\", WARNING): %d\n", mallocs/N)
-}
-
-func TestXMLConfig(t *testing.T) {
- const (
- configfile = "example.xml"
- )
-
- fd, err := os.Create(configfile)
- if err != nil {
- t.Fatalf("Could not open %s for writing: %s", configfile, err)
- }
-
- fmt.Fprintln(fd, "<logging>")
- fmt.Fprintln(fd, " <filter enabled=\"true\">")
- fmt.Fprintln(fd, " <tag>stdout</tag>")
- fmt.Fprintln(fd, " <type>console</type>")
- fmt.Fprintln(fd, " <!-- level is (:?FINEST|FINE|DEBUG|TRACE|INFO|WARNING|ERROR) -->")
- fmt.Fprintln(fd, " <level>DEBUG</level>")
- fmt.Fprintln(fd, " </filter>")
- fmt.Fprintln(fd, " <filter enabled=\"true\">")
- fmt.Fprintln(fd, " <tag>file</tag>")
- fmt.Fprintln(fd, " <type>file</type>")
- fmt.Fprintln(fd, " <level>FINEST</level>")
- fmt.Fprintln(fd, " <property name=\"filename\">test.log</property>")
- fmt.Fprintln(fd, " <!--")
- fmt.Fprintln(fd, " %T - Time (15:04:05 MST)")
- fmt.Fprintln(fd, " %t - Time (15:04)")
- fmt.Fprintln(fd, " %D - Date (2006/01/02)")
- fmt.Fprintln(fd, " %d - Date (01/02/06)")
- fmt.Fprintln(fd, " %L - Level (FNST, FINE, DEBG, TRAC, WARN, EROR, CRIT)")
- fmt.Fprintln(fd, " %S - Source")
- fmt.Fprintln(fd, " %M - Message")
- fmt.Fprintln(fd, " It ignores unknown format strings (and removes them)")
- fmt.Fprintln(fd, " Recommended: \"[%D %T] [%L] (%S) %M\"")
- fmt.Fprintln(fd, " -->")
- fmt.Fprintln(fd, " <property name=\"format\">[%D %T] [%L] (%S) %M</property>")
- fmt.Fprintln(fd, " <property name=\"rotate\">false</property> <!-- true enables log rotation, otherwise append -->")
- fmt.Fprintln(fd, " <property name=\"maxsize\">0M</property> <!-- \\d+[KMG]? Suffixes are in terms of 2**10 -->")
- fmt.Fprintln(fd, " <property name=\"maxlines\">0K</property> <!-- \\d+[KMG]? Suffixes are in terms of thousands -->")
- fmt.Fprintln(fd, " <property name=\"daily\">true</property> <!-- Automatically rotates when a log message is written after midnight -->")
- fmt.Fprintln(fd, " </filter>")
- fmt.Fprintln(fd, " <filter enabled=\"true\">")
- fmt.Fprintln(fd, " <tag>xmllog</tag>")
- fmt.Fprintln(fd, " <type>xml</type>")
- fmt.Fprintln(fd, " <level>TRACE</level>")
- fmt.Fprintln(fd, " <property name=\"filename\">trace.xml</property>")
- fmt.Fprintln(fd, " <property name=\"rotate\">true</property> <!-- true enables log rotation, otherwise append -->")
- fmt.Fprintln(fd, " <property name=\"maxsize\">100M</property> <!-- \\d+[KMG]? Suffixes are in terms of 2**10 -->")
- fmt.Fprintln(fd, " <property name=\"maxrecords\">6K</property> <!-- \\d+[KMG]? Suffixes are in terms of thousands -->")
- fmt.Fprintln(fd, " <property name=\"daily\">false</property> <!-- Automatically rotates when a log message is written after midnight -->")
- fmt.Fprintln(fd, " </filter>")
- fmt.Fprintln(fd, " <filter enabled=\"false\"><!-- enabled=false means this logger won't actually be created -->")
- fmt.Fprintln(fd, " <tag>donotopen</tag>")
- fmt.Fprintln(fd, " <type>socket</type>")
- fmt.Fprintln(fd, " <level>FINEST</level>")
- fmt.Fprintln(fd, " <property name=\"endpoint\">192.168.1.255:12124</property> <!-- recommend UDP broadcast -->")
- fmt.Fprintln(fd, " <property name=\"protocol\">udp</property> <!-- tcp or udp -->")
- fmt.Fprintln(fd, " </filter>")
- fmt.Fprintln(fd, "</logging>")
- fd.Close()
-
- log := make(Logger)
- log.LoadConfiguration(configfile)
- defer os.Remove("trace.xml")
- defer os.Remove("test.log")
- defer log.Close()
-
- // Make sure we got all loggers
- if len(log) != 3 {
- t.Fatalf("XMLConfig: Expected 3 filters, found %d", len(log))
- }
-
- // Make sure they're the right keys
- if _, ok := log["stdout"]; !ok {
- t.Errorf("XMLConfig: Expected stdout logger")
- }
- if _, ok := log["file"]; !ok {
- t.Fatalf("XMLConfig: Expected file logger")
- }
- if _, ok := log["xmllog"]; !ok {
- t.Fatalf("XMLConfig: Expected xmllog logger")
- }
-
- // Make sure they're the right type
- if _, ok := log["stdout"].LogWriter.(ConsoleLogWriter); !ok {
- t.Fatalf("XMLConfig: Expected stdout to be ConsoleLogWriter, found %T", log["stdout"].LogWriter)
- }
- if _, ok := log["file"].LogWriter.(*FileLogWriter); !ok {
- t.Fatalf("XMLConfig: Expected file to be *FileLogWriter, found %T", log["file"].LogWriter)
- }
- if _, ok := log["xmllog"].LogWriter.(*FileLogWriter); !ok {
- t.Fatalf("XMLConfig: Expected xmllog to be *FileLogWriter, found %T", log["xmllog"].LogWriter)
- }
-
- // Make sure levels are set
- if lvl := log["stdout"].Level; lvl != DEBUG {
- t.Errorf("XMLConfig: Expected stdout to be set to level %d, found %d", DEBUG, lvl)
- }
- if lvl := log["file"].Level; lvl != FINEST {
- t.Errorf("XMLConfig: Expected file to be set to level %d, found %d", FINEST, lvl)
- }
- if lvl := log["xmllog"].Level; lvl != TRACE {
- t.Errorf("XMLConfig: Expected xmllog to be set to level %d, found %d", TRACE, lvl)
- }
-
- // Make sure the w is open and points to the right file
- if fname := log["file"].LogWriter.(*FileLogWriter).file.Name(); fname != "test.log" {
- t.Errorf("XMLConfig: Expected file to have opened %s, found %s", "test.log", fname)
- }
-
- // Make sure the XLW is open and points to the right file
- if fname := log["xmllog"].LogWriter.(*FileLogWriter).file.Name(); fname != "trace.xml" {
- t.Errorf("XMLConfig: Expected xmllog to have opened %s, found %s", "trace.xml", fname)
- }
-
- // Move XML log file
- os.Rename(configfile, "examples/"+configfile) // Keep this so that an example with the documentation is available
-}
-
-func BenchmarkFormatLogRecord(b *testing.B) {
- const updateEvery = 1
- rec := &LogRecord{
- Level: CRITICAL,
- Created: now,
- Source: "source",
- Message: "message",
- }
- for i := 0; i < b.N; i++ {
- rec.Created = rec.Created.Add(1 * time.Second / updateEvery)
- if i%2 == 0 {
- FormatLogRecord(FORMAT_DEFAULT, rec)
- } else {
- FormatLogRecord(FORMAT_SHORT, rec)
- }
- }
-}
-
-func BenchmarkConsoleLog(b *testing.B) {
- /* This doesn't seem to work on OS X
- sink, err := os.Open(os.DevNull)
- if err != nil {
- panic(err)
- }
- if err := syscall.Dup2(int(sink.Fd()), syscall.Stdout); err != nil {
- panic(err)
- }
- */
-
- stdout = ioutil.Discard
- sl := NewDefaultLogger(INFO)
- for i := 0; i < b.N; i++ {
- sl.Log(WARNING, "here", "This is a log message")
- }
-}
-
-func BenchmarkConsoleNotLogged(b *testing.B) {
- sl := NewDefaultLogger(INFO)
- for i := 0; i < b.N; i++ {
- sl.Log(DEBUG, "here", "This is a log message")
- }
-}
-
-func BenchmarkConsoleUtilLog(b *testing.B) {
- sl := NewDefaultLogger(INFO)
- for i := 0; i < b.N; i++ {
- sl.Info("%s is a log message", "This")
- }
-}
-
-func BenchmarkConsoleUtilNotLog(b *testing.B) {
- sl := NewDefaultLogger(INFO)
- for i := 0; i < b.N; i++ {
- sl.Debug("%s is a log message", "This")
- }
-}
-
-func BenchmarkFileLog(b *testing.B) {
- sl := make(Logger)
- b.StopTimer()
- sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false))
- b.StartTimer()
- for i := 0; i < b.N; i++ {
- sl.Log(WARNING, "here", "This is a log message")
- }
- b.StopTimer()
- os.Remove("benchlog.log")
-}
-
-func BenchmarkFileNotLogged(b *testing.B) {
- sl := make(Logger)
- b.StopTimer()
- sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false))
- b.StartTimer()
- for i := 0; i < b.N; i++ {
- sl.Log(DEBUG, "here", "This is a log message")
- }
- b.StopTimer()
- os.Remove("benchlog.log")
-}
-
-func BenchmarkFileUtilLog(b *testing.B) {
- sl := make(Logger)
- b.StopTimer()
- sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false))
- b.StartTimer()
- for i := 0; i < b.N; i++ {
- sl.Info("%s is a log message", "This")
- }
- b.StopTimer()
- os.Remove("benchlog.log")
-}
-
-func BenchmarkFileUtilNotLog(b *testing.B) {
- sl := make(Logger)
- b.StopTimer()
- sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false))
- b.StartTimer()
- for i := 0; i < b.N; i++ {
- sl.Debug("%s is a log message", "This")
- }
- b.StopTimer()
- os.Remove("benchlog.log")
-}
-
-// Benchmark results (darwin amd64 6g)
-//elog.BenchmarkConsoleLog 100000 22819 ns/op
-//elog.BenchmarkConsoleNotLogged 2000000 879 ns/op
-//elog.BenchmarkConsoleUtilLog 50000 34380 ns/op
-//elog.BenchmarkConsoleUtilNotLog 1000000 1339 ns/op
-//elog.BenchmarkFileLog 100000 26497 ns/op
-//elog.BenchmarkFileNotLogged 2000000 821 ns/op
-//elog.BenchmarkFileUtilLog 50000 33945 ns/op
-//elog.BenchmarkFileUtilNotLog 1000000 1258 ns/op
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/termlog.go b/Godeps/_workspace/src/code.google.com/p/log4go/termlog.go
deleted file mode 100644
index 1ed2e4e0d..000000000
--- a/Godeps/_workspace/src/code.google.com/p/log4go/termlog.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved.
-
-package log4go
-
-import (
- "io"
- "os"
- "fmt"
-)
-
-var stdout io.Writer = os.Stdout
-
-// This is the standard writer that prints to standard output.
-type ConsoleLogWriter chan *LogRecord
-
-// This creates a new ConsoleLogWriter
-func NewConsoleLogWriter() ConsoleLogWriter {
- records := make(ConsoleLogWriter, LogBufferLength)
- go records.run(stdout)
- return records
-}
-
-func (w ConsoleLogWriter) run(out io.Writer) {
- var timestr string
- var timestrAt int64
-
- for rec := range w {
- if at := rec.Created.UnixNano() / 1e9; at != timestrAt {
- timestr, timestrAt = rec.Created.Format("01/02/06 15:04:05"), at
- }
- fmt.Fprint(out, "[", timestr, "] [", levelStrings[rec.Level], "] ", rec.Message, "\n")
- }
-}
-
-// This is the ConsoleLogWriter's output method. This will block if the output
-// buffer is full.
-func (w ConsoleLogWriter) LogWrite(rec *LogRecord) {
- w <- rec
-}
-
-// Close stops the logger from sending messages to standard output. Attempts to
-// send log messages to this logger after a Close have undefined behavior.
-func (w ConsoleLogWriter) Close() {
- close(w)
-}
diff --git a/Godeps/_workspace/src/github.com/alecthomas/log4go/.gitignore b/Godeps/_workspace/src/github.com/alecthomas/log4go/.gitignore
new file mode 100644
index 000000000..f6207cd8a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/.gitignore
@@ -0,0 +1,2 @@
+*.sw[op]
+.DS_Store
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/LICENSE b/Godeps/_workspace/src/github.com/alecthomas/log4go/LICENSE
index 7093402bf..7093402bf 100644
--- a/Godeps/_workspace/src/code.google.com/p/log4go/LICENSE
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/LICENSE
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/README b/Godeps/_workspace/src/github.com/alecthomas/log4go/README
index 16d80ecb7..16d80ecb7 100644
--- a/Godeps/_workspace/src/code.google.com/p/log4go/README
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/README
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/config.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/config.go
index f048b69f5..577c3eb2f 100644
--- a/Godeps/_workspace/src/code.google.com/p/log4go/config.go
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/config.go
@@ -53,7 +53,7 @@ func (log Logger) LoadConfiguration(filename string) {
for _, xmlfilt := range xc.Filter {
var filt LogWriter
- var lvl level
+ var lvl Level
bad, good, enabled := false, true, false
// Check required children
@@ -131,7 +131,7 @@ func (log Logger) LoadConfiguration(filename string) {
}
}
-func xmlToConsoleLogWriter(filename string, props []xmlProperty, enabled bool) (ConsoleLogWriter, bool) {
+func xmlToConsoleLogWriter(filename string, props []xmlProperty, enabled bool) (*ConsoleLogWriter, bool) {
// Parse properties
for _, prop := range props {
switch prop.Name {
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/examples/ConsoleLogWriter_Manual.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/ConsoleLogWriter_Manual.go
index 394ca8380..698dd332d 100644
--- a/Godeps/_workspace/src/code.google.com/p/log4go/examples/ConsoleLogWriter_Manual.go
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/ConsoleLogWriter_Manual.go
@@ -8,6 +8,7 @@ import l4g "code.google.com/p/log4go"
func main() {
log := l4g.NewLogger()
+ defer log.Close()
log.AddFilter("stdout", l4g.DEBUG, l4g.NewConsoleLogWriter())
log.Info("The time is now: %s", time.Now().Format("15:04:05 MST 2006/01/02"))
}
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/examples/FileLogWriter_Manual.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/FileLogWriter_Manual.go
index efd596aa6..efd596aa6 100644
--- a/Godeps/_workspace/src/code.google.com/p/log4go/examples/FileLogWriter_Manual.go
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/FileLogWriter_Manual.go
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/examples/SimpleNetLogServer.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/SimpleNetLogServer.go
index 83c80ad12..83c80ad12 100644
--- a/Godeps/_workspace/src/code.google.com/p/log4go/examples/SimpleNetLogServer.go
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/SimpleNetLogServer.go
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/examples/SocketLogWriter_Manual.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/SocketLogWriter_Manual.go
index 400b698ca..400b698ca 100644
--- a/Godeps/_workspace/src/code.google.com/p/log4go/examples/SocketLogWriter_Manual.go
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/SocketLogWriter_Manual.go
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/examples/XMLConfigurationExample.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/XMLConfigurationExample.go
index 164c2add4..164c2add4 100644
--- a/Godeps/_workspace/src/code.google.com/p/log4go/examples/XMLConfigurationExample.go
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/XMLConfigurationExample.go
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/examples/example.xml b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/example.xml
index e791278ce..e791278ce 100644
--- a/Godeps/_workspace/src/code.google.com/p/log4go/examples/example.xml
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/examples/example.xml
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/filelog.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/filelog.go
index 9cbd815d9..ee0ab0c04 100644
--- a/Godeps/_workspace/src/code.google.com/p/log4go/filelog.go
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/filelog.go
@@ -3,8 +3,8 @@
package log4go
import (
- "os"
"fmt"
+ "os"
"time"
)
@@ -36,7 +36,8 @@ type FileLogWriter struct {
daily_opendate int
// Keep old logfiles (.001, .002, etc)
- rotate bool
+ rotate bool
+ maxbackup int
}
// This is the FileLogWriter's output method
@@ -46,6 +47,7 @@ func (w *FileLogWriter) LogWrite(rec *LogRecord) {
func (w *FileLogWriter) Close() {
close(w.rec)
+ w.file.Sync()
}
// NewFileLogWriter creates a new LogWriter which writes to the given file and
@@ -59,11 +61,12 @@ func (w *FileLogWriter) Close() {
// [%D %T] [%L] (%S) %M
func NewFileLogWriter(fname string, rotate bool) *FileLogWriter {
w := &FileLogWriter{
- rec: make(chan *LogRecord, LogBufferLength),
- rot: make(chan bool),
- filename: fname,
- format: "[%D %T] [%L] (%S) %M",
- rotate: rotate,
+ rec: make(chan *LogRecord, LogBufferLength),
+ rot: make(chan bool),
+ filename: fname,
+ format: "[%D %T] [%L] (%S) %M",
+ rotate: rotate,
+ maxbackup: 999,
}
// open the file for the first time
@@ -138,15 +141,30 @@ func (w *FileLogWriter) intRotate() error {
// Find the next available number
num := 1
fname := ""
- for ; err == nil && num <= 999; num++ {
- fname = w.filename + fmt.Sprintf(".%03d", num)
- _, err = os.Lstat(fname)
- }
- // return error if the last file checked still existed
- if err == nil {
- return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.filename)
+ if w.daily && time.Now().Day() != w.daily_opendate {
+ yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
+
+ for ; err == nil && num <= 999; num++ {
+ fname = w.filename + fmt.Sprintf(".%s.%03d", yesterday, num)
+ _, err = os.Lstat(fname)
+ }
+ // return error if the last file checked still existed
+ if err == nil {
+ return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.filename)
+ }
+ } else {
+ num = w.maxbackup - 1
+ for ; num >= 1; num-- {
+ fname = w.filename + fmt.Sprintf(".%d", num)
+ nfname := w.filename + fmt.Sprintf(".%d", num+1)
+ _, err = os.Lstat(fname)
+ if err == nil {
+ os.Rename(fname, nfname)
+ }
+ }
}
+ w.file.Close()
// Rename the file to its newfound home
err = os.Rename(w.filename, fname)
if err != nil {
@@ -217,6 +235,13 @@ func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter {
return w
}
+// Set max backup files. Must be called before the first log message
+// is written.
+func (w *FileLogWriter) SetRotateMaxBackup(maxbackup int) *FileLogWriter {
+ w.maxbackup = maxbackup
+ return w
+}
+
// SetRotate changes whether or not the old logs are kept. (chainable) Must be
// called before the first log message is written. If rotate is false, the
// files are overwritten; otherwise, they are rotated to another file before the
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/log4go.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/log4go.go
index ab4e857f5..822e890cc 100644
--- a/Godeps/_workspace/src/code.google.com/p/log4go/log4go.go
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/log4go.go
@@ -47,11 +47,11 @@ package log4go
import (
"errors"
- "os"
"fmt"
- "time"
- "strings"
+ "os"
"runtime"
+ "strings"
+ "time"
)
// Version information
@@ -65,10 +65,10 @@ const (
/****** Constants ******/
// These are the integer logging levels used by the logger
-type level int
+type Level int
const (
- FINEST level = iota
+ FINEST Level = iota
FINE
DEBUG
TRACE
@@ -83,7 +83,7 @@ var (
levelStrings = [...]string{"FNST", "FINE", "DEBG", "TRAC", "INFO", "WARN", "EROR", "CRIT"}
)
-func (l level) String() string {
+func (l Level) String() string {
if l < 0 || int(l) > len(levelStrings) {
return "UNKNOWN"
}
@@ -101,7 +101,7 @@ var (
// A LogRecord contains all of the pertinent information for each message
type LogRecord struct {
- Level level // The log level
+ Level Level // The log level
Created time.Time // The time at which the log message was created (nanoseconds)
Source string // The message source
Message string // The log message
@@ -124,7 +124,7 @@ type LogWriter interface {
// A Filter represents the log level below which no log records are written to
// the associated LogWriter.
type Filter struct {
- Level level
+ Level Level
LogWriter
}
@@ -144,7 +144,7 @@ func NewLogger() Logger {
// or above lvl to standard output.
//
// DEPRECATED: use NewDefaultLogger instead.
-func NewConsoleLogger(lvl level) Logger {
+func NewConsoleLogger(lvl Level) Logger {
os.Stderr.WriteString("warning: use of deprecated NewConsoleLogger\n")
return Logger{
"stdout": &Filter{lvl, NewConsoleLogWriter()},
@@ -153,7 +153,7 @@ func NewConsoleLogger(lvl level) Logger {
// Create a new logger with a "stdout" filter configured to send log messages at
// or above lvl to standard output.
-func NewDefaultLogger(lvl level) Logger {
+func NewDefaultLogger(lvl Level) Logger {
return Logger{
"stdout": &Filter{lvl, NewConsoleLogWriter()},
}
@@ -174,14 +174,14 @@ func (log Logger) Close() {
// Add a new LogWriter to the Logger which will only log messages at lvl or
// higher. This function should not be called from multiple goroutines.
// Returns the logger for chaining.
-func (log Logger) AddFilter(name string, lvl level, writer LogWriter) Logger {
+func (log Logger) AddFilter(name string, lvl Level, writer LogWriter) Logger {
log[name] = &Filter{lvl, writer}
return log
}
/******* Logging *******/
// Send a formatted log message internally
-func (log Logger) intLogf(lvl level, format string, args ...interface{}) {
+func (log Logger) intLogf(lvl Level, format string, args ...interface{}) {
skip := true
// Determine if any logging will be done
@@ -225,7 +225,7 @@ func (log Logger) intLogf(lvl level, format string, args ...interface{}) {
}
// Send a closure log message internally
-func (log Logger) intLogc(lvl level, closure func() string) {
+func (log Logger) intLogc(lvl Level, closure func() string) {
skip := true
// Determine if any logging will be done
@@ -264,7 +264,7 @@ func (log Logger) intLogc(lvl level, closure func() string) {
}
// Send a log message with manual level, source, and message.
-func (log Logger) Log(lvl level, source, message string) {
+func (log Logger) Log(lvl Level, source, message string) {
skip := true
// Determine if any logging will be done
@@ -297,13 +297,13 @@ func (log Logger) Log(lvl level, source, message string) {
// Logf logs a formatted log message at the given log level, using the caller as
// its source.
-func (log Logger) Logf(lvl level, format string, args ...interface{}) {
+func (log Logger) Logf(lvl Level, format string, args ...interface{}) {
log.intLogf(lvl, format, args...)
}
// Logc logs a string returned by the closure at the given log level, using the caller as
// its source. If no log message would be written, the closure is never called.
-func (log Logger) Logc(lvl level, closure func() string) {
+func (log Logger) Logc(lvl Level, closure func() string) {
log.intLogc(lvl, closure)
}
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/pattlog.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/pattlog.go
index 8224302b3..82b4e36b1 100644
--- a/Godeps/_workspace/src/code.google.com/p/log4go/pattlog.go
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/pattlog.go
@@ -3,9 +3,10 @@
package log4go
import (
- "fmt"
"bytes"
+ "fmt"
"io"
+ "strings"
)
const (
@@ -51,7 +52,7 @@ func FormatLogRecord(format string, rec *LogRecord) string {
updated := &formatCacheType{
LastUpdateSeconds: secs,
shortTime: fmt.Sprintf("%02d:%02d", hour, minute),
- shortDate: fmt.Sprintf("%02d/%02d/%02d", month, day, year%100),
+ shortDate: fmt.Sprintf("%02d/%02d/%02d", day, month, year%100),
longTime: fmt.Sprintf("%02d:%02d:%02d %s", hour, minute, second, zone),
longDate: fmt.Sprintf("%04d/%02d/%02d", year, month, day),
}
@@ -78,6 +79,9 @@ func FormatLogRecord(format string, rec *LogRecord) string {
out.WriteString(levelStrings[rec.Level])
case 'S':
out.WriteString(rec.Source)
+ case 's':
+ slice := strings.Split(rec.Source, "/")
+ out.WriteString(slice[len(slice)-1])
case 'M':
out.WriteString(rec.Message)
}
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/socklog.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/socklog.go
index 1d224a99d..1d224a99d 100644
--- a/Godeps/_workspace/src/code.google.com/p/log4go/socklog.go
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/socklog.go
diff --git a/Godeps/_workspace/src/github.com/alecthomas/log4go/termlog.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/termlog.go
new file mode 100644
index 000000000..8a941e269
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/termlog.go
@@ -0,0 +1,49 @@
+// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved.
+
+package log4go
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "time"
+)
+
+var stdout io.Writer = os.Stdout
+
+// This is the standard writer that prints to standard output.
+type ConsoleLogWriter struct {
+ format string
+ w chan *LogRecord
+}
+
+// This creates a new ConsoleLogWriter
+func NewConsoleLogWriter() *ConsoleLogWriter {
+ consoleWriter := &ConsoleLogWriter{
+ format: "[%T %D] [%L] (%S) %M",
+ w: make(chan *LogRecord, LogBufferLength),
+ }
+ go consoleWriter.run(stdout)
+ return consoleWriter
+}
+func (c *ConsoleLogWriter) SetFormat(format string) {
+ c.format = format
+}
+func (c *ConsoleLogWriter) run(out io.Writer) {
+ for rec := range c.w {
+ fmt.Fprint(out, FormatLogRecord(c.format, rec))
+ }
+}
+
+// This is the ConsoleLogWriter's output method. This will block if the output
+// buffer is full.
+func (c *ConsoleLogWriter) LogWrite(rec *LogRecord) {
+ c.w <- rec
+}
+
+// Close stops the logger from sending messages to standard output. Attempts to
+// send log messages to this logger after a Close have undefined behavior.
+func (c *ConsoleLogWriter) Close() {
+ close(c.w)
+ time.Sleep(50 * time.Millisecond) // Try to give console I/O time to complete
+}
diff --git a/Godeps/_workspace/src/code.google.com/p/log4go/wrapper.go b/Godeps/_workspace/src/github.com/alecthomas/log4go/wrapper.go
index 10ecd88e6..2ae222b0c 100644
--- a/Godeps/_workspace/src/code.google.com/p/log4go/wrapper.go
+++ b/Godeps/_workspace/src/github.com/alecthomas/log4go/wrapper.go
@@ -4,8 +4,8 @@ package log4go
import (
"errors"
- "os"
"fmt"
+ "os"
"strings"
)
@@ -23,7 +23,7 @@ func LoadConfiguration(filename string) {
}
// Wrapper for (*Logger).AddFilter
-func AddFilter(name string, lvl level, writer LogWriter) {
+func AddFilter(name string, lvl Level, writer LogWriter) {
Global.AddFilter(name, lvl, writer)
}
@@ -88,19 +88,19 @@ func Stdoutf(format string, args ...interface{}) {
// Send a log message manually
// Wrapper for (*Logger).Log
-func Log(lvl level, source, message string) {
+func Log(lvl Level, source, message string) {
Global.Log(lvl, source, message)
}
// Send a formatted log message easily
// Wrapper for (*Logger).Logf
-func Logf(lvl level, format string, args ...interface{}) {
+func Logf(lvl Level, format string, args ...interface{}) {
Global.intLogf(lvl, format, args...)
}
// Send a closure log message
// Wrapper for (*Logger).Logc
-func Logc(lvl level, closure func() string) {
+func Logc(lvl Level, closure func() string) {
Global.intLogc(lvl, closure)
}
diff --git a/Makefile b/Makefile
index 1d7fbea5a..9fd74b959 100644
--- a/Makefile
+++ b/Makefile
@@ -126,10 +126,6 @@ package:
cp -RL web/static/js/jquery-dragster $(DIST_PATH)/web/static/js/
cp -RL web/templates $(DIST_PATH)/web
- cp -L web/static/js/react-0.14.3.js $(DIST_PATH)/web/static/js/
- cp -L web/static/js/react-dom-0.14.3.js $(DIST_PATH)/web/static/js/
- cp -L web/static/js/react-bootstrap-0.28.1.js $(DIST_PATH)/web/static/js/
-
mkdir -p $(DIST_PATH)/api
cp -RL api/templates $(DIST_PATH)/api
@@ -140,11 +136,11 @@ package:
mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js
mv $(DIST_PATH)/web/static/js/libs.min.js $(DIST_PATH)/web/static/js/libs-$(BUILD_NUMBER).min.js
- #sed -i'.bak' 's|react-0.14.3.js|react-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html
- #sed -i'.bak' 's|react-dom-0.14.3.js|react-dom-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html
+ sed -i'.bak' 's|react-0.14.3.js|react-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html
+ sed -i'.bak' 's|react-dom-0.14.3.js|react-dom-0.14.3.min.js|g' $(DIST_PATH)/web/templates/head.html
sed -i'.bak' 's|jquery-2.1.4.js|jquery-2.1.4.min.js|g' $(DIST_PATH)/web/templates/head.html
sed -i'.bak' 's|bootstrap-3.3.5.js|bootstrap-3.3.5.min.js|g' $(DIST_PATH)/web/templates/head.html
- #sed -i'.bak' 's|react-bootstrap-0.28.1.js|react-bootstrap-0.28.1.min.js|g' $(DIST_PATH)/web/templates/head.html
+ sed -i'.bak' 's|react-bootstrap-0.28.1.js|react-bootstrap-0.28.1.min.js|g' $(DIST_PATH)/web/templates/head.html
sed -i'.bak' 's|perfect-scrollbar-0.6.7.jquery.js|perfect-scrollbar-0.6.7.jquery.min.js|g' $(DIST_PATH)/web/templates/head.html
sed -i'.bak' 's|bundle.js|bundle-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html
sed -i'.bak' 's|libs.min.js|libs-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html
diff --git a/api/admin.go b/api/admin.go
index 8e0a03e4b..885a95d95 100644
--- a/api/admin.go
+++ b/api/admin.go
@@ -9,7 +9,7 @@ import (
"os"
"strings"
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
diff --git a/api/api.go b/api/api.go
index 6c7eda0a2..a6bb22982 100644
--- a/api/api.go
+++ b/api/api.go
@@ -5,7 +5,7 @@ package api
import (
"bytes"
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"html/template"
diff --git a/api/channel.go b/api/channel.go
index b85de3071..706baa004 100644
--- a/api/channel.go
+++ b/api/channel.go
@@ -4,14 +4,19 @@
package api
import (
- l4g "code.google.com/p/log4go"
"fmt"
+ l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"net/http"
+ "strconv"
"strings"
)
+const (
+ defaultExtraMemberLimit = 100
+)
+
func InitChannel(r *mux.Router) {
l4g.Debug("Initializing channel api routes")
@@ -27,6 +32,7 @@ func InitChannel(r *mux.Router) {
sr.Handle("/update_notify_props", ApiUserRequired(updateNotifyProps)).Methods("POST")
sr.Handle("/{id:[A-Za-z0-9]+}/", ApiUserRequiredActivity(getChannel, false)).Methods("GET")
sr.Handle("/{id:[A-Za-z0-9]+}/extra_info", ApiUserRequired(getChannelExtraInfo)).Methods("GET")
+ sr.Handle("/{id:[A-Za-z0-9]+}/extra_info/{member_limit:-?[0-9]+}", ApiUserRequired(getChannelExtraInfo)).Methods("GET")
sr.Handle("/{id:[A-Za-z0-9]+}/join", ApiUserRequired(join)).Methods("POST")
sr.Handle("/{id:[A-Za-z0-9]+}/leave", ApiUserRequired(leave)).Methods("POST")
sr.Handle("/{id:[A-Za-z0-9]+}/delete", ApiUserRequired(deleteChannel)).Methods("POST")
@@ -730,10 +736,19 @@ func getChannel(c *Context, w http.ResponseWriter, r *http.Request) {
}
func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) {
-
params := mux.Vars(r)
id := params["id"]
+ var memberLimit int
+ if memberLimitString, ok := params["member_limit"]; !ok {
+ memberLimit = defaultExtraMemberLimit
+ } else if memberLimitInt64, err := strconv.ParseInt(memberLimitString, 10, 0); err != nil {
+ c.Err = model.NewAppError("getChannelExtraInfo", "Failed to parse member limit", err.Error())
+ return
+ } else {
+ memberLimit = int(memberLimitInt64)
+ }
+
sc := Srv.Store.Channel().Get(id)
var channel *model.Channel
if cresult := <-sc; cresult.Err != nil {
@@ -743,13 +758,13 @@ func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) {
channel = cresult.Data.(*model.Channel)
}
- extraEtag := channel.ExtraEtag()
+ extraEtag := channel.ExtraEtag(memberLimit)
if HandleEtag(extraEtag, w, r) {
return
}
scm := Srv.Store.Channel().GetMember(id, c.Session.UserId)
- ecm := Srv.Store.Channel().GetExtraMembers(id, 100)
+ ecm := Srv.Store.Channel().GetExtraMembers(id, memberLimit)
ccm := Srv.Store.Channel().GetMemberCount(id)
if cmresult := <-scm; cmresult.Err != nil {
diff --git a/api/channel_benchmark_test.go b/api/channel_benchmark_test.go
index fb8dd61bc..d6e1e5a55 100644
--- a/api/channel_benchmark_test.go
+++ b/api/channel_benchmark_test.go
@@ -189,7 +189,7 @@ func BenchmarkGetChannelExtraInfo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := range channels {
- Client.Must(Client.GetChannelExtraInfo(channels[j].Id, ""))
+ Client.Must(Client.GetChannelExtraInfo(channels[j].Id, -1, ""))
}
}
}
diff --git a/api/channel_test.go b/api/channel_test.go
index 4ef164cba..117278378 100644
--- a/api/channel_test.go
+++ b/api/channel_test.go
@@ -674,7 +674,7 @@ func TestGetChannelExtraInfo(t *testing.T) {
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
- rget := Client.Must(Client.GetChannelExtraInfo(channel1.Id, ""))
+ rget := Client.Must(Client.GetChannelExtraInfo(channel1.Id, -1, ""))
data := rget.Data.(*model.ChannelExtra)
if data.Id != channel1.Id {
t.Fatal("couldnt't get extra info")
@@ -690,7 +690,7 @@ func TestGetChannelExtraInfo(t *testing.T) {
currentEtag := rget.Etag
- if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, currentEtag); err != nil {
+ if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil {
t.Fatal(err)
} else if cache_result.Data.(*model.ChannelExtra) != nil {
t.Log(cache_result.Data)
@@ -708,7 +708,7 @@ func TestGetChannelExtraInfo(t *testing.T) {
Client2.LoginByEmail(team.Name, user2.Email, "pwd")
Client2.Must(Client2.JoinChannel(channel1.Id))
- if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, currentEtag); err != nil {
+ if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil {
t.Fatal(err)
} else if cache_result.Data.(*model.ChannelExtra) == nil {
t.Log(cache_result.Data)
@@ -717,7 +717,7 @@ func TestGetChannelExtraInfo(t *testing.T) {
currentEtag = cache_result.Etag
}
- if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, currentEtag); err != nil {
+ if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil {
t.Fatal(err)
} else if cache_result.Data.(*model.ChannelExtra) != nil {
t.Log(cache_result.Data)
@@ -728,7 +728,7 @@ func TestGetChannelExtraInfo(t *testing.T) {
Client2.Must(Client2.LeaveChannel(channel1.Id))
- if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, currentEtag); err != nil {
+ if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil {
t.Fatal(err)
} else if cache_result.Data.(*model.ChannelExtra) == nil {
t.Log(cache_result.Data)
@@ -737,7 +737,7 @@ func TestGetChannelExtraInfo(t *testing.T) {
currentEtag = cache_result.Etag
}
- if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, currentEtag); err != nil {
+ if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil {
t.Fatal(err)
} else if cache_result.Data.(*model.ChannelExtra) != nil {
t.Log(cache_result.Data)
@@ -745,6 +745,42 @@ func TestGetChannelExtraInfo(t *testing.T) {
} else {
currentEtag = cache_result.Etag
}
+
+ Client2.Must(Client2.JoinChannel(channel1.Id))
+
+ if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, 2, currentEtag); err != nil {
+ t.Fatal(err)
+ } else if extra := cache_result.Data.(*model.ChannelExtra); extra == nil {
+ t.Fatal("response should not be empty")
+ } else if len(extra.Members) != 2 {
+ t.Fatal("should've returned 2 members")
+ } else if extra.MemberCount != 2 {
+ t.Fatal("should've returned member count of 2")
+ } else {
+ currentEtag = cache_result.Etag
+ }
+
+ if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, 1, currentEtag); err != nil {
+ t.Fatal(err)
+ } else if extra := cache_result.Data.(*model.ChannelExtra); extra == nil {
+ t.Fatal("response should not be empty")
+ } else if len(extra.Members) != 1 {
+ t.Fatal("should've returned only 1 member")
+ } else if extra.MemberCount != 2 {
+ t.Fatal("should've returned member count of 2")
+ } else {
+ currentEtag = cache_result.Etag
+ }
+
+ if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, 1, currentEtag); err != nil {
+ t.Fatal(err)
+ } else if cache_result.Data.(*model.ChannelExtra) != nil {
+ t.Log(cache_result.Data)
+ t.Fatal("response should be empty")
+ } else {
+ currentEtag = cache_result.Etag
+ }
+
}
func TestAddChannelMember(t *testing.T) {
diff --git a/api/command.go b/api/command.go
index db57f0bae..00293cf16 100644
--- a/api/command.go
+++ b/api/command.go
@@ -11,7 +11,7 @@ import (
"strings"
"time"
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
diff --git a/api/context.go b/api/context.go
index b39f03a7d..561884c14 100644
--- a/api/context.go
+++ b/api/context.go
@@ -11,7 +11,7 @@ import (
"strconv"
"strings"
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
diff --git a/api/file.go b/api/file.go
index 67ebc14b7..d023515af 100644
--- a/api/file.go
+++ b/api/file.go
@@ -5,8 +5,8 @@ package api
import (
"bytes"
- l4g "code.google.com/p/log4go"
"fmt"
+ l4g "github.com/alecthomas/log4go"
"github.com/disintegration/imaging"
"github.com/goamz/goamz/aws"
"github.com/goamz/goamz/s3"
diff --git a/api/import.go b/api/import.go
index 81de78975..5c8f99348 100644
--- a/api/import.go
+++ b/api/import.go
@@ -4,7 +4,7 @@
package api
import (
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
)
diff --git a/api/oauth.go b/api/oauth.go
index 5753db8bd..eb5e0e496 100644
--- a/api/oauth.go
+++ b/api/oauth.go
@@ -4,8 +4,8 @@
package api
import (
- l4g "code.google.com/p/log4go"
"fmt"
+ l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
diff --git a/api/post.go b/api/post.go
index 958479427..be1ecd96a 100644
--- a/api/post.go
+++ b/api/post.go
@@ -4,8 +4,8 @@
package api
import (
- l4g "code.google.com/p/log4go"
"fmt"
+ l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
diff --git a/api/preference.go b/api/preference.go
index e9c74aafe..f5c96f1dd 100644
--- a/api/preference.go
+++ b/api/preference.go
@@ -4,7 +4,7 @@
package api
import (
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"net/http"
diff --git a/api/server.go b/api/server.go
index 2bab62fac..33428009f 100644
--- a/api/server.go
+++ b/api/server.go
@@ -4,7 +4,7 @@
package api
import (
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/braintree/manners"
"github.com/gorilla/mux"
"github.com/mattermost/platform/store"
diff --git a/api/slackimport.go b/api/slackimport.go
index cab4c6184..e0a0ff036 100644
--- a/api/slackimport.go
+++ b/api/slackimport.go
@@ -6,8 +6,8 @@ package api
import (
"archive/zip"
"bytes"
- l4g "code.google.com/p/log4go"
"encoding/json"
+ l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
"io"
"mime/multipart"
diff --git a/api/team.go b/api/team.go
index fbcb301a9..e2dd8807e 100644
--- a/api/team.go
+++ b/api/team.go
@@ -5,8 +5,8 @@ package api
import (
"bytes"
- l4g "code.google.com/p/log4go"
"fmt"
+ l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
diff --git a/api/user.go b/api/user.go
index d4c7fcaf5..d014ab995 100644
--- a/api/user.go
+++ b/api/user.go
@@ -5,9 +5,9 @@ package api
import (
"bytes"
- l4g "code.google.com/p/log4go"
b64 "encoding/base64"
"fmt"
+ l4g "github.com/alecthomas/log4go"
"github.com/disintegration/imaging"
"github.com/golang/freetype"
"github.com/gorilla/mux"
@@ -1790,7 +1790,7 @@ func GetAuthorizationCode(c *Context, service, teamName string, props map[string
props["team"] = teamName
state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props)))
- redirectUri := c.GetSiteURL() + "/" + service + "/complete"
+ redirectUri := c.GetSiteURL() + "/signup/" + service + "/complete" // Remove /signup after a few releases (~1.8)
authUrl := endpoint + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + url.QueryEscape(redirectUri) + "&state=" + url.QueryEscape(state)
diff --git a/api/web_conn.go b/api/web_conn.go
index 50a003ace..2b0e29038 100644
--- a/api/web_conn.go
+++ b/api/web_conn.go
@@ -4,7 +4,7 @@
package api
import (
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/gorilla/websocket"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
diff --git a/api/web_hub.go b/api/web_hub.go
index f80488824..4361d1035 100644
--- a/api/web_hub.go
+++ b/api/web_hub.go
@@ -4,7 +4,7 @@
package api
import (
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
)
diff --git a/api/web_socket.go b/api/web_socket.go
index 298e44b44..995e2a677 100644
--- a/api/web_socket.go
+++ b/api/web_socket.go
@@ -4,7 +4,7 @@
package api
import (
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/mattermost/platform/model"
diff --git a/api/web_team_hub.go b/api/web_team_hub.go
index 2c2386317..bb9ed9526 100644
--- a/api/web_team_hub.go
+++ b/api/web_team_hub.go
@@ -4,7 +4,7 @@
package api
import (
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
)
diff --git a/api/webhook.go b/api/webhook.go
index 34c308879..a9a88b7b8 100644
--- a/api/webhook.go
+++ b/api/webhook.go
@@ -4,7 +4,7 @@
package api
import (
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
diff --git a/config/config.json b/config/config.json
index c43db1e50..076f795cc 100644
--- a/config/config.json
+++ b/config/config.json
@@ -12,10 +12,10 @@
"EnableTesting": false,
"EnableDeveloper": false,
"EnableSecurityFixAlert": true,
- "SessionLengthWebInDays" : 30,
- "SessionLengthMobileInDays" : 30,
- "SessionLengthSSOInDays" : 30,
- "SessionCacheInMinutes" : 10
+ "SessionLengthWebInDays": 30,
+ "SessionLengthMobileInDays": 30,
+ "SessionLengthSSOInDays": 30,
+ "SessionCacheInMinutes": 10
},
"TeamSettings": {
"SiteName": "Mattermost",
diff --git a/doc/developer/tests/test-attachments.md b/doc/developer/tests/test-attachments.md
index 71cc496ce..e2fda0eb6 100644
--- a/doc/developer/tests/test-attachments.md
+++ b/doc/developer/tests/test-attachments.md
@@ -29,7 +29,7 @@ Expected: Scaled thumbnail & preview window
**GIF**
`Images/GIF.gif`
-Expected: Scaled thumbnail & preview window. Click to play GIF.
+Expected: Scaled thumbnail & preview window. GIF should auto-play in the preview window.
[Permalink](https://pre-release.mattermost.com/core/pl/j49fowdkstr57g3ed9bgpfoo5w)
**TIFF**
@@ -72,37 +72,37 @@ Expected: Generic Word thumbnail & preview window.
**MP4**
`Videos/MP4.mp4`
-Expected: Generic video thumbnail & playable preview window
+Expected: Generic video thumbnail & playable preview window. View Permalink.
[Permalink](https://pre-release.mattermost.com/core/pl/5dx5qx9t9brqfnhohccxjynx7c)
**AVI**
`Videos/AVI.avi`
-Expected: View Permalink. Expected depends on the operating system, browser and plugins.
+Expected: Generic video thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins.
[Permalink](https://pre-release.mattermost.com/core/pl/qwn9eiy7j3rkjyruxhcugpogdw)
**MKV**
`Videos/MKV.mkv`
-Expected: View Permalink. Expected depends on the operating system, browser and plugins.
+Expected: Generic video thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins.
[Permalink](https://pre-release.mattermost.com/core/pl/tszyjkr1cidhxjgiusa4mde3ja)
**MOV**
`Videos/MOV.mov`
-Expected: View Permalink. Expected depends on the operating system, browser and plugins.
+Expected: Generic video thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins.
[Permalink](https://pre-release.mattermost.com/core/pl/ienzppz5i3f7tbt5jiujn8uuir)
**MPG**
`Videos/MPG.mpg`
-Expected: View Permalink. Expected depends on the operating system, browser and plugins.
+Expected: Generic video thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins.
[Permalink](https://pre-release.mattermost.com/core/pl/skggdq1hfpritc6c88bi481p5a)
**Webm**
`Videos/WEBM.webm`
-Expected: View Permalink. Expected depends on the operating system, browser and plugins.
+Expected: Generic video thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins.
[Permalink](https://pre-release.mattermost.com/core/pl/7h8tysuxgfgsxeht3sbn7e4h6y)
**WMV**
`Videos/WMV.wmv`
-Expected: View Permalink. Expected depends on the operating system, browser and plugins.
+Expected: Generic video thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins.
[Permalink](https://pre-release.mattermost.com/core/pl/kaom7j7uyjra7bzhrre6qwdrbw)
### Audio
@@ -112,7 +112,6 @@ Expected: View Permalink. Expected depends on the operating system, browser and
Expected: Generic audio thumbnail & playable preview window
[Permalink](https://pre-release.mattermost.com/core/pl/if4gn8dbrjgx8fmqmkukzefyme)
-
**M4A**
`Audio/M4a.m4a`
Expected: Generic audio thumbnail & playable preview window
@@ -120,25 +119,25 @@ Expected: Generic audio thumbnail & playable preview window
**AAC**
`Audio/AAC.aac`
-Expected: View Permalink. Expected depends on the operating system, browser and plugins.
+Expected: Generic audio thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins.
[Permalink](https://pre-release.mattermost.com/core/pl/3naoy5pr5tydbk1m6yo1ast9ny)
**FLAC**
`Audio/FLAC.flac`
-Expected: View Permalink. Expected depends on the operating system, browser and plugins.
+Expected: Generic audio thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins.
[Permalink](https://pre-release.mattermost.com/core/pl/kf4cmy44dfya5efmse7rg43eih)
**OGG**
`Audio/OGG.ogg`
-Expected: View Permalink. Expected depends on the operating system, browser and plugins.
+Expected: Generic audio thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins.
[Permalink](https://pre-release.mattermost.com/core/pl/dezrcpbxapyexe77rjuzkrp63r)
**WAV**
`Audio/WAV.wav`
-Expected: View Permalink. Expected depends on the operating system, browser and plugins.
+Expected: Generic audio thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins.
[Permalink](https://pre-release.mattermost.com/core/pl/pdkxx1udepdnbmi9j8kyas5xbh)
**WMA**
`Audio/WMA.wma`
-Expected: View Permalink. Expected depends on the operating system, browser and plugins.
+Expected: Generic audio thumbnail, view Permalink for preview window behavior. Expected depends on the operating system, browser and plugins.
[Permalink](https://pre-release.mattermost.com/core/pl/756wrmdd57dcig3m4emypp6i1h)
diff --git a/doc/developer/tests/test-markdown-basics.md b/doc/developer/tests/test-markdown-basics.md
index 7a46adeb0..cea9a0867 100644
--- a/doc/developer/tests/test-markdown-basics.md
+++ b/doc/developer/tests/test-markdown-basics.md
@@ -18,6 +18,18 @@ Normal Text_
_Normal Text
_Normal Text*
+### Carriage Return
+
+**The following text should render as:**
+Line #1 followed by Line #2
+Line #2 followed by one blank line
+
+Line #3 followed by one blank line
+
+
+Line #4 following one blank line
+
+
### Code Blocks
```
@@ -119,7 +131,7 @@ Text below line
**The following markdown should render within the block quote:**
> #### Heading 4
-> _Italics_, *Italics*, **Bold**, ***Bold-italics***, **Bold-italics_**, ~~Strikethrough~~
+> _Italics_, *Italics*, **Bold**, ***Bold-italics***, **_Bold-italics_**, ~~Strikethrough~~
> :) :-) ;) :-O :bamboo: :gift_heart: :dolls:
**The following text should render in two block quotes separated by one line of text:**
diff --git a/doc/install/Upgrade-Guide.md b/doc/install/Upgrade-Guide.md
index 4480dedd2..1f3ff9510 100644
--- a/doc/install/Upgrade-Guide.md
+++ b/doc/install/Upgrade-Guide.md
@@ -43,7 +43,7 @@ The following instructions apply to updating installations of Mattermost v0.7-Be
Mattermost is designed to be upgraded sequentially through major version releases. If you skip versions when upgrading GitLab, you may find a `panic: The database schema version of 1.1.0 cannot be upgraded. You must not skip a version` error in your `/var/log/gitlab/mattermost/current` directory. If so:
1. Run `platform -version` to check your version of Mattermost
-2. If your version of the Mattermost binary doesn't match the version listed in the database error message, downgrade the version of the Mattermost binary you are using by [following the manual upgrade steps for Mattermost](/var/log/gitlab/mattermost/current) and using the database schema version listed in the error messages for the version you select in Step 1) iv).
+2. If your version of the Mattermost binary doesn't match the version listed in the database error message, downgrade the version of the Mattermost binary you are using by [following the manual upgrade steps for Mattermost](https://github.com/mattermost/platform/blob/master/doc/install/Upgrade-Guide.md#upgrading-mattermost-to-next-major-release) and using the database schema version listed in the error messages for the version you select in Step 1) iv).
3. Once Mattermost is working again, you can use the same upgrade procedure to upgrade Mattermost version by version to your current GitLab version. After this is done, GitLab automation should continue to work for future upgrades, so long as you don't skip versions.
| GitLab Version | Mattermost Version |
diff --git a/docker/1.4/Dockerfile b/docker/1.4/Dockerfile
new file mode 100644
index 000000000..1f0acdfc9
--- /dev/null
+++ b/docker/1.4/Dockerfile
@@ -0,0 +1,49 @@
+# Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+# See License.txt for license information.
+FROM ubuntu:14.04
+
+#
+# Install SQL
+#
+
+ENV MYSQL_ROOT_PASSWORD=mostest
+ENV MYSQL_USER=mmuser
+ENV MYSQL_PASSWORD=mostest
+ENV MYSQL_DATABASE=mattermost_test
+
+RUN groupadd -r mysql && useradd -r -g mysql mysql
+
+RUN apt-key adv --keyserver pool.sks-keyservers.net --recv-keys A4A9406876FCBD3C456770C88C718D3B5072E1F5
+
+ENV MYSQL_MAJOR 5.6
+ENV MYSQL_VERSION 5.6.25
+
+RUN echo "deb http://repo.mysql.com/apt/debian/ wheezy mysql-${MYSQL_MAJOR}" > /etc/apt/sources.list.d/mysql.list
+
+RUN apt-get update \
+ && export DEBIAN_FRONTEND=noninteractive \
+ && apt-get -y install perl wget mysql-server \
+ && rm -rf /var/lib/apt/lists/* \
+ && rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql
+
+RUN sed -Ei 's/^(bind-address|log)/#&/' /etc/mysql/my.cnf
+
+VOLUME /var/lib/mysql
+# ---------------------------------------------------------------------------------------------------------------------
+
+WORKDIR /mattermost
+
+# Copy over files
+ADD https://github.com/mattermost/platform/releases/download/v1.4.0-rc2/mattermost.tar.gz /
+RUN tar -zxvf /mattermost.tar.gz --strip-components=1 && rm /mattermost.tar.gz
+ADD config_docker.json /
+ADD docker-entry.sh /
+
+RUN chmod +x /docker-entry.sh
+ENTRYPOINT /docker-entry.sh
+
+# Create default storage directory
+RUN mkdir /mattermost-data/
+
+# Ports
+EXPOSE 80
diff --git a/docker/1.4/Dockerrun.aws.zip b/docker/1.4/Dockerrun.aws.zip
new file mode 100644
index 000000000..91e9a9a2d
--- /dev/null
+++ b/docker/1.4/Dockerrun.aws.zip
Binary files differ
diff --git a/docker/1.4/Dockerrun.aws/.ebextensions/01_files.config b/docker/1.4/Dockerrun.aws/.ebextensions/01_files.config
new file mode 100644
index 000000000..7f40a8b34
--- /dev/null
+++ b/docker/1.4/Dockerrun.aws/.ebextensions/01_files.config
@@ -0,0 +1,14 @@
+files:
+ "/etc/nginx/conf.d/proxy.conf":
+ mode: "000755"
+ owner: root
+ group: root
+ content: |
+ client_max_body_size 50M;
+ "/opt/elasticbeanstalk/hooks/appdeploy/post/init.sh":
+ mode: "000755"
+ owner: root
+ group: root
+ content: |
+ #!/usr/bin/env bash
+ gpasswd -a ec2-user docker
diff --git a/docker/1.4/Dockerrun.aws/Dockerrun.aws.json b/docker/1.4/Dockerrun.aws/Dockerrun.aws.json
new file mode 100755
index 000000000..654961589
--- /dev/null
+++ b/docker/1.4/Dockerrun.aws/Dockerrun.aws.json
@@ -0,0 +1,13 @@
+{
+ "AWSEBDockerrunVersion": "1",
+ "Image": {
+ "Name": "mattermost/platform:1.4",
+ "Update": "true"
+ },
+ "Ports": [
+ {
+ "ContainerPort": "80"
+ }
+ ],
+ "Logging": "/var/log/"
+}
diff --git a/docker/1.4/README.md b/docker/1.4/README.md
new file mode 100644
index 000000000..f737a1554
--- /dev/null
+++ b/docker/1.4/README.md
@@ -0,0 +1,23 @@
+Mattermost
+==========
+
+http:/mattermost.org
+
+Mattermost is an open-source team communication service. It brings team messaging and file sharing into one place, accessible across PCs and phones, with archiving and search.
+
+Installing Mattermost
+=====================
+
+To run an instance of the latest version of mattermost on your local machine you can run:
+
+`docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform`
+
+To update this image to the latest version you can run:
+
+`docker pull mattermost/platform`
+
+To run an instance of the latest code from the master branch on GitHub you can run:
+
+`docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:dev`
+
+Any questions, please visit http://forum.mattermost.org
diff --git a/docker/1.4/config_docker.json b/docker/1.4/config_docker.json
new file mode 100644
index 000000000..1aa2ee843
--- /dev/null
+++ b/docker/1.4/config_docker.json
@@ -0,0 +1,111 @@
+{
+ "ServiceSettings": {
+ "ListenAddress": ":80",
+ "MaximumLoginAttempts": 10,
+ "SegmentDeveloperKey": "",
+ "GoogleDeveloperKey": "",
+ "EnableOAuthServiceProvider": false,
+ "EnableIncomingWebhooks": false,
+ "EnableOutgoingWebhooks": false,
+ "EnablePostUsernameOverride": false,
+ "EnablePostIconOverride": false,
+ "EnableTesting": false,
+ "EnableDeveloper": false,
+ "EnableSecurityFixAlert": true,
+ "SessionLengthWebInDays" : 30,
+ "SessionLengthMobileInDays" : 30,
+ "SessionLengthSSOInDays" : 30,
+ "SessionCacheInMinutes" : 10
+ },
+ "TeamSettings": {
+ "SiteName": "Mattermost",
+ "MaxUsersPerTeam": 50,
+ "EnableTeamCreation": true,
+ "EnableUserCreation": true,
+ "RestrictCreationToDomains": "",
+ "RestrictTeamNames": true,
+ "EnableTeamListing": false
+ },
+ "SqlSettings": {
+ "DriverName": "mysql",
+ "DataSource": "mmuser:mostest@tcp(dockerhost:3306)/mattermost_test?charset=utf8mb4,utf8",
+ "DataSourceReplicas": [],
+ "MaxIdleConns": 10,
+ "MaxOpenConns": 10,
+ "Trace": false,
+ "AtRestEncryptKey": "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QVg"
+ },
+ "LogSettings": {
+ "EnableConsole": false,
+ "ConsoleLevel": "INFO",
+ "EnableFile": true,
+ "FileLevel": "INFO",
+ "FileFormat": "",
+ "FileLocation": ""
+ },
+ "FileSettings": {
+ "DriverName": "local",
+ "Directory": "/mattermost/data/",
+ "EnablePublicLink": true,
+ "PublicLinkSalt": "A705AklYF8MFDOfcwh3I488G8vtLlVip",
+ "ThumbnailWidth": 120,
+ "ThumbnailHeight": 100,
+ "PreviewWidth": 1024,
+ "PreviewHeight": 0,
+ "ProfileWidth": 128,
+ "ProfileHeight": 128,
+ "InitialFont": "luximbi.ttf",
+ "AmazonS3AccessKeyId": "",
+ "AmazonS3SecretAccessKey": "",
+ "AmazonS3Bucket": "",
+ "AmazonS3Region": "",
+ "AmazonS3Endpoint": "",
+ "AmazonS3BucketEndpoint": "",
+ "AmazonS3LocationConstraint": false,
+ "AmazonS3LowercaseBucket": false
+ },
+ "EmailSettings": {
+ "EnableSignUpWithEmail": true,
+ "SendEmailNotifications": false,
+ "RequireEmailVerification": false,
+ "FeedbackName": "",
+ "FeedbackEmail": "",
+ "SMTPUsername": "",
+ "SMTPPassword": "",
+ "SMTPServer": "",
+ "SMTPPort": "",
+ "ConnectionSecurity": "",
+ "InviteSalt": "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9YoS",
+ "PasswordResetSalt": "vZ4DcKyVVRlKHHJpexcuXzojkE5PZ5eL",
+ "SendPushNotifications": false,
+ "PushNotificationServer": ""
+ },
+ "RateLimitSettings": {
+ "EnableRateLimiter": true,
+ "PerSec": 10,
+ "MemoryStoreSize": 10000,
+ "VaryByRemoteAddr": true,
+ "VaryByHeader": ""
+ },
+ "PrivacySettings": {
+ "ShowEmailAddress": true,
+ "ShowFullName": true
+ },
+ "SupportSettings": {
+ "TermsOfServiceLink": "/static/help/terms.html",
+ "PrivacyPolicyLink": "/static/help/privacy.html",
+ "AboutLink": "/static/help/about.html",
+ "HelpLink": "/static/help/help.html",
+ "ReportAProblemLink": "/static/help/report_problem.html",
+ "SupportEmail": "feedback@mattermost.com"
+ },
+ "GitLabSettings": {
+ "Enable": false,
+ "Secret": "",
+ "Id": "",
+ "Scope": "",
+ "AuthEndpoint": "",
+ "TokenEndpoint": "",
+ "UserApiEndpoint": ""
+ }
+}
diff --git a/docker/1.4/docker-entry.sh b/docker/1.4/docker-entry.sh
new file mode 100755
index 000000000..6bd2a1263
--- /dev/null
+++ b/docker/1.4/docker-entry.sh
@@ -0,0 +1,111 @@
+#!/bin/bash
+# Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+# See License.txt for license information.
+
+mkdir -p web/static/js
+
+echo "127.0.0.1 dockerhost" >> /etc/hosts
+/etc/init.d/networking restart
+
+echo configuring mysql
+
+# SQL!!!
+set -e
+
+get_option () {
+ local section=$1
+ local option=$2
+ local default=$3
+ ret=$(my_print_defaults $section | grep '^--'${option}'=' | cut -d= -f2-)
+ [ -z $ret ] && ret=$default
+ echo $ret
+}
+
+
+# Get config
+DATADIR="$("mysqld" --verbose --help 2>/dev/null | awk '$1 == "datadir" { print $2; exit }')"
+SOCKET=$(get_option mysqld socket "$DATADIR/mysql.sock")
+PIDFILE=$(get_option mysqld pid-file "/var/run/mysqld/mysqld.pid")
+
+if [ ! -d "$DATADIR/mysql" ]; then
+ if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" ]; then
+ echo >&2 'error: database is uninitialized and MYSQL_ROOT_PASSWORD not set'
+ echo >&2 ' Did you forget to add -e MYSQL_ROOT_PASSWORD=... ?'
+ exit 1
+ fi
+
+ mkdir -p "$DATADIR"
+ chown -R mysql:mysql "$DATADIR"
+
+ echo 'Running mysql_install_db'
+ mysql_install_db --user=mysql --datadir="$DATADIR" --rpm --keep-my-cnf
+ echo 'Finished mysql_install_db'
+
+ mysqld --user=mysql --datadir="$DATADIR" --skip-networking &
+ for i in $(seq 30 -1 0); do
+ [ -S "$SOCKET" ] && break
+ echo 'MySQL init process in progress...'
+ sleep 1
+ done
+ if [ $i = 0 ]; then
+ echo >&2 'MySQL init process failed.'
+ exit 1
+ fi
+
+ # These statements _must_ be on individual lines, and _must_ end with
+ # semicolons (no line breaks or comments are permitted).
+ # TODO proper SQL escaping on ALL the things D:
+
+ tempSqlFile=$(mktemp /tmp/mysql-first-time.XXXXXX.sql)
+ cat > "$tempSqlFile" <<-EOSQL
+ -- What's done in this file shouldn't be replicated
+ -- or products like mysql-fabric won't work
+ SET @@SESSION.SQL_LOG_BIN=0;
+
+ DELETE FROM mysql.user ;
+ CREATE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
+ GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ;
+ DROP DATABASE IF EXISTS test ;
+ EOSQL
+
+ if [ "$MYSQL_DATABASE" ]; then
+ echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" >> "$tempSqlFile"
+ fi
+
+ if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
+ echo "CREATE USER '"$MYSQL_USER"'@'%' IDENTIFIED BY '"$MYSQL_PASSWORD"' ;" >> "$tempSqlFile"
+
+ if [ "$MYSQL_DATABASE" ]; then
+ echo "GRANT ALL ON \`"$MYSQL_DATABASE"\`.* TO '"$MYSQL_USER"'@'%' ;" >> "$tempSqlFile"
+ fi
+ fi
+
+ echo 'FLUSH PRIVILEGES ;' >> "$tempSqlFile"
+
+ mysql -uroot < "$tempSqlFile"
+
+ rm -f "$tempSqlFile"
+ kill $(cat $PIDFILE)
+ for i in $(seq 30 -1 0); do
+ [ -f "$PIDFILE" ] || break
+ echo 'MySQL init process in progress...'
+ sleep 1
+ done
+ if [ $i = 0 ]; then
+ echo >&2 'MySQL hangs during init process.'
+ exit 1
+ fi
+ echo 'MySQL init process done. Ready for start up.'
+fi
+
+chown -R mysql:mysql "$DATADIR"
+
+mysqld &
+
+sleep 5
+
+# ------------------------
+
+echo starting platform
+cd /mattermost/bin
+./platform -config=/config_docker.json
diff --git a/manualtesting/manual_testing.go b/manualtesting/manual_testing.go
index b3d01a5a6..befc835fb 100644
--- a/manualtesting/manual_testing.go
+++ b/manualtesting/manual_testing.go
@@ -4,7 +4,7 @@
package manualtesting
import (
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/api"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
diff --git a/manualtesting/test_autolink.go b/manualtesting/test_autolink.go
index a13ec7a75..16d2d713a 100644
--- a/manualtesting/test_autolink.go
+++ b/manualtesting/test_autolink.go
@@ -4,7 +4,7 @@
package manualtesting
import (
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
)
diff --git a/mattermost.go b/mattermost.go
index 3d8ab736f..f86af76e3 100644
--- a/mattermost.go
+++ b/mattermost.go
@@ -17,7 +17,7 @@ import (
"syscall"
"time"
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/api"
"github.com/mattermost/platform/manualtesting"
"github.com/mattermost/platform/model"
diff --git a/model/channel.go b/model/channel.go
index 0ce09f4bc..7109500d4 100644
--- a/model/channel.go
+++ b/model/channel.go
@@ -57,8 +57,8 @@ func (o *Channel) Etag() string {
return Etag(o.Id, o.UpdateAt)
}
-func (o *Channel) ExtraEtag() string {
- return Etag(o.Id, o.ExtraUpdateAt)
+func (o *Channel) ExtraEtag(memberLimit int) string {
+ return Etag(o.Id, o.ExtraUpdateAt, memberLimit)
}
func (o *Channel) IsValid() *AppError {
diff --git a/model/client.go b/model/client.go
index f1773f3c7..75b93c971 100644
--- a/model/client.go
+++ b/model/client.go
@@ -5,8 +5,8 @@ package model
import (
"bytes"
- l4g "code.google.com/p/log4go"
"fmt"
+ l4g "github.com/alecthomas/log4go"
"io/ioutil"
"net/http"
"net/url"
@@ -591,8 +591,8 @@ func (c *Client) UpdateLastViewedAt(channelId string) (*Result, *AppError) {
}
}
-func (c *Client) GetChannelExtraInfo(id string, etag string) (*Result, *AppError) {
- if r, err := c.DoApiGet("/channels/"+id+"/extra_info", "", etag); err != nil {
+func (c *Client) GetChannelExtraInfo(id string, memberLimit int, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/channels/"+id+"/extra_info/"+strconv.FormatInt(int64(memberLimit), 10), "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
diff --git a/model/version.go b/model/version.go
index 142ddb371..88334ceea 100644
--- a/model/version.go
+++ b/model/version.go
@@ -12,6 +12,7 @@ import (
// It should be maitained in chronological order with most current
// release at the front of the list.
var versions = []string{
+ "1.4.0",
"1.3.0",
"1.2.1",
"1.2.0",
diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go
index c9e0d113f..4585647de 100644
--- a/store/sql_channel_store.go
+++ b/store/sql_channel_store.go
@@ -603,7 +603,14 @@ func (s SqlChannelStore) GetExtraMembers(channelId string, limit int) StoreChann
result := StoreResult{}
var members []model.ExtraMember
- _, err := s.GetReplica().Select(&members, "SELECT Id, Nickname, Email, ChannelMembers.Roles, Username FROM ChannelMembers, Users WHERE ChannelMembers.UserId = Users.Id AND ChannelId = :ChannelId LIMIT :Limit", map[string]interface{}{"ChannelId": channelId, "Limit": limit})
+ var err error
+
+ if limit != -1 {
+ _, err = s.GetReplica().Select(&members, "SELECT Id, Nickname, Email, ChannelMembers.Roles, Username FROM ChannelMembers, Users WHERE ChannelMembers.UserId = Users.Id AND ChannelId = :ChannelId LIMIT :Limit", map[string]interface{}{"ChannelId": channelId, "Limit": limit})
+ } else {
+ _, err = s.GetReplica().Select(&members, "SELECT Id, Nickname, Email, ChannelMembers.Roles, Username FROM ChannelMembers, Users WHERE ChannelMembers.UserId = Users.Id AND ChannelId = :ChannelId", map[string]interface{}{"ChannelId": channelId})
+ }
+
if err != nil {
result.Err = model.NewAppError("SqlChannelStore.GetExtraMembers", "We couldn't get the extra info for channel members", "channel_id="+channelId+", "+err.Error())
} else {
diff --git a/store/sql_preference_store.go b/store/sql_preference_store.go
index 307761150..6302d2f4f 100644
--- a/store/sql_preference_store.go
+++ b/store/sql_preference_store.go
@@ -4,7 +4,7 @@
package store
import (
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/go-gorp/gorp"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
diff --git a/store/sql_session_store.go b/store/sql_session_store.go
index 86604b4fe..6b0a31443 100644
--- a/store/sql_session_store.go
+++ b/store/sql_session_store.go
@@ -4,7 +4,7 @@
package store
import (
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
)
diff --git a/store/sql_store.go b/store/sql_store.go
index d17a3e8c3..d0471fa1e 100644
--- a/store/sql_store.go
+++ b/store/sql_store.go
@@ -4,7 +4,6 @@
package store
import (
- l4g "code.google.com/p/log4go"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
@@ -16,6 +15,7 @@ import (
"encoding/json"
"errors"
"fmt"
+ l4g "github.com/alecthomas/log4go"
"io"
sqltrace "log"
"math/rand"
diff --git a/store/store.go b/store/store.go
index 8e03c8ee7..179cfecd7 100644
--- a/store/store.go
+++ b/store/store.go
@@ -4,7 +4,7 @@
package store
import (
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
"time"
)
diff --git a/utils/config.go b/utils/config.go
index 18bd15241..12d03b5de 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -11,7 +11,7 @@ import (
"path/filepath"
"strconv"
- l4g "code.google.com/p/log4go"
+ l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
)
@@ -77,7 +77,9 @@ func configureLog(s *model.LogSettings) {
level = l4g.ERROR
}
- l4g.AddFilter("stdout", level, l4g.NewConsoleLogWriter())
+ lw := l4g.NewConsoleLogWriter()
+ lw.SetFormat("[%D %T] [%L] %M")
+ l4g.AddFilter("stdout", level, lw)
}
if s.EnableFile {
diff --git a/utils/mail.go b/utils/mail.go
index 6625060de..2f2c10b61 100644
--- a/utils/mail.go
+++ b/utils/mail.go
@@ -4,10 +4,10 @@
package utils
import (
- l4g "code.google.com/p/log4go"
"crypto/tls"
"encoding/base64"
"fmt"
+ l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
"net"
"net/mail"
diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx
index 6aec3c7b1..c568c5a77 100644
--- a/web/react/components/admin_console/email_settings.jsx
+++ b/web/react/components/admin_console/email_settings.jsx
@@ -581,12 +581,12 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='PushNotificationServer'
ref='PushNotificationServer'
- placeholder='E.g.: "https://push.mattermost.com"'
+ placeholder='E.g.: "https://push-test.mattermost.com"'
defaultValue={this.props.config.EmailSettings.PushNotificationServer}
onChange={this.handleChange}
disabled={!this.state.sendPushNotifications}
/>
- <p className='help-text'>{'Location of Mattermost push notification service you can set up behind your firewall using https://github.com/mattermost/push-proxy. For testing you can use https://push.mattermost.com, which connects to the sample Mattermost iOS app in the public Apple AppStore. Please do not use test service for production deployments.'}</p>
+ <p className='help-text'>{'Location of Mattermost push notification service you can set up behind your firewall using https://github.com/mattermost/push-proxy. For testing you can use https://push-test.mattermost.com, which connects to the sample Mattermost iOS app in the public Apple AppStore. Please do not use test service for production deployments.'}</p>
</div>
</div>
diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx
index 7dac39942..8b7485e5f 100644
--- a/web/react/components/channel_invite_modal.jsx
+++ b/web/react/components/channel_invite_modal.jsx
@@ -20,9 +20,14 @@ export default class ChannelInviteModal extends React.Component {
this.onListenerChange = this.onListenerChange.bind(this);
this.handleInvite = this.handleInvite.bind(this);
- this.state = this.getStateFromStores();
+ // the state gets populated when the modal is shown
+ this.state = {};
}
shouldComponentUpdate(nextProps, nextState) {
+ if (!this.props.show && !nextProps.show) {
+ return false;
+ }
+
if (!Utils.areObjectsEqual(this.props, nextProps)) {
return true;
}
@@ -34,13 +39,25 @@ export default class ChannelInviteModal extends React.Component {
return false;
}
getStateFromStores() {
- function getId(user) {
- return user.id;
+ const users = UserStore.getActiveOnlyProfiles();
+
+ if ($.isEmptyObject(users)) {
+ return {
+ loading: true
+ };
+ }
+
+ // make sure we have all members of this channel before rendering
+ const extraInfo = ChannelStore.getCurrentExtraInfo();
+ if (extraInfo.member_count !== extraInfo.members.length) {
+ AsyncClient.getChannelExtraInfo(this.props.channel.id, -1);
+
+ return {
+ loading: true
+ };
}
- var users = UserStore.getActiveOnlyProfiles();
- var memberIds = ChannelStore.getCurrentExtraInfo().members.map(getId);
- var loading = $.isEmptyObject(users);
+ const memberIds = extraInfo.members.map((user) => user.id);
var nonmembers = [];
for (var id in users) {
@@ -55,7 +72,7 @@ export default class ChannelInviteModal extends React.Component {
return {
nonmembers,
- loading
+ loading: false
};
}
onShow() {
diff --git a/web/react/components/channel_members_modal.jsx b/web/react/components/channel_members_modal.jsx
index d1b9df988..513a720e7 100644
--- a/web/react/components/channel_members_modal.jsx
+++ b/web/react/components/channel_members_modal.jsx
@@ -1,6 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import LoadingScreen from './loading_screen.jsx';
import MemberList from './member_list.jsx';
import ChannelInviteModal from './channel_invite_modal.jsx';
@@ -21,9 +22,10 @@ export default class ChannelMembersModal extends React.Component {
this.onChange = this.onChange.bind(this);
this.handleRemove = this.handleRemove.bind(this);
- const state = this.getStateFromStores();
- state.showInviteModal = false;
- this.state = state;
+ // the rest of the state gets populated when the modal is shown
+ this.state = {
+ showInviteModal: false
+ };
}
shouldComponentUpdate(nextProps, nextState) {
if (!Utils.areObjectsEqual(this.props, nextProps)) {
@@ -37,8 +39,18 @@ export default class ChannelMembersModal extends React.Component {
return false;
}
getStateFromStores() {
+ const extraInfo = ChannelStore.getCurrentExtraInfo();
+
+ if (extraInfo.member_count !== extraInfo.members.length) {
+ AsyncClient.getChannelExtraInfo(this.props.channel.id, -1);
+
+ return {
+ loading: true
+ };
+ }
+
const users = UserStore.getActiveOnlyProfiles();
- const memberList = ChannelStore.getCurrentExtraInfo().members;
+ const memberList = extraInfo.members;
const nonmemberList = [];
for (const id in users) {
@@ -71,14 +83,14 @@ export default class ChannelMembersModal extends React.Component {
return {
nonmemberList,
- memberList
+ memberList,
+ loading: false
};
}
onShow() {
if ($(window).width() > 768) {
$(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
}
- this.onChange();
}
componentDidUpdate(prevProps) {
if (this.props.show && !prevProps.show) {
@@ -89,6 +101,8 @@ export default class ChannelMembersModal extends React.Component {
if (!this.props.show && nextProps.show) {
ChannelStore.addExtraInfoChangeListener(this.onChange);
ChannelStore.addChangeListener(this.onChange);
+
+ this.onChange();
} else if (this.props.show && !nextProps.show) {
ChannelStore.removeExtraInfoChangeListener(this.onChange);
ChannelStore.removeChangeListener(this.onChange);
@@ -154,6 +168,21 @@ export default class ChannelMembersModal extends React.Component {
isAdmin = Utils.isAdmin(currentMember.roles) || Utils.isAdmin(UserStore.getCurrentUser().roles);
}
+ let content;
+ if (this.state.loading) {
+ content = (<LoadingScreen />);
+ } else {
+ content = (
+ <div className='team-member-list'>
+ <MemberList
+ memberList={this.state.memberList}
+ isAdmin={isAdmin}
+ handleRemove={this.handleRemove}
+ />
+ </div>
+ );
+ }
+
return (
<div>
<Modal
@@ -178,13 +207,7 @@ export default class ChannelMembersModal extends React.Component {
ref='modalBody'
style={{maxHeight}}
>
- <div className='team-member-list'>
- <MemberList
- memberList={this.state.memberList}
- isAdmin={isAdmin}
- handleRemove={this.handleRemove}
- />
- </div>
+ {content}
</Modal.Body>
<Modal.Footer>
<button
diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx
index c10269680..eeb218bfe 100644
--- a/web/react/components/file_attachment.jsx
+++ b/web/react/components/file_attachment.jsx
@@ -266,7 +266,7 @@ export default class FileAttachment extends React.Component {
href={fileUrl}
download={filenameString}
data-toggle='tooltip'
- title={'Download ' + filenameString}
+ title={'Download \"' + filenameString + '\"'}
className='post-image__name'
>
{trimmedFilename}
diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx
index a0c930ffb..6337afabc 100644
--- a/web/react/components/file_upload.jsx
+++ b/web/react/components/file_upload.jsx
@@ -4,7 +4,7 @@
import * as client from '../utils/client.jsx';
import Constants from '../utils/constants.jsx';
import ChannelStore from '../stores/channel_store.jsx';
-import * as utils from '../utils/utils.jsx';
+import * as Utils from '../utils/utils.jsx';
export default class FileUpload extends React.Component {
constructor(props) {
@@ -52,7 +52,7 @@ export default class FileUpload extends React.Component {
}
// generate a unique id that can be used by other components to refer back to this upload
- let clientId = utils.generateId();
+ let clientId = Utils.generateId();
// prepare data to be uploaded
var formData = new FormData();
@@ -121,14 +121,14 @@ export default class FileUpload extends React.Component {
enter(dragsterEvent, e) {
var files = e.originalEvent.dataTransfer;
- if (utils.isFileTransfer(files)) {
+ if (Utils.isFileTransfer(files)) {
$('.center-file-overlay').removeClass('hidden');
}
},
leave(dragsterEvent, e) {
var files = e.originalEvent.dataTransfer;
- if (utils.isFileTransfer(files)) {
+ if (Utils.isFileTransfer(files)) {
$('.center-file-overlay').addClass('hidden');
}
},
@@ -142,14 +142,14 @@ export default class FileUpload extends React.Component {
enter(dragsterEvent, e) {
var files = e.originalEvent.dataTransfer;
- if (utils.isFileTransfer(files)) {
+ if (Utils.isFileTransfer(files)) {
$('.right-file-overlay').removeClass('hidden');
}
},
leave(dragsterEvent, e) {
var files = e.originalEvent.dataTransfer;
- if (utils.isFileTransfer(files)) {
+ if (Utils.isFileTransfer(files)) {
$('.right-file-overlay').addClass('hidden');
}
},
@@ -205,7 +205,7 @@ export default class FileUpload extends React.Component {
var channelId = self.props.channelId || ChannelStore.getCurrentId();
// generate a unique id that can be used by other components to refer back to this file upload
- var clientId = utils.generateId();
+ var clientId = Utils.generateId();
var formData = new FormData();
formData.append('channel_id', channelId);
@@ -268,6 +268,18 @@ export default class FileUpload extends React.Component {
}
render() {
+ let multiple = true;
+ if (Utils.isMobileApp()) {
+ // iOS WebViews don't upload videos properly in multiple mode
+ multiple = false;
+ }
+
+ let accept = '';
+ if (Utils.isIosChrome()) {
+ // iOS Chrome can't upload videos at all
+ accept = 'image/*';
+ }
+
return (
<span
ref='input'
@@ -280,7 +292,8 @@ export default class FileUpload extends React.Component {
ref='fileInput'
type='file'
onChange={this.handleChange}
- multiple='true'
+ multiple={multiple}
+ accept={accept}
/>
</span>
);
diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx
index 56bc00a7e..7e1627555 100644
--- a/web/react/components/invite_member_modal.jsx
+++ b/web/react/components/invite_member_modal.jsx
@@ -8,6 +8,7 @@ import * as Client from '../utils/client.jsx';
import * as EventHelpers from '../dispatcher/event_helpers.jsx';
import ModalStore from '../stores/modal_store.jsx';
import UserStore from '../stores/user_store.jsx';
+import ChannelStore from '../stores/channel_store.jsx';
import TeamStore from '../stores/team_store.jsx';
import ConfirmModal from './confirm_modal.jsx';
@@ -304,6 +305,11 @@ export default class InviteMemberModal extends React.Component {
var content = null;
var sendButton = null;
+ var defaultChannelName = '';
+ if (ChannelStore.getByName(Constants.DEFAULT_CHANNEL)) {
+ defaultChannelName = ChannelStore.getByName(Constants.DEFAULT_CHANNEL).display_name;
+ }
+
if (this.state.emailEnabled && this.state.userCreationEnabled) {
content = (
<div>
@@ -312,10 +318,10 @@ export default class InviteMemberModal extends React.Component {
type='button'
className='btn btn-default'
onClick={this.addInviteFields}
- >Add another</button>
+ >{'Add another'}</button>
<br/>
<br/>
- <span>People invited automatically join Town Square channel.</span>
+ <span>{'People invited automatically join the '}<strong>{defaultChannelName}</strong>{' channel.'}</span>
</div>
);
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index 21683bb01..26bd6adde 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -223,13 +223,13 @@ export default class PostInfo extends React.Component {
/>
</li>
<li className='col col__reply'>
- {comments}
<div
className='dropdown'
ref='dotMenu'
>
{dropdown}
</div>
+ {comments}
<Overlay
show={this.state.show}
target={() => ReactDOM.findDOMNode(this.refs.dotMenu)}
diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx
index a28efbd04..7d8c7e265 100644
--- a/web/react/components/posts_view.jsx
+++ b/web/react/components/posts_view.jsx
@@ -24,6 +24,7 @@ export default class PostsView extends React.Component {
this.updateScrolling = this.updateScrolling.bind(this);
this.handleResize = this.handleResize.bind(this);
this.scrollToBottom = this.scrollToBottom.bind(this);
+ this.scrollToBottomAnimated = this.scrollToBottomAnimated.bind(this);
this.jumpToPostNode = null;
this.wasAtBottom = true;
@@ -339,6 +340,10 @@ export default class PostsView extends React.Component {
this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
});
}
+ scrollToBottomAnimated() {
+ var postList = $(this.refs.postlist);
+ postList.animate({scrollTop: this.refs.postlist.scrollHeight}, '500');
+ }
componentDidMount() {
if (this.props.postList != null) {
this.updateScrolling();
@@ -458,7 +463,7 @@ export default class PostsView extends React.Component {
<ScrollToBottomArrows
isScrolling={this.state.isScrolling}
atBottom={this.wasAtBottom}
- onClick={this.scrollToBottom}
+ onClick={this.scrollToBottomAnimated}
/>
<div
ref='postlist'
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index f218270da..0ee89b9fa 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -168,7 +168,7 @@ export function getMoreChannels(force) {
}
}
-export function getChannelExtraInfo(id) {
+export function getChannelExtraInfo(id, memberLimit) {
let channelId;
if (id) {
channelId = id;
@@ -185,6 +185,7 @@ export function getChannelExtraInfo(id) {
client.getChannelExtraInfo(
channelId,
+ memberLimit,
(data, textStatus, xhr) => {
callTracker['getChannelExtraInfo_' + channelId] = 0;
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index e1c331aff..96d1ef720 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -824,10 +824,17 @@ export function getChannelCounts(success, error) {
});
}
-export function getChannelExtraInfo(id, success, error) {
+export function getChannelExtraInfo(id, memberLimit, success, error) {
+ let url = '/api/v1/channels/' + id + '/extra_info';
+
+ if (memberLimit) {
+ url += '/' + memberLimit;
+ }
+
$.ajax({
- url: '/api/v1/channels/' + id + '/extra_info',
+ url,
dataType: 'json',
+ contentType: 'application/json',
type: 'GET',
success,
error: function onError(xhr, status, err) {
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 95eca7c3a..2ddd0e5e3 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -74,6 +74,21 @@ export function isSafari() {
return false;
}
+export function isIosChrome() {
+ // https://developer.chrome.com/multidevice/user-agent
+ return navigator.userAgent.indexOf('CriOS') !== -1;
+}
+
+export function isMobileApp() {
+ const userAgent = navigator.userAgent;
+
+ // the mobile app has different user agents for the native api calls and the shim, so handle them both
+ const isApi = userAgent.indexOf('Mattermost') !== -1;
+ const isShim = userAgent.indexOf('iPhone') !== -1 && userAgent.indexOf('Safari') === -1 && userAgent.indexOf('Chrome') === -1;
+
+ return isApi || isShim;
+}
+
export function isInRole(roles, inRole) {
var parts = roles.split(' ');
for (var i = 0; i < parts.length; i++) {
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index 937b08084..7b7c2d73a 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -286,8 +286,10 @@ body.ios {
z-index: 50;
@include opacity(0);
@include single-transition(all, 0.3s);
+ display: none;
&.scrolling {
+ display: block;
@include opacity(1);
}
}
@@ -417,12 +419,6 @@ body.ios {
background-color: beige;
}
- ul {
- margin: 0;
- padding: 0;
- }
-
-
p {
margin: 0;
line-height: 1.6em;
@@ -601,6 +597,7 @@ body.ios {
right: 0;
top: 30px;
width: 65px;
+ white-space: nowrap;
}
.permalink-popover {
@@ -634,8 +631,7 @@ body.ios {
.dropdown {
display: inline-block;
visibility: hidden;
- position: absolute;
- right: 0;
+ margin-right: 5px;
top: -1px;
.dropdown-menu {
@@ -671,20 +667,17 @@ body.ios {
@include legacy-pie-clearfix;
width: calc(100% - 75px);
- img {
- max-height: 400px;
+ p {
+ margin: 0 0 0.4em;
}
- ul {
- margin-bottom: 0.6em;
- padding: 5px 0 0 20px;
- }
-
- ul + p {
- margin-top: 1em;
+ img {
+ max-height: 400px;
}
ul, ol {
+ margin-bottom: 0.4em;
+
p {
margin-bottom: 0;
}
diff --git a/web/sass-files/sass/partials/_post_right.scss b/web/sass-files/sass/partials/_post_right.scss
index d820447f5..bd3d60622 100644
--- a/web/sass-files/sass/partials/_post_right.scss
+++ b/web/sass-files/sass/partials/_post_right.scss
@@ -25,6 +25,7 @@
.col__reply {
top: 0;
+ text-align: right;
}
}
diff --git a/web/templates/head.html b/web/templates/head.html
index 70c94e8ff..08d8726ea 100644
--- a/web/templates/head.html
+++ b/web/templates/head.html
@@ -98,7 +98,7 @@
});
if (window.mm_config.EnableDeveloper === 'true') {
- window.ErrorStore.storeLastError('DEVELOPER MODE: A javascript error has occured. Please use the javascript console to capture and report the error (row: ' + line + ' col: ' + column + ').');
+ window.ErrorStore.storeLastError({message: 'DEVELOPER MODE: A javascript error has occured. Please use the javascript console to capture and report the error (row: ' + line + ' col: ' + column + ').'});
window.ErrorStore.emitChange();
}
}
diff --git a/web/web.go b/web/web.go
index bf1208adc..634a9d851 100644
--- a/web/web.go
+++ b/web/web.go
@@ -4,8 +4,8 @@
package web
import (
- l4g "code.google.com/p/log4go"
"fmt"
+ l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
"github.com/mattermost/platform/api"
"github.com/mattermost/platform/model"
@@ -70,6 +70,8 @@ func InitWeb() {
mainrouter.Handle("/verify_email", api.AppHandlerIndependent(verifyEmail)).Methods("GET")
mainrouter.Handle("/find_team", api.AppHandlerIndependent(findTeam)).Methods("GET")
mainrouter.Handle("/signup_team", api.AppHandlerIndependent(signup)).Methods("GET")
+ mainrouter.Handle("/login/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET") // Remove after a few releases (~1.8)
+ mainrouter.Handle("/signup/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET") // Remove after a few releases (~1.8)
mainrouter.Handle("/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET")
mainrouter.Handle("/admin_console", api.UserRequired(adminConsole)).Methods("GET")
@@ -711,7 +713,7 @@ func completeOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
- uri := c.GetSiteURL() + "/" + service + "/complete"
+ uri := c.GetSiteURL() + "/signup/" + service + "/complete" // Remove /signup after a few releases (~1.8)
if body, team, props, err := api.AuthorizeOAuthUser(service, code, state, uri); err != nil {
c.Err = err