diff --git a/ChangeLog b/ChangeLog index d78409c0..66d3c446 100644 --- a/ChangeLog +++ b/ChangeLog @@ -48,6 +48,8 @@ current, file that was previously current must be unset automatically. * Moved error message a little bit lower. +* Added ability to download files. + 2012.08.06, Version 0.1.5 diff --git a/client.js b/client.js index c2efe6dd..0387bf60 100644 --- a/client.js +++ b/client.js @@ -680,12 +680,12 @@ CloudClient.keyBinding=(function(){ }); /* function loads and shows editor */ -CloudClient.Editor = (function() { +CloudClient.Editor = (function(pIsReadOnly) { /* loading CloudMirror plagin */ Util.jsload(CloudClient.LIBDIRCLIENT + 'editor.js',{ onload:(function(){ - CloudCommander.Editor.Keys(); + CloudCommander.Editor.Keys(pIsReadOnly); }) }); }); diff --git a/lib/client/editor.js b/lib/client/editor.js index 1ca7d509..7da2193f 100644 --- a/lib/client/editor.js +++ b/lib/client/editor.js @@ -13,13 +13,13 @@ CloudCommander.Editor.CodeMirror = new CloudCommander.Util(); CloudCommander.Editor.CodeMirror.loading = false; /* function loads CodeMirror js and css files */ -CloudCommander.Editor.CodeMirror.load = (function(pParent){ +CloudCommander.Editor.CodeMirror.load = (function(pThis){ /* function shows editor */ var createEditorDiv = function(){ - if (!pParent.getById('CloudEditor')) { - var lFM = pParent.getById('fm'); + if (!pThis.getById('CloudEditor')) { + var lFM = pThis.getById('fm'); if(lFM) - pParent.anyload({ + pThis.anyload({ name : 'div', id : 'CloudEditor', parent : lFM @@ -27,7 +27,7 @@ CloudCommander.Editor.CodeMirror.load = (function(pParent){ else console.log('Error. Something went wrong FM not found'); - pParent.show(); + pThis.show(pThis); } }; /* function loads css files @@ -59,31 +59,30 @@ CloudCommander.Editor.CodeMirror.load = (function(pParent){ }; /* load CodeMirror main module */ - pParent.jsload('lib/client/editor/' + + pThis.jsload('lib/client/editor/' + 'codemirror/pack/codemirror.pack.js', loadAll(this)); }); /* function shows CodeMirror editor */ -CloudCommander.Editor.CodeMirror.show = (function(){ +CloudCommander.Editor.CodeMirror.show = (function(pThis, pIsReadOnly){ /* if CloudEditor is not loaded - loading him */ - if(!this.getById('CloudEditor')) - return this.load(this); + if(!pThis.getById('CloudEditor')) + return pThis.load(pThis); /* if CodeMirror function show already * called do not call it again * if f4 key pressed couple times */ - if(this.loading) + if(pThis.loading) return; /* when folder view * is no need to edit * data */ - var lReadOnly = false; + var lReadOnly = pIsReadOnly || false; - var lThis = this; var initCodeMirror_f = function(pValue){ CodeMirror(lCloudEditor,{ mode : 'javascript', @@ -92,22 +91,22 @@ CloudCommander.Editor.CodeMirror.show = (function(){ lineNumbers : true, //переносим длинные строки lineWrapping: false, - autofocus : true, + autofocus : true, extraKeys: { //Сохранение - "Esc": lThis.hide(lThis) + "Esc": pThis.hide(pThis) }, readOnly : lReadOnly }); }; - var lCloudEditor = this.getById('CloudEditor'); + var lCloudEditor = pThis.getById('CloudEditor'); - var lCurrent = this.getCurrentFile(); + var lCurrent = pThis.getCurrentFile(); var lA; /* getting link */ - lA = this.getByTag('a', lCurrent); + lA = pThis.getByTag('a', lCurrent); lA = lA[0].href; @@ -115,7 +114,7 @@ CloudCommander.Editor.CodeMirror.show = (function(){ lA = '/' + lA.replace(document.location.href,''); /* checking is this link is to directory */ - var lSize = this.getByClass('size', lCurrent); + var lSize = pThis.getByClass('size', lCurrent); if(lSize){ lSize = lSize[0].textContent; @@ -124,8 +123,7 @@ CloudCommander.Editor.CodeMirror.show = (function(){ */ if (lSize === ''){ if (lA.indexOf(CloudFunc.NOJS) === - CloudFunc.FS.length) { - + CloudFunc.FS.length) { lA = lA.replace(CloudFunc.NOJS, ''); lReadOnly = true; } @@ -134,15 +132,16 @@ CloudCommander.Editor.CodeMirror.show = (function(){ this.loading = true; setTimeout(function(){ - lThis.loading = false;}, + pThis.loading = false;}, 400); /* reading data from current file */ $.ajax({ url:lA, error: (function(jqXHR, textStatus, errorThrown){ - lThis.loading = false; - return lThis.Images.showError(jqXHR, textStatus, errorThrown); + pThis.loading = false; + + return pThis.Images.showError(jqXHR); }), success:function(data, textStatus, jqXHR){ @@ -155,10 +154,10 @@ CloudCommander.Editor.CodeMirror.show = (function(){ /* removing keyBinding if set */ CloudCommander.keyBinded = false; - lThis.hidePanel(); - lThis.Images.hideLoad(); + pThis.hidePanel(); + pThis.Images.hideLoad(); - lThis.loading = false; + pThis.loading = false; } }); }); @@ -178,19 +177,28 @@ CloudCommander.Editor.CodeMirror.hide = (function(pParent) { }; }); -CloudCommander.Editor.Keys = (function(){ +CloudCommander.Editor.Keys = (function(pIsReadOnly){ "use strict"; + var lThis = this.CodeMirror; /* loading js and css of CodeMirror */ - this.CodeMirror.show(this.CodeMirror); + this.CodeMirror.show(lThis, pIsReadOnly); - var key_event = function(event){ + var key_event = function(pEvent){ /* если клавиши можно обрабатывать */ if(CloudCommander.keyBinded){ - /* if f4 pressed */ - if(event.keyCode === CloudCommander.KEY.F4){ - CloudCommander.Editor.CodeMirror.show(); + /* if f4 or f3 pressed */ + var lF3 = CloudCommander.KEY.F3; + var lF4 = CloudCommander.KEY.F4; + var lShow = CloudCommander.Editor.CodeMirror.show; + + if(!pEvent.shiftKey){ + if(pEvent.keyCode === lF4) + lShow(lThis); + else if(pEvent.keyCode === lF3){ + lShow(lThis, true); + } } } }; diff --git a/lib/client/keyBinding.js b/lib/client/keyBinding.js index 46e25481..672b6e01 100644 --- a/lib/client/keyBinding.js +++ b/lib/client/keyBinding.js @@ -82,7 +82,16 @@ CloudCommander.keyBinding = (function(){ } /* if f3 pressed */ - else if(event.keyCode === lKEY.F3){ + else if(event.keyCode === lKEY.F3 && !event.shiftKey){ + if (typeof CloudCommander.Editor === 'function') + CloudCommander.Editor(true); + + event.preventDefault();//запрет на дальнейшее действие + } + + /* if + f3 pressed */ + else if(event.keyCode === lKEY.F3 && + event.shiftKey){ if (typeof CloudCommander.Viewer === 'function') CloudCommander.Viewer(); diff --git a/lib/client/menu.js b/lib/client/menu.js index 434bef3c..85250ed0 100644 --- a/lib/client/menu.js +++ b/lib/client/menu.js @@ -44,6 +44,14 @@ CloudCommander.Menu.getConfig = (function(){ 'delete': {name: 'Delete', callback: function(key, opt){ console.log('delete menu item choosen'); + }}, + + download: {name: 'Download',callback: function(key, opt){ + var lCurrent = lThis.getCurrentFile(); + var lLink = lThis.getByTag('a', lCurrent)[0]; + + + console.log('downloadin file...'); }} } }; diff --git a/lib/client/viewer.js b/lib/client/viewer.js index 9412a296..509a555c 100644 --- a/lib/client/viewer.js +++ b/lib/client/viewer.js @@ -94,9 +94,17 @@ CloudCommander.Viewer.FancyBox.loadData = (function(pA){ var lConfig = this.getConfig(); this.Images.showLoad(); + + var lLink = pA.href; + + /* убираем адрес хоста*/ + lLink = '/' + lLink.replace(document.location.href,''); + + if (lLink.indexOf(CloudFunc.NOJS) === CloudFunc.FS.length) + lLink = lLink.replace(CloudFunc.NOJS, ''); $.ajax({ - url : pA.href, + url : lLink, error : (function(jqXHR, textStatus, errorThrown){ lThis.loading = false; return lThis.Images.showError(jqXHR, textStatus, errorThrown); @@ -179,7 +187,8 @@ CloudCommander.Viewer.Keys = (function(){ /* если клавиши можно обрабатывать */ if(CloudCommander.keyBinded) /* if f3 pressed */ - if(pEvent.keyCode === CloudCommander.KEY.F3){ + if(pEvent.keyCode === CloudCommander.KEY.F3 && + pEvent.shiftKey){ CloudCommander.Viewer.FancyBox.show(); pEvent.preventDefault(); diff --git a/server.js b/server.js index d5e7cee2..77548c16 100644 --- a/server.js +++ b/server.js @@ -1,767 +1,788 @@ -"use strict"; - -/* Обьект содержащий все функции и переменные - * серверной части Cloud Commander'а - */ -var CloudServer = { - /* main Cloud Commander configuration - * readed from config.json if it's - * exist - */ - Config : { - cache : {allowed : true}, - minification : { - js : false, - css : false, - html : true, - img : false - }, - server : true, - logs : false, - port : 31337, - ip : '127.0.0.1' - }, - - /* функция, которая генерирует заголовки - * файлов, отправляемые сервером клиенту - */ - generateHeaders : function () {}, - - /* функция высылает - * данные клиенту - */ - sendResponse : function () {}, - - /* Обьект для работы с кэшем */ - Cashe : {}, - - /* Обьект через который - * выполняеться сжатие - * скриптов и стилей - */ - - Minify : {}, - - /* Асоциативный масив обьектов для - * работы с ответами сервера - * высылаемыми на запрос о файле и - * хранащий информацию в виде - * Responses[name]=responce; - */ - Responses : {}, - - /* - * Асоциативный масив статусов - * ответов сервера - * высылаемыми на запрос о файле и - * хранащий информацию в виде - * Statuses[name] = 404; - */ - Statuses : {}, - - /* ПЕРЕМЕННЫЕ - * Поддержка браузером JS */ - NoJS : true, - /* Поддержка gzip-сжатия браузером */ - Gzip : undefined, - - /* КОНСТАНТЫ */ - INDEX : 'index.html', - LIBDIR : './lib', - LIBDIRSERVER : './lib/server' -}; - -var DirPath = '/'; - -/* модуль для работы с путями*/ -var Path = require('path'); -var Fs = require('fs'); /* модуль для работы с файловой системой*/ -var Querystring = require('querystring'); -var Zlib; -/* node v0.4 not contains zlib - */ -try { - Zlib = require('zlib'); /* модуль для сжатия данных gzip-ом*/ -} catch (error) { - console.log('to use gzip-commpression' + - 'you should use newer node version\n'); -} - /* добавляем модуль с функциями */ -var CloudFunc; -try { - CloudFunc = require(CloudServer.LIBDIR + - '/cloudfunc'); - - CloudServer.Cache = require(CloudServer.LIBDIRSERVER + - '/object').Cache; - - CloudServer.Minify = require(CloudServer.LIBDIRSERVER + - '/object').Minify; -}catch(pError){ - console.log('could not found one of Cloud Commander SS files'); - console.log(pError); -} -/* конструктор*/ -CloudServer.init=(function(){ - /* Determining server.js directory - * and chang current process directory - * (usually /) to it. - * argv[1] - is always script name - */ - var lServerDir = Path.dirname(process.argv[1]); - var lProcessDir = process.cwd(); - console.log('current dir: ' + lProcessDir); - console.log('server dir: ' + lServerDir); - if(lProcessDir !== lServerDir) - process.chdir(lServerDir); - - try{ - console.log('reading configuretion file config.json...'); - this.Config = require('./config'); - console.log('config.json readed'); - - /* if command line parameter testing resolved - * setting config to testing, so server - * not created, just init and - * all logs writed to screen - */ - if (process.argv[2] === 'test') { - console.log(process.argv); - this.Config.server = false; - this.Config.logs = false; - } - - if (this.Config.logs) { - console.log('log param setted up in config.json\n' + - 'from now all logs will be writed to log.txt'); - this.writeLogsToFile(); - } - } catch (pError) { - console.log('warning: configuretion file config.json not found...\n' + - 'using default values...\n' + - JSON.stringify(CloudServer.Config)); - } - - /* Переменная в которой храниться кэш*/ - this.Cache.setAllowed(CloudServer.Config.cache.allowed); - /* Change default parameters of - * js/css/html minification - */ - this.Minify.setAllowed(CloudServer.Config.minification); - /* Если нужно минимизируем скрипты */ - this.Minify.doit(); -}); - - -/* создаём сервер на порту 31337 */ -CloudServer.start = function () { - this.init(); - - this.Port = process.env.PORT || /* c9 */ - process.env.app_port || /* nodester */ - process.env.VCAP_APP_PORT || /* cloudfoundry */ - CloudServer.Config.port; - - this.IP = process.env.IP || /* c9 */ - CloudServer.Config.ip; - - /* if Cloud Server started on jitsu */ - if(process.env.HOME && - !process.env.HOME.indexOf('/opt/haibu')) { - this.IP = '0.0.0.0'; - } - /* server mode or testing mode */ - if (!process.argv[2] && this.Config.server) { - var http = require('http'); - - try { - http.createServer(this._controller).listen( - this.Port, this.IP); - - console.log('Cloud Commander server running at http://' + - this.IP + - ':' + - this.Port); - }catch(pError){ - console.log('Cloud Commander server could not started'); - console.log(pError); - } - }else{ - console.log('Cloud Commander testing mode'); - } -}; - - -/* Функция создаёт заголовки файлов - * в зависимости от расширения файла - * перед отправкой их клиенту - * @pName - имя файла - * @pGzip - данные сжаты gzip'ом - */ -CloudServer.generateHeaders = function(pName, pGzip){ - var lType=''; - /* высылаем заголовок в зависимости от типа файла */ - /* если расширение у файла css - - * загружаем стили - */ - if(CloudFunc.checkExtension(pName,'css')) - lType = 'text/css'; - /* загружаем js */ - else if(CloudFunc.checkExtension(pName,'js')) - lType = 'text/javascript'; - /* загружаем картинки*/ - else if(CloudFunc.checkExtension(pName,'png')) - lType = 'image/png'; - /* загружаем json*/ - else if(CloudFunc.checkExtension(pName,'json')) - lType = 'application/json'; - else if(CloudFunc.checkExtension(pName,'html')) - lType = 'text/html'; - else if(CloudFunc.checkExtension(pName,'woff')) - lType = 'font/woff'; - else if(CloudFunc.checkExtension(pName,'appcache')) - lType = 'text/cache-manifest'; - else if(CloudFunc.checkExtension(pName,'mp3')) - lType = 'audio/mpeg'; - /* если это неизвестный тип файла - - * высылаем его просто как текст - */ - else lType='text/plain'; - - return { - /* if type of file any, but img - - * then we shoud specify charset - */ - 'Content-Type': lType + (lType.indexOf('img')<0?'; charset=UTF-8':''), - 'cache-control': 'max-age='+(31337*21), - 'last-modified': new Date().toString(), - 'content-encoding': pGzip?'gzip':'', - /* https://developers.google.com/speed/docs/best-practices - /caching?hl=ru#LeverageProxyCaching */ - 'Vary': 'Accept-Encoding' - }; -}; - -/* - * Главная функция, через которую проихсодит - * взаимодействие, обмен данными с клиентом - * @req - запрос клиента (Request) - * @res - ответ сервера (Response) - */ -CloudServer._controller=function(pReq, pRes) -{ - /* Читаем содержимое папки, - переданное в url - */ - var url = require("url"); - var pathname = url.parse(pReq.url).pathname; - - /* added supporting of Russian language in directory names */ - pathname = Querystring.unescape(pathname); - console.log('pathname: ' + pathname); - - /* получаем поддерживаемые браузером кодировки*/ - var lAcceptEncoding = pReq.headers['accept-encoding']; - /* запоминаем поддерживает ли браузер - * gzip-сжатие при каждом обращении к серверу - * и доступен ли нам модуль zlib - */ - if (lAcceptEncoding && - lAcceptEncoding.match(/\bgzip\b/) && - Zlib){ - CloudServer.Gzip=true; - } - /* путь в ссылке, который говорит - * что js отключен - */ - var lNoJS_s=CloudFunc.NOJS; - var lFS_s=CloudFunc.FS; - - if(pathname!=='/favicon.ico') - { - console.log("request for " + pathname + " received..."); - var lName; - - /* если в пути нет информации ни о ФС, - * ни об отсутствии js, - * ни о том, что это корневой - * каталог - загружаем файлы проэкта - */ - if(pathname.indexOf(lFS_s)<0 && - pathname.indexOf(lNoJS_s)<0 && - pathname!=='/'){ - /* если имена файлов проекта - загружаем их*/ - /* убираем слеш и читаем файл с текущец директории*/ - - /* добавляем текующий каталог к пути */ - lName='.'+pathname; - console.log('reading '+lName); - - /* сохраняем указатель на response и имя */ - CloudServer.Responses[lName]=pRes; - - /* saving status OK for current file */ - CloudServer.Statuses[lName] = 200; - - /* Берём значение из кэша - * сжатый файл - если gzip-поддерживаеться браузером - * не сжатый - в обратном случае - */ - var lFileData=CloudServer.Cache.get( - CloudServer.Gzip?(lName+'_gzip'):lName); - console.log(Path.basename(lName)); - - var lMinify=CloudServer.Minify; - - /* object thet contains information - * about the source of file data - */ - var lFromCache_o={'cache': true}; - - /* if cache is empty and Cache allowed and Minify_allowed - * and in Minifys cache is files, so save it to - * CloudServer cache - */ - if(!lFileData && - lMinify._allowed){ - console.log('trying to read data from Minify.Cache'); - lFromCache_o.cache=false; - lFileData = CloudServer.Minify.Cache[ - Path.basename(lName)]; - } - var lReadFileFunc_f=CloudServer.getReadFileFunc(lName); - /* если там что-то есть передаём данные в функцию - * readFile - */ - if(lFileData){ - /* if file readed not from cache - - * he readed from minified cache - */ - if(lFromCache_o.cache===false) - lFromCache_o.minify=true; - else - lFromCache_o.minify=false; - - console.log(lName + ' readed from cache'); - /* передаём данные с кэша, - * если gzip включен - сжатые - * в обратном случае - несжатые - */ - lReadFileFunc_f(undefined,lFileData,lFromCache_o); - } - else Fs.readFile(lName,lReadFileFunc_f); - - }else{/* если мы имеем дело с файловой системой*/ - /* если путь не начинаеться с no-js - значит - * js включен - */ - /* убираем пометку cloud, без которой c9.io - * не работает поскольку путь из двух слешей - * (/fs/no-js/) - очень короткий, нужно - * длиннее - */ - - if(pathname.indexOf(lNoJS_s)!==lFS_s.length && pathname!=='/'){ - CloudServer.NoJS=false; - }else pathname=pathname.replace(lNoJS_s,''); - - /* убираем индекс файловой системы */ - if(pathname.indexOf(lFS_s)===0){ - pathname=pathname.replace(lFS_s,''); - /* если посетитель только зашел на сайт - * no-js будет пустым, как и fs - */ - /* если в пути нету fs - посетитель только зашел на сайт - * загружаем его полностью. - */ - }else CloudServer.NoJS=true; - /* если в итоге путь пустой - * делаем его корневым - */ - if (pathname === '') - pathname = '/'; - - DirPath = pathname; - - CloudServer.Responses[DirPath] = pRes; - - CloudServer.Statuses[DirPath] = 200; - - /* Проверяем с папкой ли мы имеем дело */ - - /* читаем основные данные о файле */ - Fs.stat(DirPath, CloudServer._stated); - - /* если установлено сжатие - * меняем название html-файла и - * загружаем сжатый html-файл в дальнейшем - */ - CloudServer.INDEX=(CloudServer.Minify._allowed.html? - '.' + CloudServer.Minify.MinFolder + 'index.min.html' - :CloudServer.INDEX); - - /* - * сохраним указатель на response - * и на статус ответа - */ - CloudServer.Responses[CloudServer.INDEX] = pRes; - CloudServer.Statuses[CloudServer.INDEX] = 200; - } - } -}; - -/* - * Function geted stat information about file - */ -CloudServer._stated = function(pError, pStat){ - if(pError){ - CloudServer.Statuses[DirPath] = 404; - CloudServer.sendResponse('OK',pError.toString(),DirPath); - - return; - } - - /* - * если это каталог - - * читаем его содержимое - */ - - if(pStat){ - if(pStat.isDirectory()) - Fs.readdir(DirPath,CloudServer._readDir); - /* отдаём файл */ - else if(pStat.isFile()){ - Fs.readFile(DirPath,CloudServer.getReadFileFunc(DirPath)); - console.log('reading file: '+DirPath); - } - } -}; - - -/* Функция читает ссылку или выводит информацию об ошибке*/ -CloudServer._readDir = function (pError, pFiles) -{ - if(pError){ - console.log(pError); - - CloudServer.Statuses[DirPath] = 404; - CloudServer.sendResponse('OK',pError.toString(), - DirPath); - return; - } - - /* Если мы не в корне добавляем слеш к будующим ссылкам */ - if(DirPath !== '/') - { - DirPath += '/'; - } - - pFiles=pFiles.sort(); - - var lCount = 0; - var lStats = {}; - /* asyn getting file states - * and putting it to lStats object - */ - var getFilesStat_f = function(pName){ - return function(pError, pStat){ - var fReturnFalse = function returnFalse(){ - return false; - }; - - if(pError) - lStats[pName] = { - 'mode':0, - 'size':0, - 'isDirectory':fReturnFalse - }; - - else - lStats[pName] = pStat; - - /* if this file is last - moving next */ - if(++lCount === pFiles.length) - CloudServer._fillJSON(lStats, pFiles); - }; - }; - - for(var i=0;i','') - .replace('/css/style.css',CloudServer.Minify.MinFolder + 'all.min.css') - :pIndex; - - pIndex = CloudServer.Minify._allowed.js?pIndex.replace('client.js', - CloudServer.Minify.MinFolder + - 'client.min.js') - :pIndex; - - pIndex = pIndex.toString().replace('
', - '
'+pList); - - /* меняем title */ - pIndex = pIndex.replace('Cloud Commander', - ''+CloudFunc.setTitle()+''); - - /* отображаем панель быстрых клавишь */ - pList = pIndex; - - var lHeader; - /* если браузер поддерживает gzip-сжатие*/ - lHeader = CloudServer.generateHeaders('text/html',CloudServer.Gzip); - - /* если браузер поддерживает gzip-сжатие - сжимаем данные*/ - if(CloudServer.Gzip) { - Zlib.gzip(pList, - CloudServer.getGzipDataFunc(lHeader,CloudServer.INDEX)); - } - /* если не поддерживаеться - отсылаем данные без сжатия*/ - else - CloudServer.sendResponse(lHeader,pList,CloudServer.INDEX); - }; -}; - -/* Функция генерирует функцию считывания файла - * таким образом, что бы у нас было - * имя считываемого файла - * @pName - полное имя файла - */ -CloudServer.getReadFileFunc = function(pName){ -/* - * @pError - ошибка - * @pData - данные - * @pFromCache_o - прочитано с файла, - * или из одного из кешей - * Пример {cache: false, minify: true} - */ - var lReadFile=function(pError, pData, pFromCache_o){ - if (!pError){ - console.log('file ' + pName + ' readed'); - - /* берём из кэша данные файла - * если их нет в кэше - - * сохраняем - */ - if(pFromCache_o && !pFromCache_o.cache && CloudServer.Cache.isAllowed) - CloudServer.Cache.set(pName,pData); - /* если кэш есть - * сохраняем его в переменную - * которая до этого будет пустая - * по скольку мы будем вызывать этот метод - * сами, ведь файл уже вычитан - */ - - var lHeader=CloudServer.generateHeaders(pName,CloudServer.Gzip); - /* если браузер поддерживает gzip-сжатие - сжимаем данные*/ - if( CloudServer.Gzip && !(pFromCache_o && pFromCache_o.cache) ){ - /* сжимаем содержимое */ - Zlib.gzip(pData,CloudServer.getGzipDataFunc(lHeader,pName)); - } - else{ - /* высылаем несжатые данные */ - CloudServer.sendResponse(lHeader,pData,pName); - } - } - else - { - console.log(pError.path); - if(pError.path!=='passwd.json') - { - console.log(pError); - - /* sending page not found */ - CloudServer.Statuses[pName] = 404; - - CloudServer.sendResponse('file not found',pError.toString(),pName); - }else{ - CloudServer.sendResponse('OK','passwd.json'); - } - } - }; - return lReadFile; -}; - -/* Функция получает сжатые данные - * @pHeader - заголовок файла - */ -CloudServer.getGzipDataFunc=function(pHeader,pName){ - return function(error,pResult){ - if(!error){ - /* отправляем сжатые данные - * вместе с заголовком - */ - /* если установлена работа с кэшем - * сохраняем сжатые данные - */ - if(CloudServer.Cache.isAllowed){ - /* устанавливаем кєш */ - console.log(pName+' gziped'); - CloudServer.Cache.set(pName+'_gzip',pResult); - } - CloudServer.sendResponse(pHeader,pResult,pName); - } - else{ - console.log(error); - CloudServer.sendResponse(pHeader,error); - } - }; -}; -/* Функция высылает ответ серверу - * @pHead - заголовок - * @pData - данные - * @pName - имя отсылаемого файла - */ -CloudServer.sendResponse = function(pHead, pData,pName){ - /* если у нас есть указатель на responce - * для соответствующего файла - - * высылаем его - */ - var lResponse = CloudServer.Responses[pName]; - var lStatus = CloudServer.Statuses[pName]; - - if(lResponse){ - lResponse.writeHead( - lStatus, - pHead); - - lResponse.end(pData); - - console.log(pName+' sended'); - } -}; - -/* function sets stdout to file log.txt */ -CloudServer.writeLogsToFile = function(){ - var stdo = require('fs').createWriteStream('./log.txt'); - - process.stdout.write = (function(write) { - return function(string, encoding, fd) { - stdo.write(string); - }; - })(process.stdout.write); -}; - +"use strict"; + +/* Обьект содержащий все функции и переменные + * серверной части Cloud Commander'а + */ +var CloudServer = { + /* main Cloud Commander configuration + * readed from config.json if it's + * exist + */ + Config : { + cache : {allowed : true}, + minification : { + js : false, + css : false, + html : true, + img : false + }, + server : true, + logs : false, + port : 31337, + ip : '127.0.0.1' + }, + + /* функция, которая генерирует заголовки + * файлов, отправляемые сервером клиенту + */ + generateHeaders : function () {}, + + /* функция высылает + * данные клиенту + */ + sendResponse : function () {}, + + /* Обьект для работы с кэшем */ + Cashe : {}, + + /* Обьект через который + * выполняеться сжатие + * скриптов и стилей + */ + + Minify : {}, + + /* Асоциативный масив обьектов для + * работы с ответами сервера + * высылаемыми на запрос о файле и + * хранащий информацию в виде + * Responses[name]=responce; + */ + Responses : {}, + + /* + * Асоциативный масив статусов + * ответов сервера + * высылаемыми на запрос о файле и + * хранащий информацию в виде + * Statuses[name] = 404; + */ + Statuses : {}, + + + /* + * queries of file params + * example: ?download + */ + Queries : {}, + /* ПЕРЕМЕННЫЕ + * Поддержка браузером JS */ + NoJS : true, + /* Поддержка gzip-сжатия браузером */ + Gzip : undefined, + + /* КОНСТАНТЫ */ + INDEX : 'index.html', + LIBDIR : './lib', + LIBDIRSERVER : './lib/server' +}; + +var DirPath = '/'; + +/* модуль для работы с путями*/ +var Path = require('path'); +var Fs = require('fs'); /* модуль для работы с файловой системой*/ +var Querystring = require('querystring'); +var Zlib; +/* node v0.4 not contains zlib + */ +try { + Zlib = require('zlib'); /* модуль для сжатия данных gzip-ом*/ +} catch (error) { + console.log('to use gzip-commpression' + + 'you should use newer node version\n'); +} + /* добавляем модуль с функциями */ +var CloudFunc; +try { + CloudFunc = require(CloudServer.LIBDIR + + '/cloudfunc'); + + CloudServer.Cache = require(CloudServer.LIBDIRSERVER + + '/object').Cache; + + CloudServer.Minify = require(CloudServer.LIBDIRSERVER + + '/object').Minify; +}catch(pError){ + console.log('could not found one of Cloud Commander SS files'); + console.log(pError); +} +/* конструктор*/ +CloudServer.init=(function(){ + /* Determining server.js directory + * and chang current process directory + * (usually /) to it. + * argv[1] - is always script name + */ + var lServerDir = Path.dirname(process.argv[1]); + var lProcessDir = process.cwd(); + console.log('current dir: ' + lProcessDir); + console.log('server dir: ' + lServerDir); + if(lProcessDir !== lServerDir) + process.chdir(lServerDir); + + try{ + console.log('reading configuretion file config.json...'); + this.Config = require('./config'); + console.log('config.json readed'); + + /* if command line parameter testing resolved + * setting config to testing, so server + * not created, just init and + * all logs writed to screen + */ + if (process.argv[2] === 'test') { + console.log(process.argv); + this.Config.server = false; + this.Config.logs = false; + } + + if (this.Config.logs) { + console.log('log param setted up in config.json\n' + + 'from now all logs will be writed to log.txt'); + this.writeLogsToFile(); + } + } catch (pError) { + console.log('warning: configuretion file config.json not found...\n' + + 'using default values...\n' + + JSON.stringify(CloudServer.Config)); + } + + /* Переменная в которой храниться кэш*/ + this.Cache.setAllowed(CloudServer.Config.cache.allowed); + /* Change default parameters of + * js/css/html minification + */ + this.Minify.setAllowed(CloudServer.Config.minification); + /* Если нужно минимизируем скрипты */ + this.Minify.doit(); +}); + + +/* создаём сервер на порту 31337 */ +CloudServer.start = function () { + this.init(); + + this.Port = process.env.PORT || /* c9 */ + process.env.app_port || /* nodester */ + process.env.VCAP_APP_PORT || /* cloudfoundry */ + CloudServer.Config.port; + + this.IP = process.env.IP || /* c9 */ + CloudServer.Config.ip; + + /* if Cloud Server started on jitsu */ + if(process.env.HOME && + !process.env.HOME.indexOf('/opt/haibu')) { + this.IP = '0.0.0.0'; + } + /* server mode or testing mode */ + if (!process.argv[2] && this.Config.server) { + var http = require('http'); + + try { + http.createServer(this._controller).listen( + this.Port, this.IP); + + console.log('Cloud Commander server running at http://' + + this.IP + + ':' + + this.Port); + }catch(pError){ + console.log('Cloud Commander server could not started'); + console.log(pError); + } + }else{ + console.log('Cloud Commander testing mode'); + } +}; + + +/* Функция создаёт заголовки файлов + * в зависимости от расширения файла + * перед отправкой их клиенту + * @pName - имя файла + * @pGzip - данные сжаты gzip'ом + */ +CloudServer.generateHeaders = function(pName, pGzip){ + var lType=''; + /* высылаем заголовок в зависимости от типа файла */ + /* если расширение у файла css - + * загружаем стили + */ + if(CloudFunc.checkExtension(pName,'css')) + lType = 'text/css'; + /* загружаем js */ + else if(CloudFunc.checkExtension(pName,'js')) + lType = 'text/javascript'; + /* загружаем картинки*/ + else if(CloudFunc.checkExtension(pName,'png')) + lType = 'image/png'; + /* загружаем json*/ + else if(CloudFunc.checkExtension(pName,'json')) + lType = 'application/json'; + else if(CloudFunc.checkExtension(pName,'html')) + lType = 'text/html'; + else if(CloudFunc.checkExtension(pName,'woff')) + lType = 'font/woff'; + else if(CloudFunc.checkExtension(pName,'appcache')) + lType = 'text/cache-manifest'; + else if(CloudFunc.checkExtension(pName,'mp3')) + lType = 'audio/mpeg'; + + /* если это неизвестный тип файла - + * высылаем его просто как текст + */ + else lType='text/plain'; + + var lQuery = CloudServer.Queries[pName]; + if(lQuery) + if(lQuery === 'download') + lType = 'application/octet-stream'; + console.log(pName + lQuery); + + return { + /* if type of file any, but img - + * then we shoud specify charset + */ + 'Content-Type': lType + (lType.indexOf('img') < 0 ? '; charset=UTF-8' : ''), + 'cache-control': 'max-age='+(31337*21), + 'last-modified': new Date().toString(), + 'content-encoding': pGzip?'gzip':'', + /* https://developers.google.com/speed/docs/best-practices + /caching?hl=ru#LeverageProxyCaching */ + 'Vary': 'Accept-Encoding' + }; +}; + +/* + * Главная функция, через которую проихсодит + * взаимодействие, обмен данными с клиентом + * @req - запрос клиента (Request) + * @res - ответ сервера (Response) + */ +CloudServer._controller=function(pReq, pRes) +{ + /* Читаем содержимое папки, + переданное в url + */ + var url = require("url"); + var lParsedUrl = url.parse(pReq.url); + var pathname = lParsedUrl.pathname; + var lQuery = lParsedUrl.query; + if(lQuery) + console.log('query= ' + lQuery); + + /* added supporting of Russian language in directory names */ + pathname = Querystring.unescape(pathname); + console.log('pathname: ' + pathname); + + /* получаем поддерживаемые браузером кодировки*/ + var lAcceptEncoding = pReq.headers['accept-encoding']; + /* запоминаем поддерживает ли браузер + * gzip-сжатие при каждом обращении к серверу + * и доступен ли нам модуль zlib + */ + if (lAcceptEncoding && + lAcceptEncoding.match(/\bgzip\b/) && + Zlib){ + CloudServer.Gzip=true; + } + /* путь в ссылке, который говорит + * что js отключен + */ + var lNoJS_s=CloudFunc.NOJS; + var lFS_s=CloudFunc.FS; + + if(pathname!=='/favicon.ico') + { + console.log("request for " + pathname + " received..."); + var lName; + + /* если в пути нет информации ни о ФС, + * ни об отсутствии js, + * ни о том, что это корневой + * каталог - загружаем файлы проэкта + */ + if(pathname.indexOf(lFS_s)<0 && + pathname.indexOf(lNoJS_s)<0 && + pathname!=='/'){ + /* если имена файлов проекта - загружаем их*/ + /* убираем слеш и читаем файл с текущец директории*/ + + /* добавляем текующий каталог к пути */ + lName='.'+pathname; + console.log('reading '+lName); + + /* сохраняем указатель на response и имя */ + CloudServer.Responses[lName]=pRes; + + /* saving status OK for current file */ + CloudServer.Statuses[lName] = 200; + + /* Берём значение из кэша + * сжатый файл - если gzip-поддерживаеться браузером + * не сжатый - в обратном случае + */ + var lFileData=CloudServer.Cache.get( + CloudServer.Gzip?(lName+'_gzip'):lName); + + console.log(Path.basename(lName)); + + var lMinify = CloudServer.Minify; + + /* object thet contains information + * about the source of file data + */ + var lFromCache_o = {'cache': true}; + + /* if cache is empty and Cache allowed and Minify_allowed + * and in Minifys cache is files, so save it to + * CloudServer cache + */ + if(!lFileData && + lMinify._allowed){ + console.log('trying to read data from Minify.Cache'); + lFromCache_o.cache=false; + lFileData = CloudServer.Minify.Cache[ + Path.basename(lName)]; + } + var lReadFileFunc_f=CloudServer.getReadFileFunc(lName); + /* если там что-то есть передаём данные в функцию + * readFile + */ + if(lFileData){ + /* if file readed not from cache - + * he readed from minified cache + */ + if(lFromCache_o.cache === false) + lFromCache_o.minify = true; + else + lFromCache_o.minify = false; + + console.log(lName + ' readed from cache'); + /* передаём данные с кэша, + * если gzip включен - сжатые + * в обратном случае - несжатые + */ + lReadFileFunc_f(undefined,lFileData,lFromCache_o); + } + else Fs.readFile(lName,lReadFileFunc_f); + + }else{/* если мы имеем дело с файловой системой*/ + /* если путь не начинаеться с no-js - значит + * js включен + */ + /* убираем пометку cloud, без которой c9.io + * не работает поскольку путь из двух слешей + * (/fs/no-js/) - очень короткий, нужно + * длиннее + */ + + if(pathname.indexOf(lNoJS_s)!==lFS_s.length && pathname!=='/'){ + CloudServer.NoJS=false; + }else pathname=pathname.replace(lNoJS_s,''); + + /* убираем индекс файловой системы */ + if(pathname.indexOf(lFS_s)===0){ + pathname = pathname.replace(lFS_s,''); + /* если посетитель только зашел на сайт + * no-js будет пустым, как и fs + */ + /* если в пути нету fs - посетитель только зашел на сайт + * загружаем его полностью. + */ + }else CloudServer.NoJS=true; + /* если в итоге путь пустой + * делаем его корневым + */ + if (pathname === '') + pathname = '/'; + + DirPath = pathname; + + CloudServer.Responses[DirPath] = pRes; + + CloudServer.Statuses[DirPath] = 200; + + /* saving query of current file */ + CloudServer.Queries[DirPath] = lQuery; + + /* Проверяем с папкой ли мы имеем дело */ + + /* читаем основные данные о файле */ + Fs.stat(DirPath, CloudServer._stated); + + /* если установлено сжатие + * меняем название html-файла и + * загружаем сжатый html-файл в дальнейшем + */ + CloudServer.INDEX=(CloudServer.Minify._allowed.html? + '.' + CloudServer.Minify.MinFolder + 'index.min.html' + :CloudServer.INDEX); + + /* + * сохраним указатель на response + * и на статус ответа + */ + CloudServer.Responses[CloudServer.INDEX] = pRes; + CloudServer.Statuses[CloudServer.INDEX] = 200; + } + } +}; + +/* + * Function geted stat information about file + */ +CloudServer._stated = function(pError, pStat){ + if(pError){ + CloudServer.Statuses[DirPath] = 404; + CloudServer.sendResponse('OK',pError.toString(), DirPath); + + return; + } + + /* + * если это каталог - + * читаем его содержимое + */ + + if(pStat){ + if(pStat.isDirectory()) + Fs.readdir(DirPath, CloudServer._readDir); + /* отдаём файл */ + else if(pStat.isFile()){ + Fs.readFile(DirPath, CloudServer.getReadFileFunc(DirPath)); + console.log('reading file: '+DirPath); + } + } +}; + + +/* Функция читает ссылку или выводит информацию об ошибке*/ +CloudServer._readDir = function (pError, pFiles) +{ + if(pError){ + console.log(pError); + + CloudServer.Statuses[DirPath] = 404; + CloudServer.sendResponse('OK',pError.toString(), + DirPath); + return; + } + + /* Если мы не в корне добавляем слеш к будующим ссылкам */ + if(DirPath !== '/') + { + DirPath += '/'; + } + + pFiles=pFiles.sort(); + + var lCount = 0; + var lStats = {}; + /* asyn getting file states + * and putting it to lStats object + */ + var getFilesStat_f = function(pName){ + return function(pError, pStat){ + var fReturnFalse = function returnFalse(){ + return false; + }; + + if(pError) + lStats[pName] = { + 'mode':0, + 'size':0, + 'isDirectory':fReturnFalse + }; + + else + lStats[pName] = pStat; + + /* if this file is last - moving next */ + if(++lCount === pFiles.length) + CloudServer._fillJSON(lStats, pFiles); + }; + }; + + for(var i=0;i','') + .replace('/css/style.css',CloudServer.Minify.MinFolder + 'all.min.css') + :pIndex; + + pIndex = CloudServer.Minify._allowed.js?pIndex.replace('client.js', + CloudServer.Minify.MinFolder + + 'client.min.js') + :pIndex; + + pIndex = pIndex.toString().replace('
', + '
'+pList); + + /* меняем title */ + pIndex = pIndex.replace('Cloud Commander', + ''+CloudFunc.setTitle()+''); + + /* отображаем панель быстрых клавишь */ + pList = pIndex; + + var lHeader; + /* если браузер поддерживает gzip-сжатие*/ + lHeader = CloudServer.generateHeaders('text/html',CloudServer.Gzip); + + /* если браузер поддерживает gzip-сжатие - сжимаем данные*/ + if(CloudServer.Gzip) { + Zlib.gzip(pList, + CloudServer.getGzipDataFunc(lHeader,CloudServer.INDEX)); + } + /* если не поддерживаеться - отсылаем данные без сжатия*/ + else + CloudServer.sendResponse(lHeader,pList,CloudServer.INDEX); + }; +}; + +/* Функция генерирует функцию считывания файла + * таким образом, что бы у нас было + * имя считываемого файла + * @pName - полное имя файла + */ +CloudServer.getReadFileFunc = function(pName){ +/* + * @pError - ошибка + * @pData - данные + * @pFromCache_o - прочитано с файла, + * или из одного из кешей + * Пример {cache: false, minify: true} + */ + var lReadFile=function(pError, pData, pFromCache_o){ + if (!pError){ + console.log('file ' + pName + ' readed'); + + /* берём из кэша данные файла + * если их нет в кэше - + * сохраняем + */ + if(pFromCache_o && !pFromCache_o.cache && CloudServer.Cache.isAllowed) + CloudServer.Cache.set(pName,pData); + /* если кэш есть + * сохраняем его в переменную + * которая до этого будет пустая + * по скольку мы будем вызывать этот метод + * сами, ведь файл уже вычитан + */ + + var lHeader = CloudServer.generateHeaders(pName, CloudServer.Gzip); + /* если браузер поддерживает gzip-сжатие - сжимаем данные*/ + if( CloudServer.Gzip && !(pFromCache_o && pFromCache_o.cache) ){ + /* сжимаем содержимое */ + Zlib.gzip(pData,CloudServer.getGzipDataFunc(lHeader,pName)); + } + else{ + /* высылаем несжатые данные */ + CloudServer.sendResponse(lHeader,pData,pName); + } + } + else + { + console.log(pError.path); + if(pError.path!=='passwd.json') + { + console.log(pError); + + /* sending page not found */ + CloudServer.Statuses[pName] = 404; + + CloudServer.sendResponse('file not found',pError.toString(),pName); + }else{ + CloudServer.sendResponse('OK','passwd.json'); + } + } + }; + return lReadFile; +}; + +/* Функция получает сжатые данные + * @pHeader - заголовок файла + */ +CloudServer.getGzipDataFunc=function(pHeader,pName){ + return function(error,pResult){ + if(!error){ + /* отправляем сжатые данные + * вместе с заголовком + */ + /* если установлена работа с кэшем + * сохраняем сжатые данные + */ + if(CloudServer.Cache.isAllowed){ + /* устанавливаем кєш */ + console.log(pName+' gziped'); + CloudServer.Cache.set(pName+'_gzip',pResult); + } + CloudServer.sendResponse(pHeader,pResult,pName); + } + else{ + console.log(error); + CloudServer.sendResponse(pHeader,error); + } + }; +}; +/* Функция высылает ответ серверу + * @pHead - заголовок + * @pData - данные + * @pName - имя отсылаемого файла + */ +CloudServer.sendResponse = function(pHead, pData,pName){ + /* если у нас есть указатель на responce + * для соответствующего файла - + * высылаем его + */ + var lResponse = CloudServer.Responses[pName]; + var lStatus = CloudServer.Statuses[pName]; + + if(lResponse){ + lResponse.writeHead( + lStatus, + pHead); + + lResponse.end(pData); + + console.log(pName+' sended'); + } +}; + +/* function sets stdout to file log.txt */ +CloudServer.writeLogsToFile = function(){ + var stdo = require('fs').createWriteStream('./log.txt'); + + process.stdout.write = (function(write) { + return function(string, encoding, fd) { + stdo.write(string); + }; + })(process.stdout.write); +}; + CloudServer.start(); \ No newline at end of file