Send the 25 last messages for each channel to the client on load

This commit is contained in:
Ken-Håvard Lieng 2017-04-20 01:51:55 +02:00
parent c840d51e16
commit eedc687f18
26 changed files with 300 additions and 268 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,13 +1,14 @@
import * as actions from '../actions'; import * as actions from '../actions';
import { findBreakpoints, messageHeight, linkify, timestamp } from '../util'; import { findBreakpoints, messageHeight, linkify, timestamp } from '../util';
function initMessage(message, state) { function initMessage(message, server, tab, state) {
message.dest = message.to || message.from || message.server; if (message.time) {
if (message.from && message.from.indexOf('.') !== -1) { message.time = timestamp(new Date(message.time * 1000));
message.dest = message.server; } else {
message.time = timestamp();
} }
if (message.dest.charAt(0) === '#') { if (tab.charAt(0) === '#') {
message.channel = true; message.channel = true;
} }
@ -32,6 +33,13 @@ function initMessage(message, state) {
return message; return message;
} }
function getMessageTab(server, to) {
if (!to || to.indexOf('.') !== -1) {
return server;
}
return to;
}
export function updateMessageHeight() { export function updateMessageHeight() {
return (dispatch, getState) => dispatch({ return (dispatch, getState) => dispatch({
type: actions.UPDATE_MESSAGE_HEIGHT, type: actions.UPDATE_MESSAGE_HEIGHT,
@ -46,13 +54,12 @@ export function sendMessage(content, to, server) {
dispatch({ dispatch({
type: actions.SEND_MESSAGE, type: actions.SEND_MESSAGE,
server,
tab: to,
message: initMessage({ message: initMessage({
from: state.servers.getIn([server, 'nick']), from: state.servers.getIn([server, 'nick']),
content, content
to, }, server, to, state),
server,
time: timestamp()
}, state),
socket: { socket: {
type: 'message', type: 'message',
data: { content, to, server } data: { content, to, server }
@ -61,27 +68,29 @@ export function sendMessage(content, to, server) {
}; };
} }
export function addMessage(message) { export function addMessage(message, server, to) {
message.time = timestamp(); const tab = getMessageTab(server, to);
return (dispatch, getState) => dispatch({ return (dispatch, getState) => dispatch({
type: actions.ADD_MESSAGE, type: actions.ADD_MESSAGE,
message: initMessage(message, getState()) server,
tab,
message: initMessage(message, server, tab, getState())
}); });
} }
export function addMessages(messages) { export function addMessages(messages, server, to) {
const now = timestamp(); const tab = getMessageTab(server, to);
return (dispatch, getState) => { return (dispatch, getState) => {
const state = getState(); const state = getState();
messages.forEach(message => { messages.forEach(message => initMessage(message, server, message.tab || tab, state));
initMessage(message, state).time = now;
});
dispatch({ dispatch({
type: actions.ADD_MESSAGES, type: actions.ADD_MESSAGES,
server,
tab,
messages messages
}); });
}; };
@ -89,29 +98,24 @@ export function addMessages(messages) {
export function broadcast(message, server, channels) { export function broadcast(message, server, channels) {
return addMessages(channels.map(channel => ({ return addMessages(channels.map(channel => ({
server, tab: channel,
to: channel,
content: message, content: message,
type: 'info' type: 'info'
}))); })), server);
} }
export function inform(message, server, channel) { export function inform(message, server, channel) {
if (Array.isArray(message)) { if (Array.isArray(message)) {
return addMessages(message.map(line => ({ return addMessages(message.map(line => ({
server,
to: channel,
content: line, content: line,
type: 'info' type: 'info'
}))); })), server, channel);
} }
return addMessage({ return addMessage({
server,
to: channel,
content: message, content: message,
type: 'info' type: 'info'
}); }, server, channel);
} }
export function runCommand(command, channel, server) { export function runCommand(command, channel, server) {

View File

@ -1,11 +1,12 @@
import { push, replace } from 'react-router-redux'; import { push, replace } from 'react-router-redux';
import * as actions from '../actions'; import * as actions from '../actions';
export function select(server, channel, pm) { export function select(server, name) {
const pm = name && name.charAt(0) !== '#';
if (pm) { if (pm) {
return push(`/${server}/pm/${channel}`); return push(`/${server}/pm/${name}`);
} else if (channel) { } else if (name) {
return push(`/${server}/${encodeURIComponent(channel)}`); return push(`/${server}/${encodeURIComponent(name)}`);
} }
return push(`/${server}`); return push(`/${server}`);
@ -22,7 +23,7 @@ export function updateSelection() {
dispatch(replace('/connect')); dispatch(replace('/connect'));
} else if (history.size > 0) { } else if (history.size > 0) {
const tab = history.last(); const tab = history.last();
dispatch(select(tab.server, tab.channel || tab.user, tab.user)); dispatch(select(tab.server, tab.name));
} else if (servers.has(server)) { } else if (servers.has(server)) {
dispatch(select(server)); dispatch(select(server));
} else { } else {
@ -31,18 +32,10 @@ export function updateSelection() {
}; };
} }
export function setSelectedChannel(server, channel = null) { export function setSelectedTab(server, name = null) {
return { return {
type: actions.SELECT_TAB, type: actions.SELECT_TAB,
server, server,
channel name
};
}
export function setSelectedUser(server, user = null) {
return {
type: actions.SELECT_TAB,
server,
user
}; };
} }

View File

@ -7,10 +7,10 @@ export default class ChatTitle extends PureComponent {
handleLeaveClick = () => { handleLeaveClick = () => {
const { tab, disconnect, part, closePrivateChat } = this.props; const { tab, disconnect, part, closePrivateChat } = this.props;
if (tab.channel) { if (tab.isChannel()) {
part([tab.channel], tab.server); part([tab.name], tab.server);
} else if (tab.user) { } else if (tab.name) {
closePrivateChat(tab.server, tab.user); closePrivateChat(tab.server, tab.name);
} else { } else {
disconnect(tab.server); disconnect(tab.server);
} }
@ -22,9 +22,9 @@ export default class ChatTitle extends PureComponent {
topic = topic ? linkify(topic) : null; topic = topic ? linkify(topic) : null;
let leaveTitle; let leaveTitle;
if (tab.channel) { if (tab.isChannel()) {
leaveTitle = 'Leave'; leaveTitle = 'Leave';
} else if (tab.user) { } else if (tab.name) {
leaveTitle = 'Close'; leaveTitle = 'Close';
} else { } else {
leaveTitle = 'Disconnect'; leaveTitle = 'Disconnect';

View File

@ -1,12 +1,7 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
export default class Message extends PureComponent { export default class Message extends PureComponent {
handleSenderClick = () => { handleNickClick = () => this.props.onNickClick(this.props.message);
const { message, openPrivateChat, select } = this.props;
openPrivateChat(message.server, message.from);
select(message.server, message.from, true);
};
render() { render() {
const { message } = this.props; const { message } = this.props;
@ -21,7 +16,7 @@ export default class Message extends PureComponent {
<p className={className} style={style}> <p className={className} style={style}>
<span className="message-time">{message.time}</span> <span className="message-time">{message.time}</span>
{message.from && {message.from &&
<span className="message-sender" onClick={this.handleSenderClick}> <span className="message-sender" onClick={this.handleNickClick}>
{' '}{message.from} {' '}{message.from}
</span> </span>
}{' '}{message.content} }{' '}{message.content}

View File

@ -31,11 +31,11 @@ export default class MessageBox extends PureComponent {
listRef = el => { this.list = el; }; listRef = el => { this.list = el; };
updateWidth = (width) => { updateWidth = (width) => {
const { isChannel, setWrapWidth, updateMessageHeight } = this.props; const { tab, setWrapWidth, updateMessageHeight } = this.props;
let wrapWidth = width || this.width; let wrapWidth = width || this.width;
if (width) { if (width) {
if (isChannel && window.innerWidth > 600) { if (tab.isChannel() && window.innerWidth > 600) {
wrapWidth += 200; wrapWidth += 200;
} }
@ -64,15 +64,14 @@ export default class MessageBox extends PureComponent {
}; };
renderMessage = ({ index, style, key }) => { renderMessage = ({ index, style, key }) => {
const { messages, select, openPrivateChat } = this.props; const { messages, onNickClick } = this.props;
return ( return (
<Message <Message
key={key} key={key}
message={messages.get(index)} message={messages.get(index)}
select={select}
openPrivateChat={openPrivateChat}
style={style} style={style}
onNickClick={onNickClick}
/> />
); );
}; };

View File

@ -11,11 +11,9 @@ export default class MessageInput extends PureComponent {
if (e.which === 13 && e.target.value) { if (e.which === 13 && e.target.value) {
if (e.target.value[0] === '/') { if (e.target.value[0] === '/') {
runCommand(e.target.value, tab.channel || tab.user, tab.server); runCommand(e.target.value, tab.name, tab.server);
} else if (tab.channel) { } else if (tab.name) {
sendMessage(e.target.value, tab.channel, tab.server); sendMessage(e.target.value, tab.name, tab.server);
} else if (tab.user) {
sendMessage(e.target.value, tab.user, tab.server);
} }
addInputHistory(e.target.value); addInputHistory(e.target.value);

View File

@ -3,7 +3,7 @@ import TabListItem from './TabListItem';
export default class TabList extends PureComponent { export default class TabList extends PureComponent {
handleTabClick = (server, target) => { handleTabClick = (server, target) => {
this.props.select(server, target, target && target.charAt(0) !== '#'); this.props.select(server, target);
this.props.hideMenu(); this.props.hideMenu();
}; };
@ -18,7 +18,7 @@ export default class TabList extends PureComponent {
}; };
render() { render() {
const { channels, servers, privateChats, showTabList, selected } = this.props; const { tab, channels, servers, privateChats, showTabList } = this.props;
const className = showTabList ? 'tablist off-canvas' : 'tablist'; const className = showTabList ? 'tablist off-canvas' : 'tablist';
const tabs = []; const tabs = [];
@ -28,11 +28,7 @@ export default class TabList extends PureComponent {
key={address} key={address}
server={address} server={address}
content={servers.getIn([address, 'name'])} content={servers.getIn([address, 'name'])}
selected={ selected={tab.server === address && tab.name === null}
selected.server === address &&
selected.channel === null &&
selected.user === null
}
connected={servers.getIn([address, 'connected'])} connected={servers.getIn([address, 'connected'])}
onClick={this.handleTabClick} onClick={this.handleTabClick}
/> />
@ -44,7 +40,7 @@ export default class TabList extends PureComponent {
server={address} server={address}
target={name} target={name}
content={name} content={name}
selected={selected.server === address && selected.channel === name} selected={tab.server === address && tab.name === name}
onClick={this.handleTabClick} onClick={this.handleTabClick}
/> />
)); ));
@ -56,7 +52,7 @@ export default class TabList extends PureComponent {
server={address} server={address}
target={nick} target={nick}
content={nick} content={nick}
selected={selected.server === address && selected.user === nick} selected={tab.server === address && tab.name === nick}
onClick={this.handleTabClick} onClick={this.handleTabClick}
/> />
)); ));

View File

@ -34,7 +34,7 @@ export default class UserList extends PureComponent {
const className = showUserList ? 'userlist off-canvas' : 'userlist'; const className = showUserList ? 'userlist off-canvas' : 'userlist';
const style = {}; const style = {};
if (!tab.channel) { if (!tab.isChannel()) {
style.display = 'none'; style.display = 'none';
} }

View File

@ -26,7 +26,7 @@ function mapStateToProps(state) {
channels: state.channels, channels: state.channels,
privateChats: state.privateChats, privateChats: state.privateChats,
showTabList: state.ui.showTabList, showTabList: state.ui.showTabList,
selected: state.tab.selected tab: state.tab.selected
}; };
} }

View File

@ -11,7 +11,7 @@ import UserList from '../components/UserList';
import { part } from '../actions/channel'; import { part } from '../actions/channel';
import { openPrivateChat, closePrivateChat } from '../actions/privateChat'; import { openPrivateChat, closePrivateChat } from '../actions/privateChat';
import { searchMessages, toggleSearch } from '../actions/search'; import { searchMessages, toggleSearch } from '../actions/search';
import { select, setSelectedChannel, setSelectedUser } from '../actions/tab'; import { select, setSelectedTab } from '../actions/tab';
import { runCommand, sendMessage, updateMessageHeight } from '../actions/message'; import { runCommand, sendMessage, updateMessageHeight } from '../actions/message';
import { disconnect } from '../actions/server'; import { disconnect } from '../actions/server';
import { setWrapWidth, setCharWidth } from '../actions/environment'; import { setWrapWidth, setCharWidth } from '../actions/environment';
@ -20,12 +20,8 @@ import { toggleUserList } from '../actions/ui';
import * as inputHistoryActions from '../actions/inputHistory'; import * as inputHistoryActions from '../actions/inputHistory';
function updateSelected({ params, dispatch }) { function updateSelected({ params, dispatch }) {
if (params.channel) { if (params.server) {
dispatch(setSelectedChannel(params.server, params.channel)); dispatch(setSelectedTab(params.server, params.channel || params.user));
} else if (params.user) {
dispatch(setSelectedUser(params.server, params.user));
} else if (params.server) {
dispatch(setSelectedChannel(params.server));
} }
} }
@ -56,19 +52,26 @@ class Chat extends PureComponent {
handleSearch = phrase => { handleSearch = phrase => {
const { dispatch, tab } = this.props; const { dispatch, tab } = this.props;
if (tab.channel) { if (tab.isChannel()) {
dispatch(searchMessages(tab.server, tab.channel, phrase)); dispatch(searchMessages(tab.server, tab.name, phrase));
} }
}; };
handleMessageNickClick = message => {
const { tab } = this.props;
this.props.openPrivateChat(tab.server, message.from);
this.props.select(tab.server, message.from);
};
render() { render() {
const { title, tab, channel, search, history, const { title, tab, channel, search, history,
messages, users, showUserList, inputActions } = this.props; messages, users, showUserList, inputActions } = this.props;
let chatClass; let chatClass;
if (tab.channel) { if (tab.isChannel()) {
chatClass = 'chat-channel'; chatClass = 'chat-channel';
} else if (tab.user) { } else if (tab.name) {
chatClass = 'chat-private'; chatClass = 'chat-private';
} else { } else {
chatClass = 'chat-server'; chatClass = 'chat-server';
@ -92,12 +95,10 @@ class Chat extends PureComponent {
/> />
<MessageBox <MessageBox
messages={messages} messages={messages}
isChannel={tab.channel !== null}
tab={tab} tab={tab}
setWrapWidth={this.props.setWrapWidth} setWrapWidth={this.props.setWrapWidth}
updateMessageHeight={this.props.updateMessageHeight} updateMessageHeight={this.props.updateMessageHeight}
select={this.props.select} onNickClick={this.handleMessageNickClick}
openPrivateChat={this.props.openPrivateChat}
/> />
<MessageInput <MessageInput
tab={tab} tab={tab}
@ -136,13 +137,13 @@ const historySelector = state => {
const selectedMessagesSelector = createSelector( const selectedMessagesSelector = createSelector(
tabSelector, tabSelector,
messageSelector, messageSelector,
(tab, messages) => messages.getIn([tab.server, tab.channel || tab.user || tab.server], List()) (tab, messages) => messages.getIn([tab.server, tab.name || tab.server], List())
); );
const selectedChannelSelector = createSelector( const selectedChannelSelector = createSelector(
tabSelector, tabSelector,
channelSelector, channelSelector,
(tab, channels) => channels.getIn([tab.server, tab.channel], Map()) (tab, channels) => channels.getIn([tab.server, tab.name], Map())
); );
const usersSelector = createSelector( const usersSelector = createSelector(
@ -153,7 +154,7 @@ const usersSelector = createSelector(
const titleSelector = createSelector( const titleSelector = createSelector(
tabSelector, tabSelector,
serverSelector, serverSelector,
(tab, servers) => tab.channel || tab.user || servers.getIn([tab.server, 'name']) (tab, servers) => tab.name || servers.getIn([tab.server, 'name'])
); );
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({

View File

@ -10,6 +10,7 @@ import createRoutes from './routes';
import Socket from './util/Socket'; import Socket from './util/Socket';
import handleSocket from './socket'; import handleSocket from './socket';
import Root from './containers/Root'; import Root from './containers/Root';
import { addMessages } from './actions/message';
const host = DEV ? `${window.location.hostname}:1337` : window.location.host; const host = DEV ? `${window.location.hostname}:1337` : window.location.host;
const socket = new Socket(host); const socket = new Socket(host);
@ -46,6 +47,11 @@ if (env.users) {
}); });
} }
if (env.messages) {
const { messages, server, to } = env.messages;
store.dispatch(addMessages(messages, server, to));
}
handleSocket(socket, store); handleSocket(socket, store);
const routes = createRoutes(); const routes = createRoutes();

View File

@ -5,9 +5,7 @@ import * as actions from '../actions';
const Message = Record({ const Message = Record({
id: null, id: null,
server: null,
from: null, from: null,
to: null,
content: '', content: '',
time: null, time: null,
type: null, type: null,
@ -17,46 +15,40 @@ const Message = Record({
breakpoints: null breakpoints: null
}); });
function addMessage(state, message) { function addMessage(state, { server, tab, message }) {
return state.updateIn([message.server, message.dest], List(), return state.updateIn([server, tab], List(), list => list.push(new Message(message)));
list => list.push(new Message(message)));
} }
export default createReducer(Map(), { export default createReducer(Map(), {
[actions.SEND_MESSAGE](state, action) { [actions.SEND_MESSAGE]: addMessage,
return addMessage(state, action.message); [actions.ADD_MESSAGE]: addMessage,
},
[actions.ADD_MESSAGE](state, action) { [actions.ADD_MESSAGES](state, { server, tab, messages }) {
return addMessage(state, action.message);
},
[actions.ADD_MESSAGES](state, action) {
return state.withMutations(s => return state.withMutations(s =>
action.messages.forEach(message => messages.forEach(message =>
addMessage(s, message) s.updateIn([server, tab], List(), list => list.push(new Message(message)))
) )
); );
}, },
[actions.DISCONNECT](state, action) { [actions.DISCONNECT](state, { server }) {
return state.delete(action.server); return state.delete(server);
}, },
[actions.PART](state, action) { [actions.PART](state, { server, channels }) {
return state.withMutations(s => return state.withMutations(s =>
action.channels.forEach(channel => channels.forEach(channel =>
s.deleteIn([action.server, channel]) s.deleteIn([server, channel])
) )
); );
}, },
[actions.UPDATE_MESSAGE_HEIGHT](state, action) { [actions.UPDATE_MESSAGE_HEIGHT](state, { wrapWidth, charWidth }) {
return state.withMutations(s => return state.withMutations(s =>
s.forEach((server, serverKey) => s.forEach((server, serverKey) =>
server.forEach((target, targetKey) => server.forEach((target, targetKey) =>
target.forEach((message, index) => s.setIn([serverKey, targetKey, index, 'height'], target.forEach((message, index) => s.setIn([serverKey, targetKey, index, 'height'],
messageHeight(message, action.wrapWidth, action.charWidth, 6 * action.charWidth)) messageHeight(message, wrapWidth, charWidth, 6 * charWidth))
) )
) )
) )

View File

@ -5,10 +5,13 @@ import * as actions from '../actions';
const Tab = Record({ const Tab = Record({
server: null, server: null,
channel: null, name: null
user: null
}); });
Tab.prototype.isChannel = function isChannel() {
return this.name && this.name.charAt(0) === '#';
};
const State = Record({ const State = Record({
selected: new Tab(), selected: new Tab(),
history: List() history: List()
@ -24,13 +27,13 @@ export default createReducer(new State(), {
[actions.PART](state, action) { [actions.PART](state, action) {
return state.set('history', state.history.filter(tab => return state.set('history', state.history.filter(tab =>
!(tab.server === action.server && tab.channel === action.channels[0]) !(tab.server === action.server && tab.name === action.channels[0])
)); ));
}, },
[actions.CLOSE_PRIVATE_CHAT](state, action) { [actions.CLOSE_PRIVATE_CHAT](state, action) {
return state.set('history', state.history.filter(tab => return state.set('history', state.history.filter(tab =>
!(tab.server === action.server && tab.user === action.nick) !(tab.server === action.server && tab.name === action.nick)
)); ));
}, },

View File

@ -22,11 +22,15 @@ function findChannels(state, server, user) {
export default function handleSocket(socket, { dispatch, getState }) { export default function handleSocket(socket, { dispatch, getState }) {
const handlers = { const handlers = {
message(message) { message(message) {
dispatch(addMessage(message)); dispatch(addMessage(message, message.server, message.to));
}, },
pm(message) { pm(message) {
dispatch(addMessage(message)); dispatch(addMessage(message, message.server, message.from));
},
messages({ messages, server, to }) {
dispatch(addMessages(messages, server, to));
}, },
join(data) { join(data) {
@ -67,11 +71,7 @@ export default function handleSocket(socket, { dispatch, getState }) {
}, },
motd({ content, server }) { motd({ content, server }) {
dispatch(addMessages(content.map(line => ({ dispatch(addMessages(content.map(line => ({ content: line })), server));
server,
to: server,
content: line
}))));
}, },
whois(data) { whois(data) {
@ -84,7 +84,7 @@ export default function handleSocket(socket, { dispatch, getState }) {
`Host: ${data.host}`, `Host: ${data.host}`,
`Server: ${data.server}`, `Server: ${data.server}`,
`Channels: ${data.channels}` `Channels: ${data.channels}`
], tab.server, tab.channel)); ], tab.server, tab.name));
}, },
print({ server, message }) { print({ server, message }) {

View File

@ -3,7 +3,7 @@ import Backoff from 'backo';
export default class Socket { export default class Socket {
constructor(host) { constructor(host) {
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'; const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
this.url = `${protocol}://${host}/ws`; this.url = `${protocol}://${host}/ws?path=${window.location.pathname}`;
this.connectTimeout = 20000; this.connectTimeout = 20000;
this.pingTimeout = 30000; this.pingTimeout = 30000;

View File

@ -26,7 +26,7 @@ type indexData struct {
Users *Userlist `json:"users,omitempty"` Users *Userlist `json:"users,omitempty"`
// Last messages in the selected channel // Last messages in the selected channel
Messages []storage.Message `json:"messages,omitempty"` Messages *Messages `json:"messages,omitempty"`
} }
func getIndexData(r *http.Request, session *Session) *indexData { func getIndexData(r *http.Request, session *Session) *indexData {
@ -68,6 +68,15 @@ func getIndexData(r *http.Request, session *Session) *indexData {
Users: users, Users: users,
} }
} }
messages, err := session.user.GetLastMessages(params[0], params[1], 25)
if err == nil && len(messages) > 0 {
data.Messages = &Messages{
Server: params[0],
To: params[1],
Messages: messages,
}
}
} }
return &data return &data

View File

@ -67,6 +67,12 @@ type Message struct {
Content string `json:"content"` Content string `json:"content"`
} }
type Messages struct {
Server string `json:"server"`
To string `json:"to"`
Messages []storage.Message `json:"messages"`
}
type Topic struct { type Topic struct {
Server string `json:"server"` Server string `json:"server"`
Channel string `json:"channel"` Channel string `json:"channel"`

View File

@ -119,7 +119,7 @@ func upgradeWS(w http.ResponseWriter, r *http.Request, session *Session) {
return return
} }
newWSHandler(conn, session).run() newWSHandler(conn, session, r).run()
} }
func createHTTPSRedirect(portHTTPS string) http.HandlerFunc { func createHTTPSRedirect(portHTTPS string) http.HandlerFunc {

View File

@ -5,6 +5,8 @@ import (
"encoding/json" "encoding/json"
"log" "log"
"net" "net"
"net/http"
"strings"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -20,13 +22,13 @@ type wsHandler struct {
handlers map[string]func([]byte) handlers map[string]func([]byte)
} }
func newWSHandler(conn *websocket.Conn, session *Session) *wsHandler { func newWSHandler(conn *websocket.Conn, session *Session, r *http.Request) *wsHandler {
h := &wsHandler{ h := &wsHandler{
ws: newWSConn(conn), ws: newWSConn(conn),
session: session, session: session,
addr: conn.RemoteAddr().String(), addr: conn.RemoteAddr().String(),
} }
h.init() h.init(r)
h.initHandlers() h.initHandlers()
return h return h
} }
@ -55,7 +57,7 @@ func (h *wsHandler) dispatchRequest(req WSRequest) {
} }
} }
func (h *wsHandler) init() { func (h *wsHandler) init(r *http.Request) {
h.session.setWS(h.addr, h.ws) h.session.setWS(h.addr, h.ws)
log.Println(h.addr, "[Session] User ID:", h.session.user.ID, "|", log.Println(h.addr, "[Session] User ID:", h.session.user.ID, "|",
@ -63,13 +65,27 @@ func (h *wsHandler) init() {
h.session.numWS(), "WebSocket connections") h.session.numWS(), "WebSocket connections")
channels := h.session.user.GetChannels() channels := h.session.user.GetChannels()
params := strings.Split(strings.Trim(r.URL.Query().Get("path"), "/"), "/")
for _, channel := range channels { for _, channel := range channels {
if len(params) > 1 && channel.Server == params[0] && channel.Name == params[1] {
continue
}
h.session.sendJSON("users", Userlist{ h.session.sendJSON("users", Userlist{
Server: channel.Server, Server: channel.Server,
Channel: channel.Name, Channel: channel.Name,
Users: channelStore.GetUsers(channel.Server, channel.Name), Users: channelStore.GetUsers(channel.Server, channel.Name),
}) })
messages, err := h.session.user.GetLastMessages(channel.Server, channel.Name, 25)
if err == nil && len(messages) > 0 {
h.session.sendJSON("messages", Messages{
Server: channel.Server,
To: channel.Name,
Messages: messages,
})
}
} }
} }

View File

@ -22,9 +22,7 @@ struct Channel {
struct Message { struct Message {
ID uint64 ID uint64
Server string
From string From string
To string
Content string Content string
Time int64 Time int64
} }

View File

@ -702,21 +702,6 @@ func (d *Channel) Unmarshal(buf []byte) (uint64, error) {
func (d *Message) Size() (s uint64) { func (d *Message) Size() (s uint64) {
{
l := uint64(len(d.Server))
{
t := l
for t >= 0x80 {
t >>= 7
s++
}
s++
}
s += l
}
{ {
l := uint64(len(d.From)) l := uint64(len(d.From))
@ -732,21 +717,6 @@ func (d *Message) Size() (s uint64) {
} }
s += l s += l
} }
{
l := uint64(len(d.To))
{
t := l
for t >= 0x80 {
t >>= 7
s++
}
s++
}
s += l
}
{ {
l := uint64(len(d.Content)) l := uint64(len(d.Content))
@ -781,25 +751,6 @@ func (d *Message) Marshal(buf []byte) ([]byte, error) {
*(*uint64)(unsafe.Pointer(&buf[0])) = d.ID *(*uint64)(unsafe.Pointer(&buf[0])) = d.ID
} }
{
l := uint64(len(d.Server))
{
t := uint64(l)
for t >= 0x80 {
buf[i+8] = byte(t) | 0x80
t >>= 7
i++
}
buf[i+8] = byte(t)
i++
}
copy(buf[i+8:], d.Server)
i += l
}
{ {
l := uint64(len(d.From)) l := uint64(len(d.From))
@ -819,25 +770,6 @@ func (d *Message) Marshal(buf []byte) ([]byte, error) {
copy(buf[i+8:], d.From) copy(buf[i+8:], d.From)
i += l i += l
} }
{
l := uint64(len(d.To))
{
t := uint64(l)
for t >= 0x80 {
buf[i+8] = byte(t) | 0x80
t >>= 7
i++
}
buf[i+8] = byte(t)
i++
}
copy(buf[i+8:], d.To)
i += l
}
{ {
l := uint64(len(d.Content)) l := uint64(len(d.Content))
@ -876,26 +808,6 @@ func (d *Message) Unmarshal(buf []byte) (uint64, error) {
{ {
l := uint64(0) l := uint64(0)
{
bs := uint8(7)
t := uint64(buf[i+8] & 0x7F)
for buf[i+8]&0x80 == 0x80 {
i++
t |= uint64(buf[i+8]&0x7F) << bs
bs += 7
}
i++
l = t
}
d.Server = string(buf[i+8 : i+8+l])
i += l
}
{
l := uint64(0)
{ {
bs := uint8(7) bs := uint8(7)
@ -916,26 +828,6 @@ func (d *Message) Unmarshal(buf []byte) (uint64, error) {
{ {
l := uint64(0) l := uint64(0)
{
bs := uint8(7)
t := uint64(buf[i+8] & 0x7F)
for buf[i+8]&0x80 == 0x80 {
i++
t |= uint64(buf[i+8]&0x7F) << bs
bs += 7
}
i++
l = t
}
d.To = string(buf[i+8 : i+8+l])
i += l
}
{
l := uint64(0)
{ {
bs := uint8(7) bs := uint8(7)

View File

@ -7,16 +7,21 @@ import (
"time" "time"
"github.com/blevesearch/bleve" "github.com/blevesearch/bleve"
"github.com/blevesearch/bleve/analysis/analyzer/keyword"
"github.com/boltdb/bolt" "github.com/boltdb/bolt"
) )
type Message struct { type Message struct {
ID uint64 `json:"id"` ID uint64 `json:"id" bleve:"-"`
Server string `json:"server"` Server string `json:"-" bleve:"server"`
From string `json:"from"` From string `json:"from" bleve:"-"`
To string `json:"to,omitempty"` To string `json:"-" bleve:"to"`
Content string `json:"content"` Content string `json:"content" bleve:"content"`
Time int64 `json:"time"` Time int64 `json:"time" bleve:"-"`
}
func (m Message) Type() string {
return "message"
} }
func (u *User) LogMessage(server, from, to, content string) error { func (u *User) LogMessage(server, from, to, content string) error {
@ -167,7 +172,27 @@ func (u *User) openMessageLog() error {
indexPath := Path.Index(u.Username) indexPath := Path.Index(u.Username)
u.messageIndex, err = bleve.Open(indexPath) u.messageIndex, err = bleve.Open(indexPath)
if err == bleve.ErrorIndexPathDoesNotExist { if err == bleve.ErrorIndexPathDoesNotExist {
keywordMapping := bleve.NewTextFieldMapping()
keywordMapping.Analyzer = keyword.Name
keywordMapping.Store = false
keywordMapping.IncludeTermVectors = false
keywordMapping.IncludeInAll = false
contentMapping := bleve.NewTextFieldMapping()
contentMapping.Analyzer = "en"
contentMapping.Store = false
contentMapping.IncludeTermVectors = false
contentMapping.IncludeInAll = false
messageMapping := bleve.NewDocumentMapping()
messageMapping.StructTagKey = "bleve"
messageMapping.AddFieldMappingsAt("server", keywordMapping)
messageMapping.AddFieldMappingsAt("to", keywordMapping)
messageMapping.AddFieldMappingsAt("content", contentMapping)
mapping := bleve.NewIndexMapping() mapping := bleve.NewIndexMapping()
mapping.AddDocumentMapping("message", messageMapping)
u.messageIndex, err = bleve.New(indexPath, mapping) u.messageIndex, err = bleve.New(indexPath, mapping)
if err != nil { if err != nil {
return err return err

View File

@ -0,0 +1,38 @@
// Copyright (c) 2014 Couchbase, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package keyword
import (
"github.com/blevesearch/bleve/analysis"
"github.com/blevesearch/bleve/analysis/tokenizer/single"
"github.com/blevesearch/bleve/registry"
)
const Name = "keyword"
func AnalyzerConstructor(config map[string]interface{}, cache *registry.Cache) (*analysis.Analyzer, error) {
keywordTokenizer, err := cache.TokenizerNamed(single.Name)
if err != nil {
return nil, err
}
rv := analysis.Analyzer{
Tokenizer: keywordTokenizer,
}
return &rv, nil
}
func init() {
registry.RegisterAnalyzer(Name, AnalyzerConstructor)
}

View File

@ -0,0 +1,49 @@
// Copyright (c) 2014 Couchbase, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package single
import (
"github.com/blevesearch/bleve/analysis"
"github.com/blevesearch/bleve/registry"
)
const Name = "single"
type SingleTokenTokenizer struct {
}
func NewSingleTokenTokenizer() *SingleTokenTokenizer {
return &SingleTokenTokenizer{}
}
func (t *SingleTokenTokenizer) Tokenize(input []byte) analysis.TokenStream {
return analysis.TokenStream{
&analysis.Token{
Term: input,
Position: 1,
Start: 0,
End: len(input),
Type: analysis.AlphaNumeric,
},
}
}
func SingleTokenTokenizerConstructor(config map[string]interface{}, cache *registry.Cache) (analysis.Tokenizer, error) {
return NewSingleTokenTokenizer(), nil
}
func init() {
registry.RegisterTokenizer(Name, SingleTokenTokenizerConstructor)
}

12
vendor/vendor.json vendored
View File

@ -14,6 +14,12 @@
"revision": "0b1034dcbe067789a206f3267f41c6a1f9760b56", "revision": "0b1034dcbe067789a206f3267f41c6a1f9760b56",
"revisionTime": "2017-04-06T22:05:36Z" "revisionTime": "2017-04-06T22:05:36Z"
}, },
{
"checksumSHA1": "OM2QW7G5DfzaUzCoe23282875TE=",
"path": "github.com/blevesearch/bleve/analysis/analyzer/keyword",
"revision": "17e21be71ad41256baee0ae3023a048fa1826bb1",
"revisionTime": "2017-04-19T13:14:44Z"
},
{ {
"checksumSHA1": "IefDmVwLU3UiILeN35DA25gPFnc=", "checksumSHA1": "IefDmVwLU3UiILeN35DA25gPFnc=",
"path": "github.com/blevesearch/bleve/analysis/analyzer/standard", "path": "github.com/blevesearch/bleve/analysis/analyzer/standard",
@ -56,6 +62,12 @@
"revision": "0b1034dcbe067789a206f3267f41c6a1f9760b56", "revision": "0b1034dcbe067789a206f3267f41c6a1f9760b56",
"revisionTime": "2017-04-06T22:05:36Z" "revisionTime": "2017-04-06T22:05:36Z"
}, },
{
"checksumSHA1": "Lnopn2j55CFd15EBle12dzqQar8=",
"path": "github.com/blevesearch/bleve/analysis/tokenizer/single",
"revision": "17e21be71ad41256baee0ae3023a048fa1826bb1",
"revisionTime": "2017-04-19T13:14:44Z"
},
{ {
"checksumSHA1": "q7C04nlJLxKmemXLop0oyJhfi5M=", "checksumSHA1": "q7C04nlJLxKmemXLop0oyJhfi5M=",
"path": "github.com/blevesearch/bleve/analysis/tokenizer/unicode", "path": "github.com/blevesearch/bleve/analysis/tokenizer/unicode",