From cb53bd1c151aee9ba0ce6a5c800bedb91a767c7d Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 26 Mar 2020 14:44:37 -0400 Subject: [PATCH 01/18] lint compliance --- lib/commands/pin-rpc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/commands/pin-rpc.js b/lib/commands/pin-rpc.js index 27a8ae28d..f3ade0489 100644 --- a/lib/commands/pin-rpc.js +++ b/lib/commands/pin-rpc.js @@ -2,7 +2,6 @@ const Core = require("./core"); const Pinning = module.exports; -const Nacl = require("tweetnacl/nacl-fast"); const Util = require("../common-util"); const nThen = require("nthen"); From a4c8039cc76dee79fcda10a035b29cb9dda6ffd0 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 26 Mar 2020 14:45:24 -0400 Subject: [PATCH 02/18] improve error handling with rpc response API --- lib/hk-util.js | 17 +++++++---------- www/common/common-util.js | 29 ++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/lib/hk-util.js b/lib/hk-util.js index 799ec547f..c04ac245b 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -772,7 +772,9 @@ HK.initializeIndexWorkers = function (Env, config, _cb) { const workers = []; - const response = Util.response(); + const response = Util.response(function (errLabel, info) { + Env.Log.error('HK_DB_WORKER__' + errLabel, info); + }); const initWorker = function (worker, cb) { //console.log("initializing index worker"); const txid = Util.uid(); @@ -798,14 +800,7 @@ HK.initializeIndexWorkers = function (Env, config, _cb) { return; } //console.log(res); - try { - response.handle(res.txid, [res.error, res.value]); - } catch (err) { - Env.Log.error("INDEX_WORKER", { - error: err, - response: res, - }); - } + response.handle(res.txid, [res.error, res.value]); }); worker.on('exit', function () { var idx = workers.indexOf(worker); @@ -945,7 +940,9 @@ HK.initializeValidationWorkers = function (Env) { workers.push(fork('lib/workers/check-signature.js')); } - const response = Util.response(); + const response = Util.response(function (errLabel, info) { + Env.Log.error('HK_VALIDATE_WORKER__' + errLabel, info); + }); var initWorker = function (worker) { worker.on('message', function (res) { diff --git a/www/common/common-util.js b/www/common/common-util.js index da373f1ad..a1c35307f 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -81,10 +81,16 @@ }); }; - Util.response = function () { + Util.response = function (errorHandler) { var pending = {}; var timeouts = {}; + if (typeof(errorHandler) !== 'function') { + errorHandler = function (label) { + throw new Error(label); + }; + } + var clear = function (id) { clearTimeout(timeouts[id]); delete timeouts[id]; @@ -92,8 +98,8 @@ }; var expect = function (id, fn, ms) { - if (typeof(id) !== 'string') { throw new Error("EXPECTED_STRING"); } - if (typeof(fn) !== 'function') { throw new Error("EXPECTED_CALLBACK"); } + if (typeof(id) !== 'string') { errorHandler('EXPECTED_STRING'); } + if (typeof(fn) !== 'function') { errorHandler('EXPECTED_CALLBACK'); } pending[id] = fn; if (typeof(ms) === 'number' && ms) { timeouts[id] = setTimeout(function () { @@ -105,8 +111,21 @@ var handle = function (id, args) { var fn = pending[id]; - if (typeof(fn) !== 'function') { throw new Error("MISSING_CALLBACK"); } - pending[id].apply(null, Array.isArray(args)? args : [args]); + if (typeof(fn) !== 'function') { + errorHandler("MISSING_CALLBACK", { + id: id, + args: args, + }); + } + try { + pending[id].apply(null, Array.isArray(args)? args : [args]); + } catch (err) { + errorHandler('HANDLER_ERROR', { + error: err, + id: id, + args: args, + }); + } clear(id); }; From 33e8e65507adcc2bf05cce567b6798afc216a8cd Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 26 Mar 2020 15:13:20 -0400 Subject: [PATCH 03/18] handle errors in the server's workers --- lib/hk-util.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/hk-util.js b/lib/hk-util.js index c04ac245b..ad03c8100 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -802,7 +802,9 @@ HK.initializeIndexWorkers = function (Env, config, _cb) { //console.log(res); response.handle(res.txid, [res.error, res.value]); }); - worker.on('exit', function () { + + var substituteWorker = Util.once(function () { + Env.Log.info("SUBSTITUTE_INDEX_WORKER", ''); var idx = workers.indexOf(worker); if (idx !== -1) { workers.splice(idx, 1); @@ -815,6 +817,15 @@ HK.initializeIndexWorkers = function (Env, config, _cb) { workers.push(w); }); }); + + worker.on('exit', substituteWorker); + worker.on('close', substituteWorker); + worker.on('error', function (err) { + substituteWorker(); + Env.log.error("INDEX_WORKER_ERROR", { + error: err, + }); + }); }; var workerIndex = 0; @@ -950,8 +961,9 @@ HK.initializeValidationWorkers = function (Env) { //console.log(+new Date(), "Received verification response"); response.handle(res.txid, [res.error, res.value]); }); - // Spawn a new process in one ends - worker.on('exit', function () { + + var substituteWorker = Util.once( function () { + Env.Log.info("SUBSTITUTE_VALIDATION_WORKER", ''); var idx = workers.indexOf(worker); if (idx !== -1) { workers.splice(idx, 1); @@ -961,6 +973,16 @@ HK.initializeValidationWorkers = function (Env) { workers.push(w); initWorker(w); }); + + // Spawn a new process in one ends + worker.on('exit', substituteWorker); + worker.on('close', substituteWorker); + worker.on('error', function (err) { + substituteWorker(); + Env.Log.error('VALIDATION_WORKER_ERROR', { + error: err, + }); + }); }; workers.forEach(initWorker); From 5f2d7c8dcf098421a0691dd2b03b53c7f2f4eb61 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 26 Mar 2020 15:53:00 -0400 Subject: [PATCH 04/18] increase worker rpc wait time before timeout --- lib/hk-util.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/hk-util.js b/lib/hk-util.js index ad03c8100..455fdaa22 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -839,7 +839,7 @@ HK.initializeIndexWorkers = function (Env, config, _cb) { } const txid = Util.uid(); msg.txid = txid; - response.expect(txid, cb, 45000); + response.expect(txid, cb, 60000); workers[workerIndex].send(msg); }; @@ -997,8 +997,8 @@ HK.initializeValidationWorkers = function (Env) { var txid = msg.txid = Util.uid(); - // expect a response within 15s - response.expect(txid, cb, 15000); + // expect a response within 45s + response.expect(txid, cb, 60000); // Send the request workers[nextWorker].send(msg); From 5f69fc18d01a3b34d431bb81198b178a4728f91e Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 26 Mar 2020 17:11:43 -0400 Subject: [PATCH 05/18] suppress some noisy errors --- lib/api.js | 3 ++- lib/historyKeeper.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/api.js b/lib/api.js index 8e6725039..4f0f08602 100644 --- a/lib/api.js +++ b/lib/api.js @@ -17,10 +17,11 @@ module.exports.create = function (config) { .on('sessionClose', historyKeeper.sessionClose) .on('error', function (error, label, info) { if (!error) { return; } + if (['EPIPE', 'ECONNRESET'].indexOf(error && error.code) !== -1) { return; } /* labels: SEND_MESSAGE_FAIL, SEND_MESSAGE_FAIL_2, FAIL_TO_DISCONNECT, FAIL_TO_TERMINATE, HANDLE_CHANNEL_LEAVE, NETFLUX_BAD_MESSAGE, - NETFLUX_WEBSOCKET_ERROR + NETFLUX_WEBSOCKET_ERROR, NF_ENOENT */ log.error(label, { code: error.code, diff --git a/lib/historyKeeper.js b/lib/historyKeeper.js index caa0ef462..6fd1ce2f5 100644 --- a/lib/historyKeeper.js +++ b/lib/historyKeeper.js @@ -190,7 +190,7 @@ module.exports.create = function (config, cb) { }, sessionClose: function (userId, reason) { HK.closeNetfluxSession(Env, userId); - if (['BAD_MESSAGE', 'SOCKET_ERROR', 'SEND_MESSAGE_FAIL_2'].indexOf(reason) !== -1) { + if (['BAD_MESSAGE', 'SEND_MESSAGE_FAIL_2'].indexOf(reason) !== -1) { if (reason && reason.code === 'ECONNRESET') { return; } return void Log.error('SESSION_CLOSE_WITH_ERROR', { userId: userId, From 9dbd32758a8dcab4001bea3af03a892c077349f7 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 27 Mar 2020 13:38:27 -0400 Subject: [PATCH 06/18] improve worker logging and move blob deletion to worker processes as well --- lib/commands/channel.js | 40 +---- lib/historyKeeper.js | 6 +- lib/hk-util.js | 266 ------------------------------ lib/log.js | 2 +- lib/workers/compute-index.js | 100 +++++++++--- lib/workers/index.js | 303 +++++++++++++++++++++++++++++++++++ 6 files changed, 386 insertions(+), 331 deletions(-) create mode 100644 lib/workers/index.js diff --git a/lib/commands/channel.js b/lib/commands/channel.js index 10131d9d8..6a773cdb9 100644 --- a/lib/commands/channel.js +++ b/lib/commands/channel.js @@ -54,50 +54,14 @@ Channel.clearOwnedChannel = function (Env, safeKey, channelId, cb, Server) { }); }; -Channel.removeOwnedChannel = function (Env, safeKey, channelId, cb, Server) { +Channel.removeOwnedChannel = function (Env, safeKey, channelId, cb, Server) { // XXX very heavy CPU usage if (typeof(channelId) !== 'string' || !Core.isValidId(channelId)) { return cb('INVALID_ARGUMENTS'); } var unsafeKey = Util.unescapeKeyCharacters(safeKey); if (Env.blobStore.isFileId(channelId)) { - var blobId = channelId; - - return void nThen(function (w) { - // check if you have permissions - Env.blobStore.isOwnedBy(safeKey, blobId, w(function (err, owned) { - if (err || !owned) { - w.abort(); - return void cb("INSUFFICIENT_PERMISSIONS"); - } - })); - }).nThen(function (w) { - // remove the blob - return void Env.blobStore.archive.blob(blobId, w(function (err) { - Env.Log.info('ARCHIVAL_OWNED_FILE_BY_OWNER_RPC', { - safeKey: safeKey, - blobId: blobId, - status: err? String(err): 'SUCCESS', - }); - if (err) { - w.abort(); - return void cb(err); - } - })); - }).nThen(function () { - // archive the proof - return void Env.blobStore.archive.proof(safeKey, blobId, function (err) { - Env.Log.info("ARCHIVAL_PROOF_REMOVAL_BY_OWNER_RPC", { - safeKey: safeKey, - blobId: blobId, - status: err? String(err): 'SUCCESS', - }); - if (err) { - return void cb("E_PROOF_REMOVAL"); - } - cb(void 0, 'OK'); - }); - }); + return void Env.removeOwnedBlob(channelId, safeKey, cb); } Metadata.getMetadata(Env, channelId, function (err, metadata) { diff --git a/lib/historyKeeper.js b/lib/historyKeeper.js index 6fd1ce2f5..6ec43aaa3 100644 --- a/lib/historyKeeper.js +++ b/lib/historyKeeper.js @@ -10,6 +10,7 @@ const Core = require("./commands/core"); const Store = require("./storage/file"); const BlobStore = require("./storage/blob"); +const Workers = require("./workers/index"); module.exports.create = function (config, cb) { const Log = config.log; @@ -78,8 +79,6 @@ module.exports.create = function (config, cb) { domain: config.domain }; - HK.initializeValidationWorkers(Env); - (function () { var pes = config.premiumUploadSize; if (!isNaN(pes) && pes >= Env.maxUploadSize) { @@ -243,7 +242,7 @@ module.exports.create = function (config, cb) { Env.blobStore = blob; })); }).nThen(function (w) { - HK.initializeIndexWorkers(Env, { + Workers.initialize(Env, { blobPath: config.blobPath, blobStagingPath: config.blobStagingPath, pinPath: pinPath, @@ -268,6 +267,7 @@ module.exports.create = function (config, cb) { if (config.disableIntegratedTasks) { return; } config.intervals = config.intervals || {}; + // XXX config.intervals.taskExpiration = setInterval(function () { tasks.runAll(function (err) { if (err) { diff --git a/lib/hk-util.js b/lib/hk-util.js index 455fdaa22..a4abab6ba 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -6,10 +6,6 @@ const nThen = require('nthen'); const Util = require("./common-util"); const MetaRPC = require("./commands/metadata"); const Nacl = require('tweetnacl/nacl-fast'); -const { fork } = require('child_process'); -const OS = require("os"); -const numCPUs = OS.cpus().length; - const now = function () { return (new Date()).getTime(); }; const ONE_DAY = 1000 * 60 * 60 * 24; // one day in milliseconds @@ -767,268 +763,6 @@ HK.onDirectMessage = function (Env, Server, seq, userId, json) { }); }; -HK.initializeIndexWorkers = function (Env, config, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - - const workers = []; - - const response = Util.response(function (errLabel, info) { - Env.Log.error('HK_DB_WORKER__' + errLabel, info); - }); - const initWorker = function (worker, cb) { - //console.log("initializing index worker"); - const txid = Util.uid(); - response.expect(txid, function (err) { - if (err) { return void cb(err); } - //console.log("worker initialized"); - workers.push(worker); - cb(); - }, 15000); - - worker.send({ - txid: txid, - config: config, - }); - - worker.on('message', function (res) { - if (!res) { return; } - if (!res.txid) { - // !report errors... - if (res.error) { - Env.Log.error(res.error, res.value); - } - return; - } - //console.log(res); - response.handle(res.txid, [res.error, res.value]); - }); - - var substituteWorker = Util.once(function () { - Env.Log.info("SUBSTITUTE_INDEX_WORKER", ''); - var idx = workers.indexOf(worker); - if (idx !== -1) { - workers.splice(idx, 1); - } - var w = fork('lib/workers/compute-index'); - initWorker(w, function (err) { - if (err) { - throw new Error(err); - } - workers.push(w); - }); - }); - - worker.on('exit', substituteWorker); - worker.on('close', substituteWorker); - worker.on('error', function (err) { - substituteWorker(); - Env.log.error("INDEX_WORKER_ERROR", { - error: err, - }); - }); - }; - - var workerIndex = 0; - var sendCommand = function (msg, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - - workerIndex = (workerIndex + 1) % workers.length; - if (workers.length === 0 || - typeof(workers[workerIndex].send) !== 'function') { - return void cb("NO_WORKERS"); - } - const txid = Util.uid(); - msg.txid = txid; - response.expect(txid, cb, 60000); - workers[workerIndex].send(msg); - }; - - nThen(function (w) { - OS.cpus().forEach(function () { - initWorker(fork('lib/workers/compute-index'), w(function (err) { - if (!err) { return; } - w.abort(); - return void cb(err); - })); - }); - }).nThen(function () { - Env.computeIndex = function (Env, channel, cb) { - Env.store.getWeakLock(channel, function (next) { - sendCommand({ - channel: channel, - command: 'COMPUTE_INDEX', - }, function (err, index) { - next(); - cb(err, index); - }); - }); - }; - - Env.computeMetadata = function (channel, cb) { - Env.store.getWeakLock(channel, function (next) { - sendCommand({ - channel: channel, - command: 'COMPUTE_METADATA', - }, function (err, metadata) { - next(); - cb(err, metadata); - }); - }); - }; - - Env.getOlderHistory = function (channel, oldestKnownHash, cb) { - Env.store.getWeakLock(channel, function (next) { - sendCommand({ - channel: channel, - command: "GET_OLDER_HISTORY", - hash: oldestKnownHash, - }, Util.both(next, cb)); - }); - }; - - Env.getPinState = function (safeKey, cb) { - Env.pinStore.getWeakLock(safeKey, function (next) { - sendCommand({ - key: safeKey, - command: 'GET_PIN_STATE', - }, Util.both(next, cb)); - }); - }; - - Env.getFileSize = function (channel, cb) { - sendCommand({ - command: 'GET_FILE_SIZE', - channel: channel, - }, cb); - }; - - Env.getDeletedPads = function (channels, cb) { - sendCommand({ - command: "GET_DELETED_PADS", - channels: channels, - }, cb); - }; - - Env.getTotalSize = function (channels, cb) { - // we could take out locks for all of these channels, - // but it's OK if the size is slightly off - sendCommand({ - command: 'GET_TOTAL_SIZE', - channels: channels, - }, cb); - }; - - Env.getMultipleFileSize = function (channels, cb) { - sendCommand({ - command: "GET_MULTIPLE_FILE_SIZE", - channels: channels, - }, cb); - }; - - Env.getHashOffset = function (channel, hash, cb) { - Env.store.getWeakLock(channel, function (next) { - sendCommand({ - command: 'GET_HASH_OFFSET', - channel: channel, - hash: hash, - }, Util.both(next, cb)); - }); - }; - - //console.log("index workers ready"); - cb(void 0); - }); -}; - -HK.initializeValidationWorkers = function (Env) { - if (typeof(Env.validateMessage) !== 'undefined') { - return void console.error("validation workers are already initialized"); - } - - // Create our workers - const workers = []; - for (let i = 0; i < numCPUs; i++) { - workers.push(fork('lib/workers/check-signature.js')); - } - - const response = Util.response(function (errLabel, info) { - Env.Log.error('HK_VALIDATE_WORKER__' + errLabel, info); - }); - - var initWorker = function (worker) { - worker.on('message', function (res) { - if (!res || !res.txid) { return; } - //console.log(+new Date(), "Received verification response"); - response.handle(res.txid, [res.error, res.value]); - }); - - var substituteWorker = Util.once( function () { - Env.Log.info("SUBSTITUTE_VALIDATION_WORKER", ''); - var idx = workers.indexOf(worker); - if (idx !== -1) { - workers.splice(idx, 1); - } - // Spawn a new one - var w = fork('lib/workers/check-signature.js'); - workers.push(w); - initWorker(w); - }); - - // Spawn a new process in one ends - worker.on('exit', substituteWorker); - worker.on('close', substituteWorker); - worker.on('error', function (err) { - substituteWorker(); - Env.Log.error('VALIDATION_WORKER_ERROR', { - error: err, - }); - }); - }; - workers.forEach(initWorker); - - var nextWorker = 0; - const send = function (msg, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - // let's be paranoid about asynchrony and only calling back once.. - nextWorker = (nextWorker + 1) % workers.length; - if (workers.length === 0 || typeof(workers[nextWorker].send) !== 'function') { - return void cb("INVALID_WORKERS"); - } - - var txid = msg.txid = Util.uid(); - - // expect a response within 45s - response.expect(txid, cb, 60000); - - // Send the request - workers[nextWorker].send(msg); - }; - - Env.validateMessage = function (signedMsg, key, cb) { - send({ - msg: signedMsg, - key: key, - command: 'INLINE', - }, cb); - }; - - Env.checkSignature = function (signedMsg, signature, publicKey, cb) { - send({ - command: 'DETACHED', - sig: signature, - msg: signedMsg, - key: publicKey, - }, cb); - }; - - Env.hashChannelList = function (channels, cb) { - send({ - command: 'HASH_CHANNEL_LIST', - channels: channels, - }, cb); - }; -}; - /* onChannelMessage Determine what we should store when a message a broadcasted to a channel" diff --git a/lib/log.js b/lib/log.js index 0e0567a69..35dfad7a3 100644 --- a/lib/log.js +++ b/lib/log.js @@ -21,7 +21,7 @@ var write = function (ctx, content) { }; // various degrees of logging -const logLevels = ['silly', 'verbose', 'debug', 'feedback', 'info', 'warn', 'error']; +const logLevels = Logger.levels = ['silly', 'verbose', 'debug', 'feedback', 'info', 'warn', 'error']; var handlers = { silly: function (ctx, time, tag, info) { diff --git a/lib/workers/compute-index.js b/lib/workers/compute-index.js index 37fa9ac1a..a1fe06645 100644 --- a/lib/workers/compute-index.js +++ b/lib/workers/compute-index.js @@ -10,8 +10,22 @@ const Meta = require("../metadata"); const Pins = require("../pins"); const Core = require("../commands/core"); const Saferphore = require("saferphore"); +const Logger = require("../log"); -const Env = {}; +const Env = { + Log: {}, +}; + +// support the usual log API but pass it to the main process +Logger.levels.forEach(function (level) { + Env.Log[level] = function (label, info) { + process.send({ + log: level, + label: label, + info: info, + }); + }; +}); var ready = false; var store; @@ -57,10 +71,6 @@ const init = function (config, _cb) { }); }; -const tryParse = function (Env, str) { - try { return JSON.parse(str); } catch (err) { } -}; - /* computeIndex can call back with an error or a computed index which includes: * cpIndex: @@ -107,7 +117,7 @@ const computeIndex = function (data, cb) { // but only check for metadata on the first line if (!i && msgObj.buff.indexOf('{') === 0) { i++; // always increment the message counter - msg = tryParse(Env, msgObj.buff.toString('utf8')); + msg = HK.tryParse(Env, msgObj.buff.toString('utf8')); if (typeof msg === "undefined") { return readMore(); } // validate that the current line really is metadata before storing it as such @@ -116,7 +126,7 @@ const computeIndex = function (data, cb) { } i++; if (msgObj.buff.indexOf('cp|') > -1) { - msg = msg || tryParse(Env, msgObj.buff.toString('utf8')); + msg = msg || HK.tryParse(Env, msgObj.buff.toString('utf8')); if (typeof msg === "undefined") { return readMore(); } // cache the offsets of checkpoints if they can be parsed if (msg[2] === 'MSG' && msg[4].indexOf('cp|') === 0) { @@ -142,7 +152,7 @@ const computeIndex = function (data, cb) { // once indexing is complete you should have a buffer of messages since the latest checkpoint // map the 'hash' of each message to its byte offset in the log, to be used for reconnecting clients messageBuf.forEach((msgObj) => { - const msg = tryParse(Env, msgObj.buff.toString('utf8')); + const msg = HK.tryParse(Env, msgObj.buff.toString('utf8')); if (typeof msg === "undefined") { return; } if (msg[0] === 0 && msg[2] === 'MSG' && typeof(msg[4]) === 'string') { // msgObj.offset is API guaranteed by our storage module @@ -166,9 +176,9 @@ const computeIndex = function (data, cb) { }); }; -const computeMetadata = function (data, cb, errorHandler) { +const computeMetadata = function (data, cb) { const ref = {}; - const lineHandler = Meta.createLineHandler(ref, errorHandler); + const lineHandler = Meta.createLineHandler(ref, Env.Log.error); return void store.readChannelMetadata(data.channel, lineHandler, function (err) { if (err) { // stream errors? @@ -199,7 +209,7 @@ const getOlderHistory = function (data, cb) { store.getMessages(channelName, function (msgStr) { if (found) { return; } - let parsed = tryParse(Env, msgStr); + let parsed = HK.tryParse(Env, msgStr); if (typeof parsed === "undefined") { return; } // identify classic metadata messages by their inclusion of a channel. @@ -221,11 +231,11 @@ const getOlderHistory = function (data, cb) { }); }; -const getPinState = function (data, cb, errorHandler) { +const getPinState = function (data, cb) { const safeKey = data.key; var ref = {}; - var lineHandler = Pins.createLineHandler(ref, errorHandler); + var lineHandler = Pins.createLineHandler(ref, Env.Log.error); // if channels aren't in memory. load them from disk // TODO replace with readMessagesBin @@ -328,7 +338,7 @@ const getHashOffset = function (data, cb) { var offset = -1; store.readMessagesBin(channelName, 0, (msgObj, readMore, abort) => { // tryParse return a parsed message or undefined - const msg = tryParse(Env, msgObj.buff.toString('utf8')); + const msg = HK.tryParse(Env, msgObj.buff.toString('utf8')); // if it was undefined then go onto the next message if (typeof msg === "undefined") { return readMore(); } if (typeof(msg[4]) !== 'string' || lastKnownHash !== HK.getHash(msg[4])) { @@ -342,6 +352,47 @@ const getHashOffset = function (data, cb) { }); }; +const removeOwnedBlob = function (data, cb) { + const blobId = data.blobId; + const safeKey = data.safeKey; + + nThen(function (w) { + // check if you have permissions + blobStore.isOwnedBy(safeKey, blobId, w(function (err, owned) { + if (err || !owned) { + w.abort(); + return void cb("INSUFFICIENT_PERMISSIONS"); + } + })); + }).nThen(function (w) { + // remove the blob + blobStore.archive.blob(blobId, w(function (err) { + Env.Log.info('ARCHIVAL_OWNED_FILE_BY_OWNER_RPC', { + safeKey: safeKey, + blobId: blobId, + status: err? String(err): 'SUCCESS', + }); + if (err) { + w.abort(); + return void cb(err); + } + })); + }).nThen(function () { + // archive the proof + blobStore.archive.proof(safeKey, blobId, function (err) { + Env.Log.info("ARCHIVAL_PROOF_REMOVAL_BY_OWNER_RPC", { + safeKey: safeKey, + blobId: blobId, + status: err? String(err): 'SUCCESS', + }); + if (err) { + return void cb("E_PROOF_REMOVAL"); + } + cb(void 0, 'OK'); + }); + }); +}; + const COMMANDS = { COMPUTE_INDEX: computeIndex, COMPUTE_METADATA: computeMetadata, @@ -352,12 +403,14 @@ const COMMANDS = { GET_DELETED_PADS: getDeletedPads, GET_MULTIPLE_FILE_SIZE: getMultipleFileSize, GET_HASH_OFFSET: getHashOffset, + REMOVE_OWNED_BLOB: removeOwnedBlob, }; process.on('message', function (data) { - if (!data || !data.txid) { + if (!data || !data.txid || !data.pid) { return void process.send({ - error:'E_INVAL' + error:'E_INVAL', + data: data, }); } @@ -365,6 +418,7 @@ process.on('message', function (data) { process.send({ error: err, txid: data.txid, + pid: data.pid, value: value, }); }; @@ -381,12 +435,12 @@ process.on('message', function (data) { if (typeof(command) !== 'function') { return void cb("E_BAD_COMMAND"); } - command(data, cb, function (label, info) { - // for streaming errors - process.send({ - error: label, - value: info, - }); - }); + command(data, cb); }); +process.on('uncaughtException', function (err) { + console.error('[%s] UNCAUGHT EXCEPTION IN DB WORKER'); + console.error(err); + console.error("TERMINATING"); + process.exit(1); +}); diff --git a/lib/workers/index.js b/lib/workers/index.js new file mode 100644 index 000000000..dc380a208 --- /dev/null +++ b/lib/workers/index.js @@ -0,0 +1,303 @@ +/* jshint esversion: 6 */ +/* global process */ +const Util = require("../common-util"); +const nThen = require('nthen'); +const OS = require("os"); +const numCPUs = OS.cpus().length; +const { fork } = require('child_process'); +const Workers = module.exports; +const PID = process.pid; + +Workers.initializeValidationWorkers = function (Env) { + if (typeof(Env.validateMessage) !== 'undefined') { + return void console.error("validation workers are already initialized"); + } + + // Create our workers + const workers = []; + for (let i = 0; i < numCPUs; i++) { + workers.push(fork('lib/workers/check-signature.js')); + } + + const response = Util.response(function (errLabel, info) { + Env.Log.error('HK_VALIDATE_WORKER__' + errLabel, info); + }); + + var initWorker = function (worker) { + worker.on('message', function (res) { + if (!res || !res.txid) { return; } + //console.log(+new Date(), "Received verification response"); + response.handle(res.txid, [res.error, res.value]); + }); + + var substituteWorker = Util.once( function () { + Env.Log.info("SUBSTITUTE_VALIDATION_WORKER", ''); + var idx = workers.indexOf(worker); + if (idx !== -1) { + workers.splice(idx, 1); + } + // Spawn a new one + var w = fork('lib/workers/check-signature.js'); + workers.push(w); + initWorker(w); + }); + + // Spawn a new process in one ends + worker.on('exit', substituteWorker); + worker.on('close', substituteWorker); + worker.on('error', function (err) { + substituteWorker(); + Env.Log.error('VALIDATION_WORKER_ERROR', { + error: err, + }); + }); + }; + workers.forEach(initWorker); + + var nextWorker = 0; + const send = function (msg, _cb) { + var cb = Util.once(Util.mkAsync(_cb)); + // let's be paranoid about asynchrony and only calling back once.. + nextWorker = (nextWorker + 1) % workers.length; + if (workers.length === 0 || typeof(workers[nextWorker].send) !== 'function') { + return void cb("INVALID_WORKERS"); + } + + var txid = msg.txid = Util.uid(); + + // expect a response within 45s + response.expect(txid, cb, 60000); + + // Send the request + workers[nextWorker].send(msg); + }; + + Env.validateMessage = function (signedMsg, key, cb) { + send({ + msg: signedMsg, + key: key, + command: 'INLINE', + }, cb); + }; + + Env.checkSignature = function (signedMsg, signature, publicKey, cb) { + send({ + command: 'DETACHED', + sig: signature, + msg: signedMsg, + key: publicKey, + }, cb); + }; + + Env.hashChannelList = function (channels, cb) { + send({ + command: 'HASH_CHANNEL_LIST', + channels: channels, + }, cb); + }; +}; + +Workers.initializeIndexWorkers = function (Env, config, _cb) { + var cb = Util.once(Util.mkAsync(_cb)); + + const workers = []; + + const response = Util.response(function (errLabel, info) { + Env.Log.error('HK_DB_WORKER__' + errLabel, info); + }); + + const Log = Env.Log; + const handleLog = function (level, label, info) { + if (typeof(Log[level]) !== 'function') { return; } + Log[level](label, info); + }; + + const initWorker = function (worker, cb) { + //console.log("initializing index worker"); + const txid = Util.uid(); + response.expect(txid, function (err) { + if (err) { return void cb(err); } + //console.log("worker initialized"); + workers.push(worker); + cb(); + }, 15000); + + worker.send({ + pid: PID, + txid: txid, + config: config, + }); + + worker.on('message', function (res) { + if (!res) { return; } + // handle log messages before checking if it was addressed to your PID + // it might still be useful to know what happened inside an orphaned worker + if (res.log) { + return void handleLog(res.log, res.label, res.info); + } + // but don't bother handling things addressed to other processes + // since it's basically guaranteed not to work + if (res.pid !== PID) { + return void Log.error("WRONG_PID", res); + } + + response.handle(res.txid, [res.error, res.value]); + }); + + var substituteWorker = Util.once(function () { + // XXX reassign jobs delegated to failed workers + Env.Log.info("SUBSTITUTE_INDEX_WORKER", ''); + var idx = workers.indexOf(worker); + if (idx !== -1) { + workers.splice(idx, 1); + } + var w = fork('lib/workers/compute-index'); + initWorker(w, function (err) { + if (err) { + throw new Error(err); + } + workers.push(w); + }); + }); + + worker.on('exit', substituteWorker); + worker.on('close', substituteWorker); + worker.on('error', function (err) { + substituteWorker(); + Env.log.error("INDEX_WORKER_ERROR", { + error: err, + }); + }); + }; + + var workerIndex = 0; + var sendCommand = function (msg, _cb) { + var cb = Util.once(Util.mkAsync(_cb)); + + workerIndex = (workerIndex + 1) % workers.length; + if (workers.length === 0 || + typeof(workers[workerIndex].send) !== 'function') { + return void cb("NO_WORKERS"); + } + + // XXX insert a queue here to prevent timeouts + // XXX track which worker is doing which jobs + + const txid = Util.uid(); + msg.txid = txid; + msg.pid = PID; + response.expect(txid, cb, 60000); + workers[workerIndex].send(msg); + }; + + nThen(function (w) { + OS.cpus().forEach(function () { + initWorker(fork('lib/workers/compute-index'), w(function (err) { + if (!err) { return; } + w.abort(); + return void cb(err); + })); + }); + }).nThen(function () { + Env.computeIndex = function (Env, channel, cb) { + Env.store.getWeakLock(channel, function (next) { + sendCommand({ + channel: channel, + command: 'COMPUTE_INDEX', + }, function (err, index) { + next(); + cb(err, index); + }); + }); + }; + + Env.computeMetadata = function (channel, cb) { + Env.store.getWeakLock(channel, function (next) { + sendCommand({ + channel: channel, + command: 'COMPUTE_METADATA', + }, function (err, metadata) { + next(); + cb(err, metadata); + }); + }); + }; + + Env.getOlderHistory = function (channel, oldestKnownHash, cb) { + Env.store.getWeakLock(channel, function (next) { + sendCommand({ + channel: channel, + command: "GET_OLDER_HISTORY", + hash: oldestKnownHash, + }, Util.both(next, cb)); + }); + }; + + Env.getPinState = function (safeKey, cb) { + Env.pinStore.getWeakLock(safeKey, function (next) { + sendCommand({ + key: safeKey, + command: 'GET_PIN_STATE', + }, Util.both(next, cb)); + }); + }; + + Env.getFileSize = function (channel, cb) { + sendCommand({ + command: 'GET_FILE_SIZE', + channel: channel, + }, cb); + }; + + Env.getDeletedPads = function (channels, cb) { + sendCommand({ + command: "GET_DELETED_PADS", + channels: channels, + }, cb); + }; + + Env.getTotalSize = function (channels, cb) { + // we could take out locks for all of these channels, + // but it's OK if the size is slightly off + sendCommand({ + command: 'GET_TOTAL_SIZE', + channels: channels, + }, cb); + }; + + Env.getMultipleFileSize = function (channels, cb) { + sendCommand({ + command: "GET_MULTIPLE_FILE_SIZE", + channels: channels, + }, cb); + }; + + Env.getHashOffset = function (channel, hash, cb) { + Env.store.getWeakLock(channel, function (next) { + sendCommand({ + command: 'GET_HASH_OFFSET', + channel: channel, + hash: hash, + }, Util.both(next, cb)); + }); + }; + + Env.removeOwnedBlob = function (blobId, safeKey, cb) { + sendCommand({ + command: 'REMOVE_OWNED_BLOB', + blobId: blobId, + safeKey: safeKey, + }, cb); + }; + + //console.log("index workers ready"); + cb(void 0); + }); +}; + +// XXX task expiration... + +Workers.initialize = function (Env, config, cb) { + Workers.initializeValidationWorkers(Env); + Workers.initializeIndexWorkers(Env, config, cb); +}; From b0179eaad9545ca4b9dfe4f0deb0a5fe13de5233 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 27 Mar 2020 14:25:07 -0400 Subject: [PATCH 07/18] drop XXX note --- lib/commands/channel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/commands/channel.js b/lib/commands/channel.js index 6a773cdb9..d35e15241 100644 --- a/lib/commands/channel.js +++ b/lib/commands/channel.js @@ -54,7 +54,7 @@ Channel.clearOwnedChannel = function (Env, safeKey, channelId, cb, Server) { }); }; -Channel.removeOwnedChannel = function (Env, safeKey, channelId, cb, Server) { // XXX very heavy CPU usage +Channel.removeOwnedChannel = function (Env, safeKey, channelId, cb, Server) { if (typeof(channelId) !== 'string' || !Core.isValidId(channelId)) { return cb('INVALID_ARGUMENTS'); } From d8a88cb4ca8c625f5abe61f56508fe23e978dfbe Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 27 Mar 2020 14:38:58 -0400 Subject: [PATCH 08/18] run expiration tasks in a worker instead of the main process --- lib/historyKeeper.js | 34 +++++++++++++++++----------------- lib/workers/compute-index.js | 21 ++++++++++++++++++++- lib/workers/index.js | 6 ++++++ 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/lib/historyKeeper.js b/lib/historyKeeper.js index 6ec43aaa3..f70dba006 100644 --- a/lib/historyKeeper.js +++ b/lib/historyKeeper.js @@ -245,6 +245,7 @@ module.exports.create = function (config, cb) { Workers.initialize(Env, { blobPath: config.blobPath, blobStagingPath: config.blobStagingPath, + taskPath: config.taskPath, pinPath: pinPath, filePath: config.filePath, archivePath: config.archivePath, @@ -257,26 +258,25 @@ module.exports.create = function (config, cb) { } })); }).nThen(function (w) { - // create a task store + // create a task store (for scheduling tasks) require("./storage/tasks").create(config, w(function (e, tasks) { - if (e) { - throw e; - } + if (e) { throw e; } Env.tasks = tasks; - config.tasks = tasks; - if (config.disableIntegratedTasks) { return; } - - config.intervals = config.intervals || {}; - // XXX - config.intervals.taskExpiration = setInterval(function () { - tasks.runAll(function (err) { - if (err) { - // either TASK_CONCURRENCY or an error with tasks.list - // in either case it is already logged. - } - }); - }, 1000 * 60 * 5); // run every five minutes })); + if (config.disableIntegratedTasks) { return; } + config.intervals = config.intervals || {}; + + var tasks_running; + config.intervals.taskExpiration = setInterval(function () { + if (tasks_running) { return; } + tasks_running = true; + Env.runTasks(function (err) { + if (err) { + Log.error('TASK_RUNNER_ERR', err); + } + tasks_running = false; + }); + }, 1000 * 60 * 5); // run every five minutes }).nThen(function () { RPC.create(Env, function (err, _rpc) { if (err) { throw err; } diff --git a/lib/workers/compute-index.js b/lib/workers/compute-index.js index a1fe06645..3b973015b 100644 --- a/lib/workers/compute-index.js +++ b/lib/workers/compute-index.js @@ -11,6 +11,7 @@ const Pins = require("../pins"); const Core = require("../commands/core"); const Saferphore = require("saferphore"); const Logger = require("../log"); +const Tasks = require("../storage/tasks"); const Env = { Log: {}, @@ -31,6 +32,7 @@ var ready = false; var store; var pinStore; var blobStore; +var tasks; const init = function (config, _cb) { const cb = Util.once(Util.mkAsync(_cb)); if (!config) { @@ -66,6 +68,18 @@ const init = function (config, _cb) { } blobStore = blob; })); + }).nThen(function (w) { + Tasks.create({ + log: Env.Log, + taskPath: config.taskPath, + store: store, + }, w(function (err, tasks) { + if (err) { + w.abort(); + return void cb(err); + } + Env.tasks = tasks; + })); }).nThen(function () { cb(); }); @@ -393,6 +407,10 @@ const removeOwnedBlob = function (data, cb) { }); }; +const runTasks = function (data, cb) { + Env.tasks.runAll(cb); +}; + const COMMANDS = { COMPUTE_INDEX: computeIndex, COMPUTE_METADATA: computeMetadata, @@ -404,6 +422,7 @@ const COMMANDS = { GET_MULTIPLE_FILE_SIZE: getMultipleFileSize, GET_HASH_OFFSET: getHashOffset, REMOVE_OWNED_BLOB: removeOwnedBlob, + RUN_TASKS: runTasks, }; process.on('message', function (data) { @@ -439,7 +458,7 @@ process.on('message', function (data) { }); process.on('uncaughtException', function (err) { - console.error('[%s] UNCAUGHT EXCEPTION IN DB WORKER'); + console.error('[%s] UNCAUGHT EXCEPTION IN DB WORKER', new Date()); console.error(err); console.error("TERMINATING"); process.exit(1); diff --git a/lib/workers/index.js b/lib/workers/index.js index dc380a208..8abdd2e15 100644 --- a/lib/workers/index.js +++ b/lib/workers/index.js @@ -290,6 +290,12 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) { }, cb); }; + Env.runTasks = function (cb) { + sendCommand({ + command: 'RUN_TASKS', + }, cb); + }; + //console.log("index workers ready"); cb(void 0); }); From 3f86b6141e778933b751e5abddd491f4f21e6b2b Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 27 Mar 2020 15:14:45 -0400 Subject: [PATCH 09/18] rename worker processes --- lib/workers/{check-signature.js => crypto-worker.js} | 0 lib/workers/{compute-index.js => db-worker.js} | 0 lib/workers/index.js | 11 +++++++---- 3 files changed, 7 insertions(+), 4 deletions(-) rename lib/workers/{check-signature.js => crypto-worker.js} (100%) rename lib/workers/{compute-index.js => db-worker.js} (100%) diff --git a/lib/workers/check-signature.js b/lib/workers/crypto-worker.js similarity index 100% rename from lib/workers/check-signature.js rename to lib/workers/crypto-worker.js diff --git a/lib/workers/compute-index.js b/lib/workers/db-worker.js similarity index 100% rename from lib/workers/compute-index.js rename to lib/workers/db-worker.js diff --git a/lib/workers/index.js b/lib/workers/index.js index 8abdd2e15..16282b202 100644 --- a/lib/workers/index.js +++ b/lib/workers/index.js @@ -8,6 +8,9 @@ const { fork } = require('child_process'); const Workers = module.exports; const PID = process.pid; +const CRYPTO_PATH = 'lib/workers/crypto-worker'; +const DB_PATH = 'lib/workers/db-worker'; + Workers.initializeValidationWorkers = function (Env) { if (typeof(Env.validateMessage) !== 'undefined') { return void console.error("validation workers are already initialized"); @@ -16,7 +19,7 @@ Workers.initializeValidationWorkers = function (Env) { // Create our workers const workers = []; for (let i = 0; i < numCPUs; i++) { - workers.push(fork('lib/workers/check-signature.js')); + workers.push(fork(CRYPTO_PATH)); } const response = Util.response(function (errLabel, info) { @@ -37,7 +40,7 @@ Workers.initializeValidationWorkers = function (Env) { workers.splice(idx, 1); } // Spawn a new one - var w = fork('lib/workers/check-signature.js'); + var w = fork(CRYPTO_PATH); workers.push(w); initWorker(w); }); @@ -151,7 +154,7 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) { if (idx !== -1) { workers.splice(idx, 1); } - var w = fork('lib/workers/compute-index'); + var w = fork(DB_PATH); initWorker(w, function (err) { if (err) { throw new Error(err); @@ -192,7 +195,7 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) { nThen(function (w) { OS.cpus().forEach(function () { - initWorker(fork('lib/workers/compute-index'), w(function (err) { + initWorker(fork(DB_PATH), w(function (err) { if (!err) { return; } w.abort(); return void cb(err); From 64b087998492ff36acda109f181c24ae1a3bf580 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 27 Mar 2020 15:15:54 -0400 Subject: [PATCH 10/18] improve logging for parse errors --- lib/hk-util.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/hk-util.js b/lib/hk-util.js index a4abab6ba..5a1db157d 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -43,7 +43,10 @@ const tryParse = function (Env, str) { try { return JSON.parse(str); } catch (err) { - Env.Log.error('HK_PARSE_ERROR', err); + Env.Log.error('HK_PARSE_ERROR', { + message: err && err.name, + input: str, + }); } }; From b5649707d1bc406b3e018586c4e75066d1a77214 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 27 Mar 2020 15:36:34 -0400 Subject: [PATCH 11/18] export 'tryParse' command --- lib/hk-util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hk-util.js b/lib/hk-util.js index 5a1db157d..4b71e7c5b 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -39,7 +39,7 @@ const STANDARD_CHANNEL_LENGTH = HK.STANDARD_CHANNEL_LENGTH = 32; // with a 34 character id const EPHEMERAL_CHANNEL_LENGTH = HK.EPHEMERAL_CHANNEL_LENGTH = 34; -const tryParse = function (Env, str) { +const tryParse = HK.tryParse = function (Env, str) { try { return JSON.parse(str); } catch (err) { From 172823c954e3dfa95ac0153b425a7d55e9de6764 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 27 Mar 2020 16:59:41 -0400 Subject: [PATCH 12/18] lint compliance --- lib/workers/db-worker.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/workers/db-worker.js b/lib/workers/db-worker.js index 3b973015b..62e182765 100644 --- a/lib/workers/db-worker.js +++ b/lib/workers/db-worker.js @@ -32,7 +32,6 @@ var ready = false; var store; var pinStore; var blobStore; -var tasks; const init = function (config, _cb) { const cb = Util.once(Util.mkAsync(_cb)); if (!config) { From 9058a59555416c0bead041b4e501941e8fa0faa7 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 27 Mar 2020 17:17:42 -0400 Subject: [PATCH 13/18] reassign db tasks if the responsible worker fails --- lib/workers/index.js | 95 ++++++++++++++++++++++++++------------- www/common/common-util.js | 3 ++ 2 files changed, 66 insertions(+), 32 deletions(-) diff --git a/lib/workers/index.js b/lib/workers/index.js index 16282b202..5eeb5e285 100644 --- a/lib/workers/index.js +++ b/lib/workers/index.js @@ -115,14 +115,58 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) { Log[level](label, info); }; + var isWorker = function (value) { + return value && value.worker && typeof(value.worker.send) === 'function'; + }; + + // pick ids that aren't already in use... + const guid = function () { + var id = Util.uid(); + return response.expected(id)? guid(): id; + }; + + var workerIndex = 0; + var sendCommand = function (msg, _cb) { + console.log("SEND_COMMAND"); + var cb = Util.once(Util.mkAsync(_cb)); + + workerIndex = (workerIndex + 1) % workers.length; + if (!isWorker(workers[workerIndex])) { + return void cb("NO_WORKERS"); + } + + var state = workers[workerIndex]; + + // XXX insert a queue here to prevent timeouts + + const txid = guid(); + msg.txid = txid; + msg.pid = PID; + + // track which worker is doing which jobs + state.tasks[txid] = msg; + response.expect(txid, function (err, value) { + // clean up when you get a response + delete state[txid]; + cb(err, value); + }, 60000); + state.worker.send(msg); + }; + const initWorker = function (worker, cb) { //console.log("initializing index worker"); - const txid = Util.uid(); + const txid = guid(); + + const state = { + worker: worker, + tasks: {}, + }; + response.expect(txid, function (err) { if (err) { return void cb(err); } //console.log("worker initialized"); - workers.push(worker); - cb(); + workers.push(state); + cb(void 0, state); }, 15000); worker.send({ @@ -148,18 +192,28 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) { }); var substituteWorker = Util.once(function () { - // XXX reassign jobs delegated to failed workers - Env.Log.info("SUBSTITUTE_INDEX_WORKER", ''); - var idx = workers.indexOf(worker); + Env.Log.info("SUBSTITUTE_DB_WORKER", ''); + var idx = workers.indexOf(state); if (idx !== -1) { workers.splice(idx, 1); } + + Object.keys(state.tasks).forEach(function (txid) { + const cb = response.expectation(txid); + if (typeof(cb) !== 'function') { return; } + const task = state.tasks[txid]; + if (!task && task.msg) { return; } + response.clear(txid); + Log.info('DB_WORKER_RESEND', task.msg); + sendCommand(task.msg, cb); + }); + var w = fork(DB_PATH); - initWorker(w, function (err) { + initWorker(w, function (err, state) { if (err) { throw new Error(err); } - workers.push(w); + workers.push(state); }); }); @@ -167,32 +221,12 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) { worker.on('close', substituteWorker); worker.on('error', function (err) { substituteWorker(); - Env.log.error("INDEX_WORKER_ERROR", { + Env.Log.error("DB_WORKER_ERROR", { error: err, }); }); }; - var workerIndex = 0; - var sendCommand = function (msg, _cb) { - var cb = Util.once(Util.mkAsync(_cb)); - - workerIndex = (workerIndex + 1) % workers.length; - if (workers.length === 0 || - typeof(workers[workerIndex].send) !== 'function') { - return void cb("NO_WORKERS"); - } - - // XXX insert a queue here to prevent timeouts - // XXX track which worker is doing which jobs - - const txid = Util.uid(); - msg.txid = txid; - msg.pid = PID; - response.expect(txid, cb, 60000); - workers[workerIndex].send(msg); - }; - nThen(function (w) { OS.cpus().forEach(function () { initWorker(fork(DB_PATH), w(function (err) { @@ -299,13 +333,10 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) { }, cb); }; - //console.log("index workers ready"); cb(void 0); }); }; -// XXX task expiration... - Workers.initialize = function (Env, config, cb) { Workers.initializeValidationWorkers(Env); Workers.initializeIndexWorkers(Env, config, cb); diff --git a/www/common/common-util.js b/www/common/common-util.js index a1c35307f..cf7d5d97e 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -134,6 +134,9 @@ expected: function (id) { return Boolean(pending[id]); }, + expectation: function (id) { + return pending[id]; + }, expect: expect, handle: handle, }; From cbd35478148142f2af57414acac65c76971119bf Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 27 Mar 2020 17:20:57 -0400 Subject: [PATCH 14/18] remove log statements --- lib/workers/index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/workers/index.js b/lib/workers/index.js index 5eeb5e285..7d8c1dcc7 100644 --- a/lib/workers/index.js +++ b/lib/workers/index.js @@ -29,7 +29,6 @@ Workers.initializeValidationWorkers = function (Env) { var initWorker = function (worker) { worker.on('message', function (res) { if (!res || !res.txid) { return; } - //console.log(+new Date(), "Received verification response"); response.handle(res.txid, [res.error, res.value]); }); @@ -127,7 +126,6 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) { var workerIndex = 0; var sendCommand = function (msg, _cb) { - console.log("SEND_COMMAND"); var cb = Util.once(Util.mkAsync(_cb)); workerIndex = (workerIndex + 1) % workers.length; @@ -154,7 +152,6 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) { }; const initWorker = function (worker, cb) { - //console.log("initializing index worker"); const txid = guid(); const state = { @@ -164,7 +161,6 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) { response.expect(txid, function (err) { if (err) { return void cb(err); } - //console.log("worker initialized"); workers.push(state); cb(void 0, state); }, 15000); From b4d61f0519f285788acd45140f9d4bb2c814e020 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 27 Mar 2020 22:22:18 +0100 Subject: [PATCH 15/18] Translated using Weblate (Swedish) Currently translated at 15.5% (193 of 1246 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/sv/ --- www/common/translations/messages.sv.json | 38 +++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.sv.json b/www/common/translations/messages.sv.json index 9ba84d691..85a199284 100644 --- a/www/common/translations/messages.sv.json +++ b/www/common/translations/messages.sv.json @@ -174,5 +174,41 @@ "button_newwhiteboard": "Ny whiteboard", "button_newslide": "Ny presentation", "button_newpoll": "Ny votering", - "button_newpad": "Nytt textdokument" + "button_newpad": "Nytt textdokument", + "kanban_deleteBoard": "Är du säker på att du vill ta bort denna vägg?", + "kanban_working": "Pågående", + "kanban_done": "Färdigt", + "kanban_todo": "Att göra", + "kanban_item": "Artikel {0}", + "kanban_newBoard": "Ny vägg", + "pad_mediatagOptions": "Bildegenskaper", + "pad_mediatagImport": "Spara i din CryptDrive", + "pad_mediatagPreview": "Förhandsvisning", + "pad_mediatagBorder": "Rambredd (px)", + "pad_mediatagRatio": "Behåll förhållande", + "pad_mediatagHeight": "Höjd (px)", + "pad_mediatagWidth": "Bredd (px)", + "pad_mediatagTitle": "Inställningar för Media Tag", + "openLinkInNewTab": "Öppna länk i ny flik", + "history_version": "Version:", + "history_restoreDone": "Dokument återställt", + "history_restorePrompt": "Är du säker på att du ville ersätta den valda versionen av dokumentet med den visade?", + "history_restoreTitle": "Återskapa den valda versionen av dokumentet", + "history_closeTitle": "Stäng historiken", + "history_loadMore": "Ladda mer historik", + "history_prev": "Äldre version", + "history_next": "Nyare version", + "historyButton": "Visa dokumenthistoriken", + "historyText": "Historik", + "help_button": "Hjälp", + "hide_help_button": "Dölj hjälp", + "show_help_button": "Visa hjälp", + "cancelButton": "Avbryt (Esc)", + "cancel": "Avbryt", + "okButton": "OK (Enter)", + "ok": "OK", + "notifyLeft": "{0} har lämnat sammarbetssessionen", + "notifyRenamed": "{0} är nu känd som {1}", + "notifyJoined": "{0} har gått med i sammarbetssessionen", + "fileEmbedTag": "Placera sedan denna Media Tag där du vill bädda in den på din sida:" } From 2df7c626137076323ffc5c6ca4166ff29926868e Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 27 Mar 2020 22:22:18 +0100 Subject: [PATCH 16/18] Translated using Weblate (Italian) Currently translated at 80.8% (1007 of 1246 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/it/ Translated using Weblate (Italian) Currently translated at 74.0% (922 of 1246 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/it/ Translated using Weblate (Italian) Currently translated at 74.0% (922 of 1246 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/it/ --- www/common/translations/messages.it.json | 554 ++++++++++++++--------- 1 file changed, 352 insertions(+), 202 deletions(-) diff --git a/www/common/translations/messages.it.json b/www/common/translations/messages.it.json index 645fc9951..bfbd4e7d5 100644 --- a/www/common/translations/messages.it.json +++ b/www/common/translations/messages.it.json @@ -25,7 +25,7 @@ "common_connectionLost": "Connessione al server persa
Rimarrai in modalità solo lettura finché la connessione non sarà ripristinata.", "websocketError": "Impossibile connettersi al WebSocket server...", "typeError": "Questo pad non è compatibile con l'applicazione selezionata", - "onLogout": "Sei uscito, {0}Clicca qui{1} per entrare
o premi Esc per accedere al tuo pad in modalità solo lettura.", + "onLogout": "Sei uscito, {0}clicca qui{1} per entrare
o premi Esc per accedere al tuo pad in modalità solo lettura.", "wrongApp": "Impossibile mostrare il contenuto di quella sessione in tempo reale nel tuo browser. Per favore, prova a ricaricare quella pagina.", "padNotPinned": "Questo pad scadrà dopo 3 mesi di inattività, {0}accedi{1} o {2}registrati{3} per conservarlo.", "anonymousStoreDisabled": "Il webmaster di questa istanza di CryptPad ha disabilitato il drive per gli utenti anonimi. Devi accedere per poter usare CryptDrive.", @@ -36,25 +36,25 @@ "invalidHashError": "Il documento richiesto ha un URL non valido.", "errorCopy": " Puoi ancora accedere al contenuto premendo Esc.
Una volta chiusa questa finestra, non sarà possibile accedere di nuovo.", "errorRedirectToHome": "Premi Esc per essere reindirizzato al tuo CryptDrive.", - "newVersionError": "Una nuova versione di CryptPad è disponibile.
Ricarica per usare la nuova versione, o premi Esc per accedere al contenuto in modalità offline.", - "loading": "Caricamento...", + "newVersionError": "Una nuova versione di CryptPad è disponibile.
Ricarica per usare la nuova versione, o premi Esc per accedere al tuo contenuto in modalità offline.", + "loading": "Caricamento in corso...", "error": "Errore", "saved": "Salvato", - "synced": "È stato tutto salvato", - "deleted": "Cancellato", - "deletedFromServer": "Pad cancellato dal server", - "mustLogin": "Devi essere loggato per poter accedere a questa pagina", + "synced": "Tutto è stato salvato", + "deleted": "Eliminato", + "deletedFromServer": "Pad eliminato dal server", + "mustLogin": "Devi essere connesso per accedere a questa pagina", "disabledApp": "Questa applicazione è stata disattivata. Contatta l'amministratore di questo CryptPad per ulteriori informazioni.", - "realtime_unrecoverableError": "Si è verificato un errore critico. Premi OK per ricaricare la pagina.", + "realtime_unrecoverableError": "Si è verificato un errore critico. Premi OK per ricaricare.", "disconnected": "Disconnesso", - "synchronizing": "Sincronizzazione", - "reconnecting": "Riconnessione", + "synchronizing": "Sincronizzazione in corso", + "reconnecting": "Riconnessione in corso", "typing": "Modifica", - "initializing": "Inizializzazione...", + "initializing": "Inizializzazione in corso...", "forgotten": "Spostato nel cestino", "errorState": "Errore critico: {0}", "lag": "Latenza", - "readonly": "Solo lettura", + "readonly": "Sola lettura", "anonymous": "Anonimo", "yourself": "Da solo", "anonymousUsers": "editori anonimi", @@ -65,10 +65,10 @@ "viewers": "visualizzatori", "editor": "editore", "editors": "editori", - "userlist_offline": "Sei offline, la lista degli utenti non è disponibile.", + "userlist_offline": "Sei offline, l'elenco degli utenti non è disponibile.", "language": "Lingua", "comingSoon": "In arrivo...", - "newVersion": "CryptPad è stato aggiornato!
Scopri cosa c'è di nuovo nell'ultima versione:
Note di rilascio per CryptPad {0}", + "newVersion": "CryptPad è stato aggiornato!
Scopri cosa c'è di nuovo nell'ultima versione:
note di rilascio di CryptPad {0}", "upgrade": "Aumenta il limite", "upgradeTitle": "Effettua l'upgrade del tuo account per incrementare il limite di spazio", "upgradeAccount": "Upgrade dell'account", @@ -92,20 +92,20 @@ "importButtonTitle": "Importa un pad da un file locale", "exportButton": "Esporta", "exportButtonTitle": "Esporta questo pad in un file locale", - "exportPrompt": "Che nome vuoi dare al file?", + "exportPrompt": "Che nome vuoi dare al tuo file?", "changeNamePrompt": "Cambia il tuo nome (lascia vuoto per essere anonimo): ", "user_rename": "Cambia il nome mostrato", "user_displayName": "Nome mostrato", - "user_accountName": "Nome dell'utente", + "user_accountName": "Nome dell'account", "clickToEdit": "Clicca per modificare", - "saveTitle": "Salva il titolo (Enter)", - "forgetButton": "Cancella", - "forgetButtonTitle": "Muovi questo pad nel cestino", - "forgetPrompt": "Cliccando OK, questo pad sarà spostato nel cestino. Sei sicuro?", + "saveTitle": "Salva il titolo (Invio)", + "forgetButton": "Elimina", + "forgetButtonTitle": "Sposta questo pad nel cestino", + "forgetPrompt": "Cliccando OK questo pad sarà spostato nel cestino. Sei sicuro?", "movedToTrash": "Questo pad è stato spostato nel cestino.
Accedi al mio Drive", "shareButton": "Condividi", - "shareSuccess": "Copiato nella clipboard", - "userListButton": "Lista degli utenti", + "shareSuccess": "Link copiato negli appunti", + "userListButton": "Elenco degli utenti", "chatButton": "Chat", "userAccountButton": "Il tuo account", "newButton": "Nuovo", @@ -117,7 +117,7 @@ "templateSaved": "Modello salvato!", "selectTemplate": "Seleziona un modello o premi Esc", "useTemplate": "Cominciare con un modello?", - "useTemplateOK": "Scegli un modello (Enter)", + "useTemplateOK": "Scegli un modello (Invio)", "useTemplateCancel": "Documento vuoto (Esc)", "template_import": "Importa un modello", "template_empty": "Nessun modello disponibile", @@ -128,33 +128,33 @@ "propertiesButton": "Proprietà", "propertiesButtonTitle": "Mostra proprietà del pad", "printText": "Stampa", - "printButton": "Stampa (Enter)", - "printButtonTitle2": "Stampa il documento o esportalo come PDF", + "printButton": "Stampa (Invio)", + "printButtonTitle2": "Stampa il tuo documento o esportalo come file PDF", "printOptions": "Opzioni di layout", "printSlideNumber": "Mostra il numero della slide", "printDate": "Mostra la data", "printTitle": "Mostra il titolo del pad", "printCSS": "Personalizza lo stile (CSS):", "printTransition": "Attiva le animazioni di transizione", - "printBackground": "Usa una immagine di sfondo", - "printBackgroundButton": "Scegli una immagine", - "printBackgroundValue": "Sfondo attuale {0}", - "printBackgroundNoValue": "Nessuno sfondo mostrato", + "printBackground": "Usa un'immagine di sfondo", + "printBackgroundButton": "Scegli un'immagine", + "printBackgroundValue": "Sfondo attuale: {0}", + "printBackgroundNoValue": "Nessuna immagine di sfondo mostrata", "printBackgroundRemove": "Rimuovi questa immagine di sfondo", - "filePickerButton": "Inserisci un file salvato in CryptDrive", + "filePickerButton": "Incorpora un file salvato in CryptDrive", "filePicker_close": "Chiudi", - "filePicker_description": "Scegli un file dal tuo CryptDrive da inserire oppure caricane uno nuovo", - "filePicker_filter": "Filtra file per nome", + "filePicker_description": "Scegli un file dal tuo CryptDrive da incorporare o caricane uno nuovo", + "filePicker_filter": "Filtra i file per nome", "or": "o", - "tags_title": "Tags (mostrati solo a te)", - "tags_add": "Aggiorna i tags di questa pagina", + "tags_title": "Tag (mostrati solo a te)", + "tags_add": "Aggiorna i tag di questa pagina", "tags_searchHint": "Inizia una ricerca con # nel tuo CryptDrive per trovare i pad taggati.", - "tags_notShared": "I tuoi tags non sono condivisi con altri utenti", + "tags_notShared": "I tuoi tag non sono condivisi con altri utenti", "tags_duplicate": "Duplica tag: {0}", - "tags_noentry": "Non puoi taggare un pad cancellato!", + "tags_noentry": "Non puoi taggare un pad eliminato!", "slideOptionsText": "Opzioni", "slideOptionsTitle": "Personalizza la presentazione", - "slideOptionsButton": "Salva (Enter)", + "slideOptionsButton": "Salva (Invio)", "slide_invalidLess": "Stile personalizzato non valido", "languageButton": "Lingua", "languageButtonTitle": "Seleziona la lingua da utilizzare per l'evidenziazione della sintassi", @@ -164,37 +164,37 @@ "editShareTitle": "Copia il link per modifica nella clipboard", "editOpen": "Apri il link per modifica in una nuova finestra", "editOpenTitle": "Apri questo pad in modalità modifica in una nuova finestra", - "viewShare": "Link solo lettura", - "viewShareTitle": "Copia il link solo lettura nella clipboard", - "viewOpen": "Apri il link solo lettura in una nuova finestra", - "viewOpenTitle": "Apri questo pad in modalità solo lettura in una nuova finestra", + "viewShare": "Link per sola lettura", + "viewShareTitle": "Copia il link per sola lettura negli appunti", + "viewOpen": "Apri il link per sola lettura in una nuova scheda", + "viewOpenTitle": "Apri questo pad in modalità di sola lettura in una nuova scheda", "fileShare": "Copia il link", "getEmbedCode": "Mostra il codice per embedding", "viewEmbedTag": "Per fare l'embed di questo pad, includi questo iframe nella tua pagina dovunque tu voglia. Puoi modificarne lo stile con gli attributi HTML o CSS.", - "fileEmbedTitle": "Fai l'embed di questo file in una pagina esterna", + "fileEmbedTitle": "Incorpora il file in una pagina esterna", "fileEmbedScript": "Per fare l'embed di questo file, includi questo script una volta nella tua pagina per caricare il Media Tag:", "fileEmbedTag": "Quindi posiziona il Media Tag dovunque tu voglia nella tua pagina:", - "notifyJoined": "{0} si è ricollegato alla sessione collaborativa", + "notifyJoined": "{0} si è unito alla sessione collaborativa", "notifyRenamed": "{0} ha cambiato il suo nome in {1}", "notifyLeft": "{0} ha abbandonato la sessione collaborativa", "ok": "OK", - "okButton": "OK (Enter)", + "okButton": "OK (Invio)", "cancel": "Annulla", - "cancelButton": "Cancella (Esc)", - "show_help_button": "Mostra l'aiuto", - "hide_help_button": "Nascondi l'aiuto", - "help_button": "Aiuto", + "cancelButton": "Annulla (Esc)", + "show_help_button": "Mostra la guida", + "hide_help_button": "Nascondi la guida", + "help_button": "Guida", "historyText": "Cronologia", - "historyButton": "Mostra la cronologia di questo documento", + "historyButton": "Mostra la cronologia del documento", "history_next": "Versione più recente", - "history_prev": "Versione più vecchia", + "history_prev": "Versione meno recente", "history_loadMore": "Carica più cronologia", "history_closeTitle": "Chiudi la cronologia", - "history_restoreTitle": "Ripristina la versione selezionata di questo documento", - "history_restorePrompt": "Sei sicuro di voler ripristinare la versione corrente con quella selezionata?", + "history_restoreTitle": "Ripristina la versione selezionata del documento", + "history_restorePrompt": "Sei sicuro di voler sostituire la versione corrente del documento con quella visualizzata?", "history_restoreDone": "Documento ripristinato", "history_version": "Versione:", - "openLinkInNewTab": "Apri link in una nuova pagina", + "openLinkInNewTab": "Apri link in una nuova scheda", "pad_mediatagTitle": "Opzioni del Media-Tag", "pad_mediatagWidth": "Larghezza (px)", "pad_mediatagHeight": "Altezza (px)", @@ -211,12 +211,12 @@ "kanban_deleteBoard": "Sei sicuro di voler cancellare questa tabella?", "kanban_addBoard": "Aggiungi una tabella", "kanban_removeItem": "Rimuovi questo elemento", - "kanban_removeItemConfirm": "Sei sicuro di voler cancellare questo elemento?", + "kanban_removeItemConfirm": "Sei sicuro di voler eliminare questo elemento?", "poll_title": "Selettore di date Zero Knowledge", "poll_subtitle": "Pianificazione Zero Knowledge, in tempo reale", - "poll_p_save": "Le tue impostazioni sono aggiornata istantaneamente, quindi non c'è bisogno di salvarle.", + "poll_p_save": "Le tue impostazioni sono aggiornate istantaneamente, quindi non c'è bisogno di salvarle.", "poll_p_encryption": "Tutto ciò che scrivi è criptato, quindi solo chi ha il link può accedervi. Nemmeno il server può vedere quello che cambi.", - "wizardLog": "Premi sul bottone in alto a sinistra per tornare al tuo sondaggio", + "wizardLog": "Clicca il pulsante in alto a sinistra per tornare al tuo sondaggio", "wizardTitle": "Usa l'assistente per creare il tuo sondaggio", "wizardConfirm": "Sei sicuro di voler aggiungere queste opzioni al tuo sondaggio?", "poll_publish_button": "Pubblica", @@ -233,13 +233,13 @@ "poll_wizardAddTimeButton": "+ Orari", "poll_optionPlaceholder": "Opzione", "poll_userPlaceholder": "Il tuo nome", - "poll_removeOption": "Sei sicuro di voler cancellare questa opzione?", + "poll_removeOption": "Sei sicuro di voler eliminare questa opzione?", "poll_removeUser": "Sei sicuro di voler rimuovere questo utente?", "poll_titleHint": "Titolo", - "poll_descriptionHint": "Descrivi il tuo sondaggio e usa il bottone ✓ (Pubblica) quando hai finito.\nLa descrizione può essere scritta usando la sintassi markdown e può contenere elementi media dal tuo CryptDrive.\nChiunque sia in possesso del link può modificarne la descrizione, anche se ciò è sconsigliato.", + "poll_descriptionHint": "Descrivi il tuo sondaggio e usa il pulsante ✓ (Pubblica) quando hai finito.\nLa descrizione può essere scritta usando la sintassi markdown e può contenere elementi media dal tuo CryptDrive.\nChiunque sia in possesso del link può modificarne la descrizione, anche se ciò è sconsigliato.", "poll_remove": "Rimuovi", "poll_edit": "Modifica", - "poll_locked": "Chiuso", + "poll_locked": "Bloccato", "poll_unlocked": "Sbloccato", "poll_bookmark_col": "Marca questa colonna come preferita, così sarà sempre sbloccata e ti sarà mostrata per prima", "poll_bookmarked_col": "Questa è la tua colonna preferita. Sarà sempre sbloccata e ti verrà mostrata per prima.", @@ -247,27 +247,27 @@ "poll_comment_list": "Commenti", "poll_comment_add": "Aggiungi un commento", "poll_comment_submit": "Invia", - "poll_comment_remove": "Cancella questo commento", + "poll_comment_remove": "Elimina questo commento", "poll_comment_placeholder": "Il tuo commento", - "poll_comment_disabled": "Pubblica questo sondaggio usando il bottone ✓ per attivare i commenti.", - "oo_reconnect": "La connessione al server è tornata. Premi OK per ricaricare e continuare la modifica.", + "poll_comment_disabled": "Pubblica questo sondaggio usando il pulsante ✓ per attivare i commenti.", + "oo_reconnect": "La connessione al server è tornata. Clicca OK per ricaricare e continuare la modifica.", "oo_cantUpload": "Impossibile importare un documento se altri utenti sono presenti.", - "oo_uploaded": "Il tuo upload è stato completato. Premi OK per ricaricare la pagina o cancella per continuare in modalità solo lettura.", + "oo_uploaded": "Il tuo caricamento è stato completato. Clicca OK per ricaricare la pagina o annulla per continuare in modalità solo lettura.", "canvas_clear": "Pulisci", - "canvas_delete": "Cancella la selezione", + "canvas_delete": "Elimina la selezione", "canvas_disable": "Disattiva il disegno", "canvas_enable": "Attiva il disegno", "canvas_width": "Larghezza", "canvas_opacity": "Opacità", "canvas_opacityLabel": "Opacità: {0}", "canvas_widthLabel": "Larghezza: {0}", - "canvas_saveToDrive": "Salva l'immagine come file nel tuo CryptDrive", + "canvas_saveToDrive": "Salva questa immagine come file nel tuo CryptDrive", "canvas_currentBrush": "Pennello attuale", "canvas_chooseColor": "Scegli un colore", - "canvas_imageEmbed": "Inserisci una immagine dal tuo computer", + "canvas_imageEmbed": "Incorpora un'immagine dal tuo computer", "profileButton": "Profilo", "profile_urlPlaceholder": "URL", - "profile_namePlaceholder": "Nome mostrato nel profilo", + "profile_namePlaceholder": "Nome mostrato nel tuo profilo", "profile_avatar": "Avatar", "profile_upload": " Carica un nuovo avatar", "profile_uploadSizeError": "Errore: il tuo avatar deve essere più piccolo di {0}", @@ -285,16 +285,16 @@ "contacts_rejected": "Invito ai contatti rifiutato", "contacts_request": "{0} vorrebbe aggiungerti ai contatti. Accettare?", "contacts_send": "Invia", - "contacts_remove": "RImuovi questo contatto", + "contacts_remove": "Rimuovi questo contatto", "contacts_confirmRemove": "Sei sicuro di voler rimuovere {0} dai tuoi contatti?", "contacts_typeHere": "Scrivi un messaggio qui...", - "contacts_warning": "Tutto ciò che scrivi qui è permanente e disponibile a tutti gli user esistenti e futuri di questo pad. Sii prudente con le tue informazioni personali!", + "contacts_warning": "Tutto ciò che scrivi qui è permanente e disponibile a tutti gli utenti esistenti e futuri di questo pad. Sii prudente con le tue informazioni personali!", "contacts_padTitle": "Chat", "contacts_info1": "Questi sono i tuoi contatti. Da qui, puoi:", "contacts_info2": "Clicca sull'icona di un contatto per chattare con lui", - "contacts_info3": "Doppio-click sulla sua icona per vederne il profilo", + "contacts_info3": "Doppio-clic sulla sua icona per vederne il profilo", "contacts_info4": "Qualsiasi partecipante può cancellare definitivamente la cronologia di una chat", - "contacts_removeHistoryTitle": "Cancellare la cronologia della chat", + "contacts_removeHistoryTitle": "Cancella la cronologia della chat", "contacts_confirmRemoveHistory": "Sei sicuro di voler rimuovere permanentemente la cronologia della chat? I dati non possono essere recuperati", "contacts_removeHistoryServerError": "C'è stato un errore nella cancellazione della cronologia della chat. Prova di nuovo più tardi", "contacts_fetchHistory": "Recupera messaggi precedenti", @@ -313,11 +313,11 @@ "fm_searchName": "Cerca", "fm_recentPadsName": "Pad recenti", "fm_ownedPadsName": "Pad posseduti", - "fm_tagsName": "Tags", + "fm_tagsName": "Tag", "fm_sharedFolderName": "Cartella condivisa", "fm_searchPlaceholder": "Cerca...", "fm_newButton": "Nuovo", - "fm_newButtonTitle": "Crea un nuovo pad o cartella, importa un file in questa cartella", + "fm_newButtonTitle": "Crea un nuovo pad o cartella, importa un file nella cartella corrente", "fm_newFolder": "Nuova cartella", "fm_newFile": "Nuovo pad", "fm_folder": "Cartella", @@ -348,28 +348,28 @@ "fm_selectError": "Impossibile selezionare l'elemento desiderato. Se il problema persiste, prova a ricaricare la pagina.", "fm_categoryError": "Impossibile aprire la categoria selezionata, mostro Documenti.", "fm_info_root": "Crea quante cartelle desideri per contenere tutti i tuoi file.", - "fm_info_unsorted": "Contiene tutto i file che hai visitato che non sono stati ancora salvati in \"Documenti\" o spostati nel \"Cestino\".", + "fm_info_unsorted": "Contiene tutti i file che hai visitato che non sono stati ancora salvati in \"Documenti\" o spostati nel \"Cestino\".", "fm_info_template": "Contiene tutti i pad salvati come modelli e che puoi riutilizzare per creare nuovi pad.", - "fm_info_recent": "Lista dei pad recentemente aperti o modificati.", + "fm_info_recent": "Questi pad sono stati recentemente aperti o modificati da te o da persone con le quali tu collabori.", "fm_info_trash": "Svuota il tuo cestino per liberare spazio nel tuo CryptDrive.", "fm_info_allFiles": "Contiene tutti i file da \"Documenti\", \"Non catalogati\", \"Cestino\". Non puoi muovere o cancellare file da qui.", "fm_info_anonymous": "Non hai effettuato l'accesso, quindi i tuoi pad scadranno fra tre mesi (scopri di più). Sono conservati nel tuo browser, quindi cancellando la cronologia potresti farli scomparire.
Registrati o Accedi per conservarli permanentemente.
", - "fm_info_sharedFolder": "Questa è una cartella condivisa. Non sei loggato, quindi puoi accederla solo in modalità solo lettura.
Registrati o Effettua il login per poterla importare nel tuo CryptDrive e per modificarla.", - "fm_info_owned": "Sei il proprietario dei pad mostrati qui. Questo significa che puoi rimuoverli permanentemente dal server, quando lo desideri. Se lo fai, gli altri utenti non potranno più accedervi.", + "fm_info_sharedFolder": "Questa è una cartella condivisa. Non hai effettuato l'accesso, quindi puoi visualizzarla solo in modalità di sola lettura.
Registrati o Accedi per poterla importare nel tuo CryptDrive e per modificarla.", + "fm_info_owned": "Sei il proprietario dei pad mostrati qui. Questo significa che puoi rimuoverli permanentemente dal server quando lo desideri. Se lo fai, gli altri utenti non potranno più accedervi.", "fm_alert_backupUrl": "Link di backup per questo drive.
Èestremamente raccomandato che tu lo tenga segreto.
Puoi usarlo per recuperare tutti i tuoi file nel caso in cui la memoria del tuo browser venga cancellata.
Chiunque in possesso di questo link può modificare o rimuovere tutti i file nel tuo file manager.
", "fm_backup_title": "Link di backup", "fm_nameFile": "Come vuoi chiamare questo file?", - "fm_error_cantPin": "Errore interno del server. Per favore, ricarica la pagina e prova di nuovo.", - "fm_viewListButton": "Visualizzazione lista", + "fm_error_cantPin": "Errore interno del server. Ricarica la pagina e prova di nuovo.", + "fm_viewListButton": "Visualizzazione elenco", "fm_viewGridButton": "Visualizzazione griglia", "fm_renamedPad": "Hai impostato un nome personalizzato per questo pad. Il suo titolo condiviso è:
{0}", "fm_canBeShared": "Questa cartella può essere condivisa", - "fm_prop_tagsList": "Tags", - "fm_burnThisDriveButton": "Cancella tutte le informazioni salvate da CryptPad nel tuo browser", + "fm_prop_tagsList": "Tag", + "fm_burnThisDriveButton": "Elimina tutte le informazioni salvate da CryptPad nel tuo browser", "fm_burnThisDrive": "Sei sicuro di voler rimuovere tutti i dati salvati da CryptPad nel tuo browser?
Questo rimuoverà il tuo CryptDrive e la sua cronologia dal tuo browser, ma i tuoi pad continueranno a esistere (criptati) nel nostro server.", "fm_padIsOwned": "Sei il proprietario di questo pad", - "fm_padIsOwnedOther": "Il proprietario di questo pad è un altro user", - "fm_deletedPads": "Questi pads non esistono più sul server, sono stati rimossi dal tuo CryptDrive: {0}", + "fm_padIsOwnedOther": "Questo pad è di proprietà di un altro utente", + "fm_deletedPads": "Questi pad non esistono più sul server, sono stati rimossi dal tuo CryptDrive: {0}", "fm_tags_name": "Nome del tag", "fm_tags_used": "Numero di utilizzi", "fm_restoreDrive": "Ripristina il tuo drive ad uno stato precedente. Per i migliori risultati, evita di fare cambiamenti al tuo drive sinchè questo processo non sarà completato.", @@ -379,45 +379,45 @@ "fc_newsharedfolder": "Nuova cartella condivisa", "fc_rename": "Rinomina", "fc_open": "Apri", - "fc_open_ro": "Apri (solo lettura)", - "fc_delete": "Muovi nel cestino", - "fc_delete_owned": "Cancella dal server", + "fc_open_ro": "Apri (sola lettura)", + "fc_delete": "Sposta nel cestino", + "fc_delete_owned": "Elimina dal server", "fc_restore": "Ripristina", "fc_remove": "Rimuovi dal tuo CryptDrive", "fc_remove_sharedfolder": "Rimuovi", "fc_empty": "Svuota il cestino", "fc_prop": "Proprietà", - "fc_hashtag": "Tags", - "fc_sizeInKilobytes": "Dimensione in Kilobytes", - "fo_moveUnsortedError": "Non puoi muovere una cartella nella lista dei modelli", - "fo_existingNameError": "Il nome è già utilizzato in questa cartella. Per favore, scegline un altro.", - "fo_moveFolderToChildError": "Non puoi muovere una cartella in una contenuta in essa", - "fo_unableToRestore": "Impossibile ripristinare questo file nella sua location originaria. Puoi provare a muoverlo in una nuova posizione.", - "fo_unavailableName": "Un file o una cartella con lo stesso nome esiste già nella nuova posizione. Rinomina l'elemento e prova di nuovo.", - "fs_migration": "Il tuo CryptDrive sta per essere aggiornato a una nuova versione. La pagina corrente sarà ricaricata.
Per favore, ricarica la pagina per continuare a usarla.", - "login_login": "Login", - "login_makeAPad": "Crea un pad in maniera anonima", - "login_nologin": "Sfoglia i pads locali", + "fc_hashtag": "Tag", + "fc_sizeInKilobytes": "Dimensione in Kilobyte", + "fo_moveUnsortedError": "Non puoi spostare una cartella nell'elenco dei modelli", + "fo_existingNameError": "Il nome è già utilizzato in questa cartella. Scegline un altro.", + "fo_moveFolderToChildError": "Non puoi spostare una cartella in una contenuta in essa", + "fo_unableToRestore": "Impossibile ripristinare questo file nella sua posizione originaria. Puoi provare a spostarlo in una nuova posizione.", + "fo_unavailableName": "Un file o una cartella con lo stesso nome esiste già nella nuova posizione. Rinomina l'elemento e riprova.", + "fs_migration": "Il tuo CryptDrive sta per essere aggiornato ad una nuova versione. La pagina corrente sarà ricaricata.
Ricarica la pagina per continuare ad usarla.", + "login_login": "Accedi", + "login_makeAPad": "Crea un pad in modalità anonima", + "login_nologin": "Sfoglia i pad locali", "login_register": "Registrati", - "logoutButton": "Logout", + "logoutButton": "Esci", "settingsButton": "Impostazioni", "login_username": "Nome utente", "login_password": "Password", "login_confirm": "Conferma la tua password", - "login_remember": "Ricorda l'accesso", + "login_remember": "Ricordami", "login_hashing": "Effettuo l'hash della tua password, questo può richiedere del tempo.", - "login_hello": "Buongiorno {0},", + "login_hello": "Ciao {0},", "login_helloNoName": "Ciao,", "login_accessDrive": "Accedi al tuo drive", "login_orNoLogin": "o", - "login_noSuchUser": "Nome utente o password non validi. Prova di nuovi, oppure registrati", + "login_noSuchUser": "Nome utente o password non validi. Prova di nuovo, oppure registrati", "login_invalUser": "Nome utente richiesto", "login_invalPass": "Password richiesta", "login_unhandledError": "Si è verificato un errore inaspettato :(", - "register_importRecent": "Importa i pads dalla tua sessione anonima", + "register_importRecent": "Importa i pad dalla tua sessione anonima", "register_acceptTerms": "Accetto i termini del servizio", - "register_passwordsDontMatch": "Le passwords non corrispondono!", - "register_passwordTooShort": "Le passwords devono essere lunghe almeno {0} caratteri.", + "register_passwordsDontMatch": "Le password non corrispondono!", + "register_passwordTooShort": "Le password devono essere lunghe almeno {0} caratteri.", "register_mustAcceptTerms": "Devi accettare i termini del servizio.", "register_mustRememberPass": "Non possiamo ripristinare la tua password se la dimentichi. È estremamente importante che tu la ricordi! Per favore, spunta la checkbox per confermare.", "register_whyRegister": "Perché registrarsi?", @@ -426,7 +426,7 @@ "register_writtenPassword": "Ho annotato il mio nome utente e la mia password, procedi", "register_cancel": "Torna indietro", "register_warning": "Zero Knowledge significa che non possiamo recuperare i tuoi dati se perdi la tua password.", - "register_alreadyRegistered": "Questo utente esiste già, vuoi effettuare il log in?", + "register_alreadyRegistered": "Questo utente esiste già, vuoi accedere?", "settings_cat_account": "Account", "settings_cat_drive": "CryptDrive", "settings_cat_cursor": "Cursore", @@ -440,59 +440,59 @@ "settings_backupHint": "Effettua il backup o importane uno con tutto il contenuto del tuo CryptDrive. Non conterrà il contenuto dei pads, solo le chiavi per potervi accedere.", "settings_backup": "Backup", "settings_restore": "Importa", - "settings_backupHint2": "Scarica il contenuto corrente dei tuoi pads. I pads saranno scaricati in un formato leggibile, se possibile.", + "settings_backupHint2": "Scarica il contenuto corrente dei tuoi pad. I pad saranno scaricati in un formato leggibile, se possibile.", "settings_backup2": "Scarica il mio CryptDrive", - "settings_backup2Confirm": "Questo scaricherà tutti i tuoi pad e files dal tuo CryptDrive. Se vuoi continuare, scegli un nome e premi OK", + "settings_backup2Confirm": "Questo scaricherà tutti i tuoi pad e file dal tuo CryptDrive. Se vuoi continuare, scegli un nome e premi OK", "settings_exportTitle": "Esporta il tuo CryptDrive", "settings_exportDescription": "Per favore attendi mentre scarichiamo e decriptiamo i tuoi documenti. Potrebbe richiedere qualche minuto. Chiudere la finestra interromperà il processo.", - "settings_exportFailed": "Se il pad richiede più di un minuto per essere scaricato, non sarà incluso nell'export. Un link a qualsiasi pad non esportato sarà mostrato.", - "settings_exportWarning": "Nota bene: questo strumento è ancora in versione beta e può presentare problemi di scalabilità. Per migliorare le prestazioni, è consigliabile lasciare attiva questa tab.", - "settings_exportCancel": "Sei sicuro di voler cancellare l'export? Dovrai iniziare da capo la prossima volta.", + "settings_exportFailed": "Se un pad richiede più di un minuto per essere scaricato, non sarà incluso nell'esportazione. Un link a qualsiasi pad non esportato sarà mostrato.", + "settings_exportWarning": "Nota bene: questo strumento è ancora in versione beta e può presentare problemi di scalabilità. Per migliorare le prestazioni, è consigliabile lasciare attiva questa scheda.", + "settings_exportCancel": "Sei sicuro di voler annullare l'esportazione? Dovrai iniziare da capo la prossima volta.", "settings_export_reading": "Lettura del tuo CryptDrive in corso...", "settings_export_download": "Scaricamento e decriptazione dei tuoi documenti in corso...", "settings_export_compressing": "Compressione dei dati in corso...", "settings_export_done": "Il tuo download è pronto!", - "settings_exportError": "Visualizza errori", - "settings_exportErrorDescription": "Non siamo riusciti ad aggiungere i seguenti documenti all'export:", - "settings_exportErrorEmpty": "Questo documento non può essere esportato (contenuto vuoto o invalido).", + "settings_exportError": "Visualizza gli errori", + "settings_exportErrorDescription": "Non siamo riusciti ad aggiungere i seguenti documenti all'esportazione:", + "settings_exportErrorEmpty": "Questo documento non può essere esportato (contenuto vuoto o non valido).", "settings_exportErrorMissing": "Questo documento non è stato trovato nei nostri server (scaduto o rimosso dal suo proprietario)", - "settings_exportErrorOther": "È accaduto un errore durante l'esportazione di questo documento: {0}", + "settings_exportErrorOther": "Si è verificato un errore durante l'esportazione di questo documento: {0}", "settings_resetNewTitle": "Pulisci CryptDrive", "settings_resetButton": "Rimuovi", "settings_reset": "Rimuovi tutti i file e le cartelle dal tuo CryptDrive", - "settings_resetPrompt": "Questa azione eliminerà tutti i pad del tuo drive.
Sei sicuro di vole continuare?
Digita “I love CryptPad” per confermare.", - "settings_resetDone": "Il tuo drive è vuoto adesso!", + "settings_resetPrompt": "Questa azione eliminerà tutti i pad dal tuo drive.
Sei sicuro di vole continuare?
Digita “I love CryptPad” per confermare.", + "settings_resetDone": "Il tuo drive adesso è vuoto!", "settings_resetError": "Testo di verifica errato. Il tuo CryptDrive non è stato modificato.", "settings_resetTipsAction": "Ripristinare", "settings_resetTips": "Suggerimenti", "settings_resetTipsButton": "Ripristinare i suggerimenti disponibili in CryptDrive", "settings_resetTipsDone": "Ora tutti i suggerimenti sono di nuovo visibili.", "settings_thumbnails": "Miniature", - "settings_disableThumbnailsAction": "Disattivare la creazione di miniature nel tuo CryptDrive", - "settings_disableThumbnailsDescription": "Le miniature sono create automaticamente e conservate nel tuo browser. Puoi disattivare questa funzione qui.", + "settings_disableThumbnailsAction": "Disattiva la creazione di miniature nel tuo CryptDrive", + "settings_disableThumbnailsDescription": "Le miniature sono create automaticamente e conservate nel tuo browser quando visiti un nuovo pad. Qui puoi disattivare questa funzione.", "settings_resetThumbnailsAction": "Pulire", - "settings_resetThumbnailsDescription": "Pulire tutte le miniature conservate nel browser.", - "settings_resetThumbnailsDone": "Tutte le miniature sono state cancellate.", + "settings_resetThumbnailsDescription": "Pulisci tutte le miniature dei pad conservate nel tuo browser.", + "settings_resetThumbnailsDone": "Tutte le miniature sono state eliminate.", "settings_import": "Importa", - "settings_importConfirm": "Sei sicuro di voler importare i tuoi pads recenti dal browser sul tuo account CryptDrive?", + "settings_importConfirm": "Sei sicuro di voler importare i tuoi pad recenti da questo browser sul tuo account CryptDrive?", "settings_autostoreMaybe": "Manuale (chiedi sempre)", - "settings_userFeedbackTitle": "Feedbacks", + "settings_userFeedbackTitle": "Feedback", "settings_userFeedbackHint1": "CryptPad fornisce solo alcuni feedback basilari al server, per aiutarci a migliorare la tua esperienza. ", - "settings_userFeedbackHint2": "Il contenuto dei tuoi pads non sarà mai condiviso con il server.", - "settings_userFeedback": "Abilita feedback dell'user", - "settings_deleteTitle": "Cancella account", - "settings_deleteHint": "La cancellazione dell'account è permanente. Il tuo CryptDrive e la tua lista di pads sarà cancellata dal server. Il resto dei tuoi pads sarà cancellato in 90 giorni se nessun altro li ha salvati nel suo CryptDrive.", + "settings_userFeedbackHint2": "Il contenuto del tuo pad non sarà mai condiviso con il server.", + "settings_userFeedback": "Abilita feedback dell'utente", + "settings_deleteTitle": "Cancellazione account", + "settings_deleteHint": "La cancellazione dell'account è permanente. Il tuo CryptDrive e il tuo elenco di pad sarà cancellato dal server. Il resto dei tuoi pad sarà eliminato in 90 giorni se nessun altro li ha salvati nel suo CryptDrive.", "settings_deleteButton": "Cancella il tuo account", "padNotPinnedVariable": "Questo pad scadrà dopo {4} giorni di inattività, {0}accedi{1} o {2}registrati{3} per conservarlo.", "storageStatus": "Spazio:
12{0}3 usati di 4{1}5", - "uploadFolderButton": "Cartella upload", + "uploadFolderButton": "Carica cartella", "fm_morePads": "Altro", "fc_color": "Cambia colore", "fc_openInCode": "Apri nell'editor di codice", "fc_expandAll": "Espandi tutto", "fc_collapseAll": "Comprimi tutto", "register_emailWarning0": "Sembra che tu abbia inserito la tua email come nome utente.", - "settings_driveDuplicateHint": "Quando sposti i tuoi pad in una cartella condivisa, una copia è conservata nel tuo CryptDrive per assicurartene il controllo. Puoi nascondere i file duplicati. Soltanto la versione condivisa verrà mostrata, a meno che non venga eliminata: in tal caso il file originale verrà mostrato nella sua posizione precedente.", + "settings_driveDuplicateHint": "Quando sposti i tuoi pad in una cartella condivisa, una copia è conservata nel tuo CryptDrive per assicurartene il controllo. Puoi nascondere i file duplicati. Soltanto la versione condivisa verrà mostrata, a meno che non venga eliminata, in tal caso il file originale verrà mostrato nella sua posizione precedente.", "settings_codeIndentation": "Indentazione dell'editor di codice (spazi)", "settings_codeFontSize": "Dimensione del testo dell'editor di codice", "uploadFolder_modal_filesPassword": "Password dei file", @@ -501,29 +501,55 @@ "upload_mustLogin": "Devi eseguire l'accesso per poter caricare file", "pad_base64": "Questo pad contiene immagini conservate in maniera inefficiente. Queste immagini aumenteranno significativamente le dimensioni del pad nel tuo CryptDrive, e lo renderanno più lento da caricare. Puoi convertire questi file in un nuovo formato che verrà conservato separatamente nel tuo CryptDrive. Vuoi convertire queste immagini ora?", "mdToolbar_code": "Codice", - "home_host": "Questa è un'istanza di CryptPad gestita indipendentemente dalla community. Il suo codice sorgente è disponibile su GitHub2.", - "whatis_drive_p3": "Puoi anche caricare file nel tuo CryptDrive e condividerli coi colleghi. I file caricati possono essere organizzati proprio come i pad collaborativi.", + "home_host": "Questa è un'istanza di CryptPad gestita indipendentemente dalla community. Il suo codice sorgente è disponibile su GitHub.", + "whatis_drive_p3": "Puoi anche caricare file nel tuo CryptDrive e condividerli con i colleghi. I file caricati possono essere organizzati proprio come i pad collaborativi.", "policy_choices_open": "Il nostro codice sorgente è open source, così hai sempre la possibilità di ospitare la tua personale istanza di CryptPad.", - "features_f_file0": "Apri file", + "features_f_file0": "Apri i file", "help": { "slide": { - "markdown": "Scrivi diapositive in Markdown e separale con una linea contente ---" + "markdown": "Scrivi diapositive in Markdown e separale con una linea contente ---", + "present": "Inizia la presentazione usando il pulsante ", + "colors": "Cambia i colori di testo e sfondo usando i pulsanti e " + }, + "pad": { + "export": "Puoi esportare il contenuto come PDF usando il pulsante nella barra degli strumenti di formattazione" + }, + "text": { + "history": "Puoi usare la cronologia per vedere o ripristinare le versioni precedenti", + "embed": "Gli utenti registrati possono incorporare un'immagine o un file salvato nel loro CryptDrive usando ", + "formatting": "Puoi visualizzare o nascondere la barra degli strumenti di formattazione cliccando i pulsanti o " + }, + "generic": { + "save": "Tutte le tue modifiche sono sincronizzate automaticamente, quindi non hai bisogno di salvare", + "share": "Usa il menu condividi () per generare un link che permetta ai tuoi collaboratori di vedere o modificare questo pad", + "more": "Impara di più su cosa può fare CryptPad per te leggendo le nostre FAQ" + }, + "title": "Cominciamo", + "oo": { + "access": "L'accesso è riservato agli utenti registrati, i collaboratori devono accedere" + }, + "whiteboard": { + "colors": "Fai doppio clic sui colori per modificare la tua palette" + }, + "poll": { + "submit": "Clicca invia per rendere le tue scelte visibili agli altri" } }, "readme_cat3_l1": "Con l'editor di codice di CryptPad, puoi collaborare su linguaggi di programmazione come Javascript e linguaggi di markup come HTML o Markdown", - "settings_codeSpellcheckLabel": "Abilita la revisione ortografica nell'editor di codice", + "settings_codeSpellcheckLabel": "Abilita il controllo ortografico nell'editor di codice", "team_inviteLinkError": "Si è verificato un errore durante la creazione del link.", "register_emailWarning1": "Puoi farlo se vuoi, ma non verrà inviato ai nostri server.", "register_emailWarning2": "Non sarai in grado di resettare la tua password usando la tua email, a differenza di come puoi fare con molti altri servizi.", "register_emailWarning3": "Se hai capito, ma intendi comunque usare la tua email come nome utente, clicca OK.", - "oo_sheetMigration_anonymousEditor": "Le modifiche da parte di utenti anonimi a questo foglio di calcolo sono disabilitate finchè un utente registrato non lo aggiorna all'ultima versione.", + "oo_sheetMigration_anonymousEditor": "Le modifiche da parte di utenti anonimi a questo foglio di calcolo sono disabilitate finché un utente registrato non lo aggiorna all'ultima versione.", "faq": { "usability": { "devices": { - "a": "È probabile che tu abbia registrato lo stesso nome due volte, usando password diverse. Poiché il server di CryptPad ti identifica usando la tua firma crittografica e non il tuo nome, non può impedire ad altre persone di registrarsi con lo stesso nome. Questo significa che ogni account ha una combinazione univoca di nome utente e password. Gli/le utenti registrati/e possono vedere il loro nome utente nella parte superiore della pagina delle impostazioni." + "a": "È probabile che tu abbia registrato lo stesso nome due volte, usando password diverse. Poiché il server di CryptPad ti identifica usando la tua firma crittografica e non il tuo nome, non può impedire ad altre persone di registrarsi con lo stesso nome. Questo significa che ogni account utente ha una combinazione univoca di nome utente e password. Gli utenti connessi possono vedere il loro nome utente nella parte superiore della pagina delle impostazioni.", + "q": "Sono connesso con due dispositivi e vedo due diversi CryptDrive, com'è possibile?" }, "forget": { - "a": "Purtroppo, se fossimo in grado di aiutarti a recuperare l'accesso ai tuoi pad criptati, saremmo noi stessi/e in grado di accedere ai tuoi pad. Se non hai salvato il tuo nome utente e password da nessuna parte e non resci a ricordarli, potresti essere in grado di recuperare i tuoi pad controllando la cronologia del browser.", + "a": "Purtroppo, se fossimo in grado di aiutarti a recuperare l'accesso ai tuoi pad criptati, saremmo noi stessi in grado di accedere ai tuoi pad. Se non hai salvato il tuo nome utente e la password da nessuna parte e non riesci a ricordarli, potresti essere in grado di recuperare i tuoi pad controllando la cronologia del browser.", "q": "Cosa succede se dimentico la mia password?" }, "change": { @@ -531,23 +557,43 @@ "q": "E se voglio modificare la mia password?" }, "register": { - "q": "Cosa ottengo registrandomi?" + "q": "Cosa ottengo registrandomi?", + "a": "Gli utenti registrati hanno accesso ad alcune funzionalità non disponibili a chi non è registrato. C'è uno schema qui." }, - "title": "Usabilità" + "title": "Usabilità", + "feature": { + "a": "Molte delle funzionalità in CryptPad esistono perché gli utenti le hanno richieste. La nostra pagina di contatto elenca i modi che puoi usare per raggiungerci.

Sfortunatamente, non possiamo garantire che aggiungeremo tutto quello che la gente chiede. Se una particolare funzionalità è essenziale per la tua organizzazione, puoi sponsorizzare il tempo di sviluppo per garantire il suo completamento. Contatta sales@cryptpad.fr per ulteriori informazioni.

Anche se non puoi permetterti di sponsorizzare lo sviluppo, siamo interessati ai feedback che possono aiutarci a migliorare CryptPad. Sentiti libero di contattarci tramite uno dei modi indicati in qualsiasi momento.", + "q": "Potete aggiungere una funzionalità particolare di cui ho bisogno?" + }, + "folder": { + "a": "Si, puoi creare una cartella condivisa nel tuo CryptDrive e così condividere tutti i pad che contiene.", + "q": "Posso condividere intere cartelle dal mio CryptDrive?" + }, + "remove": { + "a": "Solo i pad proprietari (introdotto nel febbraio 2018) possono essere cancellati. In aggiunta, questi pad possono essere cancellati solo dal loro proprietario (la persona che inizialmente ha creato il pad). Se non sei il creatore del pad, dovrai chiedere al suo titolare di cancellarlo. Per i pads di cui sei titolare, puoi cliccare con il tasto destro il pad nel tuo CryptDrive, e scegliere Cancella dal server.", + "q": "Ho rimosso un pad o un file dal mio CryptDrive, ma il contenuto è ancora disponibile. Come posso rimuoverlo?" + }, + "share": { + "a": "CryptPad mette la chiave di crittografia segreta sul tuo pad dopo il carattere # nell'URL. Tutto quello che segue questo carattere non viene inviato al server, quindi non abbiamo accesso alla tua chiave di crittografia. Condividendo il link a un pad condividi anche la possibilità di accedervi e leggerlo.", + "q": "Come posso condividere i pad crittografati con i miei contatti?" + } }, "security": { "crypto": { - "a": "CryptPad è basato su due librerie open-source di crittografia: tweetnacl.js and scrypt-async.js.

Scrypt è un algoritmo di derivazione delle chiavi basato su password. Lo utilizziamo per trasformare i tuoi username e password in una chiave unica che assicura l'accesso al tuo CryptDrive così che solo tu possa accedere ai tuoi pad.

Utilizziamo i crypters xsalsa20-poly1305 e x25519-xsalsa20-poly1305 forniti da tweetnacl rispettivamente per criptare i pad e la chat.", + "a": "CryptPad è basato su due librerie open-source di crittografia: tweetnacl.js e scrypt-async.js.

Scrypt è un algoritmo di derivazione delle chiavi basato su password. Lo utilizziamo per trasformare i tuoi nome utente e password in una chiave unica che assicura l'accesso al tuo CryptDrive così che solo tu possa accedere ai tuoi pad.

Utilizziamo i cifrari xsalsa20-poly1305 e x25519-xsalsa20-poly1305 forniti da tweetnacl rispettivamente per criptare i pad e la cronologia della chat.", "q": "Quale crittografia utilizzate?" }, "pad_password": { - "q": "Coda succede quando proteggo un pad/cartella con una password?" + "q": "Cosa succede quando proteggo un pad/cartella con una password?", + "a": "Puoi proteggere qualsiasi pad o cartella condivisa con una password quando li crei. Puoi anche usare il menu Proprietà in qualsiasi momento per impostare/cambiare/rimuovere una password.

Le password dei pad e delle cartelle condivise sono finalizzate a proteggere i link quando li condividi su canali potenzialmente non sicuri come la mail o i messaggi di testo. Se qualcuno intercettasse il tuo link ma non disponesse della password non sarebbe comunque in grado di leggere il tuo documento.

Quando condividi all'interno di CryptPad con i tuoi contatti o il tuo gruppo, le comunicazioni sono crittografate e noi presumiamo che tu voglia che questi accedano al tuo documento. In ogni caso la password è memorizzata ed inviata con il documento quando lo condividi. All'autore, o a te, non verrà richiesta quando si apre il documento." }, "compromised": { - "q": "CryptPad mi protegge se il mio dispositivo è compromesso?" + "q": "CryptPad mi protegge se il mio dispositivo è compromesso?", + "a": "Nel caso in cui un tuo device sia stato rubato, CryptPad ti permette di attivare il remote logout (disconnessione forzata in remoto) di tutti i device a parte quello che stai utilizzando. Per farlo, vai sulla tua settings page e clicca Disconnetti ovunque. Tutte le altre device che sono attualmente connesse al tuo account saranno disconnesse. Tutte le device che si sono connesse in precedenza al tuo CryptPad saranno sconnesse appena si collegheranno alla pagina.

Attualmente, Logout remoto è implemetato nel browser, non in connessione con il server. Quindi non può proteggerti da agenzie governative, ma può essere sufficiente se dimentichi di sconnetterti dopo aver usato CryptPad da un computer condiviso." }, "why": { - "q": "Perché dovrei usare CryptPad?" + "q": "Perché dovrei usare CryptPad?", + "a": "La nostra opinione è che i servizi cloud non debbano richiedere accesso ai tuoi dati perché tu possa condividerli con amici e colleghi. Se stai usando un altro servizio per collaborare e questo non dichiara esplicitamente che non può accedere ai tuoi dati, è verosimile che li stia gestendo per profitto." }, "title": "Sicurezza", "proof": { @@ -561,7 +607,7 @@ }, "policy": { "a": "Sì! È disponibile qui.", - "q": "Avete una politica di privacy dei dati?" + "q": "Avete una politica per la privacy dei dati?" }, "title": "Privacy", "anonymous": { @@ -570,7 +616,7 @@ }, "other": { "q": "Cosa possono conoscere di me gli altri collaboratori?", - "a": "Quando lavorate su un pad insieme ad altri, comunicate attraverso il server, l'unica informazione raccolta è il vostro indirizzo IP. Gli altri possono vedere il vostro nome utente, l'avatar, un link al vostro profilo (se ne avete uno), e la vostra chiave pubblica (che è utilizzata per criptare le informazioni dall'uno all'altro)." + "a": "Quando modifichi su un pad insieme ad altri comunichi attraverso il server, così conosciamo il tuo indirizzo IP. Gli altri utenti possono vedere il tuo nome utente, l'avatar, un link al tuo profilo (se ne hai uno), e la tua chiave pubblica (che è utilizzata per criptare le informazioni dall'uno all'altro)." }, "me": { "q": "Che informazioni ha il server su di me?", @@ -587,21 +633,31 @@ "q": "Assumete?" }, "goal": { - "q": "Qual è il vostro obiettivo?" + "q": "Qual è il vostro obiettivo?", + "a": "Sviluppando una tecnologia rispettosa della privacy, vogliamo anche incrementare le aspettative degli utenti relativamente al rapporto tra privacy e cloud computing. Speriamo che il nostro lavoro spinga altri fornitori di servizi in tutti gli ambiti ad eguagliare il nostro sforzo o superarlo. Nonostante il nostro ottimismo, sappiamo che la gran parte del web asa il suo gettito sulla pubblicità targettizzata. C'è molto più lavoro da fare di quanto possiamo pensare di gestire noi stessi, e dunque apprezziamo l'incoraggiamento, il sostegno ed il contributo della nostra community per il raggiungimento di questo obiettivo." }, "pay": { - "q": "Perché dovrei pagare quando così tante funzionalità sono gratuite?" + "q": "Perché dovrei pagare quando così tante funzionalità sono gratuite?", + "a": "Diamo ai supporters spazio di archiviazione aggiuntivo e la possibilità di incrementare la loro quota di contatti (approfondisci).

Oltre a questi benefici a breve termine, sottoscrivendo un account premium aiuti a finanziare in maniera continuativa, lo sviluppo attivo di CryptPad. Questo include l'eliminazione di bugs, l'aggiunta di nuove funzionalità, e semplificare per altri la diffusione di CryptPad. Inoltre, aiuti a mostrare agli altri service providers che le persone sono disposte a supportare le tecnologie per il miglioramento della privacy. Noi speriamo che i modelli di business basati sulla vendita dei dati degli utenti diventeranno cosa del passato.

Infine, offriamo la maggior parte delle funzionalità di CryptPad gratuitamente perché crediamo che everyone meritino la privacy, non solo chi può permettersela. Supportandoci, ci aiuti a continuare a rendere possibile alla popolazione con meno privilegi di accedere a funzionalità fondamentali senza il cartellino del prezzo attaccato." }, - "title": "Altre domande" + "title": "Altre domande", + "revenue": { + "a": "Se hai implementato la tua istanza di CryptPad, e vuoi attivare il agamento dei servizi e dividerlo con la community degli sviluppatori, il tuo server dovrà essere configurato come partner service.

Nella cartella di CryptPad, config.example.js contiene una spiegazione di cosa hai bisogno di configurare sul tuo server. Avrai anche bisogno di contattare sales@cryptpad.fr per verificare che nel tuo server sia configurato correttamente HTTPS, e per concordare i metodi di pagamento.", + "q": "Come posso contribuire alla compartecipazione alle entrate?" + }, + "host": { + "a": "Siamo lieti di fornire supporto per una installazione di CryptPad interna alla vostra organizzazione. Si prega di contattare sales@cryptpad.fr per maggiori informazioni.", + "q": "Potete aiutarmi a configurare la mia istanza di CryptPad?" + } }, "keywords": { "tag": { "q": "Come utilizzo i tag?", - "a": "Puoi taggare i file creati o caricati con il tuo CryptDrive, oppure utilizzando il pulsante tag () nella barra degli strumenti di qualsiasi editor. Cerca gli appunti ed i file nel tuo CryptDrive utilizzando la barra di ricerca con una parola marcata come hashtag, ad esempio #crypto." + "a": "Puoi taggare i pad e i file caricati con il tuo CryptDrive, oppure utilizzando il pulsante tag () nella barra degli strumenti di qualsiasi editor. Cerca i pad ed i file nel tuo CryptDrive utilizzando la barra di ricerca con una parola preceduta da un cancelletto, ad esempio #crypto." }, "pad": { "q": "Cos'è un pad?", - "a": "Pad è un termine popolare per Etherpad, un editor collaborativo in tempo reale.\nSi riferisce ad un documento che puoi modificare nel tuo browser, generalmente con le modifiche di altri utenti visibili in modo quasi istantaneo." + "a": "Pad è un termine reso popolare da Etherpad, un editor collaborativo in tempo reale.\nSi riferisce ad un documento che puoi modificare nel tuo browser, generalmente con le modifiche di altri utenti visibili in modo quasi istantaneo." }, "expiring": { "q": "Cos'è un pad effimero?", @@ -618,7 +674,7 @@ }, "template": { "q": "Cos'è un modello?", - "a": "Un template (modello) è un file che può essere usato per definire il contenuto iniziale per altri dello stesso tipo quando li crei. Qualsiasi salvataggio esistente può essere trasformato in un template spostandolo nella sezione Template del tuo CryptDrive. Puoi anche creare una copia di un pad da utilizzare come template cliccando il pulsante template () nella barra degli strumenti dell'editor." + "a": "Un modello è un pad che può essere usato per definire il contenuto iniziale per un altro pad dello stesso tipo quando lo crei. Qualsiasi pad esistente può essere trasformato in un modello spostandolo nella sezione Modelli del tuo CryptDrive. Puoi anche creare una copia di un pad da utilizzare come modello cliccando il pulsante template () nella barra degli strumenti dell'editor." } } }, @@ -630,10 +686,10 @@ "policy_whatweknow": "Cosa sappiamo di te", "policy_title": "Informativa sulla privacy di CryptPad", "policy_howweuse_p1": "Utilizziamo queste informazioni per migliorare le nostre decisioni in merito a come meglio pubblicizzare CryptPad, valutando quali dei nostri sforzi passati hanno avuto maggiore successo. Le informazioni sulla tua posizione ci permettono di sapere se dovremmo prendere in considerazione la possibilità di fornire un migliore supporto per lingue diverse dall'inglese.", - "policy_ads_p1": "Non mostriamo alcuna pubblicità online, anche se potremmo inserire collegamenti agli enti che finanziano la nostra ricerca.", + "policy_ads_p1": "Non mostriamo alcuna pubblicità online, anche se potremmo inserire link agli enti che finanziano la nostra ricerca.", "policy_ads": "Pubblicità", - "policy_links_p1": "Questo sito contiene collegamenti ad altri siti, compresi quelli prodotti da altre organizzazioni. Non siamo responsabili delle pratiche relative alla privacy o dei contenuti di siti esterni. Come regola generale, i collegamenti a siti esterni vengono aperti in una nuova finestra del browser, per chiarire che si sta lasciando CryptPad.fr.", - "policy_links": "Collegamenti ad altri siti", + "policy_links_p1": "Questo sito contiene link ad altri siti, compresi quelli prodotti da altre organizzazioni. Non siamo responsabili delle pratiche relative alla privacy o dei contenuti di siti esterni. Come regola generale, i link a siti esterni vengono aperti in una nuova finestra del browser, per chiarire che si sta lasciando CryptPad.fr.", + "policy_links": "LInk ad altri siti", "policy_whatwetell_p1": "Non forniamo a terzi le informazioni che raccogliamo o che ci fornite, a meno che non siamo legalmente obbligati a farlo.", "policy_whatwetell": "Cosa comunichiamo a terzi riguardo a te", "policy_howweuse_p2": "Le informazioni relative al tuo browser (sia che si tratti di un sistema operativo desktop che di uno mobile) ci aiutano a prendere decisioni quando si tratta di dare priorità ai miglioramenti delle funzionalità. Il nostro gruppo di sviluppo è piccolo e cerchiamo di fare scelte che migliorino l'esperienza del maggior numero di utenti possibile.", @@ -666,7 +722,7 @@ "mdToolbar_italic": "Corsivo", "mdToolbar_bold": "Grassetto", "mdToolbar_tutorial": "https://www.markdowntutorial.com/", - "mdToolbar_help": "Aiuto", + "mdToolbar_help": "Guida", "pad_hideToolbar": "Nascondi barra degli strumenti", "pad_showToolbar": "Mostra barra degli strumenti", "download_step1": "Scaricamento in corso", @@ -700,54 +756,54 @@ "settings_creationSkipTrue": "Salta", "settings_driveDuplicateLabel": "Nascondi i duplicati", "settings_logoutEverywhereTitle": "Chiudi le sessioni remote", - "settings_logoutEverywhereButton": "Uscire", + "settings_logoutEverywhereButton": "Esci", "settings_importDone": "Importazione completata", - "register_explanation": "

Puntualizziamo un paio di cose:

  • La tua password è la chiave segreta di tutti i tuoi pad. Se la perdi non c'è alcun modo di recuperare i tuoi dati.
  • Puoi importare i pad recenti di questo browser per averli nel tuo account.
  • Se usi un computer condiviso, devi fare il log out quando hai finito, non è sufficiente chiudere il tab.
", + "register_explanation": "

Puntualizziamo un paio di cose:

  • La tua password è la chiave segreta di tutti i tuoi pad. Se la perdi non c'è alcun modo di recuperare i tuoi dati.
  • Puoi importare i pad recenti di questo browser per averli nel tuo account.
  • Se usi un computer condiviso, devi uscire quando hai finito, non è sufficiente chiudere la scheda.
", "features_f_subscribe_note": "Devi prima accedere a CryptPad", "features_f_subscribe": "Abbonarsi a Premium", "features_f_supporter_note": "Aiutarci a dimostrare che i software che proteggono la privacy devono essere la norma", - "features_f_supporter": "Diventare un supporter della privacy", + "features_f_supporter": "Diventa un supporter della privacy", "features_f_support_note": "Supporto email professionale con il piano Team", "features_f_storage2_note": "Da 5GB a 50GB in funzione del piano selezionato", - "features_f_storage2": "Spazio d'archivio supplementare", + "features_f_storage2": "Spazio d'archiviazione supplementare", "features_f_reg_note": "Ed aiutare lo sviluppo di CryptPad", - "features_f_reg": "Tutte le funzioni degli utenti registrati", - "features_f_register_note": "Nessuna mail o informazione personale richieste", - "features_f_storage1_note": "I pads archiviati nel tuo CryptDrive non saranno mai eliminati per inattività", + "features_f_reg": "Tutte le funzionalità degli utenti registrati", + "features_f_register_note": "Nessuna mail o informazione personale richiesta", + "features_f_storage1_note": "I pad conservati nel tuo CryptDrive non saranno mai eliminati per inattività", "features_f_storage1": "Archivio permanente (50MB)", "features_f_social_note": "Crea un profilo, usa un avatar, chatta con i contatti", - "features_f_devices_note": "Accesso a CryptDrive dovunque con il tuo account utente", - "features_f_devices": "I tuoi pads su tutti i tuoi apparecchi", - "features_f_cryptdrive1_note": "Dossier, dossier condivisi, modelli, tags", - "features_f_cryptdrive1": "Completa funzionalità di CryptDrive", + "features_f_devices_note": "Accesso al tuo CryptDrive dovunque con il tuo account utente", + "features_f_devices": "I tuoi pad su tutti i tuoi dispositivi", + "features_f_cryptdrive1_note": "Cartelle, cartelle condivise, modelli, tag", + "features_f_cryptdrive1": "Funzionalità complete di CryptDrive", "features_f_anon_note": "Con migliore ergonomia e più controllo sui tuoi pads", - "features_f_anon": "Tutte le funzioni degli utenti anonimi", - "features_f_storage0_note": "I pads creati rischiano la cancellazione dopo 3 mesi di inattività", + "features_f_anon": "Tutte le funzionalità degli utenti anonimi", + "features_f_storage0_note": "I pad creati rischiano la cancellazione dopo 3 mesi di inattività", "features_f_storage0": "Tempo di conservazione limitato", "features_f_cryptdrive0_note": "Conservazione nel browser dei pad visitati per ritrovarli più tardi", "features_f_cryptdrive0": "Accesso limitato a CryptDrive", - "features_f_file0_note": "Guarda e scarica files condivisi da altri utenti", - "features_f_core_note": "Modifica, Importa & Esporta, Storia, Lista utenti, Chat", - "features_title": "Confronto delle funzioni", - "policy_choices_ads": "Se vuoi solamente bloccare la nostra piattaforma analitica puoi utilizzare uno strumento che blocca la pubblicità come Privacy Badger.", - "policy_choices_vpn": "Se vuoi utilizzare la nostra istanza ma non vuoi esporre il tuo indirizzo IP puoi proteggerlo usando il browser Tor, oppure un VPN.", - "policy_whatweknow_p2": "Utilizziamo Kibana, una piattaforma analitica open source, per conoscere meglio i nostri utenti. Kibana ci dice come hai trovato CryptPad, entrando direttamente, attraverso un motore di ricerca, o provenendo da un altro sito web come Reddit o Twitter.", + "features_f_file0_note": "Guarda e scarica file condivisi da altri utenti", + "features_f_core_note": "Modifica, Importa & Esporta, Cronologia, Elenco utenti, Chat", + "features_title": "Confronto delle funzionalità", + "policy_choices_ads": "Se vuoi solamente bloccare la nostra piattaforma di analisi puoi utilizzare uno strumento che blocca la pubblicità come Privacy Badger.", + "policy_choices_vpn": "Se vuoi utilizzare la nostra istanza ma non vuoi esporre il tuo indirizzo IP puoi proteggerlo usando il browser Tor, oppure una VPN.", + "policy_whatweknow_p2": "Utilizziamo Kibana, una piattaforma di analisi open source, per conoscere meglio i nostri utenti. Kibana ci dice come hai trovato CryptPad, entrando direttamente, attraverso un motore di ricerca o provenendo da un altro sito web come Reddit o Twitter.", "policy_whatweknow_p1": "Come applicazione ospitata sul web, CryptPad ha accesso a metadata esposti dal protocollo HTTP. Questo include il tuo indirizzo IP e altre intestazioni HTTP che possono essere usati per identificare il tuo browser. Puoi vedere quali informazioni condivide il tuo browser visitandoWhatIsMyBrowser.com.", "whatis_business_p2": "Si può installare CryptPad in sede e gli sviluppatori CryptPad di XWiki SAS possono offrire supporto commerciale, personalizzazione e sviluppo. Contattaci su sales@cryptpad.fr per maggiori informazioni.", "whatis_business_p1": "La crittografia Zero Knowledge di CryptPad moltiplica l'efficacia dei protocolli di sicurezza esistenti ricreandoli in maniera crittografica. Dato che i dati sensibili possono essere decrittati solo utilizzando le credenziali di accesso dell'utente, CryptPad è meno hackerabile dei servizi cloud tradizionali. Consulta il CryptPad Whitepaper per sapere come questo possa aiutare la tua impresa.", - "whatis_drive_p1": "Quando accedi a un pad in CryptPad il pad è automaticamente aggiunto al tuo CryptoDrive nel dossier principale. In seguito potrai organizzare questi pad in dossier o cestinarli. CryptPad ti permette di fare una ricerca tra i tuoi pad e organizzarli quando e come vuoi.", + "whatis_drive_p1": "Quando accedi a un pad in CryptPad il pad è automaticamente aggiunto al tuo CryptDrive nella cartella principale. In seguito potrai organizzare questi pad in cartelle o cestinarli. CryptPad ti permette di fare una ricerca tra i tuoi pad e di organizzarli quando e come vuoi.", "whatis_zeroknowledge": "Zero Knowledge", "whatis_zeroknowledge_p3": "Quando condividi il link di un documento, stai condividendo la chiave crittografica per accedere a quel documento, ma dato che la chiave è nelidentificatore di frammenti, non è mai inviata direttamente al server. Consulta il nostro post nel bloig privacy per capire a quali tipi di metadata abbiamo accesso ed a quali no.", "features_f_core": "Funzioni comuni delle applicazioni", "features_f_apps": "Accesso alle applicazioni principali", - "features_feature": "Funzione", - "features": "Funzioni", + "features_feature": "Funzionalità", + "features": "Funzionalità", "policy_choices": "Le tue scelte", "whatis_drive": "Organizzazione con CryptDrive", "whatis_zeroknowledge_p1": "Non vogliamo sapere cosa stai scrivendo e, grazie alla moderna crittografia, puoi essere certo che non possiamo farlo. CryptPad utilizza crittografia 100% lato clientper proteggere il contenuto che stai scrivendo da noi, che ospitiamo il server.", "whatis_collaboration_p3": "Puoi creare dei documenti di testo con CKEditor così come documenti Markdown in tempo reale mentre scrivi. Puoi anche usare l'applicazione di sondaggio per pianificare eventi con più partecipanti.", "whatis_collaboration_p2": "Puoi condividere l'accesso ad un documento CryptPad semplicemente condividendo il link. Puoi anche condividere un link che fornisce un accesso di sola lettura al pad, permettendoti di pubblicare i tuoi lavori collaborativi potendo ancora modificarli.", - "whatis_collaboration_p1": "Con CryptPad, puoi creare velocemente documenti collaborativi per prendere appunti e scrivere idee insieme. Quando ti registri e ti connetti ottieni la possibilità di importare files in un CryptDrive dove puoi organizzare tutti i tuoi pads. Come utente registrato hai 50 MB di spazio gratuito.", + "whatis_collaboration_p1": "Con CryptPad, puoi creare velocemente documenti collaborativi per prendere appunti e scrivere idee insieme. Quando ti registri e accedi ottieni la possibilità di caricare file in un CryptDrive dove puoi organizzare tutti i tuoi pad. Come utente registrato hai 50 MB di spazio gratuito.", "whatis_collaboration": "Collaborazione veloce, facile", "terms": "Condizioni", "main_footerText": "Con CryptPad, puoi creare velocemente documenti collaborativi per prendere appunti e scrivere idee insieme.", @@ -771,55 +827,55 @@ "todo_title": "CryptTodo", "download_step2": "Decodifica", "download_button": "Decodifica & Scarica", - "upload_up": "Importa", - "upload_cancelled": "Cancellato", - "upload_tooLarge": "Questo file supera la dimensione massima di importazione autorizzata per il tuo account.", + "upload_up": "Carica", + "upload_cancelled": "Annullato", + "upload_tooLarge": "Questo file supera la dimensione massima di caricamento permessa per il tuo account.", "upload_notEnoughSpace": "Non c'è spazio sufficiente per questo file nel tuo CryptDrive.", "upload_success": "Il tuo file ({0}) è stato caricato con successo e aggiunto al tuo drive.", - "upload_serverError": "Errore del server: attualmente impossibile importare il file.", - "uploadFolder_modal_title": "Opzione di importazione del folder", + "upload_serverError": "Errore del server: impossibile caricare il file in questo momento.", + "uploadFolder_modal_title": "Opzione di caricamento cartelle", "upload_modal_owner": "File posseduto", "upload_modal_filename": "Nome del file (extension {0} aggiunta automaticamente)", - "settings_cursorShowHint": "Puoi decidere se vuoi vedere la posizione del tuo cursore degli altri utenti nei documenti collaborativi.", + "settings_cursorShowHint": "Puoi decidere se vuoi vedere la posizione del cursore degli altri utenti nei documenti collaborativi.", "settings_cursorShareHint": "Puoi decidere se vuoi mostrare la posizione del tuo cursore agli altri utenti nei documenti collaborativi.", - "settings_cursorColorHint": "Cambiare il colore associato al tuo utente nei documenti collaborativi.", + "settings_cursorColorHint": "Cambia il colore associato al tuo utente nei documenti collaborativi.", "settings_changePasswordPending": "La tua password è in fase di modifica. Non chiudere o ricaricare questa pagina fino al completamento del processo.", - "settings_changePasswordError": "Errore non previsto. Se non riesci ad accedere o cambiare la tua password contatta il tuo amministratore CryptPad.", - "settings_changePasswordConfirm": "Sei sicuro di voler cambiare la tua password? Dovrai riconnetterti su tutti i tuoi apparecchi.", - "settings_changePasswordHint": "Modifica la password del tuo account. Inserisci la tua password attuale e conferma la nuova password inserendola due volte.
Non possiamo reinstallare la tua password se la dimentichi, quindi sii molto prudente!", + "settings_changePasswordError": "Si è verificato un errore imprevisto. Se non riesci ad accedere o cambiare la tua password contatta il tuo amministratore CryptPad.", + "settings_changePasswordConfirm": "Sei sicuro di voler cambiare la tua password? Dovrai accedere nuovamente su tutti i tuoi dispositivi.", + "settings_changePasswordHint": "Modifica la password del tuo account. Inserisci la tua password attuale e conferma la nuova password inserendola due volte.
Non possiamo reimpostare la tua password se la dimentichi, quindi sii molto prudente!", "settings_ownDrivePending": "Il tuo account è stato aggiornato. Non chiudere o ricaricare questa pagina prima che sia stato completato il processo.", - "settings_ownDriveConfirm": "Aggiornare il tuo account potrebbe richiedere tempo. Dovrai riconnetterti su tutti i tuoi apparecchi. Vuoi continuare?", + "settings_ownDriveConfirm": "Aggiornare il tuo account potrebbe richiedere tempo. Dovrai accedere nuovamente su tutti i tuoi dispositivi. Vuoi continuare?", "settings_ownDriveHint": "Gli account più vecchi non hanno accesso alle ultime funzioni, per motivi tecnici. Un aggiornamento gratuito permetterà di attivare le funzioni attuali e preparare il tuo CryptDrive per futuri aggiornamenti.", "settings_templateSkipHint": "Quando crei un nuovo pad vuoto, se hai dei modelli per questo tipo di pad, può apparire una finestra con la richiesta di utilizzare un modello. Qui puoi scegliere di non mostrare mai questa finestra e quindi di non utilizzare mai un modello.", - "settings_templateSkip": "Saltare la schermata di scelta di un modello", - "settings_creationSkipFalse": "Mostrare", - "settings_creationSkipHint": "La schermata di creazione di pad offre nuove opzioni per creare un pad, fornendo maggior controllo e sicurezza per i tuoi dati. Tuttavia potrebbe rallentare il lavoro aggiungendo un passaggio supplementare quindi, qui, hai la possibilità di scegliere di saltare la schermata e utilizzare i parametri di default selezionati sopra.", - "settings_creationSkip": "Salta la schermata di creazione di pad", + "settings_templateSkip": "Salta la schermata di scelta di un modello", + "settings_creationSkipFalse": "Mostra", + "settings_creationSkipHint": "La schermata di creazione del pad offre nuove opzioni per creare un pad, fornendo maggior controllo e sicurezza per i tuoi dati. Tuttavia potrebbe rallentare il lavoro aggiungendo un passaggio supplementare quindi, qui, hai la possibilità di scegliere di saltare la schermata e utilizzare i parametri predefiniti selezionati sopra.", + "settings_creationSkip": "Salta la schermata di creazione del pad", "settings_padSpellcheckLabel": "Attiva la verifica ortografica", - "settings_padSpellcheckHint": "Questa opzione permette di attivare la verifica ortografica nell'editor di testo. Gli errori saranno sottolineati in rosso e le correzioni saranno disponibili cliccando a destra sul mouse e premendo il tasto Ctrl o Meta.", - "settings_padSpellcheckTitle": "Verifica ortografica", + "settings_padSpellcheckHint": "Questa opzione permette di attivare il controllo ortografico nell'editor di testo. Gli errori saranno sottolineati in rosso e le correzioni saranno disponibili cliccando con il tasto destro del mouse e premendo il tasto Ctrl o Meta.", + "settings_padSpellcheckTitle": "Controllo ortografico", "settings_padWidthLabel": "Ridurre la larghezza dell'editor", "settings_padWidthHint": "I pad di testo occupano di default la massima larghezza disponibile sullo schermo e la lettura può essere difficile. Potete ridurre la larghezza dell'editor qui.", "settings_padWidth": "Massima larghezza dell'editor", - "settings_codeUseTabs": "Rientrare usando tabs (invece di spazi)", + "settings_codeUseTabs": "Indentare usando le tabulazioni (al posto degli spazi)", "settings_driveDuplicateTitle": "Duplicati dei pad di cui sei proprietario", - "settings_logoutEverywhereConfirm": "Sei sicuro? Dovrai riconnetterti su tutti i tuoi apparecchi.", - "settings_logoutEverywhere": "Forzare l'uscita da tutte le altre sessioni web", + "settings_logoutEverywhereConfirm": "Sei sicuro? Dovrai accedere su tutti i tuoi dispositivi.", + "settings_logoutEverywhere": "Forza l'uscita da tutte le altre sessioni web", "settings_usageAmount": "I tuoi pads appuntati occupano {0}MB", "settings_pinningError": "Qualcosa è andato storto", "settings_pinningNotAvailable": "I pads appuntati sono disponibili solo per gli utenti registrati.", "settings_usageTitle": "Guarda la quantità totale dei tuoi pads appuntati in MB", "settings_usage": "Utilizzo", "settings_publicSigningKey": "Firma digitale pubblica", - "settings_anonymous": "Non sei collegato. La configurazione è specifica per questo browser.", - "settings_deleted": "Il tuo user account è stato cancellato. Premi OK per tornare alla home page.", + "settings_anonymous": "Non sei connesso. Queste impostazioni sono specifiche per questo browser.", + "settings_deleted": "Il tuo account utente è stato cancellato. Premi OK per tornare alla home page.", "settings_deleteModal": "Condividi le seguenti informazioni con il tuo amministratore CryptPad per rimuovere i tuoi dati dal server.", - "settings_deleteConfirm": "Cliccando OK cancellerai il tuo account. Vuoi continuare?", + "settings_deleteConfirm": "Cliccando OK cancellerai il tuo account definitivamente. Sei sicuro?", "settings_autostoreHint": "Automatico Tutti i pad che visiti sono conservati nel tuo CryptDrive.
Manuale (chiedi sempre) Se non hai ancora conservato alcun pad ti verrà chiesto se vuoi conservarli nel tuo CryptDrive.
Manuale (non chiedere mai) I Pads non sono conservati automaticamente nel tuo Cryptpad. L'opzione di conservarli sarà nascosta.", "settings_autostoreNo": "Manuale (non chiedere mai)", "settings_autostoreYes": "Automatico", "settings_autostoreTitle": "Conservazione pad nel CryptDrive", - "settings_importTitle": "Importare i pad recenti di questo browser nel tuo CryptDrive", + "settings_importTitle": "Importa i pad recenti di questo browser nel tuo CryptDrive", "kanban_noTags": "Nessun tag", "kanban_tags": "Filtra per tag", "kanban_delete": "Elimina", @@ -949,5 +1005,99 @@ "sharedFolders_create_name": "Nome della cartella", "share_contactCategory": "Contatti", "share_linkCopy": "Copia", - "share_linkOpen": "Anteprima" + "share_linkOpen": "Anteprima", + "header_homeTitle": "Vai alla homepage di CryptPad", + "header_logoTitle": "Vai al tuo CryptDrive", + "updated_0_header_logoTitle": "Vai al tuo CryptDrive", + "four04_pageNotFound": "Non riusciamo a trovare la pagina che stai cercando.", + "tos_3rdparties": "Non forniamo dati individuali a terze parti a meno che non sia richiesto dalla legge.", + "tos_logs": "I metadati forniti dal tuo browser al server possono essere registrati allo scopo di mantenere il servizio.", + "tos_e2ee": "I contenuti di CryptPad possono essere letti e modificati scopra od ottenga in qualsiasi modo la porzione di pad che funge da identificativo. Raccomandiamo di usare una messaggistica con crittografia end-to-end (e2ee) per condividere link, e non ci assumiamo nessuna responsabilità nel caso in cui questo link sia diffuso.", + "tos_availability": "Speriamo che tu trovi utile questo servizio, ma la disponibilità e le performance non possono essere garantite. Esporta con regolarità i tuoi dati.", + "tos_legal": "Per favore non essere dannoso, improprio, o fare qualsiasi cosa illegale.", + "tos_title": "Termini di servizio di CryptPad", + "support_close": "Chiudi il ticket", + "profile_login": "Devi accedere per aggiungere questo utente ai tuoi contatti", + "creation_404": "Questo pad non esiste più. Usa il modulo seguente per creare un nuovo pad.", + "historyTrim_historySize": "Cronologia: {0}", + "trimHistory_button": "Elimina cronologia", + "trimHistory_success": "La cronologia è stata eliminata", + "trimHistory_needMigration": "Aggiorna il tuo CryptDrive per abilitare questa funzionalità.", + "trimHistory_currentSize": "Dimensione attuale della cronologia: {0}", + "settings_trimHistoryTitle": "Elimina cronologia", + "profile_copyKey": "Copia la chiave pubblica", + "admin_openFilesTitle": "Apri i file", + "canvas_select": "Seleziona", + "logoutEverywhere": "Esci ovunque", + "driveReadmeTitle": "Cos'è CryptPad?", + "readme_welcome": "Benvenuto in CryptPad!", + "edit": "modifica", + "readme_cat2_l2": "Modifica il titolo del pad cliccando sulla matita", + "readme_cat3": "Scopri le app di CryptPad", + "tips": { + "tags": "Tagga i tuoi pad e inizia una ricerca con # nel tuo CryptDrive per trovarli", + "shortcuts": "`ctrl+b`, `ctrl+i` e `ctrl+u`sono le scorciatoie per grassetto, corsivo e sottolineato.", + "indent": "Negli elenchi numerati e puntati puoi usare tab o shift+tab per aumentare o diminuire velocemente l'indentazione.", + "filenames": "Puoi rinominare i file nel tuo CryptDrive, questo nome è solo per te.", + "profile": "Gli utenti registrati possono creare un profilo dal menu utente in alto a destra.", + "avatars": "Puoi caricare un avatar nel tuo profilo. Le persone lo vedranno quando collabori in un pad." + }, + "creation_passwordValue": "Password", + "creation_propertiesTitle": "Disponibilità", + "password_submit": "Invia", + "password_show": "Mostra", + "properties_changePassword": "Modifica la password", + "share_linkCategory": "Link", + "share_linkEdit": "Modifica", + "share_linkView": "Visualizza", + "contact_devHint": "Per la richiesta di funzionalità, miglioramenti all'usabilità o per dire grazie.", + "admin_diskUsageTitle": "Utilizzo del disco", + "friendRequest_received": "{0} vorrebbe diventare un tuo contatto", + "share_linkFriends": "Condividi con i contatti", + "admin_cat_support": "Supporto", + "support_cat_new": "Nuovo ticket", + "support_formTitle": "Titolo del ticket", + "support_cat_tickets": "Ticket esistenti", + "support_listTitle": "Ticket di supporto", + "support_showData": "Mostra/nascondi i dati dell'utente", + "notifications_cat_friends": "Richieste di contatto", + "notifications_dismissAll": "Ignora tutte", + "team_cat_create": "Nuovo", + "team_cat_back": "Torna ai team", + "owner_addTeamText": "...o un team", + "contacts_unmute": "Suona", + "contacts_mutedUsers": "Account silenziati", + "creation_noOwner": "Nessun proprietario", + "creation_appMenuName": "Modalità avanzata (Ctrl + E)", + "password_placeholder": "Scrivi qui la password...", + "properties_addPassword": "Aggiungi una password", + "properties_passwordSame": "La nuova password deve essere diversa da quella attuale.", + "properties_passwordError": "Si è verificato un errore nel tentativo di modificare la password. Riprova.", + "properties_changePasswordButton": "Invia", + "share_linkAccess": "Diritti d'accesso", + "sharedFolders_create": "Crea una cartella condivisa", + "settings_codeSpellcheckTitle": "Controllo ortografico", + "drive_activeOld": "Pad meno recenti", + "supportPage": "Supporto", + "admin_supportAddKey": "Aggiungi chiave privata", + "support_formHint": "Questo modulo può essere usato per creare un nuovo ticket per il supporto. Usalo per contattare gli amministratori per risolvere problemi o fare domande in modo sicuro. Non creare un nuovo ticket se hai già un ticket aperto per lo stesso problema ma usa il pulsante rispondi per fornire maggiori informazioni.", + "support_remove": "Rimuovi il ticket", + "support_closed": "Il ticket è stato chiuso", + "notifications_cat_archived": "Cronologia", + "support_notification": "Un amministratore ha risposto al tuo ticket per il supporto", + "team_listLoad": "Apri", + "passwordFaqLink": "Leggi di più sulle password", + "contacts_mute": "Silenzia", + "contacts_manageMuted": "Gestisci silenziati", + "contacts_muteInfo": "Non riceverai alcuna notifica o messaggio dagli utenti silenziati.
Loro non sapranno di essere stati silenziati. ", + "team_inviteEnterPassword": "Inserisci la password dell'invito per continuare.", + "burnAfterReading_generateLink": "Clicca sul pulsante sottostante per generare un link.", + "burnAfterReading_proceed": "visualizza ed elimina", + "oo_sheetMigration_complete": "Versione aggiornata disponibile, premi OK per ricaricare.", + "readme_cat2": "Realizza pad come un professionista", + "view": "visualizza", + "timeoutError": "Un errore ha interrotto la tua connessione al server.
Premi Esc per ricaricare la pagina.", + "support_disabledTitle": "Il supporto non è abilitato", + "support_listHint": "Qui c'è l'elenco dei ticket inviati agli amministratori e delle loro risposte. Un ticket chiuso non può essere riaperto ma puoi farne uno nuovo. Puoi nascondere i ticket che sono stati chiusi.", + "creation_ownedTitle": "Tipo di pad" } From 4ce980f4068e7c8bf0857eacdc846559f93c2fce Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 27 Mar 2020 22:22:18 +0100 Subject: [PATCH 17/18] Translated using Weblate (Spanish) Currently translated at 67.3% (839 of 1246 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/es/ --- www/common/translations/messages.es.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/www/common/translations/messages.es.json b/www/common/translations/messages.es.json index ac040aca1..83ccb01e5 100644 --- a/www/common/translations/messages.es.json +++ b/www/common/translations/messages.es.json @@ -864,7 +864,7 @@ "title": "Otras preguntas", "pay": { "q": "¿ Porque debería de pagar cuando hay características que son gratis ?", - "a": "Brindamos a los seguidores almacenamiento adicional y la capacidad de aumentar las cuotas de sus amigos ( obtener más información ).

Más allá de estos beneficios a corto plazo, al suscribirse con una cuenta premium, usted ayuda a financiar el desarrollo continuo y activo de CryptPad. Eso incluye corregir errores, agregar nuevas funciones y facilitar que otros ayuden a alojar CryptPad ellos mismos. Además, ayuda a demostrar a otros proveedores de servicios que las personas están dispuestas a apoyar las tecnologías que mejoran la privacidad. Esperamos que eventualmente los modelos de negocio basados en la venta de datos de usuarios se conviertan en cosa del pasado.

Finalmente, ofrecemos la mayor parte de la funcionalidad de CryptPad de forma gratuita porque creemos que todos merecen privacidad personal, no solo aquellos con desechables. ingresos. Al apoyarnos, nos ayuda a continuar haciendo posible que las poblaciones desfavorecidas accedan a estas funciones básicas sin una etiqueta de precio adjunta." + "a": "Brindamos a los seguidores almacenamiento adicional y la capacidad de aumentar las cuotas de sus contactos ( obtener más información ).

Más allá de estos beneficios a corto plazo, al suscribirse con una cuenta premium, usted ayuda a financiar el desarrollo continuo y activo de CryptPad. Eso incluye corregir errores, agregar nuevas funciones y facilitar que otros ayuden a alojar CryptPad ellos mismos. Además, ayuda a demostrar a otros proveedores de servicios que las personas están dispuestas a apoyar las tecnologías que mejoran la privacidad. Esperamos que eventualmente los modelos de negocio basados en la venta de datos de usuarios se conviertan en cosa del pasado.

Finalmente, ofrecemos la mayor parte de la funcionalidad de CryptPad de forma gratuita porque creemos que todos merecen privacidad personal, no solo aquellos con desechables. ingresos. Al apoyarnos, nos ayuda a continuar haciendo posible que las poblaciones desfavorecidas accedan a estas funciones básicas sin una etiqueta de precio adjunta." }, "goal": { "q": "¿Cuál es tu objetivo?", @@ -916,7 +916,7 @@ "settings": "Configura las diapositivas (fondo, transiciones, números de página, etc.) con el botón en el submenú " }, "poll": { - "decisions": "Crea decisiones en privado entre verdaderos amigos", + "decisions": "Crea decisiones en privado entre contactos de confianza", "options": "Proponga opciones y exprese sus preferencias", "choices": "Haga clic en las celdas de su columna para recorrer sí (), tal vez ( ~ ) o no ()", "submit": "Haga clic en enviar para que otras personas puedan ver sus opciones" @@ -927,8 +927,8 @@ "embed": "Incrusta imágenes de tu disco o de tu CryptDrive y expórtalas como PNG a tu disco o a tu CryptDrive " }, "kanban": { - "add": "Añada nuevas placas usando el botón en la esquina superior derecha", - "task": "Mover objetos arrastrándolos y soltándolos de un tablero a otro", + "add": "Añada nuevas fichas y cuadros usando el botón ", + "task": "Mover objetos arrastrándolos y soltándolos, arrastrándolo al basurero para borarlo", "color": "Cambie los colores haciendo clic en la parte coloreada junto a los títulos de los tableros" } }, From 6d0dee979a95bfd8bbb435c7a6e45209e705d641 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 27 Mar 2020 19:59:26 -0400 Subject: [PATCH 18/18] allow admins to inspect index/metadata caches --- lib/commands/admin-rpc.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index a7cb59798..32e9b9947 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -19,6 +19,26 @@ var getFileDescriptorLimit = function (env, server, cb) { Ulimit(cb); }; +var getCacheStats = function (env, server, cb) { + var metaCount = 0; + var channelCount = 0; + + var meta = env.metadata_cache; + for (var x in meta) { + if (meta.hasOwnProperty(x)) { metaCount++; } + } + + var channels = env.channel_cache; + for (var y in channels) { + if (channels.hasOwnProperty(y)) { channelCount++; } + } + + cb(void 0, { + metadata: metaCount, + channel: channelCount, + }); +}; + var getActiveSessions = function (Env, Server, cb) { var stats = Server.getSessionStats(); cb(void 0, [ @@ -137,6 +157,7 @@ var commands = { GET_FILE_DESCRIPTOR_COUNT: getFileDescriptorCount, GET_FILE_DESCRIPTOR_LIMIT: getFileDescriptorLimit, SET_DEFAULT_STORAGE_LIMIT: setDefaultStorageLimit, + GET_CACHE_STATS: getCacheStats, }; Admin.command = function (Env, safeKey, data, _cb, Server) {