13 Commits
v0.6.3 ... v0.7

Author SHA1 Message Date
08ffc79a65 Remove local messages when closing dm tab, avoid sending open_dm if already open 2020-05-08 02:56:54 +02:00
2a72b198e3 Update dependencies 2020-05-08 02:10:22 +02:00
0d085a2b4d Clear prevSearch when closing modal 2020-05-07 08:10:25 +02:00
497f9e882c Unvendor fnv1a 2020-05-06 06:50:53 +02:00
7d97d10e76 Open dm tab on /msg 2020-05-06 04:50:27 +02:00
8305dd561d Log direct messages and keep track of open direct message tabs 2020-05-06 04:19:55 +02:00
e97bb519ed Merge pull request #61 from nhandler/patch-1
Replace Freenode with freenode
2020-05-05 07:26:38 +02:00
18acde5b2b Replace Freenode with freenode in test
The freenode IRC network brands itself with a lowercase `f`.
2020-05-04 21:37:39 -07:00
bab4732221 Replace Freenode with freenode in default config
The freenode IRC network brands itself with a lowercase `f`.
2020-05-04 21:36:21 -07:00
79af695d17 Add apple-touch-icon 2020-05-05 03:34:36 +02:00
d98312f99b Cache manifest.json 2020-05-05 03:31:14 +02:00
010bb6a102 Sleep before first reconnect attempt 2020-05-05 01:35:05 +02:00
530e08b9ee Convert withModal to useModal 2020-05-03 09:05:16 +02:00
133 changed files with 3019 additions and 3514 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,10 @@
import { COMMAND } from 'state/actions';
import { join, part, invite, kick, setTopic } from 'state/channels';
import { sendMessage, raw } from 'state/messages';
import { openPrivateChat } from 'state/privateChats';
import { setNick, disconnect, whois, away } from 'state/servers';
import { select } from 'state/tab';
import { find } from 'utils';
import { find, isChannel } from 'utils';
import createCommandMiddleware, {
beforeHandler,
notFoundHandler
@ -45,10 +46,10 @@ export default createCommandMiddleware(COMMAND, {
}
},
part({ dispatch, server, channel, isChannel }, partChannel) {
part({ dispatch, server, channel, inChannel }, partChannel) {
if (partChannel) {
dispatch(part([partChannel], server));
} else if (isChannel) {
} else if (inChannel) {
dispatch(part([channel], server));
} else {
return error('This is not a channel');
@ -98,6 +99,9 @@ export default createCommandMiddleware(COMMAND, {
const msg = message.join(' ');
if (msg !== '') {
dispatch(sendMessage(message.join(' '), target, server));
if (!isChannel(target)) {
dispatch(openPrivateChat(server, target));
}
dispatch(select(server, target));
} else {
return error('Messages can not be empty');
@ -117,8 +121,8 @@ export default createCommandMiddleware(COMMAND, {
}
},
invite({ dispatch, server, channel, isChannel }, user, inviteChannel) {
if (!inviteChannel && !isChannel) {
invite({ dispatch, server, channel, inChannel }, user, inviteChannel) {
if (!inviteChannel && !inChannel) {
return error('This is not a channel');
}
@ -131,8 +135,8 @@ export default createCommandMiddleware(COMMAND, {
}
},
kick({ dispatch, server, channel, isChannel }, user) {
if (!isChannel) {
kick({ dispatch, server, channel, inChannel }, user) {
if (!inChannel) {
return error('This is not a channel');
}

View File

@ -1,4 +1,4 @@
import React, { Suspense, lazy, useState } from 'react';
import React, { Suspense, lazy, useState, useEffect } from 'react';
import Route from 'containers/Route';
import AppInfo from 'components/AppInfo';
import TabList from 'components/TabList';
@ -28,6 +28,11 @@ const App = ({
setRenderModals(true);
}
const [starting, setStarting] = useState(true);
useEffect(() => {
setTimeout(() => setStarting(false), 1000);
}, []);
const mainClass = cn('main-container', {
'off-canvas': showTabList
});
@ -40,7 +45,7 @@ const App = ({
return (
<div className="wrap" onClick={handleClick}>
{!connected && (
{!starting && !connected && (
<AppInfo type="error">
Connection lost, attempting to reconnect...
</AppInfo>

View File

@ -61,7 +61,7 @@ export default class TabList extends PureComponent {
<div
key={`${address}-chans}`}
className="tab-label"
onClick={() => openModal('channel', { server: address })}
onClick={() => openModal('channel', address)}
>
<span>CHANNELS {chanLabel}</span>
<Button title="Join Channel">+</Button>

View File

@ -1,20 +1,23 @@
import React, { memo, useState, useEffect, useCallback, useRef } from 'react';
import get from 'lodash/get';
import React, { memo, useState, useEffect, useRef } from 'react';
import Modal from 'react-modal';
import { useSelector, useDispatch } from 'react-redux';
import { FiUsers, FiX } from 'react-icons/fi';
import withModal from 'components/modals/withModal';
import useModal from 'components/modals/useModal';
import Button from 'components/ui/Button';
import { join } from 'state/channels';
import { select } from 'state/tab';
import { searchChannels } from 'state/channelSearch';
import { linkify } from 'utils';
const Channel = memo(({ server, name, topic, userCount, joined, ...props }) => {
const handleJoinClick = useCallback(() => props.join([name], server), []);
const Channel = memo(({ server, name, topic, userCount, joined }) => {
const dispatch = useDispatch();
const handleClick = () => dispatch(join([name], server));
return (
<div className="modal-channel-result">
<div className="modal-channel-result-header">
<h2 className="modal-channel-name" onClick={handleJoinClick}>
<h2 className="modal-channel-name" onClick={handleClick}>
{name}
</h2>
<FiUsers />
@ -25,7 +28,7 @@ const Channel = memo(({ server, name, topic, userCount, joined, ...props }) => {
<Button
className="modal-channel-button-join"
category="normal"
onClick={handleJoinClick}
onClick={handleClick}
>
Join
</Button>
@ -36,7 +39,12 @@ const Channel = memo(({ server, name, topic, userCount, joined, ...props }) => {
);
});
const AddChannel = ({ search, payload: { server }, onClose, ...props }) => {
const AddChannel = () => {
const [modal, server, closeModal] = useModal('channel');
const channels = useSelector(state => state.channels);
const search = useSelector(state => state.channelSearch);
const dispatch = useDispatch();
const [q, setQ] = useState('');
const inputEl = useRef();
@ -44,52 +52,52 @@ const AddChannel = ({ search, payload: { server }, onClose, ...props }) => {
const prevSearch = useRef('');
useEffect(() => {
inputEl.current.focus();
props.searchChannels(server, '');
}, []);
if (modal.isOpen) {
dispatch(searchChannels(server, ''));
setTimeout(() => inputEl.current.focus(), 0);
} else {
prevSearch.current = '';
setQ('');
}
}, [modal.isOpen]);
const handleSearch = useCallback(
e => {
let nextQ = e.target.value.trim().toLowerCase();
setQ(nextQ);
const handleSearch = e => {
let nextQ = e.target.value.trim().toLowerCase();
setQ(nextQ);
if (nextQ !== q) {
resultsEl.current.scrollTop = 0;
if (nextQ !== q) {
resultsEl.current.scrollTop = 0;
while (nextQ.charAt(0) === '#') {
nextQ = nextQ.slice(1);
}
if (nextQ !== prevSearch.current) {
prevSearch.current = nextQ;
props.searchChannels(server, nextQ);
}
while (nextQ.charAt(0) === '#') {
nextQ = nextQ.slice(1);
}
},
[q]
);
const handleKey = useCallback(e => {
if (nextQ !== prevSearch.current) {
prevSearch.current = nextQ;
dispatch(searchChannels(server, nextQ));
}
}
};
const handleKey = e => {
if (e.key === 'Enter') {
let channel = e.target.value.trim();
if (channel !== '') {
onClose(false);
closeModal(false);
if (channel.charAt(0) !== '#') {
channel = `#${channel}`;
}
props.join([channel], server);
props.select(server, channel);
dispatch(join([channel], server));
dispatch(select(server, channel));
}
}
}, []);
};
const handleLoadMore = useCallback(
() => props.searchChannels(server, q, search.results.length),
[q, search.results.length]
);
const handleLoadMore = () =>
dispatch(searchChannels(server, q, search.results.length));
let hasMore = !search.end;
if (hasMore) {
@ -104,7 +112,7 @@ const AddChannel = ({ search, payload: { server }, onClose, ...props }) => {
}
return (
<>
<Modal {...modal}>
<div className="modal-channel-input-wrap">
<input
ref={inputEl}
@ -117,7 +125,7 @@ const AddChannel = ({ search, payload: { server }, onClose, ...props }) => {
<Button
icon={FiX}
className="modal-close modal-channel-close"
onClick={onClose}
onClick={closeModal}
/>
</div>
<div ref={resultsEl} className="modal-channel-results">
@ -125,12 +133,7 @@ const AddChannel = ({ search, payload: { server }, onClose, ...props }) => {
<Channel
key={`${server} ${channel.name}`}
server={server}
join={props.join}
joined={get(
props.channels,
[server, channel.name, 'joined'],
false
)}
joined={channels[server]?.[channel.name]?.joined}
{...channel}
/>
))}
@ -143,15 +146,8 @@ const AddChannel = ({ search, payload: { server }, onClose, ...props }) => {
</Button>
)}
</div>
</>
</Modal>
);
};
export default withModal({
name: 'channel',
state: {
channels: state => state.channels,
search: state => state.channelSearch
},
actions: { searchChannels, join, select }
})(AddChannel);
export default AddChannel;

View File

@ -1,27 +1,26 @@
import React, { useCallback } from 'react';
import withModal from 'components/modals/withModal';
import React from 'react';
import Modal from 'react-modal';
import useModal from 'components/modals/useModal';
import Button from 'components/ui/Button';
const Confirm = ({
payload: { question, confirmation, onConfirm },
onClose
}) => {
const handleConfirm = useCallback(() => {
onClose(false);
const Confirm = () => {
const [modal, payload, closeModal] = useModal('confirm');
const { question, confirmation, onConfirm } = payload;
const handleConfirm = () => {
closeModal(false);
onConfirm();
}, []);
};
return (
<>
<Modal {...modal}>
<p>{question}</p>
<Button onClick={handleConfirm}>{confirmation || 'OK'}</Button>
<Button category="normal" onClick={onClose}>
<Button category="normal" onClick={closeModal}>
Cancel
</Button>
</>
</Modal>
);
};
export default withModal({
name: 'confirm'
})(Confirm);
export default Confirm;

View File

@ -1,21 +1,26 @@
import React from 'react';
import Modal from 'react-modal';
import { useSelector } from 'react-redux';
import { FiX } from 'react-icons/fi';
import Button from 'components/ui/Button';
import withModal from 'components/modals/withModal';
import useModal from 'components/modals/useModal';
import { getSelectedChannel } from 'state/channels';
import { linkify } from 'utils';
const Topic = ({ payload: { topic, channel }, onClose }) => {
const Topic = () => {
const [modal, channel, closeModal] = useModal('topic');
const topic = useSelector(state => getSelectedChannel(state)?.topic);
return (
<>
<Modal {...modal}>
<div className="modal-header">
<h2>Topic in {channel}</h2>
<Button icon={FiX} className="modal-close" onClick={onClose} />
<Button icon={FiX} className="modal-close" onClick={closeModal} />
</div>
<p className="modal-content">{linkify(topic)}</p>
</>
</Modal>
);
};
export default withModal({
name: 'topic'
})(Topic);
export default Topic;

View File

@ -0,0 +1,46 @@
import { useCallback } from 'react';
import Modal from 'react-modal';
import { useSelector, useDispatch } from 'react-redux';
import { closeModal } from 'state/modals';
Modal.setAppElement('#root');
const defaultPayload = {};
export default function useModal(name) {
const isOpen = useSelector(state => state.modals[name]?.isOpen || false);
const payload = useSelector(
state => state.modals[name]?.payload || defaultPayload
);
const dispatch = useDispatch();
const handleRequestClose = useCallback(
(dismissed = true) => {
dispatch(closeModal(name));
if (dismissed && payload.onDismiss) {
payload.onDismiss();
}
},
[payload.onDismiss]
);
const modalProps = {
isOpen,
contentLabel: name,
onRequestClose: handleRequestClose,
className: {
base: `modal modal-${name}`,
afterOpen: 'modal-opening',
beforeClose: 'modal-closing'
},
overlayClassName: {
base: 'modal-overlay',
afterOpen: 'modal-overlay-opening',
beforeClose: 'modal-overlay-closing'
},
closeTimeoutMS: 200
};
return [modalProps, payload, handleRequestClose];
}

View File

@ -1,71 +0,0 @@
import React, { useCallback } from 'react';
import Modal from 'react-modal';
import { createStructuredSelector } from 'reselect';
import get from 'lodash/get';
import { getModals, closeModal } from 'state/modals';
import connect from 'utils/connect';
import { bindActionCreators } from 'redux';
Modal.setAppElement('#root');
export default function withModal({ name, ...modalProps }) {
modalProps = {
className: {
base: `modal modal-${name}`,
afterOpen: 'modal-opening',
beforeClose: 'modal-closing'
},
overlayClassName: {
base: 'modal-overlay',
afterOpen: 'modal-overlay-opening',
beforeClose: 'modal-overlay-closing'
},
closeTimeoutMS: 200,
...modalProps
};
return WrappedComponent => {
const ReduxModal = ({ onRequestClose, ...props }) => {
const handleRequestClose = useCallback(
(dismissed = true) => {
onRequestClose();
if (dismissed && props.payload.onDismiss) {
props.payload.onDismiss();
}
},
[props.payload.onDismiss]
);
return (
<Modal
contentLabel={name}
onRequestClose={handleRequestClose}
{...modalProps}
{...props}
>
<WrappedComponent onClose={handleRequestClose} {...props} />
</Modal>
);
};
const mapState = createStructuredSelector({
isOpen: state => get(getModals(state), [name, 'isOpen'], false),
payload: state => get(getModals(state), [name, 'payload'], {}),
...modalProps.state
});
const mapDispatch = dispatch => {
const actions = { onRequestClose: () => dispatch(closeModal(name)) };
if (modalProps.actions) {
return {
...actions,
...bindActionCreators(modalProps.actions, dispatch)
};
}
return actions;
};
return connect(mapState, mapDispatch)(ReduxModal);
};
}

View File

@ -50,12 +50,7 @@ const ChatTitle = ({
{channel && channel.topic && (
<span
className="chat-topic"
onClick={() =>
openModal('topic', {
topic: channel.topic,
channel: channel.name
})
}
onClick={() => openModal('topic', channel.name)}
>
{channel.topic}
</span>

View File

@ -5,7 +5,7 @@ export const beforeHandler = '_before';
export const notFoundHandler = 'commandNotFound';
function createContext({ dispatch, getState }, { server, channel }) {
return { dispatch, getState, server, channel, isChannel: isChannel(channel) };
return { dispatch, getState, server, channel, inChannel: isChannel(channel) };
}
// TODO: Pull this out as convenience action

View File

@ -1,5 +1,6 @@
import { socket as socketActions } from 'state/actions';
import { getWrapWidth, appSet } from 'state/app';
import { getConnected, getWrapWidth, appSet } from 'state/app';
import { searchChannels } from 'state/channelSearch';
import { addMessages } from 'state/messages';
import { setSettings } from 'state/settings';
import { when } from 'utils/observe';
@ -12,6 +13,13 @@ function loadState({ store }, env) {
type: socketActions.SERVERS,
data: env.servers
});
when(store, getConnected, () =>
// Cache top channels for each server
env.servers.forEach(({ host }) =>
store.dispatch(searchChannels(host, ''))
)
);
}
if (env.channels) {
@ -21,6 +29,13 @@ function loadState({ store }, env) {
});
}
if (env.openDMs) {
store.dispatch({
type: 'PRIVATE_CHATS',
privateChats: env.openDMs
});
}
if (env.users) {
store.dispatch({
type: socketActions.USERS,

View File

@ -1,7 +1,7 @@
import Cookie from 'js-cookie';
import debounce from 'lodash/debounce';
import { getSelectedTab } from 'state/tab';
import { isChannel, stringifyTab } from 'utils';
import { stringifyTab } from 'utils';
import { observe } from 'utils/observe';
const saveTab = debounce(
@ -11,7 +11,7 @@ const saveTab = debounce(
export default function storage({ store }) {
observe(store, getSelectedTab, tab => {
if (isChannel(tab) || (tab.server && !tab.name)) {
if (tab.server) {
saveTab(tab);
}
});

View File

@ -250,6 +250,33 @@ describe('message reducer', () => {
}
});
});
it('deletes direct messages when closing a direct message tab', () => {
let state = {
srv: {
bob: [{ content: 'msg1' }, { content: 'msg2' }],
'#chan2': [{ content: 'msg' }]
},
srv2: {
'#chan1': [{ content: 'msg' }]
}
};
state = reducer(state, {
type: actions.CLOSE_PRIVATE_CHAT,
server: 'srv',
nick: 'bob'
});
expect(state).toEqual({
srv: {
'#chan2': [{ content: 'msg' }]
},
srv2: {
'#chan1': [{ content: 'msg' }]
}
});
});
});
describe('getMessageTab()', () => {

View File

@ -26,6 +26,7 @@ export const CLOSE_MODAL = 'CLOSE_MODAL';
export const CLOSE_PRIVATE_CHAT = 'CLOSE_PRIVATE_CHAT';
export const OPEN_PRIVATE_CHAT = 'OPEN_PRIVATE_CHAT';
export const PRIVATE_CHATS = 'PRIVATE_CHATS';
export const SEARCH_MESSAGES = 'SEARCH_MESSAGES';
export const TOGGLE_SEARCH = 'TOGGLE_SEARCH';

View File

@ -9,7 +9,7 @@ export const getWindowWidth = state => state.app.windowWidth;
export const getConnectDefaults = state => state.app.connectDefaults;
const initialState = {
connected: true,
connected: false,
wrapWidth: 0,
charWidth: 0,
windowWidth: 0,

View File

@ -3,11 +3,12 @@ import * as actions from 'state/actions';
const initialState = {
results: [],
end: false
end: false,
topCache: {}
};
export default createReducer(initialState, {
[actions.socket.CHANNEL_SEARCH](state, { results, start }) {
[actions.socket.CHANNEL_SEARCH](state, { results, start, server, q }) {
if (results) {
state.end = false;
@ -15,15 +16,20 @@ export default createReducer(initialState, {
state.results.push(...results);
} else {
state.results = results;
if (!q) {
state.topCache[server] = results;
}
}
} else {
state.end = true;
}
},
[actions.OPEN_MODAL](state, { name }) {
[actions.OPEN_MODAL](state, { name, payload }) {
if (name === 'channel') {
return initialState;
state.results = state.topCache[payload] || [];
state.end = false;
}
}
});

View File

@ -140,6 +140,10 @@ export default createReducer(
channels.forEach(channel => delete state[server][channel]);
},
[actions.CLOSE_PRIVATE_CHAT](state, { server, nick }) {
delete state[server][nick];
},
[actions.socket.CHANNEL_FORWARD](state, { server, old }) {
if (state[server]) {
delete state[server][old];

View File

@ -1,5 +1,4 @@
import sortBy from 'lodash/sortBy';
import { findIndex } from 'utils';
import createReducer from 'utils/createReducer';
import { updateSelection } from './tab';
import * as actions from './actions';
@ -10,7 +9,7 @@ function open(state, server, nick) {
if (!state[server]) {
state[server] = [];
}
if (findIndex(state[server], n => n === nick) === -1) {
if (!state[server].includes(nick)) {
state[server].push(nick);
state[server] = sortBy(state[server], v => v.toLowerCase());
}
@ -24,12 +23,22 @@ export default createReducer(
},
[actions.CLOSE_PRIVATE_CHAT](state, { server, nick }) {
const i = findIndex(state[server], n => n === nick);
const i = state[server]?.findIndex(n => n === nick);
if (i !== -1) {
state[server].splice(i, 1);
}
},
[actions.PRIVATE_CHATS](state, { privateChats }) {
privateChats.forEach(({ server, name }) => {
if (!state[server]) {
state[server] = [];
}
state[server].push(name);
});
},
[actions.socket.PM](state, action) {
if (action.from.indexOf('.') === -1) {
open(state, action.server, action.from);
@ -43,10 +52,18 @@ export default createReducer(
);
export function openPrivateChat(server, nick) {
return {
type: actions.OPEN_PRIVATE_CHAT,
server,
nick
return (dispatch, getState) => {
if (!getState().privateChats[server]?.includes(nick)) {
dispatch({
type: actions.OPEN_PRIVATE_CHAT,
server,
nick,
socket: {
type: 'open_dm',
data: { server, name: nick }
}
});
}
};
}
@ -55,7 +72,11 @@ export function closePrivateChat(server, nick) {
dispatch({
type: actions.CLOSE_PRIVATE_CHAT,
server,
nick
nick,
socket: {
type: 'close_dm',
data: { server, name: nick }
}
});
dispatch(updateSelection());
};

View File

@ -1,25 +1,5 @@
/* eslint-disable no-bitwise */
import { hsluvToHex } from 'hsluv';
//
// github.com/sindresorhus/fnv1a
//
const OFFSET_BASIS_32 = 2166136261;
const fnv1a = string => {
let hash = OFFSET_BASIS_32;
for (let i = 0; i < string.length; i++) {
hash ^= string.charCodeAt(i);
// 32-bit FNV prime: 2**24 + 2**8 + 0x93 = 16777619
// Using bitshift for accuracy and performance. Numbers in JS suck.
hash +=
(hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
return hash >>> 0;
};
import fnv1a from '@sindresorhus/fnv1a';
const colors = [];

View File

@ -22,7 +22,7 @@
"@babel/preset-env": "^7.9.5",
"@babel/preset-react": "^7.9.4",
"babel-eslint": "^10.1.0",
"babel-jest": "^25.5.0",
"babel-jest": "^26.0.1",
"babel-loader": "^8.1.0",
"brotli": "^1.3.1",
"copy-webpack-plugin": "^5.1.1",
@ -37,12 +37,12 @@
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.19.0",
"eslint-plugin-react-hooks": "^3.0.0",
"eslint-plugin-react-hooks": "^4.0.0",
"express": "^4.17.1",
"express-http-proxy": "^1.6.0",
"gulp": "4.0.2",
"gulp-util": "^3.0.8",
"jest": "^25.5.0",
"jest": "^26.0.1",
"mini-css-extract-plugin": "^0.9.0",
"postcss-flexbugs-fixes": "^4.2.1",
"postcss-loader": "^3.0.0",
@ -50,7 +50,7 @@
"prettier": "2.0.5",
"react-test-renderer": "16.13.1",
"style-loader": "^1.2.1",
"terser-webpack-plugin": "^2.3.6",
"terser-webpack-plugin": "^3.0.1",
"through2": "^3.0.1",
"webpack": "^4.43.0",
"webpack-dev-middleware": "^3.7.2",
@ -59,6 +59,7 @@
"workbox-webpack-plugin": "^5.1.3"
},
"dependencies": {
"@sindresorhus/fnv1a": "^2.0.0",
"autolinker": "^3.14.1",
"backo": "^1.1.0",
"classnames": "^2.2.6",
@ -66,12 +67,12 @@
"formik": "^2.1.4",
"history": "^5.0.0-beta.8",
"hsluv": "^0.1.0",
"immer": "^6.0.3",
"immer": "^6.0.5",
"js-cookie": "^2.2.1",
"lodash": "^4.17.15",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-hot-loader": "^4.12.20",
"react-hot-loader": "^4.12.21",
"react-icons": "^3.7.0",
"react-modal": "^3.11.2",
"react-redux": "^7.2.0",

View File

@ -90,13 +90,7 @@ module.exports = {
revision: '__INDEX_REVISON__'
}
],
exclude: [
/\.map$/,
/^manifest.*\.js(?:on)?$/,
/^boot.*\.js$/,
/^runtime.*\.js$/,
/\.txt$/
]
exclude: [/\.map$/, /^boot.*\.js$/, /^runtime.*\.js$/, /\.txt$/]
})
],
optimization: {

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ verify_certificates = true
# Defaults for the client connect form
[defaults]
name = "Freenode"
name = "freenode"
host = "chat.freenode.net"
port = 6697
channels = [

14
go.mod
View File

@ -17,13 +17,13 @@ require (
github.com/fsnotify/fsnotify v1.4.9
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a // indirect
github.com/go-acme/lego/v3 v3.6.0 // indirect
github.com/golang/protobuf v1.4.0 // indirect
github.com/golang/protobuf v1.4.1 // indirect
github.com/gorilla/websocket v1.4.2
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/jpillora/backoff v1.0.0
github.com/kjk/betterguid v0.0.0-20170621091430-c442874ba63a
github.com/klauspost/cpuid v1.2.3
github.com/mailru/easyjson v0.7.1
github.com/klauspost/cpuid v1.2.4
github.com/mailru/easyjson v0.7.2-0.20200424172602-f0a000e7a8e0
github.com/miekg/dns v1.1.29 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/mapstructure v1.3.0 // indirect
@ -42,9 +42,9 @@ require (
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
github.com/tinylib/msgp v1.1.2 // indirect
go.etcd.io/bbolt v1.3.4
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc // indirect
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0
golang.org/x/sys v0.0.0-20200428200454-593003d681fa // indirect
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 // indirect
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 // indirect
gopkg.in/ini.v1 v1.55.0 // indirect
gopkg.in/square/go-jose.v2 v2.5.0 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
)

18
go.sum
View File

@ -192,6 +192,8 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
@ -246,6 +248,8 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht
github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@ -266,6 +270,8 @@ github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.2.4 h1:EBfaK0SWSwk+fgk6efYFWdzl8MwRWoOO1gkmiaTXPW4=
github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/kljensen/snowball v0.6.0 h1:6DZLCcZeL0cLfodx+Md4/OLC6b/bfurWUOUGs1ydfOU=
github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw=
github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
@ -287,6 +293,8 @@ github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzR
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8=
github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mailru/easyjson v0.7.2-0.20200424172602-f0a000e7a8e0 h1:kBQYXw1PdcnwYP5hntk8LEDPdq++fubPN76BlfGLdIM=
github.com/mailru/easyjson v0.7.2-0.20200424172602-f0a000e7a8e0/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
@ -490,6 +498,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqp
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc h1:ZGI/fILM2+ueot/UixBSoj9188jCAxVHEZEGhqq67I4=
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -550,6 +560,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -602,6 +614,8 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200428200454-593003d681fa h1:yMbJOvnfYkO1dSAviTu/ZguZWLBTXx4xE3LYrxUCCiA=
golang.org/x/sys v0.0.0-20200428200454-593003d681fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -700,6 +714,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -723,6 +739,8 @@ gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.5.0 h1:OZ4sdq+Y+SHfYB7vfthi1Ei8b0vkP8ZPQgUfUwdUSqo=
gopkg.in/square/go-jose.v2 v2.5.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=

View File

@ -6,6 +6,7 @@ import (
"net"
"strings"
"sync"
"time"
"github.com/jpillora/backoff"
)
@ -54,6 +55,8 @@ func NewClient(nick, username string) *Client {
reconnect: make(chan struct{}),
recvBuf: make([]byte, 0, 4096),
backoff: &backoff.Backoff{
Min: 500 * time.Millisecond,
Max: 30 * time.Second,
Jitter: true,
},
}

View File

@ -79,6 +79,7 @@ func (c *Client) run() {
c.sendRecv.Wait()
c.reconnect = make(chan struct{})
time.Sleep(c.backoff.Duration())
c.tryConnect()
}
}

View File

@ -38,6 +38,7 @@ const indexTemplate = `
{{end}}
<link rel="manifest" href="/manifest.json">
<link rel="apple-touch-icon" href="/icon_192.png">
</head>
<body>

View File

@ -31,6 +31,7 @@ type indexData struct {
Defaults *config.Defaults
Servers []Server
Channels []*storage.Channel
OpenDMs []storage.Tab
HexIP bool
Version dispatchVersion
@ -43,7 +44,7 @@ type indexData struct {
Messages *Messages
}
func (d *Dispatch) getIndexData(r *http.Request, path string, state *State) *indexData {
func (d *Dispatch) getIndexData(r *http.Request, state *State) *indexData {
cfg := d.Config()
data := indexData{
@ -98,35 +99,37 @@ func (d *Dispatch) getIndexData(r *http.Request, path string, state *State) *ind
}
data.Channels = channels
server, channel := getTabFromPath(path)
if isInChannel(channels, server, channel) {
data.addUsersAndMessages(server, channel, state)
return &data
openDMs, err := state.user.GetOpenDMs()
if err != nil {
return nil
}
data.OpenDMs = openDMs
server, channel = parseTabCookie(r, path)
if isInChannel(channels, server, channel) {
data.addUsersAndMessages(server, channel, state)
tab, err := tabFromRequest(r)
if err == nil && hasTab(channels, openDMs, tab.Server, tab.Name) {
data.addUsersAndMessages(tab.Server, tab.Name, state)
}
return &data
}
func (d *indexData) addUsersAndMessages(server, channel string, state *State) {
users := channelStore.GetUsers(server, channel)
if len(users) > 0 {
d.Users = &Userlist{
Server: server,
Channel: channel,
Users: users,
func (d *indexData) addUsersAndMessages(server, name string, state *State) {
if isChannel(name) {
users := channelStore.GetUsers(server, name)
if len(users) > 0 {
d.Users = &Userlist{
Server: server,
Channel: name,
Users: users,
}
}
}
messages, hasMore, err := state.user.GetLastMessages(server, channel, 50)
messages, hasMore, err := state.user.GetLastMessages(server, name, 50)
if err == nil && len(messages) > 0 {
m := Messages{
Server: server,
To: channel,
To: name,
Messages: messages,
}
@ -138,10 +141,16 @@ func (d *indexData) addUsersAndMessages(server, channel string, state *State) {
}
}
func isInChannel(channels []*storage.Channel, server, channel string) bool {
if channel != "" {
func hasTab(channels []*storage.Channel, openDMs []storage.Tab, server, name string) bool {
if name != "" {
for _, ch := range channels {
if server == ch.Server && channel == ch.Name {
if server == ch.Server && name == ch.Name {
return true
}
}
for _, tab := range openDMs {
if server == tab.Server && name == tab.Name {
return true
}
}
@ -149,30 +158,52 @@ func isInChannel(channels []*storage.Channel, server, channel string) bool {
return false
}
func getTabFromPath(rawPath string) (string, string) {
path := strings.Split(strings.Trim(rawPath, "/"), "/")
if len(path) >= 2 {
name, err := url.PathUnescape(path[len(path)-1])
if err == nil && isChannel(name) {
return path[len(path)-2], name
}
}
return "", ""
}
func tabFromRequest(r *http.Request) (Tab, error) {
tab := Tab{}
var path string
if strings.HasPrefix(r.URL.Path, "/ws") {
path = r.URL.EscapedPath()[3:]
} else {
referer, err := url.Parse(r.Referer())
if err != nil {
return tab, err
}
path = referer.EscapedPath()
}
func parseTabCookie(r *http.Request, path string) (string, string) {
if path == "/" {
cookie, err := r.Cookie("tab")
if err == nil {
v, err := url.PathUnescape(cookie.Value)
if err == nil {
tab := strings.SplitN(v, ";", 2)
if err != nil {
return tab, err
}
if len(tab) == 2 && isChannel(tab[1]) {
return tab[0], tab[1]
v, err := url.PathUnescape(cookie.Value)
if err != nil {
return tab, err
}
parts := strings.SplitN(v, ";", 2)
if len(parts) == 2 {
tab.Server = parts[0]
tab.Name = parts[1]
}
} else {
parts := strings.Split(strings.Trim(path, "/"), "/")
if len(parts) > 0 && len(parts) < 3 {
if len(parts) == 2 {
name, err := url.PathUnescape(parts[1])
if err != nil {
return tab, err
}
tab.Name = name
}
tab.Server = parts[0]
}
}
return "", ""
return tab, nil
}

View File

@ -30,7 +30,7 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer(in *jlexer.Lexer, out
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
@ -46,7 +46,7 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer(in *jlexer.Lexer, out
if out.Defaults == nil {
out.Defaults = new(config.Defaults)
}
easyjson7e607aefDecodeGithubComKhliengDispatchConfig(in, &*out.Defaults)
easyjson7e607aefDecodeGithubComKhliengDispatchConfig(in, out.Defaults)
}
case "servers":
if in.IsNull() {
@ -56,7 +56,7 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer(in *jlexer.Lexer, out
in.Delim('[')
if out.Servers == nil {
if !in.IsDelim(']') {
out.Servers = make([]Server, 0, 1)
out.Servers = make([]Server, 0, 0)
} else {
out.Servers = []Server{}
}
@ -97,13 +97,36 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer(in *jlexer.Lexer, out
if v2 == nil {
v2 = new(storage.Channel)
}
easyjson7e607aefDecodeGithubComKhliengDispatchStorage(in, &*v2)
easyjson7e607aefDecodeGithubComKhliengDispatchStorage(in, v2)
}
out.Channels = append(out.Channels, v2)
in.WantComma()
}
in.Delim(']')
}
case "openDMs":
if in.IsNull() {
in.Skip()
out.OpenDMs = nil
} else {
in.Delim('[')
if out.OpenDMs == nil {
if !in.IsDelim(']') {
out.OpenDMs = make([]storage.Tab, 0, 2)
} else {
out.OpenDMs = []storage.Tab{}
}
} else {
out.OpenDMs = (out.OpenDMs)[:0]
}
for !in.IsDelim(']') {
var v3 storage.Tab
easyjson7e607aefDecodeGithubComKhliengDispatchStorage1(in, &v3)
out.OpenDMs = append(out.OpenDMs, v3)
in.WantComma()
}
in.Delim(']')
}
case "hexIP":
out.HexIP = bool(in.Bool())
case "version":
@ -162,12 +185,8 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchServer(out *jwriter.Writer, i
_ = first
if in.Defaults != nil {
const prefix string = ",\"defaults\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
first = false
out.RawString(prefix[1:])
easyjson7e607aefEncodeGithubComKhliengDispatchConfig(out, *in.Defaults)
}
if len(in.Servers) != 0 {
@ -180,11 +199,11 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchServer(out *jwriter.Writer, i
}
{
out.RawByte('[')
for v3, v4 := range in.Servers {
if v3 > 0 {
for v4, v5 := range in.Servers {
if v4 > 0 {
out.RawByte(',')
}
out.Raw((v4).MarshalJSON())
out.Raw((v5).MarshalJSON())
}
out.RawByte(']')
}
@ -199,19 +218,38 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchServer(out *jwriter.Writer, i
}
{
out.RawByte('[')
for v5, v6 := range in.Channels {
if v5 > 0 {
for v6, v7 := range in.Channels {
if v6 > 0 {
out.RawByte(',')
}
if v6 == nil {
if v7 == nil {
out.RawString("null")
} else {
easyjson7e607aefEncodeGithubComKhliengDispatchStorage(out, *v6)
easyjson7e607aefEncodeGithubComKhliengDispatchStorage(out, *v7)
}
}
out.RawByte(']')
}
}
if len(in.OpenDMs) != 0 {
const prefix string = ",\"openDMs\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
{
out.RawByte('[')
for v8, v9 := range in.OpenDMs {
if v8 > 0 {
out.RawByte(',')
}
easyjson7e607aefEncodeGithubComKhliengDispatchStorage1(out, v9)
}
out.RawByte(']')
}
}
if in.HexIP {
const prefix string = ",\"hexIP\":"
if first {
@ -288,6 +326,61 @@ func (v *indexData) UnmarshalJSON(data []byte) error {
func (v *indexData) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson7e607aefDecodeGithubComKhliengDispatchServer(l, v)
}
func easyjson7e607aefDecodeGithubComKhliengDispatchStorage1(in *jlexer.Lexer, out *storage.Tab) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "server":
out.Server = string(in.String())
case "name":
out.Name = string(in.String())
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson7e607aefEncodeGithubComKhliengDispatchStorage1(out *jwriter.Writer, in storage.Tab) {
out.RawByte('{')
first := true
_ = first
if in.Server != "" {
const prefix string = ",\"server\":"
first = false
out.RawString(prefix[1:])
out.String(string(in.Server))
}
if in.Name != "" {
const prefix string = ",\"name\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Name))
}
out.RawByte('}')
}
func easyjson7e607aefDecodeGithubComKhliengDispatchStorage(in *jlexer.Lexer, out *storage.Channel) {
isTopLevel := in.IsStart()
if in.IsNull() {
@ -299,7 +392,7 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchStorage(in *jlexer.Lexer, out
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
@ -329,12 +422,8 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchStorage(out *jwriter.Writer,
_ = first
if in.Server != "" {
const prefix string = ",\"server\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
first = false
out.RawString(prefix[1:])
out.String(string(in.Server))
}
if in.Name != "" {
@ -370,7 +459,7 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchConfig(in *jlexer.Lexer, out
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
@ -400,9 +489,9 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchConfig(in *jlexer.Lexer, out
out.Channels = (out.Channels)[:0]
}
for !in.IsDelim(']') {
var v7 string
v7 = string(in.String())
out.Channels = append(out.Channels, v7)
var v10 string
v10 = string(in.String())
out.Channels = append(out.Channels, v10)
in.WantComma()
}
in.Delim(']')
@ -431,12 +520,8 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchConfig(out *jwriter.Writer, i
_ = first
if in.Name != "" {
const prefix string = ",\"name\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
first = false
out.RawString(prefix[1:])
out.String(string(in.Name))
}
if in.Host != "" {
@ -469,11 +554,11 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchConfig(out *jwriter.Writer, i
}
{
out.RawByte('[')
for v8, v9 := range in.Channels {
if v8 > 0 {
for v11, v12 := range in.Channels {
if v11 > 0 {
out.RawByte(',')
}
out.String(string(v9))
out.String(string(v12))
}
out.RawByte(']')
}
@ -531,7 +616,7 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer1(in *jlexer.Lexer, out
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
@ -561,12 +646,8 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchServer1(out *jwriter.Writer,
_ = first
if in.Tag != "" {
const prefix string = ",\"tag\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
first = false
out.RawString(prefix[1:])
out.String(string(in.Tag))
}
if in.Commit != "" {
@ -626,7 +707,7 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer2(in *jlexer.Lexer, out
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
@ -656,9 +737,9 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer2(in *jlexer.Lexer, out
out.Channels = (out.Channels)[:0]
}
for !in.IsDelim(']') {
var v10 string
v10 = string(in.String())
out.Channels = append(out.Channels, v10)
var v13 string
v13 = string(in.String())
out.Channels = append(out.Channels, v13)
in.WantComma()
}
in.Delim(']')
@ -687,12 +768,8 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchServer2(out *jwriter.Writer,
_ = first
if in.Name != "" {
const prefix string = ",\"name\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
first = false
out.RawString(prefix[1:])
out.String(string(in.Name))
}
if in.Host != "" {
@ -725,11 +802,11 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchServer2(out *jwriter.Writer,
}
{
out.RawByte('[')
for v11, v12 := range in.Channels {
if v11 > 0 {
for v14, v15 := range in.Channels {
if v14 > 0 {
out.RawByte(',')
}
out.String(string(v12))
out.String(string(v15))
}
out.RawByte(']')
}

View File

@ -1,47 +1,60 @@
package server
import (
"net/http"
"net/url"
"testing"
"github.com/khlieng/dispatch/storage"
"github.com/stretchr/testify/assert"
)
func TestGetTabFromPath(t *testing.T) {
cases := []struct {
input string
expectedServer string
expectedChannel string
input *http.Request
expectedTab Tab
}{
{
"/chat.freenode.net/%23r%2Fstuff%2F/",
"chat.freenode.net",
"#r/stuff/",
&http.Request{
URL: &url.URL{Path: "/init"},
Header: http.Header{"Referer": []string{"/chat.freenode.net/%23r%2Fstuff%2F"}},
},
Tab{storage.Tab{Server: "chat.freenode.net", Name: "#r/stuff/"}},
}, {
"/chat.freenode.net/%23r%2Fstuff%2F",
"chat.freenode.net",
"#r/stuff/",
&http.Request{
URL: &url.URL{Path: "/init"},
Header: http.Header{"Referer": []string{"/chat.freenode.net/%23r%2Fstuff"}},
},
Tab{storage.Tab{Server: "chat.freenode.net", Name: "#r/stuff"}},
}, {
"/chat.freenode.net/%23r%2Fstuff",
"chat.freenode.net",
"#r/stuff",
&http.Request{
URL: &url.URL{Path: "/init"},
Header: http.Header{"Referer": []string{"/chat.freenode.net/%23stuff"}},
},
Tab{storage.Tab{Server: "chat.freenode.net", Name: "#stuff"}},
}, {
"/chat.freenode.net/%23stuff",
"chat.freenode.net",
"#stuff",
&http.Request{
URL: &url.URL{Path: "/init"},
Header: http.Header{"Referer": []string{"/chat.freenode.net/stuff"}},
},
Tab{storage.Tab{Server: "chat.freenode.net", Name: "stuff"}},
}, {
"/chat.freenode.net/%23stuff/cake",
"",
"",
&http.Request{
URL: &url.URL{Path: "/init"},
Header: http.Header{"Referer": []string{"/data/chat.freenode.net/%23apples"}},
},
Tab{},
}, {
"/data/chat.freenode.net/%23apples",
"chat.freenode.net",
"#apples",
&http.Request{
URL: &url.URL{Path: "/ws/chat.freenode.net"},
},
Tab{storage.Tab{Server: "chat.freenode.net"}},
},
}
for _, tc := range cases {
server, channel := getTabFromPath(tc.input)
assert.Equal(t, tc.expectedServer, server)
assert.Equal(t, tc.expectedChannel, channel)
tab, err := tabFromRequest(tc.input)
assert.Nil(t, err)
assert.Equal(t, tc.expectedTab, tab)
}
}

View File

@ -169,17 +169,21 @@ func (i *ircHandler) message(msg *irc.Message) {
From: msg.Nick,
Content: msg.LastParam(),
}
target := msg.Params[0]
if msg.Params[0] == i.client.GetNick() {
if target == i.client.GetNick() {
i.state.sendJSON("pm", message)
i.state.user.AddOpenDM(i.client.Host, message.From)
target = message.From
} else {
message.To = msg.Params[0]
message.To = target
i.state.sendJSON("message", message)
}
if msg.Params[0] != "*" {
if target != "*" {
go i.state.user.LogMessage(message.ID,
i.client.Host, msg.Nick, msg.Params[0], msg.LastParam())
i.client.Host, msg.Nick, target, msg.LastParam())
}
}

View File

@ -212,8 +212,8 @@ type ChannelSearch struct {
}
type ChannelSearchResult struct {
ChannelSearch
Results []*storage.ChannelListItem
Start int
}
type ChannelForward struct {
@ -221,3 +221,7 @@ type ChannelForward struct {
Old string
New string
}
type Tab struct {
storage.Tab
}

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,6 @@ package server
import (
"log"
"net/http"
"net/url"
"strings"
"sync"
@ -163,14 +162,8 @@ func (d *Dispatch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
if r.URL.Path == "/init" {
referer, err := url.Parse(r.Header.Get("Referer"))
if err != nil {
fail(w, http.StatusInternalServerError)
return
}
state := d.handleAuth(w, r, true, true)
data := d.getIndexData(r, referer.EscapedPath(), state)
data := d.getIndexData(r, state)
writeJSON(w, r, data)
} else if strings.HasPrefix(r.URL.Path, "/ws") {

View File

@ -68,17 +68,15 @@ func (h *wsHandler) init(r *http.Request) {
h.state.numIRC(), "IRC connections |",
h.state.numWS(), "WebSocket connections")
tab, err := tabFromRequest(r)
channels, err := h.state.user.GetChannels()
if err != nil {
log.Println(err)
}
path := r.URL.EscapedPath()
pathServer, pathChannel := getTabFromPath(path)
cookieServer, cookieChannel := parseTabCookie(r, path[3:])
for _, channel := range channels {
if (channel.Server == pathServer && channel.Name == pathChannel) ||
(channel.Server == cookieServer && channel.Name == cookieChannel) {
if channel.Server == tab.Server && channel.Name == tab.Name {
// Userlist and messages for this channel gets embedded in the index page
continue
}
@ -91,6 +89,19 @@ func (h *wsHandler) init(r *http.Request) {
h.state.sendLastMessages(channel.Server, channel.Name, 50)
}
openDMs, err := h.state.user.GetOpenDMs()
if err != nil {
log.Println(err)
}
for _, openDM := range openDMs {
if openDM.Server == tab.Server && openDM.Name == tab.Name {
continue
}
h.state.sendLastMessages(openDM.Server, openDM.Name, 50)
}
}
func (h *wsHandler) connect(b []byte) {
@ -295,8 +306,8 @@ func (h *wsHandler) channelSearch(b []byte) {
}
h.state.sendJSON("channel_search", ChannelSearchResult{
Results: index.SearchN(data.Q, data.Start, n),
Start: data.Start,
ChannelSearch: data,
Results: index.SearchN(data.Q, data.Start, n),
})
}
@ -306,6 +317,21 @@ func (h *wsHandler) channelSearch(b []byte) {
}
}
func (h *wsHandler) openDM(b []byte) {
var data Tab
data.UnmarshalJSON(b)
h.state.sendLastMessages(data.Server, data.Name, 50)
h.state.user.AddOpenDM(data.Server, data.Name)
}
func (h *wsHandler) closeDM(b []byte) {
var data Tab
data.UnmarshalJSON(b)
h.state.user.RemoveOpenDM(data.Server, data.Name)
}
func (h *wsHandler) initHandlers() {
h.handlers = map[string]func([]byte){
"connect": h.connect,
@ -327,6 +353,8 @@ func (h *wsHandler) initHandlers() {
"set_server_name": h.setServerName,
"settings_set": h.setSettings,
"channel_search": h.channelSearch,
"open_dm": h.openDM,
"close_dm": h.closeDM,
}
}

View File

@ -15,6 +15,7 @@ var (
bucketUsers = []byte("Users")
bucketServers = []byte("Servers")
bucketChannels = []byte("Channels")
bucketOpenDMs = []byte("OpenDMs")
bucketMessages = []byte("Messages")
bucketSessions = []byte("Sessions")
)
@ -34,6 +35,7 @@ func New(path string) (*BoltStore, error) {
tx.CreateBucketIfNotExists(bucketUsers)
tx.CreateBucketIfNotExists(bucketServers)
tx.CreateBucketIfNotExists(bucketChannels)
tx.CreateBucketIfNotExists(bucketOpenDMs)
tx.CreateBucketIfNotExists(bucketMessages)
tx.CreateBucketIfNotExists(bucketSessions)
return nil
@ -168,6 +170,13 @@ func (s *BoltStore) RemoveServer(user *storage.User, address string) error {
b.Delete(k)
}
b = tx.Bucket(bucketOpenDMs)
c = b.Cursor()
for k, _ := c.Seek(serverID); bytes.HasPrefix(k, serverID); k, _ = c.Next() {
b.Delete(k)
}
return nil
})
}
@ -246,6 +255,42 @@ func (s *BoltStore) RemoveChannel(user *storage.User, server, channel string) er
})
}
func (s *BoltStore) GetOpenDMs(user *storage.User) ([]storage.Tab, error) {
var openDMs []storage.Tab
s.db.View(func(tx *bolt.Tx) error {
c := tx.Bucket(bucketOpenDMs).Cursor()
for k, _ := c.Seek(user.IDBytes); bytes.HasPrefix(k, user.IDBytes); k, _ = c.Next() {
tab := bytes.Split(k[8:], []byte{0})
openDMs = append(openDMs, storage.Tab{
Server: string(tab[0]),
Name: string(tab[1]),
})
}
return nil
})
return openDMs, nil
}
func (s *BoltStore) AddOpenDM(user *storage.User, server, nick string) error {
return s.db.Batch(func(tx *bolt.Tx) error {
b := tx.Bucket(bucketOpenDMs)
return b.Put(channelID(user, server, nick), nil)
})
}
func (s *BoltStore) RemoveOpenDM(user *storage.User, server, nick string) error {
return s.db.Batch(func(tx *bolt.Tx) error {
b := tx.Bucket(bucketOpenDMs)
return b.Delete(channelID(user, server, nick))
})
}
func (s *BoltStore) LogMessage(message *storage.Message) error {
return s.db.Batch(func(tx *bolt.Tx) error {
b, err := tx.Bucket(bucketMessages).CreateBucketIfNotExists([]byte(message.Server + ":" + message.To))

View File

@ -38,6 +38,10 @@ type Store interface {
GetChannels(user *User) ([]*Channel, error)
AddChannel(user *User, channel *Channel) error
RemoveChannel(user *User, server, channel string) error
GetOpenDMs(user *User) ([]Tab, error)
AddOpenDM(user *User, server, nick string) error
RemoveOpenDM(user *User, server, nick string) error
}
type SessionStore interface {

View File

@ -190,6 +190,23 @@ func (u *User) RemoveChannel(server, channel string) error {
return u.store.RemoveChannel(u, server, channel)
}
type Tab struct {
Server string
Name string
}
func (u *User) GetOpenDMs() ([]Tab, error) {
return u.store.GetOpenDMs(u)
}
func (u *User) AddOpenDM(server, nick string) error {
return u.store.AddOpenDM(u, server, nick)
}
func (u *User) RemoveOpenDM(server, nick string) error {
return u.store.RemoveOpenDM(u, server, nick)
}
type Message struct {
ID string `json:"-" bleve:"-"`
Server string `json:"-" bleve:"server"`

View File

@ -28,7 +28,7 @@ func easyjson9e1087fdDecodeGithubComKhliengDispatchStorage(in *jlexer.Lexer, out
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
@ -54,12 +54,8 @@ func easyjson9e1087fdEncodeGithubComKhliengDispatchStorage(out *jwriter.Writer,
_ = first
if in.ColoredNicks {
const prefix string = ",\"coloredNicks\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
first = false
out.RawString(prefix[1:])
out.Bool(bool(in.ColoredNicks))
}
out.RawByte('}')

View File

@ -28,7 +28,7 @@ func TestUser(t *testing.T) {
assert.Nil(t, err)
srv := &storage.Server{
Name: "Freenode",
Name: "freenode",
Host: "irc.freenode.net",
Nick: "test",
}
@ -80,6 +80,16 @@ func TestUser(t *testing.T) {
channels, err = user.GetChannels()
assert.Len(t, channels, 0)
user.AddOpenDM(srv.Host, "cake")
openDMs, err := user.GetOpenDMs()
assert.Nil(t, err)
assert.Len(t, openDMs, 1)
err = user.RemoveOpenDM(srv.Host, "cake")
assert.Nil(t, err)
openDMs, err = user.GetOpenDMs()
assert.Nil(t, err)
assert.Len(t, openDMs, 0)
settings := user.GetClientSettings()
assert.NotNil(t, settings)
assert.Equal(t, storage.DefaultClientSettings(), settings)

View File

@ -33,8 +33,8 @@ func SizeVarint(v uint64) int {
return protowire.SizeVarint(v)
}
// DecodeVarint parses a varint encoded integer from b, returning the
// integer value and the length of the varint.
// DecodeVarint parses a varint encoded integer from b,
// returning the integer value and the length of the varint.
// It returns (0, 0) if there is a parse error.
func DecodeVarint(b []byte) (uint64, int) {
v, n := protowire.ConsumeVarint(b)
@ -112,9 +112,9 @@ func (b *Buffer) Marshal(m Message) error {
return err
}
// Unmarshal parses the wire-format message in the buffer and places the decoded results in m.
//
// Unlike proto.Unmarshal, this does not reset the message before starting to unmarshal.
// Unmarshal parses the wire-format message in the buffer and
// places the decoded results in m.
// It does not reset m before unmarshaling.
func (b *Buffer) Unmarshal(m Message) error {
err := UnmarshalMerge(b.Unread(), m)
b.idx = len(b.buf)
@ -260,7 +260,7 @@ func (b *Buffer) DecodeStringBytes() (string, error) {
}
// DecodeMessage consumes a length-prefixed message from the buffer.
// It does not reset m.
// It does not reset m before unmarshaling.
func (b *Buffer) DecodeMessage(m Message) error {
v, err := b.DecodeRawBytes(false)
if err != nil {
@ -272,7 +272,7 @@ func (b *Buffer) DecodeMessage(m Message) error {
// DecodeGroup consumes a message group from the buffer.
// It assumes that the start group marker has already been consumed and
// consumes all bytes until (and including the end group marker).
// It does not reset m.
// It does not reset m before unmarshaling.
func (b *Buffer) DecodeGroup(m Message) error {
v, n, err := consumeGroup(b.buf[b.idx:])
if err != nil {

View File

@ -68,7 +68,7 @@ func HasExtension(m Message, xt *ExtensionDesc) (has bool) {
return has
}
// ClearExtension removes the the exntesion field from m
// ClearExtension removes the extension field from m
// either as an explicitly populated field or as an unknown field.
func ClearExtension(m Message, xt *ExtensionDesc) {
mr := MessageReflect(m)
@ -108,7 +108,7 @@ func ClearAllExtensions(m Message) {
clearUnknown(mr, mr.Descriptor().ExtensionRanges())
}
// GetExtension retrieves a proto2 extended field from pb.
// GetExtension retrieves a proto2 extended field from m.
//
// If the descriptor is type complete (i.e., ExtensionDesc.ExtensionType is non-nil),
// then GetExtension parses the encoded field and returns a Go value of the specified type.

View File

@ -29,7 +29,7 @@ var fileCache sync.Map // map[filePath]fileDescGZIP
// RegisterFile is called from generated code to register the compressed
// FileDescriptorProto with the file path for a proto source file.
//
// Deprecated: Use protoregistry.GlobalFiles.Register instead.
// Deprecated: Use protoregistry.GlobalFiles.RegisterFile instead.
func RegisterFile(s filePath, d fileDescGZIP) {
// Decompress the descriptor.
zr, err := gzip.NewReader(bytes.NewReader(d))
@ -53,7 +53,7 @@ func RegisterFile(s filePath, d fileDescGZIP) {
// FileDescriptor returns the compressed FileDescriptorProto given the file path
// for a proto source file. It returns nil if not found.
//
// Deprecated: Use protoregistry.GlobalFiles.RangeFilesByPath instead.
// Deprecated: Use protoregistry.GlobalFiles.FindFileByPath instead.
func FileDescriptor(s filePath) fileDescGZIP {
if v, ok := fileCache.Load(s); ok {
return v.(fileDescGZIP)
@ -98,7 +98,7 @@ var numFilesCache sync.Map // map[protoreflect.FullName]int
// RegisterEnum is called from the generated code to register the mapping of
// enum value names to enum numbers for the enum identified by s.
//
// Deprecated: Use protoregistry.GlobalTypes.Register instead.
// Deprecated: Use protoregistry.GlobalTypes.RegisterEnum instead.
func RegisterEnum(s enumName, _ enumsByNumber, m enumsByName) {
if _, ok := enumCache.Load(s); ok {
panic("proto: duplicate enum registered: " + s)
@ -181,7 +181,7 @@ var messageTypeCache sync.Map // map[messageName]reflect.Type
// RegisterType is called from generated code to register the message Go type
// for a message of the given name.
//
// Deprecated: Use protoregistry.GlobalTypes.Register instead.
// Deprecated: Use protoregistry.GlobalTypes.RegisterMessage instead.
func RegisterType(m Message, s messageName) {
mt := protoimpl.X.LegacyMessageTypeOf(m, protoreflect.FullName(s))
if err := protoregistry.GlobalTypes.RegisterMessage(mt); err != nil {
@ -280,7 +280,7 @@ func MessageName(m Message) messageName {
// RegisterExtension is called from the generated code to register
// the extension descriptor.
//
// Deprecated: Use protoregistry.GlobalTypes.Register instead.
// Deprecated: Use protoregistry.GlobalTypes.RegisterExtension instead.
func RegisterExtension(d *ExtensionDesc) {
if err := protoregistry.GlobalTypes.RegisterExtension(d); err != nil {
panic(err)

View File

@ -94,16 +94,16 @@ var (
)
// MarshalText writes the proto text format of m to w.
func MarshalText(w io.Writer, pb Message) error { return defaultTextMarshaler.Marshal(w, pb) }
func MarshalText(w io.Writer, m Message) error { return defaultTextMarshaler.Marshal(w, m) }
// MarshalTextString returns a proto text formatted string of m.
func MarshalTextString(pb Message) string { return defaultTextMarshaler.Text(pb) }
func MarshalTextString(m Message) string { return defaultTextMarshaler.Text(m) }
// CompactText writes the compact proto text format of m to w.
func CompactText(w io.Writer, pb Message) error { return compactTextMarshaler.Marshal(w, pb) }
func CompactText(w io.Writer, m Message) error { return compactTextMarshaler.Marshal(w, m) }
// CompactTextString returns a compact proto text formatted string of m.
func CompactTextString(pb Message) string { return compactTextMarshaler.Text(pb) }
func CompactTextString(m Message) string { return compactTextMarshaler.Text(m) }
var (
newline = []byte("\n")

5
vendor/github.com/josharian/intern/README.md generated vendored Normal file
View File

@ -0,0 +1,5 @@
Docs: https://godoc.org/github.com/josharian/intern
See also [Go issue 5160](https://golang.org/issue/5160).
License: MIT

3
vendor/github.com/josharian/intern/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/josharian/intern
go 1.5

44
vendor/github.com/josharian/intern/intern.go generated vendored Normal file
View File

@ -0,0 +1,44 @@
// Package intern interns strings.
// Interning is best effort only.
// Interned strings may be removed automatically
// at any time without notification.
// All functions may be called concurrently
// with themselves and each other.
package intern
import "sync"
var (
pool sync.Pool = sync.Pool{
New: func() interface{} {
return make(map[string]string)
},
}
)
// String returns s, interned.
func String(s string) string {
m := pool.Get().(map[string]string)
c, ok := m[s]
if ok {
pool.Put(m)
return c
}
m[s] = s
pool.Put(m)
return s
}
// Bytes returns b converted to a string, interned.
func Bytes(b []byte) string {
m := pool.Get().(map[string]string)
c, ok := m[string(b)]
if ok {
pool.Put(m)
return c
}
s := string(b)
m[s] = s
pool.Put(m)
return s
}

21
vendor/github.com/josharian/intern/license.md generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Josh Bleecher Snyder
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -10,7 +10,13 @@
// Package home: https://github.com/klauspost/cpuid
package cpuid
import "strings"
import (
"math"
"strings"
)
// AMD refererence: https://www.amd.com/system/files/TechDocs/25481.pdf
// and Processor Programming Reference (PPR)
// Vendor is a representation of a CPU vendor.
type Vendor int
@ -178,11 +184,12 @@ type CPUInfo struct {
Family int // CPU family number
Model int // CPU model number
CacheLine int // Cache line size in bytes. Will be 0 if undetectable.
Hz int64 // Clock speed, if known
Cache struct {
L1I int // L1 Instruction Cache (per core or shared). Will be -1 if undetected
L1D int // L1 Data Cache (per core or shared). Will be -1 if undetected
L2 int // L2 Cache (per core or shared). Will be -1 if undetected
L3 int // L3 Instruction Cache (per core or shared). Will be -1 if undetected
L3 int // L3 Cache (per core, per ccx or shared). Will be -1 if undetected
}
SGX SGXSupport
maxFunc uint32
@ -225,6 +232,7 @@ func Detect() {
CPU.LogicalCores = logicalCores()
CPU.PhysicalCores = physicalCores()
CPU.VendorID = vendorID()
CPU.Hz = hertz(CPU.BrandName)
CPU.cacheSize()
}
@ -601,6 +609,65 @@ func (c CPUInfo) LogicalCPU() int {
return int(ebx >> 24)
}
// hertz tries to compute the clock speed of the CPU. If leaf 15 is
// supported, use it, otherwise parse the brand string. Yes, really.
func hertz(model string) int64 {
mfi := maxFunctionID()
if mfi >= 0x15 {
eax, ebx, ecx, _ := cpuid(0x15)
if eax != 0 && ebx != 0 && ecx != 0 {
return int64((int64(ecx) * int64(ebx)) / int64(eax))
}
}
// computeHz determines the official rated speed of a CPU from its brand
// string. This insanity is *actually the official documented way to do
// this according to Intel*, prior to leaf 0x15 existing. The official
// documentation only shows this working for exactly `x.xx` or `xxxx`
// cases, e.g., `2.50GHz` or `1300MHz`; this parser will accept other
// sizes.
hz := strings.LastIndex(model, "Hz")
if hz < 3 {
return -1
}
var multiplier int64
switch model[hz-1] {
case 'M':
multiplier = 1000 * 1000
case 'G':
multiplier = 1000 * 1000 * 1000
case 'T':
multiplier = 1000 * 1000 * 1000 * 1000
}
if multiplier == 0 {
return -1
}
freq := int64(0)
divisor := int64(0)
decimalShift := int64(1)
var i int
for i = hz - 2; i >= 0 && model[i] != ' '; i-- {
if model[i] >= '0' && model[i] <= '9' {
freq += int64(model[i]-'0') * decimalShift
decimalShift *= 10
} else if model[i] == '.' {
if divisor != 0 {
return -1
}
divisor = decimalShift
} else {
return -1
}
}
// we didn't find a space
if i < 0 {
return -1
}
if divisor != 0 {
return (freq * multiplier) / divisor
}
return freq * multiplier
}
// VM Will return true if the cpu id indicates we are in
// a virtual machine. This is only a hint, and will very likely
// have many false negatives.
@ -659,11 +726,14 @@ func brandName() string {
func threadsPerCore() int {
mfi := maxFunctionID()
if mfi < 0x4 || vendorID() != Intel {
if mfi < 0x4 || (vendorID() != Intel && vendorID() != AMD) {
return 1
}
if mfi < 0xb {
if vendorID() != Intel {
return 1
}
_, b, _, d := cpuid(1)
if (d & (1 << 28)) != 0 {
// v will contain logical core count
@ -727,6 +797,13 @@ func physicalCores() int {
case Intel:
return logicalCores() / threadsPerCore()
case AMD, Hygon:
lc := logicalCores()
tpc := threadsPerCore()
if lc > 0 && tpc > 0 {
return lc / tpc
}
// The following is inaccurate on AMD EPYC 7742 64-Core Processor
if maxExtendedFunction() >= 0x80000008 {
_, _, c, _ := cpuid(0x80000008)
return int(c&0xff) + 1
@ -837,6 +914,49 @@ func (c *CPUInfo) cacheSize() {
}
_, _, ecx, _ = cpuid(0x80000006)
c.Cache.L2 = int(((ecx >> 16) & 0xFFFF) * 1024)
// CPUID Fn8000_001D_EAX_x[N:0] Cache Properties
if maxExtendedFunction() < 0x8000001D {
return
}
for i := uint32(0); i < math.MaxUint32; i++ {
eax, ebx, ecx, _ := cpuidex(0x8000001D, i)
level := (eax >> 5) & 7
cacheNumSets := ecx + 1
cacheLineSize := 1 + (ebx & 2047)
cachePhysPartitions := 1 + ((ebx >> 12) & 511)
cacheNumWays := 1 + ((ebx >> 22) & 511)
typ := eax & 15
size := int(cacheNumSets * cacheLineSize * cachePhysPartitions * cacheNumWays)
if typ == 0 {
return
}
switch level {
case 1:
switch typ {
case 1:
// Data cache
c.Cache.L1D = size
case 2:
// Inst cache
c.Cache.L1I = size
default:
if c.Cache.L1D < 0 {
c.Cache.L1I = size
}
if c.Cache.L1I < 0 {
c.Cache.L1I = size
}
}
case 2:
c.Cache.L2 = size
case 3:
c.Cache.L3 = size
}
}
}
return
@ -954,7 +1074,11 @@ func support() Flags {
rval |= HTT
}
}
if vend == AMD && (d&(1<<28)) != 0 && mfi >= 4 {
if threadsPerCore() > 1 {
rval |= HTT
}
}
// Check XGETBV, OXSAVE and AVX bits
if c&(1<<26) != 0 && c&(1<<27) != 0 && c&(1<<28) != 0 {
// Check for OS support

View File

@ -20,6 +20,12 @@ generate: build
./tests/reference_to_pointer.go \
./tests/html.go \
./tests/unknown_fields.go \
./tests/type_declaration.go \
./tests/members_escaped.go \
./tests/members_unescaped.go \
./tests/intern.go \
./tests/nocopy.go \
./tests/escaping.go \
bin/easyjson -all ./tests/data.go
bin/easyjson -all ./tests/nothing.go
@ -27,7 +33,7 @@ generate: build
bin/easyjson -all ./tests/html.go
bin/easyjson -snake_case ./tests/snake.go
bin/easyjson -omit_empty ./tests/omitempty.go
bin/easyjson -build_tags=use_easyjson ./benchmark/data.go
bin/easyjson -build_tags=use_easyjson -disable_members_unescape ./benchmark/data.go
bin/easyjson ./tests/nested_easy.go
bin/easyjson ./tests/named_type.go
bin/easyjson ./tests/custom_map_key_type.go
@ -36,6 +42,12 @@ generate: build
bin/easyjson ./tests/key_marshaler_map.go
bin/easyjson -disallow_unknown_fields ./tests/disallow_unknown.go
bin/easyjson ./tests/unknown_fields.go
bin/easyjson ./tests/type_declaration.go
bin/easyjson ./tests/members_escaped.go
bin/easyjson -disable_members_unescape ./tests/members_unescaped.go
bin/easyjson ./tests/intern.go
bin/easyjson ./tests/nocopy.go
bin/easyjson ./tests/escaping.go
test: generate
go test \

View File

@ -35,6 +35,8 @@ Usage of easyjson:
generate marshaler/unmarshalers for all structs in a file
-build_tags string
build tags to add to generated file
-byte
use simple bytes instead of Base64Bytes for slice of bytes
-leave_temps
do not delete temporary files
-no_std_marshalers
@ -55,6 +57,8 @@ Usage of easyjson:
only generate stubs for marshaler/unmarshaler funcs
-disallow_unknown_fields
return error if some unknown field in json appeared
-disable_members_unescape
disable unescaping of \uXXXX string sequences in member names
```
Using `-all` will generate marshalers/unmarshalers for all Go structs in the
@ -76,10 +80,22 @@ Additional option notes:
* `-build_tags` will add the specified build tags to generated Go sources.
## Structure json tag options
Besides standart json tag options like 'omitempty' the following are supported:
* 'nocopy' - disables allocation and copying of string values, making them
refer to original json buffer memory. This works great for short lived
objects which are not hold in memory after decoding and immediate usage.
Note if string requires unescaping it will be processed as normally.
* 'intern' - string "interning" (deduplication) to save memory when the very
same string dictionary values are often met all over the structure.
See below for more details.
## Generated Marshaler/Unmarshaler Funcs
For Go struct types, easyjson generates the funcs `MarshalEasyJSON` /
`UnmarshalEasyJSON` for marshaling/unmarshaling JSON. In turn, these satisify
`UnmarshalEasyJSON` for marshaling/unmarshaling JSON. In turn, these satisfy
the `easyjson.Marshaler` and `easyjson.Unmarshaler` interfaces and when used in
conjunction with `easyjson.Marshal` / `easyjson.Unmarshal` avoid unnecessary
reflection / type assertions during marshaling/unmarshaling to/from JSON for Go
@ -102,17 +118,17 @@ utility funcs that are available.
## Controlling easyjson Marshaling and Unmarshaling Behavior
Go types can provide their own `MarshalEasyJSON` and `UnmarshalEasyJSON` funcs
that satisify the `easyjson.Marshaler` / `easyjson.Unmarshaler` interfaces.
that satisfy the `easyjson.Marshaler` / `easyjson.Unmarshaler` interfaces.
These will be used by `easyjson.Marshal` and `easyjson.Unmarshal` when defined
for a Go type.
Go types can also satisify the `easyjson.Optional` interface, which allows the
Go types can also satisfy the `easyjson.Optional` interface, which allows the
type to define its own `omitempty` logic.
## Type Wrappers
easyjson provides additional type wrappers defined in the `easyjson/opt`
package. These wrap the standard Go primitives and in turn satisify the
package. These wrap the standard Go primitives and in turn satisfy the
easyjson interfaces.
The `easyjson/opt` type wrappers are useful when needing to distinguish between
@ -133,6 +149,27 @@ through a call to `buffer.Init()` prior to any marshaling or unmarshaling.
Please see the [GoDoc listing](https://godoc.org/github.com/mailru/easyjson/buffer)
for more information.
## String interning
During unmarshaling, `string` field values can be optionally
[interned](https://en.wikipedia.org/wiki/String_interning) to reduce memory
allocations and usage by deduplicating strings in memory, at the expense of slightly
increased CPU usage.
This will work effectively only for `string` fields being decoded that have frequently
the same value (e.g. if you have a string field that can only assume a small number
of possible values).
To enable string interning, add the `intern` keyword tag to your `json` tag on `string`
fields, e.g.:
```go
type Foo struct {
UUID string `json:"uuid"` // will not be interned during unmarshaling
State string `json:"state,intern"` // will be interned during unmarshaling
}
```
## Issues, Notes, and Limitations
* easyjson is still early in its development. As such, there are likely to be
@ -174,7 +211,7 @@ for more information.
needs to be known prior to sending the data. Currently this is not possible
with easyjson's architecture.
* easyjson parser and codegen based on reflection, so it wont works on `package main`
* easyjson parser and codegen based on reflection, so it won't work on `package main`
files, because they cant be imported by parser.
## Benchmarks
@ -239,7 +276,7 @@ since the memory is not freed between marshaling operations.
### easyjson vs 'ujson' python module
[ujson](https://github.com/esnme/ultrajson) is using C code for parsing, so it
is interesting to see how plain golang compares to that. It is imporant to note
is interesting to see how plain golang compares to that. It is important to note
that the resulting object for python is slower to access, since the library
parses JSON object into dictionaries.

View File

@ -4,6 +4,7 @@ package buffer
import (
"io"
"net"
"sync"
)
@ -52,14 +53,12 @@ func putBuf(buf []byte) {
// getBuf gets a chunk from reuse pool or creates a new one if reuse failed.
func getBuf(size int) []byte {
if size < config.PooledSize {
return make([]byte, 0, size)
}
if c := buffers[size]; c != nil {
v := c.Get()
if v != nil {
return v.([]byte)
if size >= config.PooledSize {
if c := buffers[size]; c != nil {
v := c.Get()
if v != nil {
return v.([]byte)
}
}
}
return make([]byte, 0, size)
@ -78,9 +77,12 @@ type Buffer struct {
// EnsureSpace makes sure that the current chunk contains at least s free bytes,
// possibly creating a new chunk.
func (b *Buffer) EnsureSpace(s int) {
if cap(b.Buf)-len(b.Buf) >= s {
return
if cap(b.Buf)-len(b.Buf) < s {
b.ensureSpaceSlow(s)
}
}
func (b *Buffer) ensureSpaceSlow(s int) {
l := len(b.Buf)
if l > 0 {
if cap(b.toPool) != cap(b.Buf) {
@ -105,18 +107,22 @@ func (b *Buffer) EnsureSpace(s int) {
// AppendByte appends a single byte to buffer.
func (b *Buffer) AppendByte(data byte) {
if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined.
b.EnsureSpace(1)
}
b.EnsureSpace(1)
b.Buf = append(b.Buf, data)
}
// AppendBytes appends a byte slice to buffer.
func (b *Buffer) AppendBytes(data []byte) {
if len(data) <= cap(b.Buf)-len(b.Buf) {
b.Buf = append(b.Buf, data...) // fast path
} else {
b.appendBytesSlow(data)
}
}
func (b *Buffer) appendBytesSlow(data []byte) {
for len(data) > 0 {
if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined.
b.EnsureSpace(1)
}
b.EnsureSpace(1)
sz := cap(b.Buf) - len(b.Buf)
if sz > len(data) {
@ -128,12 +134,18 @@ func (b *Buffer) AppendBytes(data []byte) {
}
}
// AppendBytes appends a string to buffer.
// AppendString appends a string to buffer.
func (b *Buffer) AppendString(data string) {
if len(data) <= cap(b.Buf)-len(b.Buf) {
b.Buf = append(b.Buf, data...) // fast path
} else {
b.appendStringSlow(data)
}
}
func (b *Buffer) appendStringSlow(data string) {
for len(data) > 0 {
if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined.
b.EnsureSpace(1)
}
b.EnsureSpace(1)
sz := cap(b.Buf) - len(b.Buf)
if sz > len(data) {
@ -156,18 +168,14 @@ func (b *Buffer) Size() int {
// DumpTo outputs the contents of a buffer to a writer and resets the buffer.
func (b *Buffer) DumpTo(w io.Writer) (written int, err error) {
var n int
for _, buf := range b.bufs {
if err == nil {
n, err = w.Write(buf)
written += n
}
putBuf(buf)
bufs := net.Buffers(b.bufs)
if len(b.Buf) > 0 {
bufs = append(bufs, b.Buf)
}
n, err := bufs.WriteTo(w)
if err == nil {
n, err = w.Write(b.Buf)
written += n
for _, buf := range b.bufs {
putBuf(buf)
}
putBuf(b.toPool)
@ -175,7 +183,7 @@ func (b *Buffer) DumpTo(w io.Writer) (written int, err error) {
b.Buf = nil
b.toPool = nil
return
return int(n), err
}
// BuildBytes creates a single byte slice with all the contents of the buffer. Data is
@ -192,7 +200,7 @@ func (b *Buffer) BuildBytes(reuse ...[]byte) []byte {
var ret []byte
size := b.Size()
// If we got a buffer as argument and it is big enought, reuse it.
// If we got a buffer as argument and it is big enough, reuse it.
if len(reuse) == 1 && cap(reuse[0]) >= size {
ret = reuse[0][:0]
} else {

View File

@ -1,3 +1,5 @@
module github.com/mailru/easyjson
go 1.12
require github.com/josharian/intern v1.0.0

View File

@ -6,6 +6,7 @@ import (
"io/ioutil"
"net/http"
"strconv"
"unsafe"
"github.com/mailru/easyjson/jlexer"
"github.com/mailru/easyjson/jwriter"
@ -36,9 +37,17 @@ type UnknownsMarshaler interface {
MarshalUnknowns(w *jwriter.Writer, first bool)
}
func isNilInterface(i interface{}) bool {
return (*[2]uintptr)(unsafe.Pointer(&i))[1] == 0
}
// Marshal returns data as a single byte slice. Method is suboptimal as the data is likely to be copied
// from a chain of smaller chunks.
func Marshal(v Marshaler) ([]byte, error) {
if isNilInterface(v) {
return nullBytes, nil
}
w := jwriter.Writer{}
v.MarshalEasyJSON(&w)
return w.BuildBytes()
@ -46,6 +55,10 @@ func Marshal(v Marshaler) ([]byte, error) {
// MarshalToWriter marshals the data to an io.Writer.
func MarshalToWriter(v Marshaler, w io.Writer) (written int, err error) {
if isNilInterface(v) {
return w.Write(nullBytes)
}
jw := jwriter.Writer{}
v.MarshalEasyJSON(&jw)
return jw.DumpTo(w)
@ -56,6 +69,13 @@ func MarshalToWriter(v Marshaler, w io.Writer) (written int, err error) {
// false if an error occurred before any http.ResponseWriter methods were actually
// invoked (in this case a 500 reply is possible).
func MarshalToHTTPResponseWriter(v Marshaler, w http.ResponseWriter) (started bool, written int, err error) {
if isNilInterface(v) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Length", strconv.Itoa(len(nullBytes)))
written, err = w.Write(nullBytes)
return true, written, err
}
jw := jwriter.Writer{}
v.MarshalEasyJSON(&jw)
if jw.Error != nil {

View File

@ -5,6 +5,7 @@
package jlexer
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
@ -14,6 +15,8 @@ import (
"unicode"
"unicode/utf16"
"unicode/utf8"
"github.com/josharian/intern"
)
// tokenKind determines type of a token.
@ -32,9 +35,10 @@ const (
type token struct {
kind tokenKind // Type of a token.
boolValue bool // Value if a boolean literal token.
byteValue []byte // Raw value of a token.
delimValue byte
boolValue bool // Value if a boolean literal token.
byteValueCloned bool // true if byteValue was allocated and does not refer to original json body
byteValue []byte // Raw value of a token.
delimValue byte
}
// Lexer is a JSON lexer: it iterates over JSON tokens in a byte slice.
@ -240,23 +244,65 @@ func (r *Lexer) fetchNumber() {
// findStringLen tries to scan into the string literal for ending quote char to determine required size.
// The size will be exact if no escapes are present and may be inexact if there are escaped chars.
func findStringLen(data []byte) (isValid, hasEscapes bool, length int) {
delta := 0
for i := 0; i < len(data); i++ {
switch data[i] {
case '\\':
i++
delta++
if i < len(data) && data[i] == 'u' {
delta++
}
case '"':
return true, (delta > 0), (i - delta)
func findStringLen(data []byte) (isValid bool, length int) {
for {
idx := bytes.IndexByte(data, '"')
if idx == -1 {
return false, len(data)
}
if idx == 0 || (idx > 0 && data[idx-1] != '\\') {
return true, length + idx
}
// count \\\\\\\ sequences. even number of slashes means quote is not really escaped
cnt := 1
for idx-cnt-1 >= 0 && data[idx-cnt-1] == '\\' {
cnt++
}
if cnt%2 == 0 {
return true, length + idx
}
length += idx + 1
data = data[idx+1:]
}
}
// unescapeStringToken performs unescaping of string token.
// if no escaping is needed, original string is returned, otherwise - a new one allocated
func (r *Lexer) unescapeStringToken() (err error) {
data := r.token.byteValue
var unescapedData []byte
for {
i := bytes.IndexByte(data, '\\')
if i == -1 {
break
}
escapedRune, escapedBytes, err := decodeEscape(data[i:])
if err != nil {
r.errParse(err.Error())
return err
}
if unescapedData == nil {
unescapedData = make([]byte, 0, len(r.token.byteValue))
}
var d [4]byte
s := utf8.EncodeRune(d[:], escapedRune)
unescapedData = append(unescapedData, data[:i]...)
unescapedData = append(unescapedData, d[:s]...)
data = data[i+escapedBytes:]
}
return false, false, len(data)
if unescapedData != nil {
r.token.byteValue = append(unescapedData, data...)
r.token.byteValueCloned = true
}
return
}
// getu4 decodes \uXXXX from the beginning of s, returning the hex value,
@ -286,36 +332,30 @@ func getu4(s []byte) rune {
return val
}
// processEscape processes a single escape sequence and returns number of bytes processed.
func (r *Lexer) processEscape(data []byte) (int, error) {
// decodeEscape processes a single escape sequence and returns number of bytes processed.
func decodeEscape(data []byte) (decoded rune, bytesProcessed int, err error) {
if len(data) < 2 {
return 0, fmt.Errorf("syntax error at %v", string(data))
return 0, 0, errors.New("incorrect escape symbol \\ at the end of token")
}
c := data[1]
switch c {
case '"', '/', '\\':
r.token.byteValue = append(r.token.byteValue, c)
return 2, nil
return rune(c), 2, nil
case 'b':
r.token.byteValue = append(r.token.byteValue, '\b')
return 2, nil
return '\b', 2, nil
case 'f':
r.token.byteValue = append(r.token.byteValue, '\f')
return 2, nil
return '\f', 2, nil
case 'n':
r.token.byteValue = append(r.token.byteValue, '\n')
return 2, nil
return '\n', 2, nil
case 'r':
r.token.byteValue = append(r.token.byteValue, '\r')
return 2, nil
return '\r', 2, nil
case 't':
r.token.byteValue = append(r.token.byteValue, '\t')
return 2, nil
return '\t', 2, nil
case 'u':
rr := getu4(data)
if rr < 0 {
return 0, errors.New("syntax error")
return 0, 0, errors.New("incorrectly escaped \\uXXXX sequence")
}
read := 6
@ -328,13 +368,10 @@ func (r *Lexer) processEscape(data []byte) (int, error) {
rr = unicode.ReplacementChar
}
}
var d [4]byte
s := utf8.EncodeRune(d[:], rr)
r.token.byteValue = append(r.token.byteValue, d[:s]...)
return read, nil
return rr, read, nil
}
return 0, errors.New("syntax error")
return 0, 0, errors.New("incorrectly escaped bytes")
}
// fetchString scans a string literal token.
@ -342,43 +379,14 @@ func (r *Lexer) fetchString() {
r.pos++
data := r.Data[r.pos:]
isValid, hasEscapes, length := findStringLen(data)
isValid, length := findStringLen(data)
if !isValid {
r.pos += length
r.errParse("unterminated string literal")
return
}
if !hasEscapes {
r.token.byteValue = data[:length]
r.pos += length + 1
return
}
r.token.byteValue = make([]byte, 0, length)
p := 0
for i := 0; i < len(data); {
switch data[i] {
case '"':
r.pos += i + 1
r.token.byteValue = append(r.token.byteValue, data[p:i]...)
i++
return
case '\\':
r.token.byteValue = append(r.token.byteValue, data[p:i]...)
off, err := r.processEscape(data[i:])
if err != nil {
r.errParse(err.Error())
return
}
i += off
p = i
default:
i++
}
}
r.errParse("unterminated string literal")
r.token.byteValue = data[:length]
r.pos += length + 1 // skip closing '"' as well
}
// scanToken scans the next token if no token is currently available in the lexer.
@ -602,7 +610,7 @@ func (r *Lexer) Consumed() {
}
}
func (r *Lexer) unsafeString() (string, []byte) {
func (r *Lexer) unsafeString(skipUnescape bool) (string, []byte) {
if r.token.kind == tokenUndef && r.Ok() {
r.FetchToken()
}
@ -610,6 +618,13 @@ func (r *Lexer) unsafeString() (string, []byte) {
r.errInvalidToken("string")
return "", nil
}
if !skipUnescape {
if err := r.unescapeStringToken(); err != nil {
r.errInvalidToken("string")
return "", nil
}
}
bytes := r.token.byteValue
ret := bytesToStr(r.token.byteValue)
r.consume()
@ -621,13 +636,19 @@ func (r *Lexer) unsafeString() (string, []byte) {
// Warning: returned string may point to the input buffer, so the string should not outlive
// the input buffer. Intended pattern of usage is as an argument to a switch statement.
func (r *Lexer) UnsafeString() string {
ret, _ := r.unsafeString()
ret, _ := r.unsafeString(false)
return ret
}
// UnsafeBytes returns the byte slice if the token is a string literal.
func (r *Lexer) UnsafeBytes() []byte {
_, ret := r.unsafeString()
_, ret := r.unsafeString(false)
return ret
}
// UnsafeFieldName returns current member name string token
func (r *Lexer) UnsafeFieldName(skipUnescape bool) string {
ret, _ := r.unsafeString(skipUnescape)
return ret
}
@ -640,7 +661,34 @@ func (r *Lexer) String() string {
r.errInvalidToken("string")
return ""
}
ret := string(r.token.byteValue)
if err := r.unescapeStringToken(); err != nil {
r.errInvalidToken("string")
return ""
}
var ret string
if r.token.byteValueCloned {
ret = bytesToStr(r.token.byteValue)
} else {
ret = string(r.token.byteValue)
}
r.consume()
return ret
}
// StringIntern reads a string literal, and performs string interning on it.
func (r *Lexer) StringIntern() string {
if r.token.kind == tokenUndef && r.Ok() {
r.FetchToken()
}
if !r.Ok() || r.token.kind != tokenString {
r.errInvalidToken("string")
return ""
}
if err := r.unescapeStringToken(); err != nil {
r.errInvalidToken("string")
return ""
}
ret := intern.Bytes(r.token.byteValue)
r.consume()
return ret
}
@ -839,7 +887,7 @@ func (r *Lexer) Int() int {
}
func (r *Lexer) Uint8Str() uint8 {
s, b := r.unsafeString()
s, b := r.unsafeString(false)
if !r.Ok() {
return 0
}
@ -856,7 +904,7 @@ func (r *Lexer) Uint8Str() uint8 {
}
func (r *Lexer) Uint16Str() uint16 {
s, b := r.unsafeString()
s, b := r.unsafeString(false)
if !r.Ok() {
return 0
}
@ -873,7 +921,7 @@ func (r *Lexer) Uint16Str() uint16 {
}
func (r *Lexer) Uint32Str() uint32 {
s, b := r.unsafeString()
s, b := r.unsafeString(false)
if !r.Ok() {
return 0
}
@ -890,7 +938,7 @@ func (r *Lexer) Uint32Str() uint32 {
}
func (r *Lexer) Uint64Str() uint64 {
s, b := r.unsafeString()
s, b := r.unsafeString(false)
if !r.Ok() {
return 0
}
@ -915,7 +963,7 @@ func (r *Lexer) UintptrStr() uintptr {
}
func (r *Lexer) Int8Str() int8 {
s, b := r.unsafeString()
s, b := r.unsafeString(false)
if !r.Ok() {
return 0
}
@ -932,7 +980,7 @@ func (r *Lexer) Int8Str() int8 {
}
func (r *Lexer) Int16Str() int16 {
s, b := r.unsafeString()
s, b := r.unsafeString(false)
if !r.Ok() {
return 0
}
@ -949,7 +997,7 @@ func (r *Lexer) Int16Str() int16 {
}
func (r *Lexer) Int32Str() int32 {
s, b := r.unsafeString()
s, b := r.unsafeString(false)
if !r.Ok() {
return 0
}
@ -966,7 +1014,7 @@ func (r *Lexer) Int32Str() int32 {
}
func (r *Lexer) Int64Str() int64 {
s, b := r.unsafeString()
s, b := r.unsafeString(false)
if !r.Ok() {
return 0
}
@ -1004,7 +1052,7 @@ func (r *Lexer) Float32() float32 {
}
func (r *Lexer) Float32Str() float32 {
s, b := r.unsafeString()
s, b := r.unsafeString(false)
if !r.Ok() {
return 0
}
@ -1037,7 +1085,7 @@ func (r *Lexer) Float64() float64 {
}
func (r *Lexer) Float64Str() float64 {
s, b := r.unsafeString()
s, b := r.unsafeString(false)
if !r.Ok() {
return 0
}

View File

@ -297,11 +297,9 @@ func (w *Writer) String(s string) {
p := 0 // last non-escape symbol
var escapeTable [128]bool
escapeTable := &htmlEscapeTable
if w.NoEscapeHTML {
escapeTable = htmlNoEscapeTable
} else {
escapeTable = htmlEscapeTable
escapeTable = &htmlNoEscapeTable
}
for i := 0; i < len(s); {

View File

@ -1,8 +1,6 @@
package easyjson
import (
json "encoding/json"
jlexer "github.com/mailru/easyjson/jlexer"
"github.com/mailru/easyjson/jwriter"
)
@ -10,14 +8,14 @@ import (
// UnknownFieldsProxy implemets UnknownsUnmarshaler and UnknownsMarshaler
// use it as embedded field in your structure to parse and then serialize unknown struct fields
type UnknownFieldsProxy struct {
unknownFields map[string]interface{}
unknownFields map[string][]byte
}
func (s *UnknownFieldsProxy) UnmarshalUnknown(in *jlexer.Lexer, key string) {
if s.unknownFields == nil {
s.unknownFields = make(map[string]interface{}, 1)
s.unknownFields = make(map[string][]byte, 1)
}
s.unknownFields[key] = in.Interface()
s.unknownFields[key] = in.Raw()
}
func (s UnknownFieldsProxy) MarshalUnknowns(out *jwriter.Writer, first bool) {
@ -29,6 +27,6 @@ func (s UnknownFieldsProxy) MarshalUnknowns(out *jwriter.Writer, first bool) {
}
out.String(string(key))
out.RawByte(':')
out.Raw(json.Marshal(val))
out.Raw(val, nil)
}
}

View File

@ -14,9 +14,8 @@ import (
)
const (
Version = 4 // protocol version
HeaderLen = 20 // header length without extension headers
maxHeaderLen = 60 // sensible default, revisit if later RFCs define new usage of version and header length fields
Version = 4 // protocol version
HeaderLen = 20 // header length without extension headers
)
type HeaderFlags int

View File

@ -0,0 +1,30 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package unsafeheader contains header declarations for the Go runtime's
// slice and string implementations.
//
// This package allows x/sys to use types equivalent to
// reflect.SliceHeader and reflect.StringHeader without introducing
// a dependency on the (relatively heavy) "reflect" package.
package unsafeheader
import (
"unsafe"
)
// Slice is the runtime representation of a slice.
// It cannot be used safely or portably and its representation may change in a later release.
type Slice struct {
Data unsafe.Pointer
Len int
Cap int
}
// String is the runtime representation of a string.
// It cannot be used safely or portably and its representation may change in a later release.
type String struct {
Data unsafe.Pointer
Len int
}

View File

@ -187,6 +187,7 @@ struct ltchars {
#include <sys/select.h>
#include <sys/signalfd.h>
#include <sys/socket.h>
#include <sys/timerfd.h>
#include <sys/uio.h>
#include <sys/xattr.h>
#include <linux/bpf.h>
@ -480,7 +481,7 @@ ccflags="$@"
$2 ~ /^(MS|MNT|UMOUNT)_/ ||
$2 ~ /^NS_GET_/ ||
$2 ~ /^TUN(SET|GET|ATTACH|DETACH)/ ||
$2 ~ /^(O|F|[ES]?FD|NAME|S|PTRACE|PT)_/ ||
$2 ~ /^(O|F|[ES]?FD|NAME|S|PTRACE|PT|TFD)_/ ||
$2 ~ /^KEXEC_/ ||
$2 ~ /^LINUX_REBOOT_CMD_/ ||
$2 ~ /^LINUX_REBOOT_MAGIC[12]$/ ||

View File

@ -6,7 +6,11 @@
package unix
import "unsafe"
import (
"unsafe"
"golang.org/x/sys/internal/unsafeheader"
)
//sys closedir(dir uintptr) (err error)
//sys readdir_r(dir uintptr, entry *Dirent, result **Dirent) (res Errno)
@ -71,6 +75,7 @@ func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
cnt++
continue
}
reclen := int(entry.Reclen)
if reclen > len(buf) {
// Not enough room. Return for now.
@ -79,13 +84,15 @@ func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
// restarting is O(n^2) in the length of the directory. Oh well.
break
}
// Copy entry into return buffer.
s := struct {
ptr unsafe.Pointer
siz int
cap int
}{ptr: unsafe.Pointer(&entry), siz: reclen, cap: reclen}
copy(buf, *(*[]byte)(unsafe.Pointer(&s)))
var s []byte
hdr := (*unsafeheader.Slice)(unsafe.Pointer(&s))
hdr.Data = unsafe.Pointer(&entry)
hdr.Cap = reclen
hdr.Len = reclen
copy(buf, s)
buf = buf[reclen:]
n += reclen
cnt++

View File

@ -1633,6 +1633,15 @@ func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err e
//sys CopyFileRange(rfd int, roff *int64, wfd int, woff *int64, len int, flags int) (n int, err error)
//sys DeleteModule(name string, flags int) (err error)
//sys Dup(oldfd int) (fd int, err error)
func Dup2(oldfd, newfd int) error {
// Android O and newer blocks dup2; riscv and arm64 don't implement dup2.
if runtime.GOOS == "android" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "arm64" {
return Dup3(oldfd, newfd, 0)
}
return dup2(oldfd, newfd)
}
//sys Dup3(oldfd int, newfd int, flags int) (err error)
//sysnb EpollCreate1(flag int) (fd int, err error)
//sysnb EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error)
@ -1757,6 +1766,9 @@ func Signalfd(fd int, sigmask *Sigset_t, flags int) (newfd int, err error) {
//sys Syncfs(fd int) (err error)
//sysnb Sysinfo(info *Sysinfo_t) (err error)
//sys Tee(rfd int, wfd int, len int, flags int) (n int64, err error)
//sysnb TimerfdCreate(clockid int, flags int) (fd int, err error)
//sysnb TimerfdGettime(fd int, currValue *ItimerSpec) (err error)
//sysnb TimerfdSettime(fd int, flags int, newValue *ItimerSpec, oldValue *ItimerSpec) (err error)
//sysnb Tgkill(tgid int, tid int, sig syscall.Signal) (err error)
//sysnb Times(tms *Tms) (ticks uintptr, err error)
//sysnb Umask(mask int) (oldmask int)
@ -2178,7 +2190,6 @@ func Klogset(typ int, arg int) (err error) {
// TimerGetoverrun
// TimerGettime
// TimerSettime
// Timerfd
// Tkill (obsolete)
// Tuxcall
// Umount2

View File

@ -49,7 +49,7 @@ func Pipe2(p []int, flags int) (err error) {
// 64-bit file system and 32-bit uid calls
// (386 default is 32-bit file system and 16-bit uid).
//sys Dup2(oldfd int, newfd int) (err error)
//sys dup2(oldfd int, newfd int) (err error)
//sysnb EpollCreate(size int) (fd int, err error)
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64_64

View File

@ -6,7 +6,7 @@
package unix
//sys Dup2(oldfd int, newfd int) (err error)
//sys dup2(oldfd int, newfd int) (err error)
//sysnb EpollCreate(size int) (fd int, err error)
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64

View File

@ -80,7 +80,7 @@ func Seek(fd int, offset int64, whence int) (newoffset int64, err error) {
// 64-bit file system and 32-bit uid calls
// (16-bit uid calls are not always supported in newer kernels)
//sys Dup2(oldfd int, newfd int) (err error)
//sys dup2(oldfd int, newfd int) (err error)
//sysnb EpollCreate(size int) (fd int, err error)
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
//sys Fchown(fd int, uid int, gid int) (err error) = SYS_FCHOWN32

View File

@ -210,9 +210,9 @@ func InotifyInit() (fd int, err error) {
return InotifyInit1(0)
}
func Dup2(oldfd int, newfd int) (err error) {
return Dup3(oldfd, newfd, 0)
}
// dup2 exists because func Dup3 in syscall_linux.go references
// it in an unreachable path. dup2 isn't available on arm64.
func dup2(oldfd int, newfd int) error
func Pause() error {
_, err := ppoll(nil, 0, nil, nil)

View File

@ -7,7 +7,7 @@
package unix
//sys Dup2(oldfd int, newfd int) (err error)
//sys dup2(oldfd int, newfd int) (err error)
//sysnb EpollCreate(size int) (fd int, err error)
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64

View File

@ -14,7 +14,7 @@ import (
func Syscall9(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err syscall.Errno)
//sys Dup2(oldfd int, newfd int) (err error)
//sys dup2(oldfd int, newfd int) (err error)
//sysnb EpollCreate(size int) (fd int, err error)
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64

View File

@ -7,7 +7,7 @@
package unix
//sys Dup2(oldfd int, newfd int) (err error)
//sys dup2(oldfd int, newfd int) (err error)
//sysnb EpollCreate(size int) (fd int, err error)
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64

View File

@ -191,10 +191,6 @@ func InotifyInit() (fd int, err error) {
return InotifyInit1(0)
}
func Dup2(oldfd int, newfd int) (err error) {
return Dup3(oldfd, newfd, 0)
}
func Pause() error {
_, err := ppoll(nil, 0, nil, nil)
return err
@ -228,3 +224,7 @@ func KexecFileLoad(kernelFd int, initrdFd int, cmdline string, flags int) error
}
return kexecFileLoad(kernelFd, initrdFd, cmdlineLen, cmdline, flags)
}
// dup2 exists because func Dup3 in syscall_linux.go references
// it in an unreachable path. dup2 isn't available on arm64.
func dup2(oldfd int, newfd int) error

View File

@ -10,7 +10,7 @@ import (
"unsafe"
)
//sys Dup2(oldfd int, newfd int) (err error)
//sys dup2(oldfd int, newfd int) (err error)
//sysnb EpollCreate(size int) (fd int, err error)
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64

View File

@ -8,7 +8,7 @@ package unix
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
//sys Dup2(oldfd int, newfd int) (err error)
//sys dup2(oldfd int, newfd int) (err error)
//sys Fchown(fd int, uid int, gid int) (err error)
//sys Fstat(fd int, stat *Stat_t) (err error)
//sys Fstatat(dirfd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64

View File

@ -12,6 +12,8 @@ import (
"sync"
"syscall"
"unsafe"
"golang.org/x/sys/internal/unsafeheader"
)
var (
@ -113,15 +115,12 @@ func (m *mmapper) Mmap(fd int, offset int64, length int, prot int, flags int) (d
return nil, errno
}
// Slice memory layout
var sl = struct {
addr uintptr
len int
cap int
}{addr, length, length}
// Use unsafe to turn sl into a []byte.
b := *(*[]byte)(unsafe.Pointer(&sl))
// Use unsafe to convert addr into a []byte.
var b []byte
hdr := (*unsafeheader.Slice)(unsafe.Pointer(&b))
hdr.Data = unsafe.Pointer(addr)
hdr.Cap = length
hdr.Len = length
// Register mapping in m and return it.
p := &b[cap(b)-1]

View File

@ -2165,6 +2165,8 @@ const (
TCP_USER_TIMEOUT = 0x12
TCP_WINDOW_CLAMP = 0xa
TCP_ZEROCOPY_RECEIVE = 0x23
TFD_TIMER_ABSTIME = 0x1
TFD_TIMER_CANCEL_ON_SET = 0x2
TIMER_ABSTIME = 0x1
TIOCM_DTR = 0x2
TIOCM_LE = 0x1

View File

@ -342,6 +342,8 @@ const (
TCSETXF = 0x5434
TCSETXW = 0x5435
TCXONC = 0x540a
TFD_CLOEXEC = 0x80000
TFD_NONBLOCK = 0x800
TIOCCBRK = 0x5428
TIOCCONS = 0x541d
TIOCEXCL = 0x540c

View File

@ -343,6 +343,8 @@ const (
TCSETXF = 0x5434
TCSETXW = 0x5435
TCXONC = 0x540a
TFD_CLOEXEC = 0x80000
TFD_NONBLOCK = 0x800
TIOCCBRK = 0x5428
TIOCCONS = 0x541d
TIOCEXCL = 0x540c

View File

@ -349,6 +349,8 @@ const (
TCSETXF = 0x5434
TCSETXW = 0x5435
TCXONC = 0x540a
TFD_CLOEXEC = 0x80000
TFD_NONBLOCK = 0x800
TIOCCBRK = 0x5428
TIOCCONS = 0x541d
TIOCEXCL = 0x540c

View File

@ -336,6 +336,8 @@ const (
TCSETXF = 0x5434
TCSETXW = 0x5435
TCXONC = 0x540a
TFD_CLOEXEC = 0x80000
TFD_NONBLOCK = 0x800
TIOCCBRK = 0x5428
TIOCCONS = 0x541d
TIOCEXCL = 0x540c

View File

@ -339,6 +339,8 @@ const (
TCSETSW = 0x540f
TCSETSW2 = 0x8030542c
TCXONC = 0x5406
TFD_CLOEXEC = 0x80000
TFD_NONBLOCK = 0x80
TIOCCBRK = 0x5428
TIOCCONS = 0x80047478
TIOCEXCL = 0x740d

View File

@ -339,6 +339,8 @@ const (
TCSETSW = 0x540f
TCSETSW2 = 0x8030542c
TCXONC = 0x5406
TFD_CLOEXEC = 0x80000
TFD_NONBLOCK = 0x80
TIOCCBRK = 0x5428
TIOCCONS = 0x80047478
TIOCEXCL = 0x740d

View File

@ -339,6 +339,8 @@ const (
TCSETSW = 0x540f
TCSETSW2 = 0x8030542c
TCXONC = 0x5406
TFD_CLOEXEC = 0x80000
TFD_NONBLOCK = 0x80
TIOCCBRK = 0x5428
TIOCCONS = 0x80047478
TIOCEXCL = 0x740d

View File

@ -339,6 +339,8 @@ const (
TCSETSW = 0x540f
TCSETSW2 = 0x8030542c
TCXONC = 0x5406
TFD_CLOEXEC = 0x80000
TFD_NONBLOCK = 0x80
TIOCCBRK = 0x5428
TIOCCONS = 0x80047478
TIOCEXCL = 0x740d

View File

@ -393,6 +393,8 @@ const (
TCSETSF = 0x802c7416
TCSETSW = 0x802c7415
TCXONC = 0x2000741e
TFD_CLOEXEC = 0x80000
TFD_NONBLOCK = 0x800
TIOCCBRK = 0x5428
TIOCCONS = 0x541d
TIOCEXCL = 0x540c

View File

@ -393,6 +393,8 @@ const (
TCSETSF = 0x802c7416
TCSETSW = 0x802c7415
TCXONC = 0x2000741e
TFD_CLOEXEC = 0x80000
TFD_NONBLOCK = 0x800
TIOCCBRK = 0x5428
TIOCCONS = 0x541d
TIOCEXCL = 0x540c

View File

@ -330,6 +330,8 @@ const (
TCSETXF = 0x5434
TCSETXW = 0x5435
TCXONC = 0x540a
TFD_CLOEXEC = 0x80000
TFD_NONBLOCK = 0x800
TIOCCBRK = 0x5428
TIOCCONS = 0x541d
TIOCEXCL = 0x540c

View File

@ -403,6 +403,8 @@ const (
TCSETXF = 0x5434
TCSETXW = 0x5435
TCXONC = 0x540a
TFD_CLOEXEC = 0x80000
TFD_NONBLOCK = 0x800
TIOCCBRK = 0x5428
TIOCCONS = 0x541d
TIOCEXCL = 0x540c

View File

@ -392,6 +392,8 @@ const (
TCSETSW = 0x8024540a
TCSETSW2 = 0x802c540e
TCXONC = 0x20005406
TFD_CLOEXEC = 0x400000
TFD_NONBLOCK = 0x4000
TIOCCBRK = 0x2000747a
TIOCCONS = 0x20007424
TIOCEXCL = 0x2000740d

View File

@ -1450,6 +1450,37 @@ func Sysinfo(info *Sysinfo_t) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func TimerfdCreate(clockid int, flags int) (fd int, err error) {
r0, _, e1 := RawSyscall(SYS_TIMERFD_CREATE, uintptr(clockid), uintptr(flags), 0)
fd = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func TimerfdGettime(fd int, currValue *ItimerSpec) (err error) {
_, _, e1 := RawSyscall(SYS_TIMERFD_GETTIME, uintptr(fd), uintptr(unsafe.Pointer(currValue)), 0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func TimerfdSettime(fd int, flags int, newValue *ItimerSpec, oldValue *ItimerSpec) (err error) {
_, _, e1 := RawSyscall6(SYS_TIMERFD_SETTIME, uintptr(fd), uintptr(flags), uintptr(unsafe.Pointer(newValue)), uintptr(unsafe.Pointer(oldValue)), 0, 0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
_, _, e1 := RawSyscall(SYS_TGKILL, uintptr(tgid), uintptr(tid), uintptr(sig))
if e1 != 0 {

View File

@ -55,7 +55,7 @@ func pipe(p *[2]_C_int) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Dup2(oldfd int, newfd int) (err error) {
func dup2(oldfd int, newfd int) (err error) {
_, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)
if e1 != 0 {
err = errnoErr(e1)

View File

@ -45,7 +45,7 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Dup2(oldfd int, newfd int) (err error) {
func dup2(oldfd int, newfd int) (err error) {
_, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)
if e1 != 0 {
err = errnoErr(e1)

View File

@ -234,7 +234,7 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Dup2(oldfd int, newfd int) (err error) {
func dup2(oldfd int, newfd int) (err error) {
_, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)
if e1 != 0 {
err = errnoErr(e1)

View File

@ -45,7 +45,7 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Dup2(oldfd int, newfd int) (err error) {
func dup2(oldfd int, newfd int) (err error) {
_, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)
if e1 != 0 {
err = errnoErr(e1)

View File

@ -45,7 +45,7 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Dup2(oldfd int, newfd int) (err error) {
func dup2(oldfd int, newfd int) (err error) {
_, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)
if e1 != 0 {
err = errnoErr(e1)

View File

@ -45,7 +45,7 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Dup2(oldfd int, newfd int) (err error) {
func dup2(oldfd int, newfd int) (err error) {
_, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)
if e1 != 0 {
err = errnoErr(e1)

View File

@ -45,7 +45,7 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Dup2(oldfd int, newfd int) (err error) {
func dup2(oldfd int, newfd int) (err error) {
_, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)
if e1 != 0 {
err = errnoErr(e1)

View File

@ -45,7 +45,7 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Dup2(oldfd int, newfd int) (err error) {
func dup2(oldfd int, newfd int) (err error) {
_, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)
if e1 != 0 {
err = errnoErr(e1)

View File

@ -45,7 +45,7 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Dup2(oldfd int, newfd int) (err error) {
func dup2(oldfd int, newfd int) (err error) {
_, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)
if e1 != 0 {
err = errnoErr(e1)

View File

@ -45,7 +45,7 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Dup2(oldfd int, newfd int) (err error) {
func dup2(oldfd int, newfd int) (err error) {
_, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)
if e1 != 0 {
err = errnoErr(e1)

Some files were not shown because too many files have changed in this diff Show More