unitylock 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.babelrc +8 -0
- data/.gitignore +11 -0
- data/.node-version +1 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +53 -0
- data/LICENSE.txt +21 -0
- data/Procfile +1 -0
- data/README.md +32 -0
- data/Rakefile +10 -0
- data/app.json +7 -0
- data/app/action_creators/Lock.js +22 -0
- data/app/action_creators/Login.js +10 -0
- data/app/action_creators/LoginDialogAction.js +9 -0
- data/app/action_creators/Search.js +13 -0
- data/app/action_creators/SnackMessage.js +6 -0
- data/app/action_creators/Unlock.js +22 -0
- data/app/client.js +1 -0
- data/app/components/AppSnackBarComponent.js +24 -0
- data/app/components/FooterComponent.js +40 -0
- data/app/components/LockAppBarComponent.js +60 -0
- data/app/components/LockTableComponent.js +59 -0
- data/app/components/LoginDialogComponent.js +69 -0
- data/app/containers/App.js +18 -0
- data/app/containers/AppSnackBar.js +19 -0
- data/app/containers/LockAppBar.js +30 -0
- data/app/containers/LockTable.js +44 -0
- data/app/containers/LoginDialog.js +32 -0
- data/app/index.html +20 -0
- data/app/index.js +44 -0
- data/app/reducers/index.js +33 -0
- data/app/server.js +2 -0
- data/app/src/client.js +35 -0
- data/app/src/helper.js +85 -0
- data/app/src/row.js +60 -0
- data/app/src/socket.js +47 -0
- data/app/test/helper.spec.js +47 -0
- data/app/utils/FileUtils.js +11 -0
- data/art/unitylock-logo.png +0 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/config.ru +4 -0
- data/config/puma.rb +9 -0
- data/exe/unitylock +44 -0
- data/lib/unitylock/client.rb +2 -0
- data/lib/unitylock/client/main.rb +90 -0
- data/lib/unitylock/server.rb +2 -0
- data/lib/unitylock/server/entity/unityfile.rb +38 -0
- data/lib/unitylock/server/model.rb +59 -0
- data/lib/unitylock/server/router.rb +71 -0
- data/lib/unitylock/server/service.rb +41 -0
- data/lib/unitylock/version.rb +3 -0
- data/package.json +52 -0
- data/public/favicon.ico +0 -0
- data/public/static/bundle.js +98 -0
- data/public/static/main.css +82 -0
- data/unitylock.gemspec +25 -0
- data/webpack.config.js +40 -0
- metadata +148 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
import { basename } from '../utils/FileUtils'
|
2
|
+
import React, { Component, PropTypes } from 'react'
|
3
|
+
import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table'
|
4
|
+
|
5
|
+
class LockTableComponent extends Component {
|
6
|
+
constructor(props) {
|
7
|
+
super(props)
|
8
|
+
this.handleOnCellClick = this.handleOnCellClick.bind(this);
|
9
|
+
}
|
10
|
+
|
11
|
+
handleOnCellClick(index) {
|
12
|
+
let selectedData = this.props.list[index];
|
13
|
+
this.props.onDataClick(this.props.user, selectedData);
|
14
|
+
}
|
15
|
+
|
16
|
+
render() {
|
17
|
+
const _data = this.props.list;
|
18
|
+
const _user = this.props.user;
|
19
|
+
this.data = _data;
|
20
|
+
|
21
|
+
return (
|
22
|
+
<Table
|
23
|
+
multiSelectable={false}
|
24
|
+
onCellClick={this.handleOnCellClick}
|
25
|
+
>
|
26
|
+
<TableHeader enableSelectAll={false}>
|
27
|
+
<TableRow>
|
28
|
+
<TableHeaderColumn>User</TableHeaderColumn>
|
29
|
+
<TableHeaderColumn>File</TableHeaderColumn>
|
30
|
+
<TableHeaderColumn>Path</TableHeaderColumn>
|
31
|
+
<TableHeaderColumn>Time</TableHeaderColumn>
|
32
|
+
</TableRow>
|
33
|
+
</TableHeader>
|
34
|
+
<TableBody>
|
35
|
+
{_data.map( (item, index) => (
|
36
|
+
<TableRow key={index} selectable={item.user == null || item.user == _user}>
|
37
|
+
<TableRowColumn>{item.user}</TableRowColumn>
|
38
|
+
<TableRowColumn>{basename(item.file)}</TableRowColumn>
|
39
|
+
<TableRowColumn>{item.file}</TableRowColumn>
|
40
|
+
<TableRowColumn>{item.updated_at}</TableRowColumn>
|
41
|
+
</TableRow>
|
42
|
+
))}
|
43
|
+
</TableBody>
|
44
|
+
</Table>
|
45
|
+
)
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
LockTableComponent.propTypes = {
|
50
|
+
user: PropTypes.string.isRequired,
|
51
|
+
list: PropTypes.arrayOf(PropTypes.shape({
|
52
|
+
user: PropTypes.string.isDefined,
|
53
|
+
file: PropTypes.string.isRequired,
|
54
|
+
updated_at: PropTypes.string.isRequired
|
55
|
+
}).isRequired).isRequired,
|
56
|
+
onDataClick: PropTypes.func.isRequired,
|
57
|
+
}
|
58
|
+
|
59
|
+
export default LockTableComponent
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import React, { Component, PropTypes } from 'react'
|
2
|
+
import Dialog from 'material-ui/Dialog'
|
3
|
+
import FlatButton from 'material-ui/FlatButton'
|
4
|
+
import RaisedButton from 'material-ui/RaisedButton'
|
5
|
+
import TextField from 'material-ui/TextField'
|
6
|
+
|
7
|
+
class LoginDialogComponent extends Component {
|
8
|
+
constructor(props) {
|
9
|
+
super(props)
|
10
|
+
this.state = {
|
11
|
+
user: this.props.user,
|
12
|
+
}
|
13
|
+
this.handleOnLoginClick = this.handleOnLoginClick.bind(this)
|
14
|
+
this.handleOnTextChange = this.handleOnTextChange.bind(this)
|
15
|
+
}
|
16
|
+
|
17
|
+
handleOnLoginClick() {
|
18
|
+
this.props.onLoginRequest(this.state.user)
|
19
|
+
}
|
20
|
+
|
21
|
+
handleOnTextChange(event) {
|
22
|
+
this.setState({
|
23
|
+
user: event.target.value,
|
24
|
+
})
|
25
|
+
}
|
26
|
+
|
27
|
+
render () {
|
28
|
+
const actions = [
|
29
|
+
<FlatButton
|
30
|
+
label="Cancel"
|
31
|
+
onTouchTap={this.props.onCancelRequest}
|
32
|
+
/>,
|
33
|
+
<FlatButton
|
34
|
+
label="Login"
|
35
|
+
primary={true}
|
36
|
+
onTouchTap={this.handleOnLoginClick}
|
37
|
+
/>,
|
38
|
+
]
|
39
|
+
|
40
|
+
return (
|
41
|
+
<div>
|
42
|
+
<Dialog
|
43
|
+
title='Login'
|
44
|
+
actions={actions}
|
45
|
+
modal={false}
|
46
|
+
open={this.props.open}
|
47
|
+
>
|
48
|
+
Input your name:
|
49
|
+
<br/>
|
50
|
+
<TextField
|
51
|
+
id='text-field-controlled'
|
52
|
+
hintText='User Name'
|
53
|
+
value={this.state.user}
|
54
|
+
onChange={this.handleOnTextChange}
|
55
|
+
/>
|
56
|
+
</Dialog>
|
57
|
+
</div>
|
58
|
+
)
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
LoginDialogComponent.propTypes = {
|
63
|
+
open: PropTypes.bool.isRequired,
|
64
|
+
user: PropTypes.string.isRequired,
|
65
|
+
onLoginRequest: PropTypes.func.isRequired,
|
66
|
+
onCancelRequest: PropTypes.func.isRequired,
|
67
|
+
}
|
68
|
+
|
69
|
+
export default LoginDialogComponent
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import React, { Component, PropTypes } from 'react'
|
2
|
+
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
|
3
|
+
import LockTable from './LockTable'
|
4
|
+
import LockAppBar from './LockAppBar'
|
5
|
+
import AppSnackBar from './AppSnackBar'
|
6
|
+
import LoginDialog from './LoginDialog'
|
7
|
+
import FooterComponent from '../components/FooterComponent'
|
8
|
+
|
9
|
+
const App = () => (
|
10
|
+
<div>
|
11
|
+
<LockAppBar />
|
12
|
+
<LockTable />
|
13
|
+
<AppSnackBar />
|
14
|
+
<LoginDialog />
|
15
|
+
<FooterComponent />
|
16
|
+
</div>
|
17
|
+
)
|
18
|
+
export default App
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import AppSnackBarComponent from '../components/AppSnackBarComponent'
|
2
|
+
import { connect } from 'react-redux'
|
3
|
+
|
4
|
+
const mapStateToProps = (state) => {
|
5
|
+
return {
|
6
|
+
message: state.snack_message || '',
|
7
|
+
}
|
8
|
+
};
|
9
|
+
|
10
|
+
const mapDispatchToProps = (dispatch, ownProps) => {
|
11
|
+
return {}
|
12
|
+
};
|
13
|
+
|
14
|
+
const AppSnackBar = connect(
|
15
|
+
mapStateToProps,
|
16
|
+
mapDispatchToProps
|
17
|
+
)(AppSnackBarComponent)
|
18
|
+
|
19
|
+
export default AppSnackBar
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import { connect } from 'react-redux'
|
2
|
+
import LockAppBarComponent from '../components/LockAppBarComponent'
|
3
|
+
import search from '../action_creators/Search'
|
4
|
+
import snack_message from '../action_creators/SnackMessage'
|
5
|
+
import login_dialog_action from '../action_creators/LoginDialogAction'
|
6
|
+
|
7
|
+
const mapStateToProps = (state) => {
|
8
|
+
return {}
|
9
|
+
};
|
10
|
+
|
11
|
+
const mapDispatchToProps = (dispatch, ownProps) => {
|
12
|
+
return {
|
13
|
+
onLoginClick: () => {
|
14
|
+
login_dialog_action(dispatch, true)
|
15
|
+
},
|
16
|
+
onSyncClick: () => {
|
17
|
+
search(dispatch)
|
18
|
+
.then((_) => {
|
19
|
+
snack_message(dispatch, 'synchronized');
|
20
|
+
})
|
21
|
+
},
|
22
|
+
}
|
23
|
+
};
|
24
|
+
|
25
|
+
const LockAppBar = connect(
|
26
|
+
mapStateToProps,
|
27
|
+
mapDispatchToProps
|
28
|
+
)(LockAppBarComponent)
|
29
|
+
|
30
|
+
export default LockAppBar
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import { connect } from 'react-redux'
|
2
|
+
import LockTableComponent from '../components/LockTableComponent'
|
3
|
+
import search from '../action_creators/Search'
|
4
|
+
import lock from '../action_creators/Lock'
|
5
|
+
import unlock from '../action_creators/Unlock'
|
6
|
+
|
7
|
+
const mapStateToProps = (state) => {
|
8
|
+
return {
|
9
|
+
list: state['data'],
|
10
|
+
user: state['user'] || 'sample_user',
|
11
|
+
}
|
12
|
+
};
|
13
|
+
|
14
|
+
const mapDispatchToProps = (dispatch, ownProps) => {
|
15
|
+
return {
|
16
|
+
onDataClick: (user, selectedData) => {
|
17
|
+
if (user == null || user == '') {
|
18
|
+
console.warn("user not defined");
|
19
|
+
return;
|
20
|
+
}
|
21
|
+
|
22
|
+
let dataFile = selectedData['file']
|
23
|
+
let dataUser = selectedData['user']
|
24
|
+
|
25
|
+
// unlock
|
26
|
+
if (dataUser) {
|
27
|
+
unlock(dispatch, user, dataFile)
|
28
|
+
.then((it) => { search(dispatch) })
|
29
|
+
}
|
30
|
+
// lock
|
31
|
+
else {
|
32
|
+
lock(dispatch, user, dataFile)
|
33
|
+
.then((it) => { search(dispatch) })
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
};
|
38
|
+
|
39
|
+
const LockTable = connect(
|
40
|
+
mapStateToProps,
|
41
|
+
mapDispatchToProps
|
42
|
+
)(LockTableComponent)
|
43
|
+
|
44
|
+
export default LockTable
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import { connect } from 'react-redux'
|
2
|
+
import LoginDialogComponent from '../components/LoginDialogComponent'
|
3
|
+
import login_dialog_action from '../action_creators/LoginDialogAction'
|
4
|
+
import login from '../action_creators/Login'
|
5
|
+
import snack_message from '../action_creators/SnackMessage'
|
6
|
+
|
7
|
+
const mapStateToProps = (state) => {
|
8
|
+
return {
|
9
|
+
open: state.login_dialog_open,
|
10
|
+
user: state.user,
|
11
|
+
}
|
12
|
+
};
|
13
|
+
|
14
|
+
const mapDispatchToProps = (dispatch, ownProps) => {
|
15
|
+
return {
|
16
|
+
onLoginRequest: (user) => {
|
17
|
+
login(dispatch, user)
|
18
|
+
.then(_ => login_dialog_action(dispatch, false))
|
19
|
+
.then(_ => snack_message(dispatch, 'Logined as ' + user))
|
20
|
+
},
|
21
|
+
onCancelRequest: () => {
|
22
|
+
login_dialog_action(dispatch, false)
|
23
|
+
},
|
24
|
+
}
|
25
|
+
};
|
26
|
+
|
27
|
+
const LoginDialog = connect(
|
28
|
+
mapStateToProps,
|
29
|
+
mapDispatchToProps
|
30
|
+
)(LoginDialogComponent)
|
31
|
+
|
32
|
+
export default LoginDialog
|
data/app/index.html
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8" />
|
5
|
+
<title>unitylock</title>
|
6
|
+
<link rel="stylesheet" type="text/css" href="/static/main.css">
|
7
|
+
<!--
|
8
|
+
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
9
|
+
<link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32">
|
10
|
+
<link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16">
|
11
|
+
<link rel="manifest" href="/manifest.json">
|
12
|
+
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
|
13
|
+
<meta name="theme-color" content="#ffffff">
|
14
|
+
-->
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
<div id="root"></div>
|
18
|
+
<script src="/static/bundle.js"></script>
|
19
|
+
</body>
|
20
|
+
</html>
|
data/app/index.js
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import ReactDOM from 'react-dom'
|
3
|
+
import { Provider } from 'react-redux'
|
4
|
+
import { compose, createStore } from 'redux'
|
5
|
+
import persistState from 'redux-localstorage'
|
6
|
+
import injectTapEventPlugin from 'react-tap-event-plugin'
|
7
|
+
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
|
8
|
+
|
9
|
+
import reducers from './reducers'
|
10
|
+
import App from './containers/App'
|
11
|
+
import search from './action_creators/Search'
|
12
|
+
import login from './action_creators/Login'
|
13
|
+
import snack_message from './action_creators/SnackMessage'
|
14
|
+
|
15
|
+
injectTapEventPlugin()
|
16
|
+
|
17
|
+
const enhancer = compose(persistState('user'))
|
18
|
+
const store = createStore(reducers, enhancer)
|
19
|
+
const rootElement = document.getElementById('root')
|
20
|
+
|
21
|
+
function render()
|
22
|
+
{
|
23
|
+
ReactDOM.render(
|
24
|
+
<MuiThemeProvider>
|
25
|
+
<Provider store={store}>
|
26
|
+
<App />
|
27
|
+
</Provider>
|
28
|
+
</MuiThemeProvider>,
|
29
|
+
rootElement
|
30
|
+
)
|
31
|
+
}
|
32
|
+
|
33
|
+
if (store.state) {
|
34
|
+
console.log("logined user: " + store.state.user);
|
35
|
+
}
|
36
|
+
|
37
|
+
render()
|
38
|
+
store.subscribe(render)
|
39
|
+
|
40
|
+
// initial request
|
41
|
+
search(store.dispatch).then((_) =>
|
42
|
+
snack_message(store.dispatch, 'Welcome! ' + store.getState().user)
|
43
|
+
)
|
44
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
export default function list(
|
2
|
+
state = {},
|
3
|
+
action
|
4
|
+
)
|
5
|
+
{
|
6
|
+
var newState = Object.assign({
|
7
|
+
data: [],
|
8
|
+
user: 'guest',
|
9
|
+
snack_message: '',
|
10
|
+
login_dialog_open: false,
|
11
|
+
}, state)
|
12
|
+
|
13
|
+
switch (action.type) {
|
14
|
+
case 'search':
|
15
|
+
newState['data'] = action['data']
|
16
|
+
break;
|
17
|
+
case 'login':
|
18
|
+
newState['user'] = action['user']
|
19
|
+
break;
|
20
|
+
case 'snack_message':
|
21
|
+
newState['snack_message'] = action['snack_message']
|
22
|
+
break;
|
23
|
+
case 'login_dialog_open':
|
24
|
+
newState['login_dialog_open'] = action.login_dialog_open
|
25
|
+
break;
|
26
|
+
default:
|
27
|
+
break;
|
28
|
+
}
|
29
|
+
|
30
|
+
console.log("reduced: " + action.type);
|
31
|
+
|
32
|
+
return newState;
|
33
|
+
}
|
data/app/server.js
ADDED
data/app/src/client.js
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
var WebSocketClient = require('websocket').client;
|
2
|
+
var port = 8080
|
3
|
+
var client = new WebSocketClient();
|
4
|
+
|
5
|
+
client.on('connectFailed', function(error) {
|
6
|
+
console.log('Connect Error: ' + error.toString());
|
7
|
+
});
|
8
|
+
|
9
|
+
client.on('connect', function(connection) {
|
10
|
+
console.log('WebSocket Client Connected');
|
11
|
+
connection.on('error', function(error) {
|
12
|
+
console.log("Connection Error: " + error.toString());
|
13
|
+
});
|
14
|
+
connection.on('close', function() {
|
15
|
+
console.log('echo-protocol Connection Closed');
|
16
|
+
});
|
17
|
+
|
18
|
+
|
19
|
+
connection.on('message', function(message) {
|
20
|
+
if (message.type === 'utf8') {
|
21
|
+
console.log("Received: '" + message.utf8Data + "'");
|
22
|
+
}
|
23
|
+
});
|
24
|
+
|
25
|
+
function sendNumber() {
|
26
|
+
if (connection.connected) {
|
27
|
+
var number = Math.round(Math.random() * 0xFFFFFF);
|
28
|
+
connection.sendUTF(number.toString());
|
29
|
+
setTimeout(sendNumber, 1000);
|
30
|
+
}
|
31
|
+
}
|
32
|
+
sendNumber();
|
33
|
+
});
|
34
|
+
|
35
|
+
client.connect('ws://localhost:' + port, 'echo-protocol');
|
data/app/src/helper.js
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
var fs = require('fs')
|
2
|
+
|
3
|
+
// { path: { path:"", owner:"", updated_at: 0 } }
|
4
|
+
// TODO: synchronize
|
5
|
+
// TODO: mysql support
|
6
|
+
class Helper {
|
7
|
+
constructor(file) {
|
8
|
+
this.file = file
|
9
|
+
}
|
10
|
+
|
11
|
+
raw() {
|
12
|
+
return new Promise((resolve, reject) => {
|
13
|
+
fs.readFile(this.file, (err, text) => {
|
14
|
+
if (err) {
|
15
|
+
// reject(err)
|
16
|
+
resolve({})
|
17
|
+
return
|
18
|
+
}
|
19
|
+
resolve(JSON.parse(text))
|
20
|
+
})
|
21
|
+
})
|
22
|
+
}
|
23
|
+
|
24
|
+
search() {
|
25
|
+
return this.raw()
|
26
|
+
.then(hash => {
|
27
|
+
return hash.values || []
|
28
|
+
})
|
29
|
+
}
|
30
|
+
|
31
|
+
fetch_by_path(path) {
|
32
|
+
return this.raw()
|
33
|
+
.then(hash => {
|
34
|
+
return hash[path]
|
35
|
+
})
|
36
|
+
}
|
37
|
+
|
38
|
+
update_user_by_row(row) {
|
39
|
+
this.row()
|
40
|
+
.then(hash => {
|
41
|
+
let hashkeys = hash.keys || []
|
42
|
+
hash[row.path]
|
43
|
+
|
44
|
+
})
|
45
|
+
}
|
46
|
+
|
47
|
+
create_or_update_by_pathes(pathes) {
|
48
|
+
return new Promise((resolve, reject) => {
|
49
|
+
this.raw()
|
50
|
+
.then(hash => {
|
51
|
+
let hashkeys = hash.keys || []
|
52
|
+
pathes.forEach(path => {
|
53
|
+
if (!hashkeys.includes(path)) {
|
54
|
+
hash[path] = { path: path, updated_at: Date.now() }
|
55
|
+
} else {
|
56
|
+
hash[path]["updated_at"] = Date.now()
|
57
|
+
}
|
58
|
+
})
|
59
|
+
return hash
|
60
|
+
})
|
61
|
+
.then(hash => {
|
62
|
+
var writeData = JSON.stringify(hash)
|
63
|
+
fs.writeFile(this.file, writeData, (err) => {
|
64
|
+
if (err != null) { reject(err) }
|
65
|
+
let hashvalues = (Object.keys(hash) || []).map(it => hash[it])
|
66
|
+
let result = hashvalues.filter(it => pathes.includes(it.path)) || []
|
67
|
+
resolve(result)
|
68
|
+
})
|
69
|
+
})
|
70
|
+
})
|
71
|
+
}
|
72
|
+
|
73
|
+
delete_by_path(path) {
|
74
|
+
return raw()
|
75
|
+
.then(hash => {
|
76
|
+
delete hash[path]
|
77
|
+
return hash
|
78
|
+
})
|
79
|
+
.then(hash => {
|
80
|
+
fs.writeFile(this.file, JSON.stringify(hash))
|
81
|
+
})
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
export default Helper
|