unitylock 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|