unitylock 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.babelrc +8 -0
  3. data/.gitignore +11 -0
  4. data/.node-version +1 -0
  5. data/.rspec +2 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +5 -0
  8. data/CODE_OF_CONDUCT.md +49 -0
  9. data/Gemfile +8 -0
  10. data/Gemfile.lock +53 -0
  11. data/LICENSE.txt +21 -0
  12. data/Procfile +1 -0
  13. data/README.md +32 -0
  14. data/Rakefile +10 -0
  15. data/app.json +7 -0
  16. data/app/action_creators/Lock.js +22 -0
  17. data/app/action_creators/Login.js +10 -0
  18. data/app/action_creators/LoginDialogAction.js +9 -0
  19. data/app/action_creators/Search.js +13 -0
  20. data/app/action_creators/SnackMessage.js +6 -0
  21. data/app/action_creators/Unlock.js +22 -0
  22. data/app/client.js +1 -0
  23. data/app/components/AppSnackBarComponent.js +24 -0
  24. data/app/components/FooterComponent.js +40 -0
  25. data/app/components/LockAppBarComponent.js +60 -0
  26. data/app/components/LockTableComponent.js +59 -0
  27. data/app/components/LoginDialogComponent.js +69 -0
  28. data/app/containers/App.js +18 -0
  29. data/app/containers/AppSnackBar.js +19 -0
  30. data/app/containers/LockAppBar.js +30 -0
  31. data/app/containers/LockTable.js +44 -0
  32. data/app/containers/LoginDialog.js +32 -0
  33. data/app/index.html +20 -0
  34. data/app/index.js +44 -0
  35. data/app/reducers/index.js +33 -0
  36. data/app/server.js +2 -0
  37. data/app/src/client.js +35 -0
  38. data/app/src/helper.js +85 -0
  39. data/app/src/row.js +60 -0
  40. data/app/src/socket.js +47 -0
  41. data/app/test/helper.spec.js +47 -0
  42. data/app/utils/FileUtils.js +11 -0
  43. data/art/unitylock-logo.png +0 -0
  44. data/bin/console +14 -0
  45. data/bin/setup +8 -0
  46. data/config.ru +4 -0
  47. data/config/puma.rb +9 -0
  48. data/exe/unitylock +44 -0
  49. data/lib/unitylock/client.rb +2 -0
  50. data/lib/unitylock/client/main.rb +90 -0
  51. data/lib/unitylock/server.rb +2 -0
  52. data/lib/unitylock/server/entity/unityfile.rb +38 -0
  53. data/lib/unitylock/server/model.rb +59 -0
  54. data/lib/unitylock/server/router.rb +71 -0
  55. data/lib/unitylock/server/service.rb +41 -0
  56. data/lib/unitylock/version.rb +3 -0
  57. data/package.json +52 -0
  58. data/public/favicon.ico +0 -0
  59. data/public/static/bundle.js +98 -0
  60. data/public/static/main.css +82 -0
  61. data/unitylock.gemspec +25 -0
  62. data/webpack.config.js +40 -0
  63. 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
@@ -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>
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ require('./src/server.js')
2
+ require('./src/socket.js')
@@ -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');
@@ -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