🎨 Update files structure
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"debug": false,
|
||||
"port": 1571,
|
||||
"maxFileSize": 99999,
|
||||
"exclude":
|
||||
[
|
||||
"**/.DS_Store",
|
||||
"node_modules/**",
|
||||
"vendor/**",
|
||||
".git"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
'use strict'
|
||||
|
||||
// Depedencies
|
||||
const ip = require('ip')
|
||||
const Site = require('./site')
|
||||
const Watcher = require('./watcher')
|
||||
const defaultConfig = require('./config.default.json')
|
||||
|
||||
/**
|
||||
* App class
|
||||
*/
|
||||
class App
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(_options)
|
||||
{
|
||||
this.setArguments()
|
||||
this.setOptions(_options)
|
||||
this.setSite()
|
||||
this.setWatcher()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set options
|
||||
*/
|
||||
setOptions(_options)
|
||||
{
|
||||
const options = typeof _options === 'object' ? _options : {}
|
||||
|
||||
// Defaults
|
||||
if(typeof options.debug === 'undefined')
|
||||
{
|
||||
if(this.arguments.length > 1)
|
||||
{
|
||||
options.debug = this.arguments[ this.arguments.length - 1 ] === 'true'
|
||||
}
|
||||
else
|
||||
{
|
||||
options.debug = defaultConfig.debug
|
||||
}
|
||||
}
|
||||
|
||||
if(typeof options.port === 'undefined')
|
||||
{
|
||||
options.port = defaultConfig.port
|
||||
}
|
||||
|
||||
if(typeof options.domain === 'undefined')
|
||||
{
|
||||
options.domain = `http://${ip.address()}:${options.port}`
|
||||
}
|
||||
|
||||
if(typeof options.maxFileSize === 'undefined')
|
||||
{
|
||||
options.maxFileSize = defaultConfig.maxFileSize
|
||||
}
|
||||
|
||||
if(typeof options.exclude === 'undefined')
|
||||
{
|
||||
options.exclude = defaultConfig.exclude
|
||||
}
|
||||
|
||||
// Save
|
||||
this.options = options
|
||||
}
|
||||
|
||||
/**
|
||||
* Set arguments
|
||||
* Retrieve arguments and test if missing
|
||||
*/
|
||||
setArguments()
|
||||
{
|
||||
// Set up
|
||||
this.arguments = process.argv.slice(2)
|
||||
|
||||
// Missing project name
|
||||
if(this.arguments.length === 0)
|
||||
{
|
||||
// Stop process
|
||||
throw new Error('Missing arguments: first argument should be the projet name'.red)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set site
|
||||
* Instantiate site
|
||||
*/
|
||||
setSite()
|
||||
{
|
||||
this.site = new Site({
|
||||
port : this.options.port,
|
||||
domain: this.options.domain,
|
||||
debug : this.options.debug
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Set watcher
|
||||
* Instantiate watcher
|
||||
*/
|
||||
setWatcher()
|
||||
{
|
||||
this.watcher = new Watcher({
|
||||
port : this.options.port,
|
||||
domain : this.options.domain,
|
||||
debug : this.options.debug,
|
||||
maxFileSize: this.options.maxFileSize,
|
||||
exclude : this.options.exclude,
|
||||
name : this.arguments[ 0 ]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = App
|
||||
@@ -0,0 +1,19 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
|
||||
router.get('/', function(request, response)
|
||||
{
|
||||
response.render('pages/index/projects.pug', {})
|
||||
})
|
||||
|
||||
router.get(/project\/(.+)/, function(request, response)
|
||||
{
|
||||
response.render(
|
||||
'pages/index/project.pug',
|
||||
{
|
||||
projectSlug: request.params['0']
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
@@ -0,0 +1,332 @@
|
||||
'use strict'
|
||||
|
||||
// Dependencies
|
||||
const express = require('express')
|
||||
const helmet = require('helmet')
|
||||
const http = require('http')
|
||||
const socketIo = require('socket.io')
|
||||
const colors = require('colors')
|
||||
const ip = require('ip')
|
||||
const util = require('util')
|
||||
const path = require('path')
|
||||
const Projects = require('./models/projects.js')
|
||||
const fs = require('fs')
|
||||
const opener = require('opener')
|
||||
|
||||
/**
|
||||
* Site class
|
||||
*/
|
||||
class Site
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(_options)
|
||||
{
|
||||
this.setOptions(_options)
|
||||
this.setExpress()
|
||||
this.setServer()
|
||||
this.setSocket()
|
||||
this.setModels()
|
||||
this.setDummy()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set options
|
||||
*/
|
||||
setOptions(_options)
|
||||
{
|
||||
const options = typeof _options === 'object' ? _options : {}
|
||||
|
||||
// Defaults
|
||||
if(typeof options.debug === 'undefined')
|
||||
{
|
||||
options.debug = false
|
||||
}
|
||||
|
||||
if(typeof options.port === 'undefined')
|
||||
{
|
||||
options.port = 1571
|
||||
}
|
||||
|
||||
if(typeof options.domain === 'undefined')
|
||||
{
|
||||
options.domain = `http://${ip.address()}:${options.port}`
|
||||
}
|
||||
|
||||
// Save
|
||||
this.options = options
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Dummy
|
||||
*/
|
||||
setDummy()
|
||||
{
|
||||
if(!this.options.debug)
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
// Default project
|
||||
const project = this.projects.createProject('Dummy project')
|
||||
|
||||
// // Same name projects
|
||||
// let project_2 = this.projects.createProject('dummy')
|
||||
// let project_3 = this.projects.createProject('dummy')
|
||||
// let project_4 = this.projects.createProject('dummy')
|
||||
// let project_5 = this.projects.createProject('dummy')
|
||||
|
||||
// Some file
|
||||
project.files.createVersion('./folder-test/test-4.css', fs.readFileSync('../test-folder/folder-1/test-4.css', 'utf8'))
|
||||
project.files.createVersion('./folder-test/depth-test/test-3.js', fs.readFileSync('../test-folder/folder-2/test-3.js', 'utf8'))
|
||||
project.files.createVersion('./folder-test/depth-test/test-3.js', fs.readFileSync('../test-folder/folder-2/test-3-diff-1.js', 'utf8'))
|
||||
project.files.createVersion('./folder-test/depth-test/test-3.js', fs.readFileSync('../test-folder/folder-2/test-3-diff-2.js', 'utf8'))
|
||||
project.files.createVersion('./folder-test/depth-test/test-3.js', fs.readFileSync('../test-folder/folder-2/test-3-diff-3.js', 'utf8'))
|
||||
project.files.createVersion('./folder-test/depth-test/test-3.js', fs.readFileSync('../test-folder/folder-2/test-3-diff-4.js', 'utf8'))
|
||||
project.files.createVersion('./folder-test/test-1.html', fs.readFileSync('../test-folder/test-1.html', 'utf8'))
|
||||
project.files.createVersion('./folder-test/test-2.php', fs.readFileSync('../test-folder/test-2.php', 'utf8'))
|
||||
project.files.createVersion('./folder-test/big-one.txt', 'line\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline\nline')
|
||||
project.files.createVersion('./folder-test/loooooooooooooooooooooooooooooooong-one.txt', 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz')
|
||||
|
||||
project.files.createVersion('./icons/test.js', 'Test icon')
|
||||
project.files.createVersion('./icons/test.html', 'Test icon')
|
||||
project.files.createVersion('./icons/test.sass', 'Test icon')
|
||||
project.files.createVersion('./icons/test.scss', 'Test icon')
|
||||
project.files.createVersion('./icons/test.less', 'Test icon')
|
||||
project.files.createVersion('./icons/test.stylus', 'Test icon')
|
||||
project.files.createVersion('./icons/test.styl', 'Test icon')
|
||||
project.files.createVersion('./icons/test.css', 'Test icon')
|
||||
project.files.createVersion('./icons/test.php', 'Test icon')
|
||||
project.files.createVersion('./icons/test.json', 'Test icon')
|
||||
project.files.createVersion('./icons/test.jade', 'Test icon')
|
||||
project.files.createVersion('./icons/test.pug', 'Test icon')
|
||||
project.files.createVersion('./icons/test.md', 'Test icon')
|
||||
project.files.createVersion('./icons/test.sql', 'Test icon')
|
||||
project.files.createVersion('./icons/test.htaccess', 'Test icon')
|
||||
project.files.createVersion('./icons/test.htpasswd', 'Test icon')
|
||||
project.files.createVersion('./icons/test.yml', 'Test icon')
|
||||
project.files.createVersion('./icons/test.svg', 'Test icon')
|
||||
project.files.createVersion('./icons/test.eot', 'Test icon')
|
||||
project.files.createVersion('./icons/test.ttf', 'Test icon')
|
||||
project.files.createVersion('./icons/test.woff', 'Test icon')
|
||||
project.files.createVersion('./icons/test.woff2', 'Test icon')
|
||||
project.files.createVersion('./icons/test.jpeg', 'Test icon')
|
||||
project.files.createVersion('./icons/test.jpg', 'Test icon')
|
||||
project.files.createVersion('./icons/test.tiff', 'Test icon')
|
||||
project.files.createVersion('./icons/test.gif', 'Test icon')
|
||||
project.files.createVersion('./icons/test.bmp', 'Test icon')
|
||||
project.files.createVersion('./icons/test.png', 'Test icon')
|
||||
project.files.createVersion('./icons/test.webp', 'Test icon')
|
||||
project.files.createVersion('./icons/test.mpeg', 'Test icon')
|
||||
project.files.createVersion('./icons/test.mpg', 'Test icon')
|
||||
project.files.createVersion('./icons/test.mp4', 'Test icon')
|
||||
project.files.createVersion('./icons/test.amv', 'Test icon')
|
||||
project.files.createVersion('./icons/test.wmv', 'Test icon')
|
||||
project.files.createVersion('./icons/test.mov', 'Test icon')
|
||||
project.files.createVersion('./icons/test.avi', 'Test icon')
|
||||
project.files.createVersion('./icons/test.ogv', 'Test icon')
|
||||
project.files.createVersion('./icons/test.mkv', 'Test icon')
|
||||
project.files.createVersion('./icons/test.webm', 'Test icon')
|
||||
project.files.createVersion('./icons/test.mp3', 'Test icon')
|
||||
project.files.createVersion('./icons/test.wav', 'Test icon')
|
||||
project.files.createVersion('./icons/test.ogg', 'Test icon')
|
||||
project.files.createVersion('./icons/test.raw', 'Test icon')
|
||||
project.files.createVersion('./icons/test.zip', 'Test icon')
|
||||
project.files.createVersion('./icons/test.rar', 'Test icon')
|
||||
project.files.createVersion('./icons/test.7z', 'Test icon')
|
||||
project.files.createVersion('./icons/test.gz', 'Test icon')
|
||||
project.files.createVersion('./icons/test.txt', 'Test icon')
|
||||
project.files.createVersion('./icons/test.coffee', 'Test icon')
|
||||
project.files.createVersion('./icons/test.gitignore', 'Test icon')
|
||||
project.files.createVersion('./icons/test.gitkeep', 'Test icon')
|
||||
project.files.createVersion('./icons/test.xml', 'Test icon')
|
||||
project.files.createVersion('./icons/test.twig', 'Test icon')
|
||||
project.files.createVersion('./icons/test.c', 'Test icon')
|
||||
project.files.createVersion('./icons/test.h', 'Test icon')
|
||||
project.files.createVersion('./icons/test.pwet', 'Test icon')
|
||||
|
||||
// project.files.createVersion('./toto/tata/lorem.txt', '123456789')
|
||||
// project.files.createVersion('./toto/tata/lorem.txt', '1aze')
|
||||
// project.files.createVersion('./toto/tata/ipsum.txt', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Mollitia asperiores iure, animi voluptatibus ut officiis. Molestias, quod perferendis hic totam doloremque, porro aperiam enim tenetur, maxime inventore consequuntur nisi in?')
|
||||
|
||||
// Adding file versions
|
||||
let counting = 0
|
||||
setInterval(function()
|
||||
{
|
||||
project.files.createVersion('./folder-test/multi-version.txt', 'test: ' + counting++)
|
||||
}, 2000)
|
||||
|
||||
// Creating and deleting file
|
||||
let toggle = true
|
||||
setInterval(function()
|
||||
{
|
||||
if(toggle)
|
||||
project.files.create('./folder-test/toggle.txt', 'content')
|
||||
else
|
||||
project.files.delete('./folder-test/toggle.txt', 'content')
|
||||
|
||||
toggle = !toggle
|
||||
}, 3000)
|
||||
|
||||
// // Log
|
||||
// console.log(util.inspect(project.files.describe(), { depth: null, colors: true }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Set models
|
||||
*/
|
||||
setModels()
|
||||
{
|
||||
// Set up
|
||||
this.projects = new Projects({ socket: this.sockets.main, debug: this.options.debug })
|
||||
}
|
||||
|
||||
/**
|
||||
* Set express
|
||||
* Start express and set controllers
|
||||
*/
|
||||
setExpress()
|
||||
{
|
||||
// Set up
|
||||
this.express = express()
|
||||
this.express.use(helmet())
|
||||
this.express.set('view engine', 'pug')
|
||||
this.express.set('views', path.join(__dirname, 'views'))
|
||||
this.express.use(express.static(path.join(__dirname, 'public')))
|
||||
|
||||
this.express.locals.domain = this.options.domain
|
||||
|
||||
// Controllers
|
||||
this.express.use('/', require('./controllers/index.js'))
|
||||
}
|
||||
|
||||
/**
|
||||
* Set server
|
||||
*/
|
||||
setServer()
|
||||
{
|
||||
// Set up
|
||||
this.server = http.createServer(this.express)
|
||||
|
||||
// Error event
|
||||
this.server.on('error', (error) =>
|
||||
{
|
||||
// Server already running
|
||||
if(error.code === 'EADDRINUSE')
|
||||
{
|
||||
// Debug
|
||||
if(this.options.debug)
|
||||
{
|
||||
console.log('server already running'.green.bold)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
console.log(error.message)
|
||||
})
|
||||
|
||||
// Start
|
||||
this.server.listen(this.options.port, () =>
|
||||
{
|
||||
// URL
|
||||
console.log(colors.green('---------------------------'))
|
||||
console.log('server'.green.bold + ' - ' + 'started'.cyan)
|
||||
console.log('server'.green.bold + ' - ' + this.options.domain.cyan)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Set socket
|
||||
*/
|
||||
setSocket()
|
||||
{
|
||||
// Set up
|
||||
this.sockets = {}
|
||||
this.sockets.main = socketIo.listen(this.server)
|
||||
this.sockets.app = this.sockets.main.of('/app')
|
||||
|
||||
// App connection event
|
||||
this.sockets.app.on('connection', (socket) =>
|
||||
{
|
||||
// Set up
|
||||
let project = null
|
||||
|
||||
// Debug
|
||||
if(this.options.debug)
|
||||
{
|
||||
console.log('socket app'.green.bold + ' - ' + 'connect'.cyan + ' - ' + socket.id.cyan)
|
||||
}
|
||||
|
||||
// Start project
|
||||
socket.on('start_project', (data) =>
|
||||
{
|
||||
project = this.projects.createProject(data.name)
|
||||
|
||||
const url = this.options.domain + '/project/' + project.slug
|
||||
|
||||
console.log('server'.green.bold + ' - ' + url.cyan)
|
||||
|
||||
// Open in browser
|
||||
opener(url)
|
||||
|
||||
// Debug
|
||||
if(this.options.debug)
|
||||
{
|
||||
console.log(util.inspect(project.files.describe(), { depth: null, colors: true }))
|
||||
}
|
||||
})
|
||||
|
||||
// Update file
|
||||
socket.on('update_file', (data) =>
|
||||
{
|
||||
project.files.createVersion(data.path, data.content)
|
||||
|
||||
// Debug
|
||||
if(this.options.debug)
|
||||
{
|
||||
console.log(util.inspect(project.files.describe(), { depth: null, colors: true }))
|
||||
}
|
||||
})
|
||||
|
||||
// Create file
|
||||
socket.on('create_file', (data) =>
|
||||
{
|
||||
project.files.create(data.path, data.content)
|
||||
|
||||
// Debug
|
||||
if(this.options.debug)
|
||||
{
|
||||
console.log(util.inspect(project.files.describe(), { depth: null, colors: true }))
|
||||
}
|
||||
})
|
||||
|
||||
// Delete file
|
||||
socket.on('delete_file', (data) =>
|
||||
{
|
||||
project.files.delete(data.path)
|
||||
|
||||
// Debug
|
||||
if(this.options.debug)
|
||||
{
|
||||
console.log(util.inspect(project.files.describe(), { depth: null, colors: true }))
|
||||
}
|
||||
})
|
||||
|
||||
// Disconnect
|
||||
socket.on('disconnect', () =>
|
||||
{
|
||||
this.projects.delete_project(project.slug)
|
||||
|
||||
// Debug
|
||||
if(this.options.debug)
|
||||
{
|
||||
console.log('socket app'.green.bold + ' - ' + 'disconnect'.cyan + ' - ' + socket.id.cyan)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Site
|
||||
@@ -0,0 +1,224 @@
|
||||
'use strict'
|
||||
|
||||
class EventEmitter
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor()
|
||||
{
|
||||
this.callbacks = {}
|
||||
this.callbacks.base = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* On
|
||||
*/
|
||||
on(_names, callback)
|
||||
{
|
||||
const that = this
|
||||
|
||||
// Errors
|
||||
if(typeof _names === 'undefined' || _names === '')
|
||||
{
|
||||
console.warn('wrong names')
|
||||
return false
|
||||
}
|
||||
|
||||
if(typeof callback === 'undefined')
|
||||
{
|
||||
console.warn('wrong callback')
|
||||
return false
|
||||
}
|
||||
|
||||
// Resolve names
|
||||
const names = this.resolveNames(names)
|
||||
|
||||
// Each name
|
||||
names.forEach(function(_name)
|
||||
{
|
||||
// Resolve name
|
||||
const name = that.resolveName(_name)
|
||||
|
||||
// Create namespace if not exist
|
||||
if(!(that.callbacks[ name.namespace ] instanceof Object))
|
||||
that.callbacks[ name.namespace ] = {}
|
||||
|
||||
// Create callback if not exist
|
||||
if(!(that.callbacks[ name.namespace ][ name.value ] instanceof Array))
|
||||
that.callbacks[ name.namespace ][ name.value ] = []
|
||||
|
||||
// Add callback
|
||||
that.callbacks[ name.namespace ][ name.value ].push(callback)
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Off
|
||||
*/
|
||||
off(_names)
|
||||
{
|
||||
const that = this
|
||||
|
||||
// Errors
|
||||
if(typeof _names === 'undefined' || _names === '')
|
||||
{
|
||||
console.warn('wrong name')
|
||||
return false
|
||||
}
|
||||
|
||||
// Resolve names
|
||||
const names = this.resolveNames(_names)
|
||||
|
||||
// Each name
|
||||
names.forEach(function(_name)
|
||||
{
|
||||
// Resolve name
|
||||
const name = that.resolveName(_name)
|
||||
|
||||
// Remove namespace
|
||||
if(name.namespace !== 'base' && name.value === '')
|
||||
{
|
||||
delete that.callbacks[ name.namespace ]
|
||||
}
|
||||
|
||||
// Remove specific callback in namespace
|
||||
else
|
||||
{
|
||||
// Default
|
||||
if(name.namespace === 'base')
|
||||
{
|
||||
// Try to remove from each namespace
|
||||
for(const namespace in that.callbacks)
|
||||
{
|
||||
if(that.callbacks[ namespace ] instanceof Object && that.callbacks[ namespace ][ name.value ] instanceof Array)
|
||||
{
|
||||
delete that.callbacks[ namespace ][ name.value ]
|
||||
|
||||
// Remove namespace if empty
|
||||
if(Object.keys(that.callbacks[ namespace ]).length === 0)
|
||||
delete that.callbacks[ namespace ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Specified namespace
|
||||
else if(that.callbacks[ name.namespace ] instanceof Object && that.callbacks[ name.namespace ][ name.value ] instanceof Array)
|
||||
{
|
||||
delete that.callbacks[ name.namespace ][ name.value ]
|
||||
|
||||
// Remove namespace if empty
|
||||
if(Object.keys(that.callbacks[ name.namespace ]).length === 0)
|
||||
delete that.callbacks[ name.namespace ]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger
|
||||
*/
|
||||
trigger(_name, _args)
|
||||
{
|
||||
// Errors
|
||||
if(typeof _name === 'undefined' || _name === '')
|
||||
{
|
||||
console.warn('wrong name')
|
||||
return false
|
||||
}
|
||||
|
||||
const that = this
|
||||
let finalResult = null
|
||||
let result = null
|
||||
|
||||
// Default args
|
||||
const args = !(args instanceof Array) ? [] : _args
|
||||
|
||||
// Resolve names (should on have one event)
|
||||
let name = this.resolveNames(_name)
|
||||
|
||||
// Resolve name
|
||||
name = this.resolveName(name[ 0 ])
|
||||
|
||||
// Default namespace
|
||||
if(name.namespace === 'base')
|
||||
{
|
||||
// Try to find callback in each namespace
|
||||
for(const namespace in that.callbacks)
|
||||
{
|
||||
if(that.callbacks[ namespace ] instanceof Object && that.callbacks[ namespace ][ name.value ] instanceof Array)
|
||||
{
|
||||
that.callbacks[ namespace ][ name.value ].forEach(function(callback)
|
||||
{
|
||||
result = callback.apply(that,args)
|
||||
|
||||
if(typeof finalResult === 'undefined')
|
||||
{
|
||||
finalResult = result
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Specified namespace
|
||||
else if(this.callbacks[ name.namespace ] instanceof Object)
|
||||
{
|
||||
if(name.value === '')
|
||||
{
|
||||
console.warn('wrong name')
|
||||
return this
|
||||
}
|
||||
|
||||
that.callbacks[ name.namespace ][ name.value ].forEach(function(callback)
|
||||
{
|
||||
result = callback.apply(that, args)
|
||||
|
||||
if(typeof finalResult === 'undefined')
|
||||
finalResult = result
|
||||
})
|
||||
}
|
||||
|
||||
return finalResult
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve names
|
||||
*/
|
||||
resolveNames(_names)
|
||||
{
|
||||
let names = _names
|
||||
names = names.replace(/[^a-zA-Z0-9 ,\/.]/g, '')
|
||||
names = names.replace(/[,\/]+/g, ' ')
|
||||
names = names.split(' ')
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve name
|
||||
*/
|
||||
resolveName(name)
|
||||
{
|
||||
const newName = {}
|
||||
const parts = name.split('.')
|
||||
|
||||
newName.original = name
|
||||
newName.value = parts[ 0 ]
|
||||
newName.namespace = 'base' // Base namespace
|
||||
|
||||
// Specified namespace
|
||||
if(parts.length > 1 && parts[ 1 ] !== '')
|
||||
{
|
||||
newName.namespace = parts[ 1 ]
|
||||
}
|
||||
|
||||
return newName
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EventEmitter
|
||||
@@ -0,0 +1,112 @@
|
||||
'use strict'
|
||||
|
||||
// Depedencies
|
||||
const diff = require('diff')
|
||||
const ids = require('../utils/ids.js')
|
||||
|
||||
class File
|
||||
{
|
||||
constructor(_options)
|
||||
{
|
||||
// Set up
|
||||
this.id = ids.get_id()
|
||||
this.name = _options.name
|
||||
this.path = {}
|
||||
this.path.directory = _options.path
|
||||
this.path.full = this.path.directory + '/' + this.name
|
||||
this.versions = []
|
||||
this.socket = _options.socket
|
||||
|
||||
const nameParts = this.name.split('.')
|
||||
|
||||
if(nameParts.length > 1)
|
||||
{
|
||||
this.extension = nameParts[ nameParts.length - 1 ]
|
||||
}
|
||||
else
|
||||
{
|
||||
this.extension = ''
|
||||
}
|
||||
|
||||
// Create first version
|
||||
if(typeof _options.content !== 'undefined')
|
||||
{
|
||||
this.createVersion(_options.content)
|
||||
}
|
||||
}
|
||||
|
||||
createVersion(content)
|
||||
{
|
||||
// Create version
|
||||
const version = {}
|
||||
const lastVersion = this.getLastVersion()
|
||||
|
||||
version.date = new Date()
|
||||
version.content = content
|
||||
|
||||
if(!lastVersion)
|
||||
{
|
||||
version.diff = false
|
||||
}
|
||||
else if(version.content === '')
|
||||
{
|
||||
version.diff = [{ count: 1, added: true, removed: undefined, value: 'a' }]
|
||||
|
||||
if(lastVersion.content === '')
|
||||
{
|
||||
return false
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
version.diff = diff.diffLines(
|
||||
lastVersion.content,
|
||||
version.content,
|
||||
{
|
||||
// ignoreWhitespace: true
|
||||
}
|
||||
)
|
||||
|
||||
// No changed
|
||||
if(version.diff.length === 1 && lastVersion.content !== '')
|
||||
{
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Emit
|
||||
this.socket.emit('createVersion', { file: this.path.full, version })
|
||||
|
||||
// Save
|
||||
this.versions.push(version)
|
||||
}
|
||||
|
||||
getLastVersion()
|
||||
{
|
||||
if(this.versions.length === 0)
|
||||
return false
|
||||
|
||||
return this.versions[ this.versions.length - 1 ]
|
||||
}
|
||||
|
||||
describe()
|
||||
{
|
||||
// Set up
|
||||
const result = {}
|
||||
|
||||
result.id = this.id
|
||||
result.name = this.name
|
||||
result.path = this.path
|
||||
result.versions = this.versions
|
||||
result.extension = this.extension
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
destructor()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = File
|
||||
@@ -0,0 +1,146 @@
|
||||
'use strict'
|
||||
|
||||
// Dependencies
|
||||
const paths = require('../utils/paths.js')
|
||||
const File = require('./file.js')
|
||||
|
||||
class Files
|
||||
{
|
||||
constructor(_options)
|
||||
{
|
||||
this.items = {}
|
||||
this.count = 0
|
||||
this.socket = _options.socket
|
||||
this.lastVersionDate = new Date()
|
||||
}
|
||||
|
||||
create(_path, _content)
|
||||
{
|
||||
// Set up
|
||||
const normalizedPath = paths.normalize(_path)
|
||||
const parsedPath = paths.parse(normalizedPath)
|
||||
|
||||
// Retrieve file
|
||||
let file = this.get(normalizedPath)
|
||||
|
||||
// File already exist
|
||||
if(file)
|
||||
{
|
||||
return false
|
||||
}
|
||||
|
||||
// Create
|
||||
file = new File({
|
||||
name : parsedPath.base,
|
||||
path : parsedPath.dir,
|
||||
content: _content,
|
||||
socket : this.socket
|
||||
})
|
||||
|
||||
// Save
|
||||
this.items[ normalizedPath ] = file
|
||||
this.count++
|
||||
this.lastVersionDate = new Date()
|
||||
|
||||
// Emit
|
||||
this.socket.emit('create_file', file.describe())
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
createVersion(_path, _content)
|
||||
{
|
||||
// Set up
|
||||
const normalizedPath = paths.normalize(_path)
|
||||
|
||||
// Retrieve file
|
||||
const file = this.get(normalizedPath, true)
|
||||
|
||||
if(typeof _content !== 'undefined')
|
||||
{
|
||||
// Create version
|
||||
file.createVersion(_content)
|
||||
}
|
||||
|
||||
// Save
|
||||
this.lastVersionDate = new Date()
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
get(_path, _forceCreation)
|
||||
{
|
||||
const forceCreation = typeof _forceCreation === 'undefined' ? false : _forceCreation
|
||||
|
||||
// Set up
|
||||
const normalizedPath = paths.normalize(_path)
|
||||
|
||||
// Retrieve file
|
||||
let file = this.items[ normalizedPath ]
|
||||
|
||||
// File found
|
||||
if(typeof file !== 'undefined')
|
||||
{
|
||||
return file
|
||||
}
|
||||
|
||||
// Force creation
|
||||
if(forceCreation)
|
||||
{
|
||||
file = this.create(normalizedPath)
|
||||
return file
|
||||
}
|
||||
|
||||
// Not found
|
||||
return false
|
||||
}
|
||||
|
||||
delete(_path)
|
||||
{
|
||||
// Set up
|
||||
const normalizedPath = paths.normalize(_path)
|
||||
|
||||
// Retrieve file
|
||||
const file = this.get(normalizedPath)
|
||||
|
||||
// File found
|
||||
if(file)
|
||||
{
|
||||
// Delete file
|
||||
file.destructor()
|
||||
delete this.items[ normalizedPath ]
|
||||
this.count--
|
||||
|
||||
// Save
|
||||
this.lastVersionDate = new Date()
|
||||
|
||||
// Emit
|
||||
this.socket.emit('delete_file', file.describe())
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
describe()
|
||||
{
|
||||
// Set up
|
||||
const result = {}
|
||||
|
||||
result.count = this.count
|
||||
result.items = {}
|
||||
|
||||
// Each file
|
||||
for(const _filePath in this.items)
|
||||
{
|
||||
const file = this.items[_filePath]
|
||||
|
||||
result.items[_filePath] = file.describe()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Files
|
||||
@@ -0,0 +1,76 @@
|
||||
'use strict'
|
||||
|
||||
// Dependencies
|
||||
const slug = require('slug')
|
||||
const Files = require('./files.js')
|
||||
|
||||
class Project
|
||||
{
|
||||
constructor(_options)
|
||||
{
|
||||
this.setOptions(_options)
|
||||
this.setName(_options.name)
|
||||
this.setSocket(_options.socket)
|
||||
|
||||
this.files = new Files({ socket: this.socket })
|
||||
this.date = new Date()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set options
|
||||
*/
|
||||
setOptions(_options)
|
||||
{
|
||||
if(typeof _options.debug === 'undefined')
|
||||
{
|
||||
_options.debug = false
|
||||
}
|
||||
|
||||
// Save
|
||||
this.options = _options
|
||||
}
|
||||
|
||||
setName(_name)
|
||||
{
|
||||
this.name = _name
|
||||
this.slug = slug(this.name, { lower: true })
|
||||
}
|
||||
|
||||
setSocket(socket)
|
||||
{
|
||||
// Set up
|
||||
this.originalSocket = socket
|
||||
this.socket = this.originalSocket.of('/project/' + this.slug)
|
||||
|
||||
// Connection event
|
||||
this.socket.on('connection', (socket) =>
|
||||
{
|
||||
this.socket.emit('update_project', this.describe())
|
||||
|
||||
// Debug
|
||||
if(this.options.debug)
|
||||
{
|
||||
console.log('socket projects'.green.bold + ' - ' + 'connect'.cyan + ' - ' + socket.id.cyan)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe()
|
||||
{
|
||||
// Set up
|
||||
const result = {}
|
||||
|
||||
result.name = this.name
|
||||
result.files = this.files.describe()
|
||||
result.date = this.date
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
destructor()
|
||||
{
|
||||
this.socket.emit('destruct')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Project
|
||||
@@ -0,0 +1,138 @@
|
||||
'use strict'
|
||||
|
||||
const Project = require('./project.js')
|
||||
|
||||
class Projects
|
||||
{
|
||||
constructor(_options)
|
||||
{
|
||||
this.all = {}
|
||||
|
||||
this.setOptions(_options)
|
||||
this.setSocket(_options.socket)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set options
|
||||
*/
|
||||
setOptions(_options)
|
||||
{
|
||||
if(typeof _options.debug === 'undefined')
|
||||
{
|
||||
_options.debug = false
|
||||
}
|
||||
|
||||
// Save
|
||||
this.options = _options
|
||||
}
|
||||
|
||||
setSocket(socket)
|
||||
{
|
||||
// Set up
|
||||
this.originalSocket = socket
|
||||
this.socket = this.originalSocket.of('/projects')
|
||||
|
||||
// Connection event
|
||||
this.socket.on('connection', (socket) =>
|
||||
{
|
||||
this.socket.emit('update_projects', this.describe())
|
||||
|
||||
if(this.options.debug)
|
||||
{
|
||||
console.log('socket projects'.green.bold + ' - ' + 'connect'.cyan + ' - ' + socket.id.cyan)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
createProject(_name)
|
||||
{
|
||||
// Create project
|
||||
const project = new Project({ name: _name, socket: this.originalSocket, debug: this.options.debug })
|
||||
let sameNameProject = this.all[ project.slug ]
|
||||
|
||||
// Try to found same name project
|
||||
while(typeof sameNameProject !== 'undefined')
|
||||
{
|
||||
// Found new number
|
||||
let lastNumber = sameNameProject.name.match(/\d+$/)
|
||||
let newNumber = 2
|
||||
|
||||
if(lastNumber && lastNumber.length)
|
||||
{
|
||||
lastNumber = ~~lastNumber[ 0 ]
|
||||
|
||||
newNumber = lastNumber + 1
|
||||
}
|
||||
|
||||
// Update project name
|
||||
project.set_name(_name + ' ' + newNumber)
|
||||
|
||||
// Try to found
|
||||
sameNameProject = this.all[ project.slug ]
|
||||
}
|
||||
|
||||
// Save
|
||||
this.all[ project.slug ] = project
|
||||
|
||||
// Emit
|
||||
this.socket.emit('update_projects', this.describe())
|
||||
|
||||
// Return
|
||||
return project
|
||||
}
|
||||
|
||||
deleteProject(_slug)
|
||||
{
|
||||
// Set up
|
||||
const project = this.all[ _slug ]
|
||||
|
||||
// Project found
|
||||
if(typeof project !== 'undefined')
|
||||
{
|
||||
// Delete
|
||||
delete this.all[ _slug ]
|
||||
project.destructor()
|
||||
|
||||
// Emit
|
||||
this.socket.emit('update_projects', this.describe())
|
||||
}
|
||||
}
|
||||
|
||||
getProjectBySlug(_slug)
|
||||
{
|
||||
// Find project
|
||||
const project = this.all[ _slug ]
|
||||
|
||||
// Found
|
||||
if(project)
|
||||
{
|
||||
return project
|
||||
}
|
||||
|
||||
// Not found
|
||||
return false
|
||||
}
|
||||
|
||||
describe()
|
||||
{
|
||||
const result = {}
|
||||
result.all = {}
|
||||
|
||||
for(const _slug in this.all)
|
||||
{
|
||||
const _project = this.all[ _slug ]
|
||||
|
||||
result.all[ _slug ] = {
|
||||
slug: _project.slug,
|
||||
name: _project.name,
|
||||
filesCount: _project.files.count,
|
||||
date: _project.date,
|
||||
lastUpdateDate: _project.files.last_version_date
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Projects
|
||||
@@ -0,0 +1,20 @@
|
||||
'use strict'
|
||||
|
||||
class IDs
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this.lastId = 0
|
||||
}
|
||||
|
||||
getId()
|
||||
{
|
||||
const lastId = ++this.lastId
|
||||
|
||||
return lastId
|
||||
}
|
||||
}
|
||||
|
||||
const ids = new IDs()
|
||||
|
||||
module.exports = ids
|
||||
@@ -0,0 +1,38 @@
|
||||
'use strict'
|
||||
|
||||
// Dependencies
|
||||
const path = require('path')
|
||||
|
||||
class Paths
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this.separator = path.sep
|
||||
}
|
||||
|
||||
normalize(_path)
|
||||
{
|
||||
if(_path === '.' || _path === '')
|
||||
{
|
||||
const path = '.'
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
const normalizedPath = './' + path.normalize(_path)
|
||||
|
||||
return normalizedPath
|
||||
}
|
||||
|
||||
parse(_path)
|
||||
{
|
||||
const normalizedPath = this.normalize(_path)
|
||||
const parsedPath = path.parse(normalizedPath)
|
||||
|
||||
return parsedPath
|
||||
}
|
||||
}
|
||||
|
||||
const paths = new Paths()
|
||||
|
||||
module.exports = paths
|
||||
@@ -0,0 +1 @@
|
||||
| Project
|
||||
@@ -0,0 +1 @@
|
||||
| Projects
|
||||
@@ -0,0 +1,256 @@
|
||||
'use strict'
|
||||
|
||||
// Depedencies
|
||||
const chokidar = require('chokidar')
|
||||
const ip = require('ip')
|
||||
const socketIoClient = require('socket.io-client')
|
||||
const fs = require('fs')
|
||||
const mime = require('mime')
|
||||
|
||||
/**
|
||||
* Watcher class
|
||||
*/
|
||||
class Watcher
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(_options)
|
||||
{
|
||||
this.setOptions(_options)
|
||||
this.setWatcher()
|
||||
this.setSocket()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set options
|
||||
*/
|
||||
setOptions(_options)
|
||||
{
|
||||
const options = typeof _options !== 'object' ? {} : _options
|
||||
|
||||
// Defaults
|
||||
if(typeof options.debug === 'undefined')
|
||||
{
|
||||
options.debug = false
|
||||
}
|
||||
|
||||
if(typeof options.port === 'undefined')
|
||||
{
|
||||
options.port = 1571
|
||||
}
|
||||
|
||||
if(typeof options.domain === 'undefined')
|
||||
{
|
||||
options.domain = `http://${ip.address()}:${options.port}`
|
||||
}
|
||||
|
||||
if(typeof options.maxFileSize === 'undefined')
|
||||
{
|
||||
options.maxFileSize = 2000
|
||||
}
|
||||
|
||||
// Save
|
||||
this.options = options
|
||||
}
|
||||
|
||||
/**
|
||||
* Set socket
|
||||
* Connect to site
|
||||
*/
|
||||
setSocket()
|
||||
{
|
||||
// Set up
|
||||
this.socket = socketIoClient(`${this.options.domain}/app`)
|
||||
|
||||
// Connect event
|
||||
this.socket.on('connect', () =>
|
||||
{
|
||||
this.socket.emit('start_project', { name: this.options.name })
|
||||
|
||||
// Debug
|
||||
if(this.options.debug)
|
||||
{
|
||||
console.log('connected'.green.bold)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Set watcher
|
||||
* Listen to modifications on file and folders
|
||||
*/
|
||||
setWatcher()
|
||||
{
|
||||
// Create ignored regex
|
||||
const regexs = []
|
||||
const Minimatch = require('minimatch').Minimatch
|
||||
|
||||
for(const _excludeKey in this.options.exclude)
|
||||
{
|
||||
const _exclude = this.options.exclude[ _excludeKey ]
|
||||
const minimatch = new Minimatch(_exclude, { dot: true })
|
||||
|
||||
let regex = '' + minimatch.makeRe()
|
||||
|
||||
regex = regex.replace('/^', '')
|
||||
regex = regex.replace('$/', '')
|
||||
|
||||
regexs.push(regex)
|
||||
}
|
||||
|
||||
const regex = new RegExp(regexs.join('|'))
|
||||
|
||||
// Set up
|
||||
this.watcher = chokidar.watch(
|
||||
process.cwd(),
|
||||
{
|
||||
// ignored : /[\/\\]\./,
|
||||
ignored : regex,
|
||||
ignoreInitial: true
|
||||
}
|
||||
)
|
||||
|
||||
// Add event
|
||||
this.watcher.on('add', (_path) =>
|
||||
{
|
||||
// Set up
|
||||
const relativePath = _path.replace(process.cwd(), '.')
|
||||
const mimeType = mime.lookup(relativePath)
|
||||
const file = {}
|
||||
|
||||
file.path = relativePath
|
||||
file.canRead = true
|
||||
|
||||
// Test mime type
|
||||
if(mimeType.match(/^(audio)|(video)|(image)/))
|
||||
{
|
||||
file.canRead = false
|
||||
}
|
||||
|
||||
// Retrieve stats
|
||||
fs.stat(_path, (error, stats) =>
|
||||
{
|
||||
// Max file size
|
||||
if(this.options.maxFileSize < stats.size)
|
||||
{
|
||||
file.canRead = false
|
||||
}
|
||||
|
||||
// Read
|
||||
fs.readFile(_path, (error, data) =>
|
||||
{
|
||||
if(file.canRead)
|
||||
{
|
||||
file.content = data.toString()
|
||||
}
|
||||
|
||||
// Send
|
||||
this.socket.emit('create_file', file)
|
||||
})
|
||||
})
|
||||
|
||||
// Debug
|
||||
if(this.options.debug)
|
||||
{
|
||||
console.log('add:'.green.bold, relativePath)
|
||||
}
|
||||
})
|
||||
|
||||
// Change event
|
||||
this.watcher.on('change', (_path) =>
|
||||
{
|
||||
// Set up
|
||||
const relativePath = _path.replace(process.cwd(), '.')
|
||||
const mimeType = mime.lookup(relativePath)
|
||||
const file = {}
|
||||
|
||||
file.path = relativePath
|
||||
file.canRead = true
|
||||
|
||||
// Test mime type
|
||||
if(mimeType.match(/^(audio)|(video)|(image)/))
|
||||
{
|
||||
file.canRead = false
|
||||
}
|
||||
|
||||
// Retrieve stats
|
||||
fs.stat(_path, (error, stats) =>
|
||||
{
|
||||
// Test max file size
|
||||
if(this.options.maxFileSize < stats.size)
|
||||
{
|
||||
file.canRead = false
|
||||
}
|
||||
|
||||
// Read
|
||||
fs.readFile(_path, (error, data) =>
|
||||
{
|
||||
if(file.canRead)
|
||||
{
|
||||
file.content = data.toString()
|
||||
}
|
||||
|
||||
// Send
|
||||
this.socket.emit('update_file', file)
|
||||
})
|
||||
})
|
||||
|
||||
// Debug
|
||||
if(this.options.debug)
|
||||
{
|
||||
console.log('change:'.green.bold, relativePath)
|
||||
}
|
||||
})
|
||||
|
||||
// Unlink event
|
||||
this.watcher.on('unlink', (_path) =>
|
||||
{
|
||||
// Set up
|
||||
const relativePath = _path.replace(process.cwd(), '.')
|
||||
|
||||
// Send
|
||||
this.socket.emit('delete_file', { path: relativePath })
|
||||
|
||||
// Debug
|
||||
if(this.options.debug)
|
||||
{
|
||||
console.log('unlink:'.green.bold, relativePath)
|
||||
}
|
||||
})
|
||||
|
||||
// AddDir event
|
||||
this.watcher.on('addDir', (_path) =>
|
||||
{
|
||||
// Set up
|
||||
const relativePath = _path.replace(process.cwd(), '.')
|
||||
|
||||
// Send
|
||||
this.socket.emit('create_folder', { path: relativePath })
|
||||
|
||||
// Debug
|
||||
if(this.options.debug)
|
||||
{
|
||||
console.log('addDir:'.green.bold, relativePath)
|
||||
}
|
||||
})
|
||||
|
||||
// UnlinkDir event
|
||||
this.watcher.on('unlinkDir', (_path) =>
|
||||
{
|
||||
// Set up
|
||||
const relativePath = _path.replace(process.cwd(), '.')
|
||||
|
||||
// Send
|
||||
this.socket.emit('delete_folder', { path: relativePath })
|
||||
|
||||
// Debug
|
||||
if(this.options.debug)
|
||||
{
|
||||
console.log('unlinkDir:'.green.bold, relativePath)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Watcher
|
||||
Reference in New Issue
Block a user