🎨 Update files structure

This commit is contained in:
brunosimon
2017-07-18 03:47:13 +02:00
parent 5a7431a6b9
commit 2b49b8a847
29 changed files with 13 additions and 24 deletions
+19
View File
@@ -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
+332
View File
@@ -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
+224
View File
@@ -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
+112
View File
@@ -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
+146
View 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
+76
View File
@@ -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
+138
View File
@@ -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
+20
View File
@@ -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
+38
View File
@@ -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