diff --git a/services/web/app/coffee/Features/Authentication/AuthenticationManager.coffee b/services/web/app/coffee/Features/Authentication/AuthenticationManager.coffee
index 254c5c6c41..d815b426fe 100644
--- a/services/web/app/coffee/Features/Authentication/AuthenticationManager.coffee
+++ b/services/web/app/coffee/Features/Authentication/AuthenticationManager.coffee
@@ -1,4 +1,3 @@
-Settings = require 'settings-sharelatex'
User = require("../../models/User").User
{db, ObjectId} = require("../../infrastructure/mongojs")
crypto = require 'crypto'
diff --git a/services/web/app/coffee/Features/Blog/BlogController.coffee b/services/web/app/coffee/Features/Blog/BlogController.coffee
index 186d11d348..8f726602c3 100644
--- a/services/web/app/coffee/Features/Blog/BlogController.coffee
+++ b/services/web/app/coffee/Features/Blog/BlogController.coffee
@@ -4,15 +4,13 @@ logger = require("logger-sharelatex")
_ = require("underscore")
ErrorController = require "../Errors/ErrorController"
-extensionsToProxy = [".png", ".xml", ".jpeg", ".json", ".zip", ".eps"]
-
module.exports = BlogController =
getPage: (req, res, next)->
url = req.url?.toLowerCase()
blogUrl = "#{settings.apis.blog.url}#{url}"
- extensionsToProxy = [".png", ".xml", ".jpeg", ".json", ".zip", ".eps"]
+ extensionsToProxy = [".png", ".xml", ".jpeg", ".json", ".zip", ".eps", ".gif"]
shouldProxy = _.find extensionsToProxy, (extension)->
url.indexOf(extension) != -1
diff --git a/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee b/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee
index b19ac582b5..dcf0615b25 100644
--- a/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee
+++ b/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee
@@ -1,6 +1,5 @@
request = require 'request'
request = request.defaults()
-async = require 'async'
settings = require 'settings-sharelatex'
_ = require 'underscore'
async = require 'async'
diff --git a/services/web/app/coffee/Features/Email/EmailBuilder.coffee b/services/web/app/coffee/Features/Email/EmailBuilder.coffee
index b6967cc4a8..e14b9e4582 100644
--- a/services/web/app/coffee/Features/Email/EmailBuilder.coffee
+++ b/services/web/app/coffee/Features/Email/EmailBuilder.coffee
@@ -1,5 +1,4 @@
_ = require('underscore')
-
PersonalEmailLayout = require("./Layouts/PersonalEmailLayout")
NotificationEmailLayout = require("./Layouts/NotificationEmailLayout")
settings = require("settings-sharelatex")
diff --git a/services/web/app/coffee/Features/Email/EmailSender.coffee b/services/web/app/coffee/Features/Email/EmailSender.coffee
index c02d76d0da..c5ff09b1b0 100644
--- a/services/web/app/coffee/Features/Email/EmailSender.coffee
+++ b/services/web/app/coffee/Features/Email/EmailSender.coffee
@@ -1,7 +1,6 @@
logger = require('logger-sharelatex')
metrics = require('../../infrastructure/Metrics')
Settings = require('settings-sharelatex')
-metrics = require("../../infrastructure/Metrics")
nodemailer = require("nodemailer")
sesTransport = require('nodemailer-ses-transport')
_ = require("underscore")
@@ -25,7 +24,6 @@ else if Settings?.email?.parameters?
logger.log "using smtp for email"
- console.log smtp
nm_client = nodemailer.createTransport(smtp)
else
nm_client = client
diff --git a/services/web/app/coffee/Features/FileStore/FileStoreHandler.coffee b/services/web/app/coffee/Features/FileStore/FileStoreHandler.coffee
index 34ab6f8ece..09790cf99f 100644
--- a/services/web/app/coffee/Features/FileStore/FileStoreHandler.coffee
+++ b/services/web/app/coffee/Features/FileStore/FileStoreHandler.coffee
@@ -6,24 +6,32 @@ settings = require("settings-sharelatex")
oneMinInMs = 60 * 1000
fiveMinsInMs = oneMinInMs * 5
-module.exports =
+module.exports = FileStoreHandler =
uploadFileFromDisk: (project_id, file_id, fsPath, callback)->
- logger.log project_id:project_id, file_id:file_id, fsPath:fsPath, "uploading file from disk"
- readStream = fs.createReadStream(fsPath)
- opts =
- method: "post"
- uri: @_buildUrl(project_id, file_id)
- timeout:fiveMinsInMs
- writeStream = request(opts)
- readStream.pipe writeStream
- writeStream.on "end", callback
- readStream.on "error", (err)->
- logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the read stream of uploadFileFromDisk"
- callback err
- writeStream.on "error", (err)->
- logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the write stream of uploadFileFromDisk"
- callback err
+ fs.lstat fsPath, (err, stat)->
+ if err?
+ logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "error stating file"
+ callback(err)
+ if !stat.isFile()
+ logger.log project_id:project_id, file_id:file_id, fsPath:fsPath, "tried to upload symlink, not contining"
+ return callback(new Error("can not upload symlink"))
+
+ logger.log project_id:project_id, file_id:file_id, fsPath:fsPath, "uploading file from disk"
+ readStream = fs.createReadStream(fsPath)
+ opts =
+ method: "post"
+ uri: FileStoreHandler._buildUrl(project_id, file_id)
+ timeout:fiveMinsInMs
+ writeStream = request(opts)
+ readStream.pipe writeStream
+ writeStream.on "end", callback
+ readStream.on "error", (err)->
+ logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the read stream of uploadFileFromDisk"
+ callback err
+ writeStream.on "error", (err)->
+ logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the write stream of uploadFileFromDisk"
+ callback err
getFileStream: (project_id, file_id, query, callback)->
logger.log project_id:project_id, file_id:file_id, query:query, "getting file stream from file store"
diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee
index a2287a5d44..e2e021bd97 100644
--- a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee
+++ b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee
@@ -1,7 +1,6 @@
SecurityManager = require '../../managers/SecurityManager'
SubscriptionHandler = require './SubscriptionHandler'
PlansLocator = require("./PlansLocator")
-SubscriptionFormatters = require("./SubscriptionFormatters")
SubscriptionViewModelBuilder = require('./SubscriptionViewModelBuilder')
LimitationsManager = require("./LimitationsManager")
RecurlyWrapper = require './RecurlyWrapper'
diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee
index e7fdc4f3e3..af8b2414f3 100644
--- a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee
+++ b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee
@@ -1,10 +1,7 @@
SubscriptionGroupHandler = require("./SubscriptionGroupHandler")
logger = require("logger-sharelatex")
SubscriptionLocator = require("./SubscriptionLocator")
-
ErrorsController = require("../Errors/ErrorController")
-settings = require("settings-sharelatex")
-
SubscriptionDomainHandler = require("./SubscriptionDomainHandler")
_ = require("underscore")
@@ -12,7 +9,7 @@ module.exports =
addUserToGroup: (req, res)->
adminUserId = req.session.user._id
- newEmail = req.body.email
+ newEmail = req.body?.email?.toLowerCase()?.trim()
logger.log adminUserId:adminUserId, newEmail:newEmail, "adding user to group subscription"
SubscriptionGroupHandler.addUserToGroup adminUserId, newEmail, (err, user)->
if err?
@@ -90,11 +87,12 @@ module.exports =
logger.log subscription_id:subscription_id, user_id:req?.session?.user?._id, email:email, "starting the completion of joining group"
SubscriptionGroupHandler.processGroupVerification email, subscription_id, req.query?.token, (err)->
if err? and err == "token_not_found"
- res.redirect "/user/subscription/#{subscription_id}/group/invited?expired=true"
+ return res.redirect "/user/subscription/#{subscription_id}/group/invited?expired=true"
else if err?
- res.sendStatus 500
+ return res.sendStatus 500
else
- res.redirect "/user/subscription/#{subscription_id}/group/successful-join"
+ logger.log subscription_id:subscription_id, email:email, "user successful completed join of group subscription"
+ return res.redirect "/user/subscription/#{subscription_id}/group/successful-join"
renderSuccessfulJoinPage: (req, res)->
subscription_id = req.params.subscription_id
diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee
index 4905203282..1f078f6674 100644
--- a/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee
+++ b/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee
@@ -14,9 +14,10 @@ NotificationsBuilder = require("../Notifications/NotificationsBuilder")
module.exports = SubscriptionGroupHandler =
addUserToGroup: (adminUserId, newEmail, callback)->
+ logger.log adminUserId:adminUserId, newEmail:newEmail, "adding user to group"
UserCreator.getUserOrCreateHoldingAccount newEmail, (err, user)->
if err?
- logger.err err:err, "error creating user for holding account"
+ logger.err err:err, adminUserId:adminUserId, newEmail:newEmail, "error creating user for holding account"
return callback(err)
if !user?
msg = "no user returned whenc reating holidng account or getting user"
@@ -24,8 +25,10 @@ module.exports = SubscriptionGroupHandler =
return callback(msg)
LimitationsManager.hasGroupMembersLimitReached adminUserId, (err, limitReached, subscription)->
if err?
+ logger.err err:err, adminUserId:adminUserId, newEmail:newEmail, "error checking if limit reached for group plan"
return callback(err)
if limitReached
+ logger.err adminUserId:adminUserId, newEmail:newEmail, "group subscription limit reached not adding user to group"
return callback(limitReached:limitReached)
SubscriptionUpdater.addUserToGroup adminUserId, user._id, (err)->
if err?
@@ -74,8 +77,8 @@ module.exports = SubscriptionGroupHandler =
EmailHandler.sendEmail "completeJoinGroupAccount", opts, callback
processGroupVerification: (userEmail, subscription_id, token, callback)->
+ logger.log userEmail:userEmail, subscription_id:subscription_id, "processing group verification for user"
OneTimeTokenHandler.getValueFromTokenAndExpire token, (err, token_subscription_id)->
-
if err? or subscription_id != token_subscription_id
logger.err userEmail:userEmail, token:token, "token value not found for processing group verification"
return callback("token_not_found")
@@ -84,7 +87,7 @@ module.exports = SubscriptionGroupHandler =
logger.err err:err, subscription:subscription, userEmail:userEmail, subscription_id:subscription_id, "error getting subscription"
return callback(err)
if !subscription?
- logger.warn subscription_id:subscription_id, "no subscription found"
+ logger.warn subscription_id:subscription_id, userEmail:userEmail, "no subscription found"
return callback()
SubscriptionGroupHandler.addUserToGroup subscription?.admin_id, userEmail, callback
diff --git a/services/web/app/coffee/Features/Subscription/UserFeaturesUpdater.coffee b/services/web/app/coffee/Features/Subscription/UserFeaturesUpdater.coffee
index 83a76d5f8e..c0b691e677 100644
--- a/services/web/app/coffee/Features/Subscription/UserFeaturesUpdater.coffee
+++ b/services/web/app/coffee/Features/Subscription/UserFeaturesUpdater.coffee
@@ -1,4 +1,3 @@
-Settings = require "settings-sharelatex"
logger = require("logger-sharelatex")
User = require('../../models/User').User
PlansLocator = require("./PlansLocator")
diff --git a/services/web/app/coffee/Features/Uploads/ArchiveManager.coffee b/services/web/app/coffee/Features/Uploads/ArchiveManager.coffee
index e666b83f24..a615810fe6 100644
--- a/services/web/app/coffee/Features/Uploads/ArchiveManager.coffee
+++ b/services/web/app/coffee/Features/Uploads/ArchiveManager.coffee
@@ -1,21 +1,23 @@
child = require "child_process"
logger = require "logger-sharelatex"
metrics = require "../../infrastructure/Metrics"
+fs = require "fs"
+Path = require "path"
+_ = require("underscore")
+
+ONE_MEG = 1024 * 1024
module.exports = ArchiveManager =
- extractZipArchive: (source, destination, _callback = (err) ->) ->
- callback = (args...) ->
- _callback(args...)
- _callback = () ->
- timer = new metrics.Timer("unzipDirectory")
- logger.log source: source, destination: destination, "unzipping file"
- unzip = child.spawn("unzip", [source, "-d", destination])
+ _isZipTooLarge: (source, callback = (err, isTooLarge)->)->
+ callback = _.once callback
- # don't remove this line, some zips need
- # us to listen on this for some unknow reason
+ unzip = child.spawn("unzip", ["-l", source])
+
+ output = ""
unzip.stdout.on "data", (d)->
+ output += d
error = null
unzip.stderr.on "data", (chunk) ->
@@ -29,9 +31,80 @@ module.exports = ArchiveManager =
callback(err)
unzip.on "exit", () ->
- timer.done()
if error?
error = new Error(error)
- logger.error err:error, source: source, destination: destination, "error unzipping file"
- callback(error)
+ logger.error err:error, source: source, destination: destination, "error checking zip size"
+
+ lines = output.split("\n")
+ lastLine = lines[lines.length - 2]?.trim()
+ totalSizeInBytes = lastLine?.split(" ")?[0]
+
+ totalSizeInBytes = parseInt(totalSizeInBytes)
+
+ if !totalSizeInBytes? or isNaN(totalSizeInBytes)
+ logger.err source:source, "error getting bytes of zip"
+ return callback(new Error("something went wrong"))
+
+ isTooLarge = totalSizeInBytes > (ONE_MEG * 300)
+
+ callback(error, isTooLarge)
+
+
+
+
+
+ extractZipArchive: (source, destination, _callback = (err) ->) ->
+ callback = (args...) ->
+ _callback(args...)
+ _callback = () ->
+
+ ArchiveManager._isZipTooLarge source, (err, isTooLarge)->
+ if err?
+ logger.err err:err, "error checking size of zip file"
+ return callback(err)
+
+ if isTooLarge
+ return callback(new Error("zip_too_large"))
+
+
+ timer = new metrics.Timer("unzipDirectory")
+ logger.log source: source, destination: destination, "unzipping file"
+
+ unzip = child.spawn("unzip", [source, "-d", destination])
+
+ # don't remove this line, some zips need
+ # us to listen on this for some unknow reason
+ unzip.stdout.on "data", (d)->
+
+ error = null
+ unzip.stderr.on "data", (chunk) ->
+ error ||= ""
+ error += chunk
+
+ unzip.on "error", (err) ->
+ logger.error {err, source, destination}, "unzip failed"
+ if err.code == "ENOENT"
+ logger.error "unzip command not found. Please check the unzip command is installed"
+ callback(err)
+
+ unzip.on "exit", () ->
+ timer.done()
+ if error?
+ error = new Error(error)
+ logger.error err:error, source: source, destination: destination, "error unzipping file"
+ callback(error)
+
+ findTopLevelDirectory: (directory, callback = (error, topLevelDir) ->) ->
+ fs.readdir directory, (error, files) ->
+ return callback(error) if error?
+ if files.length == 1
+ childPath = Path.join(directory, files[0])
+ fs.stat childPath, (error, stat) ->
+ return callback(error) if error?
+ if stat.isDirectory()
+ return callback(null, childPath)
+ else
+ return callback(null, directory)
+ else
+ return callback(null, directory)
diff --git a/services/web/app/coffee/Features/Uploads/FileSystemImportManager.coffee b/services/web/app/coffee/Features/Uploads/FileSystemImportManager.coffee
index 0c297147d1..04c7c3bcc7 100644
--- a/services/web/app/coffee/Features/Uploads/FileSystemImportManager.coffee
+++ b/services/web/app/coffee/Features/Uploads/FileSystemImportManager.coffee
@@ -4,64 +4,108 @@ _ = require "underscore"
FileTypeManager = require "./FileTypeManager"
EditorController = require "../Editor/EditorController"
ProjectLocator = require "../Project/ProjectLocator"
-logger = require "logger-sharelatex"
+logger = require("logger-sharelatex")
module.exports = FileSystemImportManager =
- addDoc: (project_id, folder_id, name, path, replace, callback = (error, doc)-> )->
- fs.readFile path, "utf8", (error, content = "") ->
- return callback(error) if error?
- content = content.replace(/\r/g, "")
- lines = content.split("\n")
- EditorController.addDocWithoutLock project_id, folder_id, name, lines, "upload", callback
-
- addFile: (project_id, folder_id, name, path, replace, callback = (error, file)-> )->
- logger.log project_id:project_id, folder_id:folder_id, name:name, path:path, replace:replace, "adding file from filesystem"
- if replace
- ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) ->
+ addDoc: (user_id, project_id, folder_id, name, path, replace, callback = (error, doc)-> )->
+ FileSystemImportManager._isSafeOnFileSystem path, (err, isSafe)->
+ if !isSafe
+ logger.log user_id:user_id, project_id:project_id, folder_id:folder_id, name:name, path:path, "add doc is from symlink, stopping process"
+ return callback("path is symlink")
+ fs.readFile path, "utf8", (error, content = "") ->
return callback(error) if error?
- return callback(new Error("Couldn't find folder")) if !folder?
- existingFile = null
- for fileRef in folder.fileRefs
- if fileRef.name == name
- existingFile = fileRef
- break
- if existingFile?
- EditorController.replaceFile project_id, existingFile._id, path, "upload", callback
- else
- EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", callback
- else
- EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", callback
-
- addFolder: (project_id, folder_id, name, path, replace, callback = (error)-> ) ->
- EditorController.addFolderWithoutLock project_id, folder_id, name, "upload", (error, new_folder) =>
- return callback(error) if error?
- @addFolderContents project_id, new_folder._id, path, replace, (error) ->
- return callback(error) if error?
- callback null, new_folder
-
- addFolderContents: (project_id, parent_folder_id, folderPath, replace, callback = (error)-> ) ->
- fs.readdir folderPath, (error, entries = []) =>
- return callback(error) if error?
- jobs = _.map entries, (entry) =>
- (callback) =>
- FileTypeManager.shouldIgnore entry, (error, ignore) =>
+ content = content.replace(/\r/g, "")
+ lines = content.split("\n")
+ if replace
+ ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) ->
return callback(error) if error?
- if !ignore
- @addEntity project_id, parent_folder_id, entry, "#{folderPath}/#{entry}", replace, callback
+ return callback(new Error("Couldn't find folder")) if !folder?
+ existingDoc = null
+ for doc in folder.docs
+ if doc.name == name
+ existingDoc = doc
+ break
+ if existingDoc?
+ EditorController.setDoc project_id, existingDoc._id, user_id, lines, "upload", callback
else
- callback()
- async.parallelLimit jobs, 5, callback
+ EditorController.addDocWithoutLock project_id, folder_id, name, lines, "upload", callback
+ else
+ EditorController.addDocWithoutLock project_id, folder_id, name, lines, "upload", callback
- addEntity: (project_id, folder_id, name, path, replace, callback = (error, entity)-> ) ->
- FileTypeManager.isDirectory path, (error, isDirectory) =>
- return callback(error) if error?
- if isDirectory
- @addFolder project_id, folder_id, name, path, replace, callback
+ addFile: (user_id, project_id, folder_id, name, path, replace, callback = (error, file)-> )->
+ FileSystemImportManager._isSafeOnFileSystem path, (err, isSafe)->
+ if !isSafe
+ logger.log user_id:user_id, project_id:project_id, folder_id:folder_id, name:name, path:path, "add file is from symlink, stopping insert"
+ return callback("path is symlink")
+
+ if !replace
+ EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", callback
else
- FileTypeManager.isBinary name, path, (error, isBinary) =>
+ ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) ->
return callback(error) if error?
- if isBinary
- @addFile project_id, folder_id, name, path, replace, callback
+ return callback(new Error("Couldn't find folder")) if !folder?
+ existingFile = null
+ for fileRef in folder.fileRefs
+ if fileRef.name == name
+ existingFile = fileRef
+ break
+ if existingFile?
+ EditorController.replaceFile project_id, existingFile._id, path, "upload", callback
else
- @addDoc project_id, folder_id, name, path, replace, callback
+ EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", callback
+
+ addFolder: (user_id, project_id, folder_id, name, path, replace, callback = (error)-> ) ->
+ FileSystemImportManager._isSafeOnFileSystem path, (err, isSafe)->
+ if !isSafe
+ logger.log user_id:user_id, project_id:project_id, folder_id:folder_id, path:path, "add folder is from symlink, stopping insert"
+ return callback("path is symlink")
+ EditorController.addFolderWithoutLock project_id, folder_id, name, "upload", (error, new_folder) =>
+ return callback(error) if error?
+ FileSystemImportManager.addFolderContents user_id, project_id, new_folder._id, path, replace, (error) ->
+ return callback(error) if error?
+ callback null, new_folder
+
+ addFolderContents: (user_id, project_id, parent_folder_id, folderPath, replace, callback = (error)-> ) ->
+ FileSystemImportManager._isSafeOnFileSystem folderPath, (err, isSafe)->
+ if !isSafe
+ logger.log user_id:user_id, project_id:project_id, parent_folder_id:parent_folder_id, folderPath:folderPath, "add folder contents is from symlink, stopping insert"
+ return callback("path is symlink")
+ fs.readdir folderPath, (error, entries = []) =>
+ return callback(error) if error?
+ jobs = _.map entries, (entry) =>
+ (callback) =>
+ FileTypeManager.shouldIgnore entry, (error, ignore) =>
+ return callback(error) if error?
+ if !ignore
+ FileSystemImportManager.addEntity user_id, project_id, parent_folder_id, entry, "#{folderPath}/#{entry}", replace, callback
+ else
+ callback()
+ async.parallelLimit jobs, 5, callback
+
+ addEntity: (user_id, project_id, folder_id, name, path, replace, callback = (error, entity)-> ) ->
+ FileSystemImportManager._isSafeOnFileSystem path, (err, isSafe)->
+ if !isSafe
+ logger.log user_id:user_id, project_id:project_id, folder_id:folder_id, path:path, "add entry is from symlink, stopping insert"
+ return callback("path is symlink")
+
+ FileTypeManager.isDirectory path, (error, isDirectory) =>
+ return callback(error) if error?
+ if isDirectory
+ FileSystemImportManager.addFolder user_id, project_id, folder_id, name, path, replace, callback
+ else
+ FileTypeManager.isBinary name, path, (error, isBinary) =>
+ return callback(error) if error?
+ if isBinary
+ FileSystemImportManager.addFile user_id, project_id, folder_id, name, path, replace, callback
+ else
+ FileSystemImportManager.addDoc user_id, project_id, folder_id, name, path, replace, callback
+
+
+ _isSafeOnFileSystem: (path, callback = (err, isSafe)->)->
+ fs.lstat path, (err, stat)->
+ if err?
+ logger.err err:err, "error with path symlink check"
+ return callback(err)
+ isSafe = stat.isFile() or stat.isDirectory()
+ callback(err, isSafe)
diff --git a/services/web/app/coffee/Features/Uploads/ProjectUploadController.coffee b/services/web/app/coffee/Features/Uploads/ProjectUploadController.coffee
index 36fbde4ebe..b7cab7ac5c 100644
--- a/services/web/app/coffee/Features/Uploads/ProjectUploadController.coffee
+++ b/services/web/app/coffee/Features/Uploads/ProjectUploadController.coffee
@@ -35,7 +35,8 @@ module.exports = ProjectUploadController =
logger.err project_id:project_id, name:name, "bad name when trying to upload file"
return res.send success: false
logger.log folder_id:folder_id, project_id:project_id, "getting upload file request"
- FileSystemImportManager.addEntity project_id, folder_id, name, path, true, (error, entity) ->
+ user_id = req.session.user._id
+ FileSystemImportManager.addEntity user_id, project_id, folder_id, name, path, true, (error, entity) ->
fs.unlink path, ->
timer.done()
if error?
diff --git a/services/web/app/coffee/Features/Uploads/ProjectUploadManager.coffee b/services/web/app/coffee/Features/Uploads/ProjectUploadManager.coffee
index b5d108d21d..127c6bc94f 100644
--- a/services/web/app/coffee/Features/Uploads/ProjectUploadManager.coffee
+++ b/services/web/app/coffee/Features/Uploads/ProjectUploadManager.coffee
@@ -9,19 +9,21 @@ module.exports = ProjectUploadHandler =
createProjectFromZipArchive: (owner_id, name, zipPath, callback = (error, project) ->) ->
ProjectCreationHandler.createBlankProject owner_id, name, (error, project) =>
return callback(error) if error?
- @insertZipArchiveIntoFolder project._id, project.rootFolder[0]._id, zipPath, (error) ->
+ @insertZipArchiveIntoFolder owner_id, project._id, project.rootFolder[0]._id, zipPath, (error) ->
return callback(error) if error?
ProjectRootDocManager.setRootDocAutomatically project._id, (error) ->
return callback(error) if error?
callback(error, project)
- insertZipArchiveIntoFolder: (project_id, folder_id, path, callback = (error) ->) ->
+ insertZipArchiveIntoFolder: (owner_id, project_id, folder_id, path, callback = (error) ->) ->
destination = @_getDestinationDirectory path
ArchiveManager.extractZipArchive path, destination, (error) ->
return callback(error) if error?
- FileSystemImportManager.addFolderContents project_id, folder_id, destination, false, (error) ->
+ ArchiveManager.findTopLevelDirectory destination, (error, topLevelDestination) ->
return callback(error) if error?
- rimraf(destination, callback)
+ FileSystemImportManager.addFolderContents owner_id, project_id, folder_id, topLevelDestination, false, (error) ->
+ return callback(error) if error?
+ rimraf(destination, callback)
_getDestinationDirectory: (source) ->
return path.join(path.dirname(source), "#{path.basename(source, ".zip")}-#{Date.now()}")
diff --git a/services/web/app/coffee/Features/User/UserController.coffee b/services/web/app/coffee/Features/User/UserController.coffee
index f0ccbde89d..451729d7e9 100644
--- a/services/web/app/coffee/Features/User/UserController.coffee
+++ b/services/web/app/coffee/Features/User/UserController.coffee
@@ -1,3 +1,4 @@
+UserHandler = require("./UserHandler")
UserDeleter = require("./UserDeleter")
UserLocator = require("./UserLocator")
User = require("../../models/User").User
@@ -10,7 +11,7 @@ AuthenticationManager = require("../Authentication/AuthenticationManager")
UserUpdater = require("./UserUpdater")
settings = require "settings-sharelatex"
-module.exports =
+module.exports = UserController =
deleteUser: (req, res)->
user_id = req.session.user._id
@@ -67,7 +68,14 @@ module.exports =
else
message = req.i18n.translate("problem_changing_email_address")
return res.send 500, {message:message}
- res.sendStatus(200)
+ User.findById user_id, (err, user)->
+ if err?
+ logger.err err:err, user_id:user_id, "error getting user for email update"
+ return res.send 500
+ UserHandler.populateGroupLicenceInvite user, (err)-> #need to refresh this in the background
+ if err?
+ logger.err err:err, "error populateGroupLicenceInvite"
+ res.sendStatus(200)
logout : (req, res)->
metrics.inc "user.logout"
diff --git a/services/web/app/coffee/Features/User/UserHandler.coffee b/services/web/app/coffee/Features/User/UserHandler.coffee
index 81b94d834f..8af78573d6 100644
--- a/services/web/app/coffee/Features/User/UserHandler.coffee
+++ b/services/web/app/coffee/Features/User/UserHandler.coffee
@@ -6,7 +6,8 @@ logger = require("logger-sharelatex")
module.exports = UserHandler =
- _populateGroupLicenceInvite: (user, callback)->
+ populateGroupLicenceInvite: (user, callback)->
+ logger.log user_id:user._id, "populating any potential group licence invites"
licence = SubscriptionDomainHandler.getLicenceUserCanJoin user
if !licence?
return callback()
@@ -21,5 +22,5 @@ module.exports = UserHandler =
NotificationsBuilder.groupPlan(user, licence).create(callback)
setupLoginData: (user, callback = ->)->
- @_populateGroupLicenceInvite user, callback
+ @populateGroupLicenceInvite user, callback
diff --git a/services/web/app/views/project/editor.jade b/services/web/app/views/project/editor.jade
index b30301c0cc..e666724b09 100644
--- a/services/web/app/views/project/editor.jade
+++ b/services/web/app/views/project/editor.jade
@@ -53,7 +53,13 @@ block content
include ./editor/share
- #ide-body(ng-cloak, layout="main", ng-hide="state.loading", resize-on="layout:chat:resize")
+ #ide-body(
+ ng-cloak,
+ layout="main",
+ ng-hide="state.loading",
+ resize-on="layout:chat:resize",
+ minimum-restore-size-west="130"
+ )
.ui-layout-west
include ./editor/file-tree
diff --git a/services/web/app/views/project/editor/editor.jade b/services/web/app/views/project/editor/editor.jade
index 2241eb3cd2..f79f6e3c19 100644
--- a/services/web/app/views/project/editor/editor.jade
+++ b/services/web/app/views/project/editor/editor.jade
@@ -6,6 +6,7 @@ div.full-size(
resize-on="layout:main:resize"
resize-proportionally="true"
initial-size-east="'50%'"
+ minimum-restore-size-east="300"
)
.ui-layout-center
.loading-panel(ng-show="!editor.sharejs_doc || editor.opening")
@@ -19,6 +20,7 @@ div.full-size(
keybindings="settings.mode",
font-size="settings.fontSize",
auto-complete="settings.autoComplete",
+ spell-check="true",
spell-check-language="project.spellCheckLanguage",
highlights="onlineUserCursorHighlights[editor.open_doc_id]"
show-print-margin="false",
diff --git a/services/web/app/views/project/editor/file-tree.jade b/services/web/app/views/project/editor/file-tree.jade
index 7399ea7a0e..bb07ee9e88 100644
--- a/services/web/app/views/project/editor/file-tree.jade
+++ b/services/web/app/views/project/editor/file-tree.jade
@@ -46,26 +46,24 @@ aside#file-tree(ng-controller="FileTreeController", ng-class="{ 'multi-selected'
ng-controller="FileTreeRootFolderController",
ng-class="{ 'no-toolbar': !permissions.write }"
)
-
- div(ng-show="ui.pdfLayout == 'flat' && (ui.view == 'editor' || ui.view == 'pdf' || ui.view == 'file')")
- ul.list-unstyled.file-tree-list
- li(
- ng-class="{ 'selected': ui.view == 'pdf' }"
- ng-controller="PdfViewToggleController"
- )
- .entity
- .entity-name(
- ng-click="togglePdfView()"
- )
- i.fa.fa-fw.toggle
- i.fa.fa-fw.fa-file-pdf-o
- | PDF
-
ul.list-unstyled.file-tree-list(
droppable="permissions.write"
accept=".entity-name"
on-drop-callback="onDrop"
)
+ li(
+ ng-show="ui.pdfLayout == 'flat' && (ui.view == 'editor' || ui.view == 'pdf' || ui.view == 'file')"
+ ng-class="{ 'selected': ui.view == 'pdf' }"
+ ng-controller="PdfViewToggleController"
+ )
+ .entity
+ .entity-name(
+ ng-click="togglePdfView()"
+ )
+ i.fa.fa-fw.toggle
+ i.fa.fa-fw.fa-file-pdf-o
+ | PDF
+
file-entity(
entity="entity",
permissions="permissions",
@@ -364,6 +362,17 @@ script(type="text/ng-template", id="uploadFileModalTemplate")
.alert.alert-warning.small.modal-alert(ng-if="tooManyFiles") #{translate("maximum_files_uploaded_together", {max:"{{max_files}}"})}
.alert.alert-warning.small.modal-alert(ng-if="rateLimitHit") #{translate("too_many_files_uploaded_throttled_short_period")}
.alert.alert-warning.small.modal-alert(ng-if="notLoggedIn") #{translate("session_expired_redirecting_to_login", {seconds:"{{secondsToRedirect}}"})}
+ .alert.alert-warning.small.modal-alert(ng-if="conflicts.length > 0")
+ p.text-center
+ | The following files already exist in this project:
+ ul.text-center.list-unstyled.row-spaced-small
+ li(ng-repeat="conflict in conflicts"): strong {{ conflict }}
+ p.text-center.row-spaced-small
+ | Do you want to overwrite them?
+ p.text-center
+ a(href, ng-click="doUpload()").btn.btn-primary Overwrite
+ |
+ a(href, ng-click="cancel()").btn.btn-default Cancel
.modal-body(
fine-upload
@@ -374,10 +383,14 @@ script(type="text/ng-template", id="uploadFileModalTemplate")
drag-area-text="{{drag_files}}"
hint-text="{{hint_press_and_hold_control_key}}"
multiple="true"
+ auto-upload="false"
on-complete-callback="onComplete"
on-upload-callback="onUpload"
on-validate-batch="onValidateBatch"
on-error-callback="onError"
+ on-submit-callback="onSubmit"
+ on-cancel-callback="onCancel"
+ control="control"
params="{'folder_id': parent_folder_id}"
)
span #{translate("upload_files")}
diff --git a/services/web/app/views/project/editor/share.jade b/services/web/app/views/project/editor/share.jade
index 32bac2628e..dad1d42108 100644
--- a/services/web/app/views/project/editor/share.jade
+++ b/services/web/app/views/project/editor/share.jade
@@ -125,7 +125,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
span.text-danger.error(ng-show="state.error") #{translate("generic_something_went_wrong")}
button.btn.btn-primary(
ng-click="done()"
- ) #{translate("done")}
+ ) #{translate("close")}
script(type="text/ng-template", id="makePublicModalTemplate")
.modal-header
diff --git a/services/web/public/coffee/directives/fineUpload.coffee b/services/web/public/coffee/directives/fineUpload.coffee
index 9856da5076..92cdf720f4 100644
--- a/services/web/public/coffee/directives/fineUpload.coffee
+++ b/services/web/public/coffee/directives/fineUpload.coffee
@@ -16,7 +16,11 @@ define [
onUploadCallback: "="
onValidateBatch: "="
onErrorCallback: "="
+ onSubmitCallback: "="
+ onCancelCallback: "="
+ autoUpload: "="
params: "="
+ control: "="
}
link: (scope, element, attrs) ->
multiple = scope.multiple or false
@@ -37,12 +41,19 @@ define [
onUpload = scope.onUploadCallback or () ->
onError = scope.onErrorCallback or () ->
onValidateBatch = scope.onValidateBatch or () ->
+ onSubmit = scope.onSubmitCallback or () ->
+ onCancel = scope.onCancelCallback or () ->
+ if !scope.autoUpload?
+ autoUpload = true
+ else
+ autoUpload = scope.autoUpload
params = scope.params or {}
params._csrf = window.csrfToken
q = new qq.FineUploader
element: element[0]
multiple: multiple
+ autoUpload: autoUpload
disabledCancelForFormUploads: true
validation: validation
maxConnections: maxConnections
@@ -56,6 +67,8 @@ define [
onUpload: onUpload
onValidateBatch: onValidateBatch
onError: onError
+ onSubmit: onSubmit
+ onCancel: onCancel
text: text
template: """
@@ -70,5 +83,7 @@ define [
"""
+ window.q = q
+ scope.control?.q = q
return q
}
\ No newline at end of file
diff --git a/services/web/public/coffee/ide/directives/layout.coffee b/services/web/public/coffee/ide/directives/layout.coffee
index 3421b0a613..146952dc23 100644
--- a/services/web/public/coffee/ide/directives/layout.coffee
+++ b/services/web/public/coffee/ide/directives/layout.coffee
@@ -37,8 +37,12 @@ define [
# Restore previously recorded state
if (state = ide.localStorage("layout.#{name}"))?
- options.west = state.west
- options.east = state.east
+ if state.east?
+ if !attrs.minimumRestoreSizeEast? or (state.east.size >= attrs.minimumRestoreSizeEast and !state.east.initClosed)
+ options.east = state.east
+ if state.west?
+ if !attrs.minimumRestoreSizeWest? or (state.west.size >= attrs.minimumRestoreSizeWest and !state.west.initClosed)
+ options.west = state.west
repositionControls = () ->
state = element.layout().readState()
diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee
index ba9d69be47..596f350812 100644
--- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee
+++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee
@@ -18,7 +18,7 @@ define [
url = ace.config._moduleUrl(args...) + "?fingerprint=#{window.aceFingerprint}"
return url
- App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage) ->
+ App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory) ->
monkeyPatchSearch($rootScope, $compile)
return {
@@ -29,6 +29,7 @@ define [
fontSize: "="
autoComplete: "="
sharejsDoc: "="
+ spellCheck: "="
spellCheckLanguage: "="
highlights: "="
text: "="
@@ -55,7 +56,9 @@ define [
scope.name = attrs.aceEditor
autoCompleteManager = new AutoCompleteManager(scope, editor, element)
- spellCheckManager = new SpellCheckManager(scope, editor, element)
+ if scope.spellCheck # only enable spellcheck when explicitly required
+ spellCheckCache = $cacheFactory("spellCheck-#{scope.name}", {capacity: 1000})
+ spellCheckManager = new SpellCheckManager(scope, editor, element, spellCheckCache)
undoManager = new UndoManager(scope, editor, element)
highlightsManager = new HighlightsManager(scope, editor, element)
cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage)
diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
index a8afdeaa44..7b4889a81e 100644
--- a/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
+++ b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
@@ -66,12 +66,13 @@ define [
}
if references.keys and references.keys.length > 0
references.keys.forEach (key) ->
- result.push({
- caption: "\\#{commandName}{#{previousArgsCaption}#{key}",
- value: "\\#{commandName}{#{previousArgs}#{key}",
- meta: "reference",
- score: 10000
- })
+ if !(key in [null, undefined])
+ result.push({
+ caption: "\\#{commandName}{#{previousArgsCaption}#{key}",
+ value: "\\#{commandName}{#{previousArgs}#{key}",
+ meta: "reference",
+ score: 10000
+ })
callback null, result
else
callback null, result
diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee
index 68d45ac6e8..95a6519d59 100644
--- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee
+++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee
@@ -5,7 +5,7 @@ define [
Range = ace.require("ace/range").Range
class SpellCheckManager
- constructor: (@$scope, @editor, @element) ->
+ constructor: (@$scope, @editor, @element, @cache) ->
$(document.body).append @element.find(".spell-check-menu")
@updatedLines = []
@@ -102,6 +102,8 @@ define [
learnWord: (highlight) ->
@apiRequest "/learn", word: highlight.word
@highlightedWordManager.removeWord highlight.word
+ language = @$scope.spellCheckLanguage
+ @cache?.put("#{language}:#{highlight.word}", true)
getHighlightedWordAtCursor: () ->
cursor = @editor.getCursorPosition()
@@ -143,24 +145,67 @@ define [
runSpellCheck: (linesToProcess) ->
{words, positions} = @getWords(linesToProcess)
language = @$scope.spellCheckLanguage
- @apiRequest "/check", {language: language, words: words}, (error, result) =>
- if error? or !result? or !result.misspellings?
- return null
+ highlights = []
+ seen = {}
+ newWords = []
+ newPositions = []
+
+ # iterate through all words, building up a list of
+ # newWords/newPositions not in the cache
+ for word, i in words
+ key = "#{language}:#{word}"
+ seen[key] ?= @cache.get(key) # avoid hitting the cache unnecessarily
+ cached = seen[key]
+ if not cached?
+ newWords.push words[i]
+ newPositions.push positions[i]
+ else if cached is true
+ # word is correct
+ else
+ highlights.push
+ column: positions[i].column
+ row: positions[i].row
+ word: word
+ suggestions: cached
+ words = newWords
+ positions = newPositions
+
+ displayResult = (highlights) =>
if linesToProcess?
for shouldProcess, row in linesToProcess
@highlightedWordManager.clearRows(row, row) if shouldProcess
else
@highlightedWordManager.clearRows()
+ for highlight in highlights
+ @highlightedWordManager.addHighlight highlight
- for misspelling in result.misspellings
- word = words[misspelling.index]
- position = positions[misspelling.index]
- @highlightedWordManager.addHighlight
- column: position.column
- row: position.row
- word: word
- suggestions: misspelling.suggestions
+ if not words.length
+ displayResult highlights
+ else
+ @apiRequest "/check", {language: language, words: words}, (error, result) =>
+ if error? or !result? or !result.misspellings?
+ return null
+ mispelled = []
+ for misspelling in result.misspellings
+ word = words[misspelling.index]
+ position = positions[misspelling.index]
+ mispelled[misspelling.index] = true
+ highlights.push
+ column: position.column
+ row: position.row
+ word: word
+ suggestions: misspelling.suggestions
+ key = "#{language}:#{word}"
+ if not seen[key]
+ @cache.put key, misspelling.suggestions
+ seen[key] = true
+ for word, i in words when not mispelled[i]
+ key = "#{language}:#{word}"
+ if not seen[key]
+ @cache.put(key, true)
+ seen[key] = true
+ displayResult highlights
getWords: (linesToProcess) ->
lines = @editor.getValue().split("\n")
diff --git a/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee b/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee
index c93ed4f4c0..8c49d54c23 100644
--- a/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee
+++ b/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee
@@ -135,6 +135,12 @@ define [
multiSelectSelectedEntity: () ->
@findSelectedEntity()?.multiSelected = true
+ existsInFolder: (folder_id, name) ->
+ folder = @findEntityById(folder_id)
+ return false if !folder?
+ entity = @_findEntityByPathInFolder(folder, name)
+ return entity?
+
findSelectedEntity: () ->
selected = null
@forEachEntity (entity) ->
diff --git a/services/web/public/coffee/ide/file-tree/controllers/FileTreeController.coffee b/services/web/public/coffee/ide/file-tree/controllers/FileTreeController.coffee
index 996ad1f490..cdc261053f 100644
--- a/services/web/public/coffee/ide/file-tree/controllers/FileTreeController.coffee
+++ b/services/web/public/coffee/ide/file-tree/controllers/FileTreeController.coffee
@@ -110,7 +110,8 @@ define [
$scope.rateLimitHit = false
$scope.secondsToRedirect = 10
$scope.notLoggedIn = false
-
+ $scope.conflicts = []
+ $scope.control = {}
needToLogBackIn = ->
$scope.notLoggedIn = true
@@ -125,11 +126,6 @@ define [
decreseTimeout()
-
- uploadCount = 0
- $scope.onUpload = () ->
- uploadCount++
-
$scope.max_files = 40
$scope.onComplete = (error, name, response) ->
$timeout (() ->
@@ -154,6 +150,34 @@ define [
else if reason.indexOf("403") != -1
needToLogBackIn()
+ _uploadTimer = null
+ uploadIfNoConflicts = () ->
+ if $scope.conflicts.length == 0
+ $scope.doUpload()
+
+ uploadCount = 0
+ $scope.onSubmit = (id, name) ->
+ uploadCount++
+ if ide.fileTreeManager.existsInFolder($scope.parent_folder_id, name)
+ $scope.conflicts.push name
+ $scope.$apply()
+ if !_uploadTimer?
+ _uploadTimer = setTimeout () ->
+ _uploadTimer = null
+ uploadIfNoConflicts()
+ , 0
+ return true
+
+ $scope.onCancel = (id, name) ->
+ uploadCount--
+ index = $scope.conflicts.indexOf(name)
+ if index > -1
+ $scope.conflicts.splice(index, 1)
+ $scope.$apply()
+ uploadIfNoConflicts()
+
+ $scope.doUpload = () ->
+ $scope.control?.q?.uploadStoredFiles()
$scope.cancel = () ->
$modalInstance.dismiss('cancel')
diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
index 1d834e4399..8586d218a7 100644
--- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -1,7 +1,8 @@
define [
"base"
"libs/latex-log-parser"
-], (App, LogParser) ->
+ "libs/bib-log-parser"
+], (App, LogParser, BibLogParser) ->
App.controller "PdfController", ($scope, $http, ide, $modal, synctex, event_tracking, localStorage) ->
autoCompile = true
$scope.$on "project:joined", () ->
@@ -12,7 +13,7 @@ define [
$scope.$on "pdf:error:display", () ->
$scope.pdf.error = true
-
+
$scope.draft = localStorage("draft:#{$scope.project_id}") or false
$scope.$watch "draft", (new_value, old_value) ->
if new_value? and old_value != new_value
@@ -82,25 +83,42 @@ define [
qs = if outputFile?.build? then "?build=#{outputFile.build}" else ""
$http.get "/project/#{$scope.project_id}/output/output.log" + qs
.success (log) ->
+ #console.log ">>", log
$scope.pdf.rawLog = log
logEntries = LogParser.parse(log, ignoreDuplicates: true)
+ #console.log ">>", logEntries
$scope.pdf.logEntries = logEntries
$scope.pdf.logEntries.all = logEntries.errors.concat(logEntries.warnings).concat(logEntries.typesetting)
-
- $scope.pdf.logEntryAnnotations = {}
- for entry in logEntries.all
- if entry.file?
- entry.file = normalizeFilePath(entry.file)
-
- entity = ide.fileTreeManager.findEntityByPath(entry.file)
- if entity?
- $scope.pdf.logEntryAnnotations[entity.id] ||= []
- $scope.pdf.logEntryAnnotations[entity.id].push {
- row: entry.line - 1
- type: if entry.level == "error" then "error" else "warning"
- text: entry.message
- }
-
+ # # # #
+ proceed = () ->
+ $scope.pdf.logEntryAnnotations = {}
+ for entry in logEntries.all
+ if entry.file?
+ entry.file = normalizeFilePath(entry.file)
+ entity = ide.fileTreeManager.findEntityByPath(entry.file)
+ if entity?
+ $scope.pdf.logEntryAnnotations[entity.id] ||= []
+ $scope.pdf.logEntryAnnotations[entity.id].push {
+ row: entry.line - 1
+ type: if entry.level == "error" then "error" else "warning"
+ text: entry.message
+ }
+ # Get the biber log and parse it too
+ $http.get "/project/#{$scope.project_id}/output/output.blg" + qs
+ .success (log) ->
+ window._s = $scope
+ biberLogEntries = BibLogParser.parse(log, {})
+ if $scope.pdf.logEntries
+ entries = $scope.pdf.logEntries
+ all = biberLogEntries.errors.concat(biberLogEntries.warnings)
+ entries.all = entries.all.concat(all)
+ entries.errors = entries.errors.concat(biberLogEntries.errors)
+ entries.warnings = entries.warnings.concat(biberLogEntries.warnings)
+ proceed()
+ .error (e) ->
+ console.error ">> error", e
+ proceed()
+ # # # #
.error () ->
$scope.pdf.logEntries = []
$scope.pdf.rawLog = ""
@@ -127,7 +145,7 @@ define [
$scope.recompile = (options = {}) ->
return if $scope.pdf.compiling
$scope.pdf.compiling = true
-
+
ide.$scope.$broadcast("flush-changes")
options.rootDocOverride_id = getRootDocOverride_id()
@@ -140,7 +158,7 @@ define [
.error () ->
$scope.pdf.compiling = false
$scope.pdf.error = true
-
+
# This needs to be public.
ide.$scope.recompile = $scope.recompile
@@ -177,17 +195,17 @@ define [
.then (data) ->
{doc, line} = data
ide.editorManager.openDoc(doc, gotoLine: line)
-
+
$scope.switchToFlatLayout = () ->
$scope.ui.pdfLayout = 'flat'
$scope.ui.view = 'pdf'
ide.localStorage "pdf.layout", "flat"
-
+
$scope.switchToSideBySideLayout = () ->
$scope.ui.pdfLayout = 'sideBySide'
$scope.ui.view = 'editor'
localStorage "pdf.layout", "split"
-
+
if pdfLayout = localStorage("pdf.layout")
$scope.switchToSideBySideLayout() if pdfLayout == "split"
$scope.switchToFlatLayout() if pdfLayout == "flat"
@@ -216,7 +234,7 @@ define [
if !path?
deferred.reject()
return deferred.promise
-
+
# If the root file is folder/main.tex, then synctex sees the
# path as folder/./main.tex
rootDocDirname = ide.fileTreeManager.getRootDocDirname()
@@ -226,7 +244,7 @@ define [
{row, column} = cursorPosition
$http({
- url: "/project/#{ide.project_id}/sync/code",
+ url: "/project/#{ide.project_id}/sync/code",
method: "GET",
params: {
file: path
@@ -253,7 +271,7 @@ define [
position.offset.top = position.offset.top + 80
$http({
- url: "/project/#{ide.project_id}/sync/pdf",
+ url: "/project/#{ide.project_id}/sync/pdf",
method: "GET",
params: {
page: position.page + 1
@@ -316,4 +334,4 @@ define [
$scope.cancel = () ->
$modalInstance.dismiss('cancel')
- ]
\ No newline at end of file
+ ]
diff --git a/services/web/public/coffee/ide/settings/controllers/ProjectNameController.coffee b/services/web/public/coffee/ide/settings/controllers/ProjectNameController.coffee
index 01f441b603..6195feff13 100644
--- a/services/web/public/coffee/ide/settings/controllers/ProjectNameController.coffee
+++ b/services/web/public/coffee/ide/settings/controllers/ProjectNameController.coffee
@@ -1,6 +1,7 @@
define [
"base"
], (App) ->
+ MAX_PROJECT_NAME_LENGTH = 150
App.controller "ProjectNameController", ["$scope", "settings", "ide", ($scope, settings, ide) ->
$scope.state =
renaming: false
@@ -12,11 +13,14 @@ define [
$scope.$emit "project:rename:start"
$scope.finishRenaming = () ->
- newName = $scope.inputs.name
- if newName.length < 150
- $scope.project.name = newName
- settings.saveProjectSettings({name: $scope.project.name})
$scope.state.renaming = false
+ newName = $scope.inputs.name
+ if !newName? or newName.length == 0 or newName.length > MAX_PROJECT_NAME_LENGTH
+ return
+ if $scope.project.name == newName
+ return
+ $scope.project.name = newName
+ settings.saveProjectSettings({name: $scope.project.name})
ide.socket.on "projectNameUpdated", (name) ->
$scope.$apply () ->
diff --git a/services/web/public/coffee/main/project-list/project-list.coffee b/services/web/public/coffee/main/project-list/project-list.coffee
index 4c4c1daf03..a528c0b96f 100644
--- a/services/web/public/coffee/main/project-list/project-list.coffee
+++ b/services/web/public/coffee/main/project-list/project-list.coffee
@@ -257,9 +257,11 @@ define [
modalInstance.result.then (project_id) ->
window.location = "/project/#{project_id}"
+ MAX_PROJECT_NAME_LENGTH = 150
$scope.renameProject = (project, newName) ->
- if newName.length < 150
- project.name = newName
+ if !newName? or newName.length == 0 or newName.length > MAX_PROJECT_NAME_LENGTH
+ return
+ project.name = newName
queuedHttp.post "/project/#{project.id}/rename", {
newProjectName: project.name
_csrf: window.csrfToken
diff --git a/services/web/public/coffee/main/universties-site.coffee b/services/web/public/coffee/main/universties-site.coffee
index 5826404f01..c62f4adc50 100644
--- a/services/web/public/coffee/main/universties-site.coffee
+++ b/services/web/public/coffee/main/universties-site.coffee
@@ -12,12 +12,13 @@ define [
console.log "email not set"
return
$scope.sending = true
+ ticketNumber = Math.floor((1 + Math.random()) * 0x10000).toString(32)
params =
name: $scope.form.name || $scope.form.email
email: $scope.form.email
labels: $scope.form.source
message: "Please contact me with more details"
- subject: $scope.form.subject
+ subject: $scope.form.subject + " - [#{ticketNumber}]"
about : "#{$scope.form.position || ''} #{$scope.form.university || ''}"
Groove.createTicket params, (err, json)->
diff --git a/services/web/public/js/libs/bib-log-parser.js b/services/web/public/js/libs/bib-log-parser.js
new file mode 100644
index 0000000000..f726eb6c52
--- /dev/null
+++ b/services/web/public/js/libs/bib-log-parser.js
@@ -0,0 +1,190 @@
+// Generated by CoffeeScript 1.10.0
+define(function() {
+ var BAD_CROSS_REFERENCE_REGEX, BibLogParser, LINE_SPLITTER_REGEX, MESSAGE_LEVELS, MULTILINE_COMMAND_ERROR_REGEX, MULTILINE_ERROR_REGEX, MULTILINE_WARNING_REGEX, SINGLELINE_WARNING_REGEX, consume, errorParsers, warningParsers;
+ LINE_SPLITTER_REGEX = /^\[(\d+)].*>\s(INFO|WARN|ERROR)\s-\s(.*)$/;
+ MESSAGE_LEVELS = {
+ "INFO": "info",
+ "WARN": "warning",
+ "ERROR": "error"
+ };
+ BibLogParser = function(text, options) {
+ if (typeof text !== 'string') {
+ throw new Error("BibLogParser Error: text parameter must be a string");
+ }
+ this.text = text.replace(/(\r\n)|\r/g, '\n');
+ this.options = options || {};
+ this.lines = text.split('\n');
+ };
+ consume = function(logText, regex, process) {
+ var iterationCount, match, newEntry, re, result, text;
+ text = logText;
+ result = [];
+ re = regex;
+ iterationCount = 0;
+ while (match = re.exec(text)) {
+ iterationCount += 1;
+ if (iterationCount >= 10000) {
+ return result;
+ }
+ newEntry = process(match);
+ result.push(newEntry);
+ text = (match.input.slice(0, match.index)) + (match.input.slice(match.index + match[0].length + 1, match.input.length));
+ }
+ return [result, text];
+ };
+ MULTILINE_WARNING_REGEX = /^Warning--(.+)\n--line (\d+) of file (.+)$/m;
+ SINGLELINE_WARNING_REGEX = /^Warning--(.+)$/m;
+ MULTILINE_ERROR_REGEX = /^(.*)---line (\d+) of file (.*)\n([^]+?)\nI'm skipping whatever remains of this entry$/m;
+ BAD_CROSS_REFERENCE_REGEX = /^(A bad cross reference---entry ".+?"\nrefers to entry.+?, which doesn't exist)$/m;
+ MULTILINE_COMMAND_ERROR_REGEX = /^(.*)\n---line (\d+) of file (.*)\n([^]+?)\nI'm skipping whatever remains of this command$/m;
+ warningParsers = [
+ [
+ MULTILINE_WARNING_REGEX, function(match) {
+ var fileName, fullMatch, lineNumber, message;
+ fullMatch = match[0], message = match[1], lineNumber = match[2], fileName = match[3];
+ return {
+ file: fileName,
+ level: "warning",
+ message: message,
+ line: lineNumber,
+ raw: fullMatch
+ };
+ }
+ ], [
+ SINGLELINE_WARNING_REGEX, function(match) {
+ var fullMatch, message;
+ fullMatch = match[0], message = match[1];
+ return {
+ file: '',
+ level: "warning",
+ message: message,
+ line: '',
+ raw: fullMatch
+ };
+ }
+ ]
+ ];
+ errorParsers = [
+ [
+ MULTILINE_ERROR_REGEX, function(match) {
+ var fileName, firstMessage, fullMatch, lineNumber, secondMessage;
+ fullMatch = match[0], firstMessage = match[1], lineNumber = match[2], fileName = match[3], secondMessage = match[4];
+ return {
+ file: fileName,
+ level: "error",
+ message: firstMessage + '\n' + secondMessage,
+ line: lineNumber,
+ raw: fullMatch
+ };
+ }
+ ], [
+ BAD_CROSS_REFERENCE_REGEX, function(match) {
+ var fullMatch, message;
+ fullMatch = match[0], message = match[1];
+ return {
+ file: '',
+ level: "error",
+ message: message,
+ line: '',
+ raw: fullMatch
+ };
+ }
+ ], [
+ MULTILINE_COMMAND_ERROR_REGEX, function(match) {
+ var fileName, firstMessage, fullMatch, lineNumber, secondMessage;
+ fullMatch = match[0], firstMessage = match[1], lineNumber = match[2], fileName = match[3], secondMessage = match[4];
+ return {
+ file: fileName,
+ level: "error",
+ message: firstMessage + '\n' + secondMessage,
+ line: lineNumber,
+ raw: fullMatch
+ };
+ }
+ ]
+ ];
+ (function() {
+ this.parseBibtex = function() {
+ var allErrors, allWarnings, ref, ref1, remainingText, result;
+ result = {
+ all: [],
+ errors: [],
+ warnings: [],
+ files: [],
+ typesetting: []
+ };
+ ref = warningParsers.reduce(function(accumulator, parser) {
+ var _remainingText, currentWarnings, process, ref, regex, text, warnings;
+ currentWarnings = accumulator[0], text = accumulator[1];
+ regex = parser[0], process = parser[1];
+ ref = consume(text, regex, process), warnings = ref[0], _remainingText = ref[1];
+ return [currentWarnings.concat(warnings), _remainingText];
+ }, [[], this.text]), allWarnings = ref[0], remainingText = ref[1];
+ ref1 = errorParsers.reduce(function(accumulator, parser) {
+ var _remainingText, currentErrors, errors, process, ref1, regex, text;
+ currentErrors = accumulator[0], text = accumulator[1];
+ regex = parser[0], process = parser[1];
+ ref1 = consume(text, regex, process), errors = ref1[0], _remainingText = ref1[1];
+ return [currentErrors.concat(errors), _remainingText];
+ }, [[], remainingText]), allErrors = ref1[0], remainingText = ref1[1];
+ result.warnings = allWarnings;
+ result.errors = allErrors;
+ result.all = allWarnings.concat(allErrors);
+ return result;
+ };
+ this.parseBiber = function() {
+ var result;
+ result = {
+ all: [],
+ errors: [],
+ warnings: [],
+ files: [],
+ typesetting: []
+ };
+ this.lines.forEach(function(line) {
+ var _, fileName, fullLine, lineMatch, lineNumber, match, message, messageType, newEntry, realMessage;
+ match = line.match(LINE_SPLITTER_REGEX);
+ if (match) {
+ fullLine = match[0], lineNumber = match[1], messageType = match[2], message = match[3];
+ newEntry = {
+ file: '',
+ level: MESSAGE_LEVELS[messageType] || "INFO",
+ message: message,
+ line: '',
+ raw: fullLine
+ };
+ lineMatch = newEntry.message.match(/^BibTeX subsystem: \/.+\/(\w+\.\w+)_.+, line (\d+), (.+)$/);
+ if (lineMatch && lineMatch.length === 4) {
+ _ = lineMatch[0], fileName = lineMatch[1], lineNumber = lineMatch[2], realMessage = lineMatch[3];
+ newEntry.file = fileName;
+ newEntry.line = lineNumber;
+ newEntry.message = realMessage;
+ }
+ result.all.push(newEntry);
+ switch (newEntry.level) {
+ case 'error':
+ return result.errors.push(newEntry);
+ case 'warning':
+ return result.warnings.push(newEntry);
+ }
+ }
+ });
+ return result;
+ };
+ return this.parse = function() {
+ var firstLine;
+ firstLine = this.lines[0];
+ if (firstLine.match(/^.*INFO - This is Biber.*$/)) {
+ return this.parseBiber();
+ } else if (firstLine.match(/^This is BibTeX, Version.+$/)) {
+ return this.parseBibtex();
+ } else {
+ throw new Error("BibLogParser Error: cannot determine whether text is biber or bibtex output");
+ }
+ };
+ }).call(BibLogParser.prototype);
+ BibLogParser.parse = function(text, options) {
+ return new BibLogParser(text, options).parse();
+ };
+ return BibLogParser;
+});
diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less
index eea5ee5e77..153ea19fca 100644
--- a/services/web/public/stylesheets/app/editor.less
+++ b/services/web/public/stylesheets/app/editor.less
@@ -109,6 +109,15 @@
}
a.rename {
visibility: hidden;
+ display: inline-block;
+ color: @gray-light;
+ padding: 5px;
+ border-radius: @border-radius-small;
+ &:hover {
+ text-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+ color: @gray-dark;
+ text-decoration: none;
+ }
}
&:hover {
a.rename {
@@ -243,6 +252,20 @@
margin-bottom:0px;
}
+.sl_references_search_hint {
+ position: absolute;
+ bottom: -22px;
+ left: -1px;
+ right: 0px;
+ text-align: center;
+ padding: 2px;
+ background: rgb(202, 214, 250);
+ border: 1px solid lightgray;
+ box-shadow: 3px 3px 5px rgba(0,0,0,.2);
+ span {
+ color: black;
+ }
+}
// -- References Search Modal --
.references-search-modal-backdrop {
// don't grey out the editor when the
@@ -291,7 +314,6 @@
}
// search result items list
.search-results {
- margin-top: 14px;
font-size: 12px;
.no-results-message {
font-size: 16px;
@@ -312,6 +334,9 @@
.hit-year.small {
color: white;
}
+ .hit-journal.small {
+ color: white;
+ }
}
.hit-title {
font-size: 1.3em;
diff --git a/services/web/public/stylesheets/app/editor/toolbar.less b/services/web/public/stylesheets/app/editor/toolbar.less
index bc1976764f..f83135972f 100644
--- a/services/web/public/stylesheets/app/editor/toolbar.less
+++ b/services/web/public/stylesheets/app/editor/toolbar.less
@@ -95,15 +95,16 @@
&.toolbar-small {
height: 32px;
> a, .toolbar-right > a {
- padding: 4px 2px 2px;
+ padding: 2px 4px 1px 4px;
margin: 0;
+ margin-top: 2px;
}
> a {
- margin-left: 6px;
+ margin-left: 2px;
}
.toolbar-right > a {
margin-left: 0;
- margin-right: 6px;
+ margin-right: 2px;
}
}
diff --git a/services/web/test/UnitTests/coffee/DocumentUpdater/DocumentUpdaterHandlerTests.coffee b/services/web/test/UnitTests/coffee/DocumentUpdater/DocumentUpdaterHandlerTests.coffee
index ea585fdb83..aaae05219b 100644
--- a/services/web/test/UnitTests/coffee/DocumentUpdater/DocumentUpdaterHandlerTests.coffee
+++ b/services/web/test/UnitTests/coffee/DocumentUpdater/DocumentUpdaterHandlerTests.coffee
@@ -8,7 +8,7 @@ path = require 'path'
_ = require 'underscore'
modulePath = path.join __dirname, '../../../../app/js/Features/DocumentUpdater/DocumentUpdaterHandler'
-describe 'Flushing documents :', ->
+describe 'DocumentUpdaterHandler - Flushing documents :', ->
beforeEach ->
@project_id = "project-id-923"
@@ -33,6 +33,9 @@ describe 'Flushing documents :', ->
"../../models/Project": Project: @Project={}
'../../Features/Project/ProjectLocator':{}
'redis-sharelatex' : createClient: () => @rclient
+ "../../infrastructure/Metrics":
+ Timer:->
+ done:->
describe 'queueChange', ->
beforeEach ->
diff --git a/services/web/test/UnitTests/coffee/Editor/EditorRealTimeControllerTests.coffee b/services/web/test/UnitTests/coffee/Editor/EditorRealTimeControllerTests.coffee
index 49c60a6ccf..bbad9b1ae5 100644
--- a/services/web/test/UnitTests/coffee/Editor/EditorRealTimeControllerTests.coffee
+++ b/services/web/test/UnitTests/coffee/Editor/EditorRealTimeControllerTests.coffee
@@ -10,6 +10,7 @@ describe "EditorRealTimeController", ->
createClient: () ->
auth:->
"../../infrastructure/Server" : io: @io = {}
+ "settings-sharelatex":{redis:{}}
@EditorRealTimeController.rclientPub = publish: sinon.stub()
@EditorRealTimeController.rclientSub =
subscribe: sinon.stub()
diff --git a/services/web/test/UnitTests/coffee/Email/EmailBuilderTests.coffee b/services/web/test/UnitTests/coffee/Email/EmailBuilderTests.coffee
index f9688c4a84..02865f7eea 100644
--- a/services/web/test/UnitTests/coffee/Email/EmailBuilderTests.coffee
+++ b/services/web/test/UnitTests/coffee/Email/EmailBuilderTests.coffee
@@ -9,7 +9,7 @@ _ = require('underscore')
_.templateSettings =
interpolate: /\{\{(.+?)\}\}/g
-describe "Email Templator ", ->
+describe "EmailBuilder", ->
beforeEach ->
diff --git a/services/web/test/UnitTests/coffee/Email/EmailSenderTests.coffee b/services/web/test/UnitTests/coffee/Email/EmailSenderTests.coffee
index 555e9c1a1e..5f49b6f599 100644
--- a/services/web/test/UnitTests/coffee/Email/EmailSenderTests.coffee
+++ b/services/web/test/UnitTests/coffee/Email/EmailSenderTests.coffee
@@ -6,7 +6,7 @@ sinon = require('sinon')
modulePath = path.join __dirname, "../../../../app/js/Features/Email/EmailSender.js"
expect = require("chai").expect
-describe "Email", ->
+describe "EmailSender", ->
beforeEach ->
@@ -30,6 +30,9 @@ describe "Email", ->
log:->
warn:->
err:->
+ "../../infrastructure/Metrics": inc:->
+
+
@opts =
to: "bob@bob.com"
diff --git a/services/web/test/UnitTests/coffee/FileStore/FileStoreHandlerTests.coffee b/services/web/test/UnitTests/coffee/FileStore/FileStoreHandlerTests.coffee
index 2a82ba944f..8f9d8a555e 100644
--- a/services/web/test/UnitTests/coffee/FileStore/FileStoreHandlerTests.coffee
+++ b/services/web/test/UnitTests/coffee/FileStore/FileStoreHandlerTests.coffee
@@ -10,6 +10,10 @@ describe "FileStoreHandler", ->
beforeEach ->
@fs =
createReadStream : sinon.stub()
+ lstat: sinon.stub().callsArgWith(1, null, {
+ isFile:=> @isSafeOnFileSystem
+ isDirectory:-> return false
+ })
@writeStream =
my:"writeStream"
on: (type, cb)->
@@ -31,6 +35,7 @@ describe "FileStoreHandler", ->
describe "uploadFileFromDisk", ->
beforeEach ->
@request.returns(@writeStream)
+ @isSafeOnFileSystem = true
it "should create read stream", (done)->
@fs.createReadStream.returns
@@ -74,6 +79,13 @@ describe "FileStoreHandler", ->
@handler._buildUrl.calledWith(@project_id, @file_id).should.equal true
done()
+ describe "symlink", ->
+ it "should not read file if it is symlink", (done)->
+ @isSafeOnFileSystem = false
+ @handler.uploadFileFromDisk @project_id, @file_id, @fsPath, =>
+ @fs.createReadStream.called.should.equal false
+ done()
+
describe "deleteFile", ->
it "should send a delete request to filestore api", (done)->
diff --git a/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee b/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee
index 3ecb31ba68..0867fb821f 100644
--- a/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee
+++ b/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee
@@ -55,6 +55,10 @@ describe "ProjectController", ->
"logger-sharelatex":
log:->
err:->
+ "../../infrastructure/Metrics":
+ Timer:->
+ done:->
+ inc:->
"./ProjectDeleter": @ProjectDeleter
"./ProjectDuplicator": @ProjectDuplicator
"./ProjectCreationHandler": @ProjectCreationHandler
diff --git a/services/web/test/UnitTests/coffee/Project/ProjectCreationHandlerTests.coffee b/services/web/test/UnitTests/coffee/Project/ProjectCreationHandlerTests.coffee
index 5ce1d5687d..38a86d1eff 100644
--- a/services/web/test/UnitTests/coffee/Project/ProjectCreationHandlerTests.coffee
+++ b/services/web/test/UnitTests/coffee/Project/ProjectCreationHandlerTests.coffee
@@ -50,6 +50,9 @@ describe 'ProjectCreationHandler', ->
'./ProjectEntityHandler':@ProjectEntityHandler
"settings-sharelatex": @Settings = {}
'logger-sharelatex': {log:->}
+ "../../infrastructure/Metrics": inc:->
+
+
describe 'Creating a Blank project', ->
beforeEach ->
diff --git a/services/web/test/UnitTests/coffee/Project/ProjectEntityHandlerTests.coffee b/services/web/test/UnitTests/coffee/Project/ProjectEntityHandlerTests.coffee
index b669ce29cd..a44a16c10b 100644
--- a/services/web/test/UnitTests/coffee/Project/ProjectEntityHandlerTests.coffee
+++ b/services/web/test/UnitTests/coffee/Project/ProjectEntityHandlerTests.coffee
@@ -64,6 +64,8 @@ describe 'ProjectEntityHandler', ->
@projectUpdater = markAsUpdated:sinon.stub()
@projectLocator =
findElement : sinon.stub()
+ @settings =
+ maxEntitiesPerProject:200
@ProjectEntityHandler = SandboxedModule.require modulePath, requires:
'../../models/Project': Project:@ProjectModel
'../../models/Doc': Doc:@DocModel
@@ -77,6 +79,7 @@ describe 'ProjectEntityHandler', ->
'logger-sharelatex': @logger = {log:sinon.stub(), error: sinon.stub(), err:->}
'./ProjectUpdateHandler': @projectUpdater
"./ProjectGetter": @ProjectGetter
+ "settings-sharelatex":@settings
describe 'mkdirp', ->
diff --git a/services/web/test/UnitTests/coffee/Security/LoginRateLimiterTests.coffee b/services/web/test/UnitTests/coffee/Security/LoginRateLimiterTests.coffee
index 57d044d524..2c4dc59262 100644
--- a/services/web/test/UnitTests/coffee/Security/LoginRateLimiterTests.coffee
+++ b/services/web/test/UnitTests/coffee/Security/LoginRateLimiterTests.coffee
@@ -26,6 +26,7 @@ describe "LoginRateLimiter", ->
@LoginRateLimiter = SandboxedModule.require modulePath, requires:
'redis-sharelatex' : createClient: () => @rclient
+ "settings-sharelatex":{redis:{}}
describe "processLoginRequest", ->
diff --git a/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupControllerTests.coffee b/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupControllerTests.coffee
index dbaf751c70..455c441239 100644
--- a/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupControllerTests.coffee
+++ b/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupControllerTests.coffee
@@ -54,11 +54,11 @@ describe "SubscriptionGroupController", ->
describe "addUserToGroup", ->
it "should use the admin id for the logged in user and take the email address from the body", (done)->
- newEmail = "31231"
+ newEmail = " boB@gmaiL.com "
@req.body = email: newEmail
res =
json : (data)=>
- @GroupHandler.addUserToGroup.calledWith(@adminUserId, newEmail).should.equal true
+ @GroupHandler.addUserToGroup.calledWith(@adminUserId, "bob@gmail.com").should.equal true
data.user.should.deep.equal @user
done()
@Controller.addUserToGroup @req, res
diff --git a/services/web/test/UnitTests/coffee/Subscription/UserFeaturesUpdaterTests.coffee b/services/web/test/UnitTests/coffee/Subscription/UserFeaturesUpdaterTests.coffee
index b286574a80..d388d67c3b 100644
--- a/services/web/test/UnitTests/coffee/Subscription/UserFeaturesUpdaterTests.coffee
+++ b/services/web/test/UnitTests/coffee/Subscription/UserFeaturesUpdaterTests.coffee
@@ -5,7 +5,7 @@ modulePath = "../../../../app/js/Features/Subscription/UserFeaturesUpdater"
assert = require("chai").assert
-describe "user Features updater", ->
+describe "UserFeaturesUpdater", ->
beforeEach ->
diff --git a/services/web/test/UnitTests/coffee/ThirdPartyDataStore/TpdsControllerTests.coffee b/services/web/test/UnitTests/coffee/ThirdPartyDataStore/TpdsControllerTests.coffee
index 5c538b21c9..fef915423e 100644
--- a/services/web/test/UnitTests/coffee/ThirdPartyDataStore/TpdsControllerTests.coffee
+++ b/services/web/test/UnitTests/coffee/ThirdPartyDataStore/TpdsControllerTests.coffee
@@ -14,6 +14,8 @@ describe 'TpdsController', ->
'logger-sharelatex':
log:->
err:->
+ "../../infrastructure/Metrics": inc:->
+
@user_id = "dsad29jlkjas"
describe 'getting an update', ->
diff --git a/services/web/test/UnitTests/coffee/ThirdPartyDataStore/TpdsUpdateSenderTests.coffee b/services/web/test/UnitTests/coffee/ThirdPartyDataStore/TpdsUpdateSenderTests.coffee
index cd84080d17..bb434c8fb6 100644
--- a/services/web/test/UnitTests/coffee/ThirdPartyDataStore/TpdsUpdateSenderTests.coffee
+++ b/services/web/test/UnitTests/coffee/ThirdPartyDataStore/TpdsUpdateSenderTests.coffee
@@ -38,6 +38,8 @@ describe 'TpdsUpdateSender', ->
"logger-sharelatex":{log:->}
'../../models/Project': Project:@Project
'request':@request
+ "../../infrastructure/Metrics":
+ inc:->
describe "_enqueue", ->
diff --git a/services/web/test/UnitTests/coffee/ThirdPartyDataStore/UpdateMergerTests.coffee b/services/web/test/UnitTests/coffee/ThirdPartyDataStore/UpdateMergerTests.coffee
index 6f16ce354c..f4cd157115 100644
--- a/services/web/test/UnitTests/coffee/ThirdPartyDataStore/UpdateMergerTests.coffee
+++ b/services/web/test/UnitTests/coffee/ThirdPartyDataStore/UpdateMergerTests.coffee
@@ -20,9 +20,13 @@ describe 'UpdateMerger :', ->
'../Project/ProjectEntityHandler': @projectEntityHandler
'fs': @fs
'../Uploads/FileTypeManager':@FileTypeManager
+ 'settings-sharelatex':{path:{dumpPath:"dump_here"}}
'logger-sharelatex':
log: ->
err: ->
+ "../../infrastructure/Metrics":
+ Timer:->
+ done:->
@project_id = "project_id_here"
@user_id = "mock-user-id"
@source = "dropbox"
diff --git a/services/web/test/UnitTests/coffee/Uploads/ArchiveManagerTests.coffee b/services/web/test/UnitTests/coffee/Uploads/ArchiveManagerTests.coffee
index d1f72a780e..89b14c78c9 100644
--- a/services/web/test/UnitTests/coffee/Uploads/ArchiveManagerTests.coffee
+++ b/services/web/test/UnitTests/coffee/Uploads/ArchiveManagerTests.coffee
@@ -1,4 +1,5 @@
sinon = require('sinon')
+expect = require("chai").expect
chai = require('chai')
should = chai.should()
modulePath = "../../../../app/js/Features/Uploads/ArchiveManager.js"
@@ -9,12 +10,16 @@ describe "ArchiveManager", ->
beforeEach ->
@logger =
error: sinon.stub()
+ err:->
log: sinon.stub()
@process = new events.EventEmitter
@process.stdout = new events.EventEmitter
@process.stderr = new events.EventEmitter
+
@child =
spawn: sinon.stub().returns(@process)
+
+
@metrics =
Timer: class Timer
done: sinon.stub()
@@ -22,12 +27,14 @@ describe "ArchiveManager", ->
"child_process": @child
"logger-sharelatex": @logger
"../../infrastructure/Metrics": @metrics
+ "fs": @fs = {}
describe "extractZipArchive", ->
beforeEach ->
@source = "/path/to/zip/source.zip"
@destination = "/path/to/zip/destination"
@callback = sinon.stub()
+ @ArchiveManager._isZipTooLarge = sinon.stub().callsArgWith(1, null, false)
describe "successfully", ->
beforeEach (done) ->
@@ -57,6 +64,19 @@ describe "ArchiveManager", ->
it "should log out the error", ->
@logger.error.called.should.equal true
+ describe "with a zip that is too large", ->
+ beforeEach (done) ->
+ @ArchiveManager._isZipTooLarge = sinon.stub().callsArgWith(1, null, true)
+ @ArchiveManager.extractZipArchive @source, @destination, (error) =>
+ @callback(error)
+ done()
+
+ it "should return the callback with an error", ->
+ @callback.calledWithExactly(new Error("zip_too_large")).should.equal true
+
+ it "should not call spawn", ->
+ @child.spawn.called.should.equal false
+
describe "with an error on the process", ->
beforeEach (done) ->
@ArchiveManager.extractZipArchive @source, @destination, (error) =>
@@ -69,4 +89,96 @@ describe "ArchiveManager", ->
it "should log out the error", ->
@logger.error.called.should.equal true
+
+ describe "_isZipTooLarge", ->
+ beforeEach ->
+ @output = (totalSize)->" Length Date Time Name \n-------- ---- ---- ---- \n241 03-12-16 12:20 main.tex \n108801 03-12-16 12:20 ddd/x1J5kHh.jpg \n-------- ------- \n#{totalSize} 2 files\n"
+
+ it "should return false with small output", (done)->
+ @ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
+ isTooLarge.should.equal false
+ done()
+ @process.stdout.emit "data", @output("109042")
+ @process.emit "exit"
+
+ it "should return true with large bytes", (done)->
+ @ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
+ isTooLarge.should.equal true
+ done()
+ @process.stdout.emit "data", @output("1090000000000000042")
+ @process.emit "exit"
+
+ it "should return error on no data", (done)->
+ @ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
+ expect(error).to.exist
+ done()
+ @process.stdout.emit "data", ""
+ @process.emit "exit"
+
+ it "should return error if it didn't get a number", (done)->
+ @ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
+ expect(error).to.exist
+ done()
+ @process.stdout.emit "data", @output("total_size_string")
+ @process.emit "exit"
+
+ it "should return error if the is only a bit of data", (done)->
+ @ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
+ expect(error).to.exist
+ done()
+ @process.stdout.emit "data", " Length Date Time Name \n--------"
+ @process.emit "exit"
+
+ describe "findTopLevelDirectory", ->
+ beforeEach ->
+ @fs.readdir = sinon.stub()
+ @fs.stat = sinon.stub()
+ @directory = "test/directory"
+
+ describe "with multiple files", ->
+ beforeEach ->
+ @fs.readdir.callsArgWith(1, null, ["multiple", "files"])
+ @ArchiveManager.findTopLevelDirectory(@directory, @callback)
+
+ it "should find the files in the directory", ->
+ @fs.readdir
+ .calledWith(@directory)
+ .should.equal true
+
+ it "should return the original directory", ->
+ @callback
+ .calledWith(null, @directory)
+ .should.equal true
+
+ describe "with a single file (not folder)", ->
+ beforeEach ->
+ @fs.readdir.callsArgWith(1, null, ["foo.tex"])
+ @fs.stat.callsArgWith(1, null, { isDirectory: () -> false })
+ @ArchiveManager.findTopLevelDirectory(@directory, @callback)
+
+ it "should check if the file is a directory", ->
+ @fs.stat
+ .calledWith(@directory + "/foo.tex")
+ .should.equal true
+
+ it "should return the original directory", ->
+ @callback
+ .calledWith(null, @directory)
+ .should.equal true
+
+ describe "with a single top-level folder", ->
+ beforeEach ->
+ @fs.readdir.callsArgWith(1, null, ["folder"])
+ @fs.stat.callsArgWith(1, null, { isDirectory: () -> true })
+ @ArchiveManager.findTopLevelDirectory(@directory, @callback)
+
+ it "should check if the file is a directory", ->
+ @fs.stat
+ .calledWith(@directory + "/folder")
+ .should.equal true
+
+ it "should return the child directory", ->
+ @callback
+ .calledWith(null, @directory + "/folder")
+ .should.equal true
diff --git a/services/web/test/UnitTests/coffee/Uploads/FileSystemImportManagerTests.coffee b/services/web/test/UnitTests/coffee/Uploads/FileSystemImportManagerTests.coffee
index fa385f7261..33013be91b 100644
--- a/services/web/test/UnitTests/coffee/Uploads/FileSystemImportManagerTests.coffee
+++ b/services/web/test/UnitTests/coffee/Uploads/FileSystemImportManagerTests.coffee
@@ -11,48 +11,128 @@ describe "FileSystemImportManager", ->
@name = "test-file.tex"
@path_on_disk = "/path/to/file/#{@name}"
@replace = "replace-boolean-flag-mock"
+ @user_id = "mock-user-123"
+ @callback = sinon.stub()
@FileSystemImportManager = SandboxedModule.require modulePath, requires:
"fs" : @fs = {}
"../Editor/EditorController": @EditorController = {}
"./FileTypeManager": @FileTypeManager = {}
"../Project/ProjectLocator": @ProjectLocator = {}
-
+ "logger-sharelatex":
+ log:->
+ err:->
+
describe "addDoc", ->
beforeEach ->
@docContent = "one\ntwo\nthree"
@docLines = @docContent.split("\n")
@fs.readFile = sinon.stub().callsArgWith(2, null, @docContent)
- @EditorController.addDocWithoutLock = sinon.stub().callsArg(5)
- @FileSystemImportManager.addDoc @project_id, @folder_id, @name, @path_on_disk, false, @callback
+ @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true)
- it "should read the file from disk", ->
- @fs.readFile.calledWith(@path_on_disk, "utf8").should.equal true
- it "should insert the doc", ->
- @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload")
- .should.equal true
+ describe "when path is symlink", ->
+ beforeEach ->
+ @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, false)
+ @EditorController.addDocWithoutLock = sinon.stub()
+ @FileSystemImportManager.addDoc @user_id, @project_id, @folder_id, @name, @path_on_disk, false, @callback
- describe "addDoc with windows line ending", ->
- beforeEach ->
- @docContent = "one\r\ntwo\r\nthree"
- @docLines = ["one", "two", "three"]
- @fs.readFile = sinon.stub().callsArgWith(2, null, @docContent)
- @EditorController.addDocWithoutLock = sinon.stub().callsArg(5)
- @FileSystemImportManager.addDoc @project_id, @folder_id, @name, @path_on_disk, false, @callback
+ it "should not read the file from disk", ->
+ @fs.readFile.called.should.equal false
- it "should strip the \\r characters before adding", ->
- @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload")
- .should.equal true
+ it "should not insert the doc", ->
+ @EditorController.addDocWithoutLock.called.should.equal false
+
+ describe "with replace set to false", ->
+ beforeEach ->
+ @EditorController.addDocWithoutLock = sinon.stub().callsArg(5)
+ @FileSystemImportManager.addDoc @user_id, @project_id, @folder_id, @name, @path_on_disk, false, @callback
+
+ it "should read the file from disk", ->
+ @fs.readFile.calledWith(@path_on_disk, "utf8").should.equal true
+
+ it "should insert the doc", ->
+ @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload")
+ .should.equal true
+
+ describe "with windows line ending", ->
+ beforeEach ->
+ @docContent = "one\r\ntwo\r\nthree"
+ @docLines = ["one", "two", "three"]
+ @fs.readFile = sinon.stub().callsArgWith(2, null, @docContent)
+ @EditorController.addDocWithoutLock = sinon.stub().callsArg(5)
+ @FileSystemImportManager.addDoc @user_id, @project_id, @folder_id, @name, @path_on_disk, false, @callback
+
+ it "should strip the \\r characters before adding", ->
+ @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload")
+ .should.equal true
+
+ describe "with replace set to true", ->
+ describe "when the doc doesn't exist", ->
+ beforeEach ->
+ @folder = {
+ docs: [{
+ _id: "doc-id-2"
+ name: "not-the-right-file.tex"
+ }]
+ }
+ @ProjectLocator.findElement = sinon.stub().callsArgWith(1, null, @folder)
+ @EditorController.addDocWithoutLock = sinon.stub().callsArg(5)
+ @FileSystemImportManager.addDoc @user_id, @project_id, @folder_id, @name, @path_on_disk, true, @callback
+
+ it "should look up the folder", ->
+ @ProjectLocator.findElement
+ .calledWith(project_id: @project_id, element_id: @folder_id, type: "folder")
+ .should.equal true
+
+ it "should insert the doc", ->
+ @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload")
+ .should.equal true
+
+ describe "when the doc does exist", ->
+ beforeEach ->
+ @folder = {
+ docs: [{
+ _id: @doc_id = "doc-id-1"
+ name: @name
+ }, {
+ _id: "doc-id-2"
+ name: "not-the-right-file.tex"
+ }]
+ }
+ @ProjectLocator.findElement = sinon.stub().callsArgWith(1, null, @folder)
+ @EditorController.setDoc = sinon.stub().callsArg(5)
+ @FileSystemImportManager.addDoc @user_id, @project_id, @folder_id, @name, @path_on_disk, true, @callback
+
+ it "should look up the folder", ->
+ @ProjectLocator.findElement
+ .calledWith(project_id: @project_id, element_id: @folder_id, type: "folder")
+ .should.equal true
+
+ it "should set the doc with the new doc lines", ->
+ @EditorController.setDoc.calledWith(@project_id, @doc_id, @user_id, @docLines, "upload")
+ .should.equal true
describe "addFile with replace set to false", ->
beforeEach ->
@EditorController.addFileWithoutLock = sinon.stub().callsArg(5)
- @FileSystemImportManager.addFile @project_id, @folder_id, @name, @path_on_disk, false, @callback
+ @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true)
+ @FileSystemImportManager.addFile @user_id, @project_id, @folder_id, @name, @path_on_disk, false, @callback
it "should add the file", ->
@EditorController.addFileWithoutLock.calledWith(@project_id, @folder_id, @name, @path_on_disk, "upload")
.should.equal true
+ describe "addFile with symlink", ->
+ beforeEach ->
+ @EditorController.addFileWithoutLock = sinon.stub()
+ @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, false)
+ @EditorController.replaceFile = sinon.stub()
+ @FileSystemImportManager.addFile @user_id, @project_id, @folder_id, @name, @path_on_disk, false, @callback
+
+ it "should node add the file", ->
+ @EditorController.addFileWithoutLock.called.should.equal false
+ @EditorController.replaceFile.called.should.equal false
+
describe "addFile with replace set to true", ->
describe "when the file doesn't exist", ->
beforeEach ->
@@ -62,9 +142,10 @@ describe "FileSystemImportManager", ->
name: "not-the-right-file.tex"
}]
}
+ @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true)
@ProjectLocator.findElement = sinon.stub().callsArgWith(1, null, @folder)
@EditorController.addFileWithoutLock = sinon.stub().callsArg(5)
- @FileSystemImportManager.addFile @project_id, @folder_id, @name, @path_on_disk, true, @callback
+ @FileSystemImportManager.addFile @user_id, @project_id, @folder_id, @name, @path_on_disk, true, @callback
it "should look up the folder", ->
@ProjectLocator.findElement
@@ -86,9 +167,10 @@ describe "FileSystemImportManager", ->
name: "not-the-right-file.tex"
}]
}
+ @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true)
@ProjectLocator.findElement = sinon.stub().callsArgWith(1, null, @folder)
@EditorController.replaceFile = sinon.stub().callsArg(4)
- @FileSystemImportManager.addFile @project_id, @folder_id, @name, @path_on_disk, true, @callback
+ @FileSystemImportManager.addFile @user_id, @project_id, @folder_id, @name, @path_on_disk, true, @callback
it "should look up the folder", ->
@ProjectLocator.findElement
@@ -100,38 +182,53 @@ describe "FileSystemImportManager", ->
.should.equal true
describe "addFolder", ->
+
beforeEach ->
@new_folder_id = "new-folder-id"
@EditorController.addFolderWithoutLock = sinon.stub().callsArgWith(4, null, _id: @new_folder_id)
- @FileSystemImportManager.addFolderContents = sinon.stub().callsArg(4)
- @FileSystemImportManager.addFolder @project_id, @folder_id, @name, @path_on_disk, @replace, @callback
+ @FileSystemImportManager.addFolderContents = sinon.stub().callsArg(5)
- it "should add a folder to the project", ->
- @EditorController.addFolderWithoutLock.calledWith(@project_id, @folder_id, @name, "upload")
- .should.equal true
+ describe "successfully", ->
+ beforeEach ->
+ @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true)
+ @FileSystemImportManager.addFolder @user_id, @project_id, @folder_id, @name, @path_on_disk, @replace, @callback
- it "should add the folders contents", ->
- @FileSystemImportManager.addFolderContents.calledWith(@project_id, @new_folder_id, @path_on_disk, @replace)
- .should.equal true
+ it "should add a folder to the project", ->
+ @EditorController.addFolderWithoutLock.calledWith(@project_id, @folder_id, @name, "upload")
+ .should.equal true
+ it "should add the folders contents", ->
+ @FileSystemImportManager.addFolderContents.calledWith(@user_id, @project_id, @new_folder_id, @path_on_disk, @replace)
+ .should.equal true
+
+ describe "with symlink", ->
+ beforeEach ->
+ @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, false)
+ @FileSystemImportManager.addFolder @user_id, @project_id, @folder_id, @name, @path_on_disk, @replace, @callback
+
+ it "should not add a folder to the project", ->
+ @EditorController.addFolderWithoutLock.called.should.equal false
+ @FileSystemImportManager.addFolderContents.called.should.equal false
+
describe "addFolderContents", ->
beforeEach ->
@folderEntries = ["path1", "path2", "path3"]
@ignoredEntries = [".DS_Store"]
@fs.readdir = sinon.stub().callsArgWith(1, null, @folderEntries.concat @ignoredEntries)
- @FileSystemImportManager.addEntity = sinon.stub().callsArg(5)
+ @FileSystemImportManager.addEntity = sinon.stub().callsArg(6)
@FileTypeManager.shouldIgnore = (path, callback) =>
callback null, @ignoredEntries.indexOf(require("path").basename(path)) != -1
- @FileSystemImportManager.addFolderContents @project_id, @folder_id, @path_on_disk, @replace, @callback
+ @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true)
+ @FileSystemImportManager.addFolderContents @user_id, @project_id, @folder_id, @path_on_disk, @replace, @callback
it "should call addEntity for each file in the folder which is not ignored", ->
for name in @folderEntries
- @FileSystemImportManager.addEntity.calledWith(@project_id, @folder_id, name, "#{@path_on_disk}/#{name}", @replace)
+ @FileSystemImportManager.addEntity.calledWith(@user_id, @project_id, @folder_id, name, "#{@path_on_disk}/#{name}", @replace)
.should.equal true
it "should not call addEntity for the ignored files", ->
for name in @ignoredEntries
- @FileSystemImportManager.addEntity.calledWith(@project_id, @folder_id, name, "#{@path_on_disk}/#{name}", @replace)
+ @FileSystemImportManager.addEntity.calledWith(@user_id, @project_id, @folder_id, name, "#{@path_on_disk}/#{name}", @replace)
.should.equal false
it "should look in the correct directory", ->
@@ -141,33 +238,36 @@ describe "FileSystemImportManager", ->
describe "with directory", ->
beforeEach ->
@FileTypeManager.isDirectory = sinon.stub().callsArgWith(1, null, true)
- @FileSystemImportManager.addFolder = sinon.stub().callsArg(5)
- @FileSystemImportManager.addEntity @project_id, @folder_id, @name, @path_on_disk, @replace, @callback
+ @FileSystemImportManager.addFolder = sinon.stub().callsArg(6)
+ @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true)
+ @FileSystemImportManager.addEntity @user_id, @project_id, @folder_id, @name, @path_on_disk, @replace, @callback
it "should call addFolder", ->
- @FileSystemImportManager.addFolder.calledWith(@project_id, @folder_id, @name, @path_on_disk, @replace, @callback)
+ @FileSystemImportManager.addFolder.calledWith(@user_id, @project_id, @folder_id, @name, @path_on_disk, @replace)
.should.equal true
describe "with binary file", ->
beforeEach ->
@FileTypeManager.isDirectory = sinon.stub().callsArgWith(1, null, false)
@FileTypeManager.isBinary = sinon.stub().callsArgWith(2, null, true)
- @FileSystemImportManager.addFile = sinon.stub().callsArg(5)
- @FileSystemImportManager.addEntity @project_id, @folder_id, @name, @path_on_disk, @replace, @callback
+ @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true)
+ @FileSystemImportManager.addFile = sinon.stub().callsArg(6)
+ @FileSystemImportManager.addEntity @user_id, @project_id, @folder_id, @name, @path_on_disk, @replace, @callback
it "should call addFile", ->
- @FileSystemImportManager.addFile.calledWith(@project_id, @folder_id, @name, @path_on_disk, @replace, @callback)
+ @FileSystemImportManager.addFile.calledWith(@user_id, @project_id, @folder_id, @name, @path_on_disk, @replace, @callback)
.should.equal true
describe "with text file", ->
beforeEach ->
@FileTypeManager.isDirectory = sinon.stub().callsArgWith(1, null, false)
@FileTypeManager.isBinary = sinon.stub().callsArgWith(2, null, false)
- @FileSystemImportManager.addDoc = sinon.stub().callsArg(5)
- @FileSystemImportManager.addEntity @project_id, @folder_id, @name, @path_on_disk, @replace, @callback
+ @FileSystemImportManager.addDoc = sinon.stub().callsArg(6)
+ @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true)
+ @FileSystemImportManager.addEntity @user_id, @project_id, @folder_id, @name, @path_on_disk, @replace, @callback
it "should call addFile", ->
- @FileSystemImportManager.addDoc.calledWith(@project_id, @folder_id, @name, @path_on_disk, @replace, @callback)
+ @FileSystemImportManager.addDoc.calledWith(@user_id, @project_id, @folder_id, @name, @path_on_disk, @replace, @callback)
.should.equal true
diff --git a/services/web/test/UnitTests/coffee/Uploads/ProjectUploadControllerTests.coffee b/services/web/test/UnitTests/coffee/Uploads/ProjectUploadControllerTests.coffee
index dd4240d1b8..176ae96d24 100644
--- a/services/web/test/UnitTests/coffee/Uploads/ProjectUploadControllerTests.coffee
+++ b/services/web/test/UnitTests/coffee/Uploads/ProjectUploadControllerTests.coffee
@@ -103,6 +103,9 @@ describe "ProjectUploadController", ->
qqfile:
path: @path
originalname: @name
+ @req.session =
+ user:
+ _id: @user_id
@req.params =
Project_id: @project_id
@req.query =
@@ -115,27 +118,12 @@ describe "ProjectUploadController", ->
beforeEach ->
@entity =
_id : "1234"
- @FileSystemImportManager.addEntity = sinon.stub().callsArgWith(5, null, @entity)
+ @FileSystemImportManager.addEntity = sinon.stub().callsArgWith(6, null, @entity)
@ProjectUploadController.uploadFile @req, @res
- it "should insert the file into the correct project", ->
+ it "should insert the file", ->
@FileSystemImportManager.addEntity
- .calledWith(@project_id)
- .should.equal true
-
- it "should insert the file into the provided folder", ->
- @FileSystemImportManager.addEntity
- .calledWith(sinon.match.any, @folder_id)
- .should.equal true
-
- it "should insert the file with the correct name", ->
- @FileSystemImportManager.addEntity
- .calledWith(sinon.match.any, sinon.match.any, @name)
- .should.equal true
-
- it "should insert the file from the uploaded path", ->
- @FileSystemImportManager.addEntity
- .calledWith(sinon.match.any, sinon.match.any, sinon.match.any, @path)
+ .calledWith(@user_id, @project_id, @folder_id, @name, @path)
.should.equal true
it "should return a successful response to the FileUploader client", ->
@@ -143,7 +131,6 @@ describe "ProjectUploadController", ->
success: true
entity_id: @entity._id
-
it "should output a log line", ->
@logger.log
.calledWith(sinon.match.any, "uploaded file")
@@ -158,7 +145,7 @@ describe "ProjectUploadController", ->
describe "when FileSystemImportManager.addEntity returns an error", ->
beforeEach ->
@FileSystemImportManager.addEntity = sinon.stub()
- .callsArgWith(5, new Error("Sorry something went wrong"))
+ .callsArgWith(6, new Error("Sorry something went wrong"))
@ProjectUploadController.uploadFile @req, @res
it "should return an unsuccessful response to the FileUploader client", ->
diff --git a/services/web/test/UnitTests/coffee/Uploads/ProjectUploadManagerTests.coffee b/services/web/test/UnitTests/coffee/Uploads/ProjectUploadManagerTests.coffee
index 53b7cb2064..d4571f0474 100644
--- a/services/web/test/UnitTests/coffee/Uploads/ProjectUploadManagerTests.coffee
+++ b/services/web/test/UnitTests/coffee/Uploads/ProjectUploadManagerTests.coffee
@@ -8,6 +8,7 @@ describe "ProjectUploadManager", ->
beforeEach ->
@project_id = "project-id-123"
@folder_id = "folder-id-123"
+ @owner_id = "onwer-id-123"
@callback = sinon.stub()
@ProjectUploadManager = SandboxedModule.require modulePath, requires:
"./FileSystemImportManager" : @FileSystemImportManager = {}
@@ -26,7 +27,7 @@ describe "ProjectUploadManager", ->
_id: @project_id
rootFolder: [ _id: @root_folder_id ]
@ProjectCreationHandler.createBlankProject = sinon.stub().callsArgWith(2, null, @project)
- @ProjectUploadManager.insertZipArchiveIntoFolder = sinon.stub().callsArg(3)
+ @ProjectUploadManager.insertZipArchiveIntoFolder = sinon.stub().callsArg(4)
@ProjectRootDocManager.setRootDocAutomatically = sinon.stub().callsArg(1)
@ProjectUploadManager.createProjectFromZipArchive @owner_id, @name, @source, @callback
@@ -45,7 +46,7 @@ describe "ProjectUploadManager", ->
it "should insert the zip file contents into the root folder", ->
@ProjectUploadManager
.insertZipArchiveIntoFolder
- .calledWith(@project_id, @root_folder_id, @source)
+ .calledWith(@owner_id, @project_id, @root_folder_id, @source)
.should.equal true
it "should automatically set the root doc", ->
@@ -63,9 +64,10 @@ describe "ProjectUploadManager", ->
@destination = "/path/to/zile/file-extracted"
@ProjectUploadManager._getDestinationDirectory = sinon.stub().returns @destination
@ArchiveManager.extractZipArchive = sinon.stub().callsArg(2)
- @FileSystemImportManager.addFolderContents = sinon.stub().callsArg(4)
+ @ArchiveManager.findTopLevelDirectory = sinon.stub().callsArgWith(1, null, @topLevelDestination = "/path/to/zip/file-extracted/nested")
+ @FileSystemImportManager.addFolderContents = sinon.stub().callsArg(5)
- @ProjectUploadManager.insertZipArchiveIntoFolder @project_id, @folder_id, @source, @callback
+ @ProjectUploadManager.insertZipArchiveIntoFolder @owner_id, @project_id, @folder_id, @source, @callback
it "should set up the directory to extract the archive to", ->
@ProjectUploadManager._getDestinationDirectory.calledWith(@source).should.equal true
@@ -73,8 +75,11 @@ describe "ProjectUploadManager", ->
it "should extract the archive", ->
@ArchiveManager.extractZipArchive.calledWith(@source, @destination).should.equal true
+ it "should find the top level directory", ->
+ @ArchiveManager.findTopLevelDirectory.calledWith(@destination).should.equal true
+
it "should insert the extracted archive into the folder", ->
- @FileSystemImportManager.addFolderContents.calledWith(@project_id, @folder_id, @destination, false)
+ @FileSystemImportManager.addFolderContents.calledWith(@owner_id, @project_id, @folder_id, @topLevelDestination, false)
.should.equal true
it "should return the callback", ->
diff --git a/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee b/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee
index 5010f91299..829cbf17fb 100644
--- a/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee
+++ b/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee
@@ -42,6 +42,8 @@ describe "UserController", ->
changeEmailAddress:sinon.stub()
@settings =
siteUrl: "sharelatex.example.com"
+ @UserHandler =
+ populateGroupLicenceInvite:sinon.stub().callsArgWith(1)
@UserController = SandboxedModule.require modulePath, requires:
"./UserLocator": @UserLocator
"./UserDeleter": @UserDeleter
@@ -53,9 +55,10 @@ describe "UserController", ->
"../Authentication/AuthenticationManager": @AuthenticationManager
"../Referal/ReferalAllocator":@ReferalAllocator
"../Subscription/SubscriptionDomainHandler":@SubscriptionDomainHandler
+ "./UserHandler":@UserHandler
"settings-sharelatex": @settings
"logger-sharelatex": {log:->}
-
+ "../../infrastructure/Metrics": inc:->
@req =
session:
@@ -151,7 +154,14 @@ describe "UserController", ->
done()
@UserController.updateUserSettings @req, @res
-
+ it "should call populateGroupLicenceInvite", (done)->
+ @req.body.email = @newEmail.toUpperCase()
+ @UserUpdater.changeEmailAddress.callsArgWith(2)
+ @res.sendStatus = (code)=>
+ code.should.equal 200
+ @UserHandler.populateGroupLicenceInvite.calledWith(@user).should.equal true
+ done()
+ @UserController.updateUserSettings @req, @res
describe "logout", ->
@@ -178,7 +188,6 @@ describe "UserController", ->
.should.equal true
it "should return the user and activation url", ->
- console.log @res.json.args
@res.json
.calledWith({
email: @email,
diff --git a/services/web/test/UnitTests/coffee/User/UserHandlerTests.coffee b/services/web/test/UnitTests/coffee/User/UserHandlerTests.coffee
index 8e463c69eb..8bccd1b12c 100644
--- a/services/web/test/UnitTests/coffee/User/UserHandlerTests.coffee
+++ b/services/web/test/UnitTests/coffee/User/UserHandlerTests.coffee
@@ -29,12 +29,12 @@ describe "UserHandler", ->
"../Subscription/SubscriptionDomainHandler":@SubscriptionDomainHandler
"../Subscription/SubscriptionGroupHandler":@SubscriptionGroupHandler
- describe "_populateGroupLicenceInvite", ->
+ describe "populateGroupLicenceInvite", ->
describe "no licence", ->
beforeEach (done)->
@SubscriptionDomainHandler.getLicenceUserCanJoin.returns()
- @UserHandler._populateGroupLicenceInvite @user, done
+ @UserHandler.populateGroupLicenceInvite @user, done
it "should not call NotificationsBuilder", (done)->
@NotificationsBuilder.groupPlan.called.should.equal false
@@ -49,7 +49,7 @@ describe "UserHandler", ->
beforeEach (done)->
@SubscriptionDomainHandler.getLicenceUserCanJoin.returns(@licence)
@SubscriptionGroupHandler.isUserPartOfGroup.callsArgWith(2, null, false)
- @UserHandler._populateGroupLicenceInvite @user, done
+ @UserHandler.populateGroupLicenceInvite @user, done
it "should create notifcation", (done)->
@NotificationsBuilder.groupPlan.calledWith(@user, @licence).should.equal true
@@ -62,7 +62,7 @@ describe "UserHandler", ->
beforeEach (done)->
@SubscriptionDomainHandler.getLicenceUserCanJoin.returns(@licence)
@SubscriptionGroupHandler.isUserPartOfGroup.callsArgWith(2, null, true)
- @UserHandler._populateGroupLicenceInvite @user, done
+ @UserHandler.populateGroupLicenceInvite @user, done
it "should create notifcation", (done)->
@NotificationsBuilder.groupPlan.called.should.equal false
diff --git a/services/web/test/UnitTests/coffee/infrastructure/LockManager/getLockTests.coffee b/services/web/test/UnitTests/coffee/infrastructure/LockManager/getLockTests.coffee
index bb77c4c9dd..a7271a59f6 100644
--- a/services/web/test/UnitTests/coffee/infrastructure/LockManager/getLockTests.coffee
+++ b/services/web/test/UnitTests/coffee/infrastructure/LockManager/getLockTests.coffee
@@ -12,6 +12,9 @@ describe 'LockManager - getting the lock', ->
"redis-sharelatex":
createClient : () =>
auth:->
+ "settings-sharelatex":{redis:{}}
+ "./Metrics": inc:->
+
@callback = sinon.stub()
@doc_id = "doc-id-123"
diff --git a/services/web/test/UnitTests/coffee/infrastructure/LockManager/tryLockTests.coffee b/services/web/test/UnitTests/coffee/infrastructure/LockManager/tryLockTests.coffee
index 3d5a19f265..9f8d680b2d 100644
--- a/services/web/test/UnitTests/coffee/infrastructure/LockManager/tryLockTests.coffee
+++ b/services/web/test/UnitTests/coffee/infrastructure/LockManager/tryLockTests.coffee
@@ -13,6 +13,8 @@ describe 'LockManager - trying the lock', ->
createClient : () =>
auth:->
set: @set = sinon.stub()
+ "settings-sharelatex":{redis:{}}
+ "./Metrics": inc:->
@callback = sinon.stub()
@doc_id = "doc-id-123"