@ -25,9 +25,9 @@ var sumChannelSizes = function (sizes) {
// FIXME it's possible for this to respond before the server has had a chance
// to fetch the limits. Maybe we should respond with an error...
// or wait until we actually know the limits before responding
var getLimit = Pinning . getLimit = function ( Env , public Key, cb ) {
var un escapedKey = unescapeKeyCharacters ( public Key) ;
var limit = Env . limits [ un escaped Key] ;
var getLimit = Pinning . getLimit = function ( Env , safe Key, cb ) {
var un safeKey = unescapeKeyCharacters ( safe Key) ;
var limit = Env . limits [ un safe Key] ;
var defaultLimit = typeof ( Env . defaultStorageLimit ) === 'number' ?
Env . defaultStorageLimit : Core . DEFAULT _LIMIT ;
@ -37,32 +37,89 @@ var getLimit = Pinning.getLimit = function (Env, publicKey, cb) {
cb ( void 0 , toSend ) ;
} ;
const answerDeferred = function ( Env , channel , bool ) {
const pending = Env . pendingPinInquiries ;
const stack = pending [ channel ] ;
if ( ! Array . isArray ( stack ) ) { return ; }
delete pending [ channel ] ;
stack . forEach ( function ( cb ) {
cb ( void 0 , bool ) ;
} ) ;
} ;
var addPinned = function (
Env ,
publicKey /*:string*/ ,
safe Key /*:string*/ ,
channelList /*Array<string>*/ ,
cb /*:()=>void*/ )
{
Env . evPinnedPadsReady . reg ( ( ) => {
channelList . forEach ( ( c ) => {
const x = Env . pinnedPads [ c ] = Env . pinnedPads [ c ] || { } ;
x [ publicKey ] = 1 ;
} ) ;
channelList . forEach ( function ( channel ) {
Pins . addUserPinToState ( Env . pinnedPads , safeKey , channel ) ;
answerDeferred ( Env , channel , true ) ;
} ) ;
cb ( ) ;
} ;
const isEmpty = function ( obj ) {
if ( ! obj || typeof ( obj ) !== 'object' ) { return true ; }
for ( var key in obj ) {
if ( obj . hasOwnProperty ( key ) ) { return true ; }
}
return false ;
} ;
const deferUserTask = function ( Env , safeKey , deferred ) {
const pending = Env . pendingUnpins ;
( pending [ safeKey ] = pending [ safeKey ] || [ ] ) . push ( deferred ) ;
} ;
const runUserDeferred = function ( Env , safeKey ) {
const pending = Env . pendingUnpins ;
const stack = pending [ safeKey ] ;
if ( ! Array . isArray ( stack ) ) { return ; }
delete pending [ safeKey ] ;
stack . forEach ( function ( cb ) {
cb ( ) ;
} ) ;
} ;
const runRemainingDeferred = function ( Env ) {
const pending = Env . pendingUnpins ;
for ( var safeKey in pending ) {
runUserDeferred ( Env , safeKey ) ;
}
} ;
const removeSelfFromPinned = function ( Env , safeKey , channelList ) {
channelList . forEach ( function ( channel ) {
const channelPinStatus = Env . pinnedPads [ channel ] ;
if ( ! channelPinStatus ) { return ; }
delete channelPinStatus [ safeKey ] ;
if ( isEmpty ( channelPinStatus ) ) {
delete Env . pinnedPads [ channel ] ;
}
} ) ;
} ;
var removePinned = function (
Env ,
publicKey /*:string*/ ,
safe Key /*:string*/ ,
channelList /*Array<string>*/ ,
cb /*:()=>void*/ )
{
Env . evPinnedPadsReady . reg ( ( ) => {
channelList . forEach ( ( c ) => {
const x = Env . pinnedPads [ c ] ;
if ( ! x ) { return ; }
delete x [ publicKey ] ;
} ) ;
// if pins are already loaded then you can just unpin normally
if ( Env . pinsLoaded ) {
removeSelfFromPinned ( Env , safeKey , channelList ) ;
return void cb ( ) ;
}
// otherwise defer until later...
deferUserTask ( Env , safeKey , function ( ) {
removeSelfFromPinned ( Env , safeKey , channelList ) ;
cb ( ) ;
} ) ;
} ;
@ -100,24 +157,24 @@ var getMultipleFileSize = function (Env, channels, cb) {
} ;
const batchUserPins = BatchRead ( "LOAD_USER_PINS" ) ;
var loadUserPins = function ( Env , public Key, cb ) {
var session = Core . getSession ( Env . Sessions , public Key) ;
var loadUserPins = function ( Env , safe Key, cb ) {
var session = Core . getSession ( Env . Sessions , safe Key) ;
if ( session . channels ) {
return cb ( session . channels ) ;
}
batchUserPins ( public Key, cb , function ( done ) {
batchUserPins ( safe Key, cb , function ( done ) {
var ref = { } ;
var lineHandler = Pins . createLineHandler ( ref , function ( label , data ) {
Env . Log . error ( label , {
log : public Key,
log : safe Key,
data : data ,
} ) ;
} ) ;
// if channels aren't in memory. load them from disk
Env . pinStore . getMessages ( public Key, lineHandler , function ( ) {
Env . pinStore . getMessages ( safe Key, lineHandler , function ( ) {
// no more messages
// only put this into the cache if it completes
@ -133,27 +190,27 @@ var truthyKeys = function (O) {
} ) ;
} ;
var getChannelList = Pinning . getChannelList = function ( Env , public Key, _cb ) {
var getChannelList = Pinning . getChannelList = function ( Env , safe Key, _cb ) {
var cb = Util . once ( Util . mkAsync ( _cb ) ) ;
loadUserPins ( Env , public Key, function ( pins ) {
loadUserPins ( Env , safe Key, function ( pins ) {
cb ( truthyKeys ( pins ) ) ;
} ) ;
} ;
const batchTotalSize = BatchRead ( "GET_TOTAL_SIZE" ) ;
Pinning . getTotalSize = function ( Env , public Key, cb ) {
var un escapedKey = unescapeKeyCharacters ( public Key) ;
var limit = Env . limits [ un escaped Key] ;
Pinning . getTotalSize = function ( Env , safe Key, cb ) {
var un safeKey = unescapeKeyCharacters ( safe Key) ;
var limit = Env . limits [ un safe Key] ;
// Get a common key if multiple users share the same quota, otherwise take the public key
var batchKey = ( limit && Array . isArray ( limit . users ) ) ? limit . users . join ( '' ) : public Key;
var batchKey = ( limit && Array . isArray ( limit . users ) ) ? limit . users . join ( '' ) : safe Key;
batchTotalSize ( batchKey , cb , function ( done ) {
var channels = [ ] ;
var bytes = 0 ;
nThen ( function ( waitFor ) {
// Get the channels list for our user account
Pinning. getChannelList( Env , public Key, waitFor ( function ( _channels ) {
getChannelList( Env , safe Key, waitFor ( function ( _channels ) {
if ( ! _channels ) {
waitFor . abort ( ) ;
return done ( 'INVALID_PIN_LIST' ) ;
@ -163,7 +220,7 @@ Pinning.getTotalSize = function (Env, publicKey, cb) {
// Get the channels list for users sharing our quota
if ( limit && Array . isArray ( limit . users ) && limit . users . length > 1 ) {
limit . users . forEach ( function ( key ) {
if ( key === un escaped Key) { return ; } // Don't count ourselves twice
if ( key === un safe Key) { return ; } // Don't count ourselves twice
getChannelList ( Env , key , waitFor ( function ( _channels ) {
if ( ! _channels ) { return ; } // Broken user, don't count their quota
Array . prototype . push . apply ( channels , _channels ) ;
@ -207,10 +264,10 @@ Pinning.trimPins = function (Env, safeKey, cb) {
cb ( "NOT_IMPLEMENTED" ) ;
} ;
var getFreeSpace = Pinning . getFreeSpace = function ( Env , public Key, cb ) {
getLimit ( Env , public Key, function ( e , limit ) {
var getFreeSpace = Pinning . getFreeSpace = function ( Env , safe Key, cb ) {
getLimit ( Env , safe Key, function ( e , limit ) {
if ( e ) { return void cb ( e ) ; }
Pinning . getTotalSize ( Env , public Key, function ( e , size ) {
Pinning . getTotalSize ( Env , safe Key, function ( e , size ) {
if ( typeof ( size ) === 'undefined' ) { return void cb ( e ) ; }
var rem = limit [ 0 ] - size ;
@ -236,20 +293,20 @@ var hashChannelList = function (A) {
return hash ;
} ;
var getHash = Pinning . getHash = function ( Env , public Key, cb ) {
getChannelList ( Env , public Key, function ( channels ) {
var getHash = Pinning . getHash = function ( Env , safe Key, cb ) {
getChannelList ( Env , safe Key, function ( channels ) {
cb ( void 0 , hashChannelList ( channels ) ) ;
} ) ;
} ;
Pinning . pinChannel = function ( Env , public Key, channels , cb ) {
Pinning . pinChannel = function ( Env , safe Key, channels , cb ) {
if ( ! channels && channels . filter ) {
return void cb ( 'INVALID_PIN_LIST' ) ;
}
// get channel list ensures your session has a cached channel list
getChannelList ( Env , public Key, function ( pinned ) {
var session = Core . getSession ( Env . Sessions , public Key) ;
getChannelList ( Env , safe Key, function ( pinned ) {
var session = Core . getSession ( Env . Sessions , safe Key) ;
// only pin channels which are not already pinned
var toStore = channels . filter ( function ( channel ) {
@ -257,42 +314,42 @@ Pinning.pinChannel = function (Env, publicKey, channels, cb) {
} ) ;
if ( toStore . length === 0 ) {
return void getHash ( Env , public Key, cb ) ;
return void getHash ( Env , safe Key, cb ) ;
}
getMultipleFileSize ( Env , toStore , function ( e , sizes ) {
if ( typeof ( sizes ) === 'undefined' ) { return void cb ( e ) ; }
var pinSize = sumChannelSizes ( sizes ) ;
getFreeSpace ( Env , public Key, function ( e , free ) {
getFreeSpace ( Env , safe Key, function ( e , free ) {
if ( typeof ( free ) === 'undefined' ) {
Env . WARN ( 'getFreeSpace' , e ) ;
return void cb ( e ) ;
}
if ( pinSize > free ) { return void cb ( 'E_OVER_LIMIT' ) ; }
Env . pinStore . message ( public Key, JSON . stringify ( [ 'PIN' , toStore , + new Date ( ) ] ) ,
Env . pinStore . message ( safe Key, JSON . stringify ( [ 'PIN' , toStore , + new Date ( ) ] ) ,
function ( e ) {
if ( e ) { return void cb ( e ) ; }
toStore . forEach ( function ( channel ) {
session . channels [ channel ] = true ;
} ) ;
addPinned ( Env , public Key, toStore , ( ) => { } ) ;
getHash ( Env , public Key, cb ) ;
addPinned ( Env , safe Key, toStore , ( ) => { } ) ;
getHash ( Env , safe Key, cb ) ;
} ) ;
} ) ;
} ) ;
} ) ;
} ;
Pinning . unpinChannel = function ( Env , public Key, channels , cb ) {
Pinning . unpinChannel = function ( Env , safe Key, channels , cb ) {
if ( ! channels && channels . filter ) {
// expected array
return void cb ( 'INVALID_PIN_LIST' ) ;
}
getChannelList ( Env , public Key, function ( pinned ) {
var session = Core . getSession ( Env . Sessions , public Key) ;
getChannelList ( Env , safe Key, function ( pinned ) {
var session = Core . getSession ( Env . Sessions , safe Key) ;
// only unpin channels which are pinned
var toStore = channels . filter ( function ( channel ) {
@ -300,27 +357,27 @@ Pinning.unpinChannel = function (Env, publicKey, channels, cb) {
} ) ;
if ( toStore . length === 0 ) {
return void getHash ( Env , public Key, cb ) ;
return void getHash ( Env , safe Key, cb ) ;
}
Env . pinStore . message ( public Key, JSON . stringify ( [ 'UNPIN' , toStore , + new Date ( ) ] ) ,
Env . pinStore . message ( safe Key, JSON . stringify ( [ 'UNPIN' , toStore , + new Date ( ) ] ) ,
function ( e ) {
if ( e ) { return void cb ( e ) ; }
toStore . forEach ( function ( channel ) {
delete session . channels [ channel ] ;
} ) ;
removePinned ( Env , public Key, toStore , ( ) => { } ) ;
getHash ( Env , public Key, cb ) ;
removePinned ( Env , safe Key, toStore , ( ) => { } ) ;
getHash ( Env , safe Key, cb ) ;
} ) ;
} ) ;
} ;
Pinning . resetUserPins = function ( Env , public Key, channelList , cb ) {
Pinning . resetUserPins = function ( Env , safe Key, channelList , cb ) {
if ( ! Array . isArray ( channelList ) ) { return void cb ( 'INVALID_PIN_LIST' ) ; }
var session = Core . getSession ( Env . Sessions , public Key) ;
var session = Core . getSession ( Env . Sessions , safe Key) ;
if ( ! channelList . length ) {
return void getHash ( Env , public Key, function ( e , hash ) {
return void getHash ( Env , safe Key, function ( e , hash ) {
if ( e ) { return cb ( e ) ; }
cb ( void 0 , hash ) ;
} ) ;
@ -332,7 +389,7 @@ Pinning.resetUserPins = function (Env, publicKey, channelList, cb) {
var pinSize = sumChannelSizes ( sizes ) ;
getLimit ( Env , public Key, function ( e , limit ) {
getLimit ( Env , safe Key, function ( e , limit ) {
if ( e ) {
Env . WARN ( '[RESET_ERR]' , e ) ;
return void cb ( e ) ;
@ -347,7 +404,7 @@ Pinning.resetUserPins = function (Env, publicKey, channelList, cb) {
They will not be able to pin additional pads until they upgrade
or delete enough files to go back under their limit . * /
if ( pinSize > limit [ 0 ] && session . hasPinned ) { return void ( cb ( 'E_OVER_LIMIT' ) ) ; }
Env . pinStore . message ( public Key, JSON . stringify ( [ 'RESET' , channelList , + new Date ( ) ] ) ,
Env . pinStore . message ( safe Key, JSON . stringify ( [ 'RESET' , channelList , + new Date ( ) ] ) ,
function ( e ) {
if ( e ) { return void cb ( e ) ; }
channelList . forEach ( function ( channel ) {
@ -360,13 +417,13 @@ Pinning.resetUserPins = function (Env, publicKey, channelList, cb) {
} else {
oldChannels = [ ] ;
}
removePinned ( Env , public Key, oldChannels , ( ) => {
addPinned ( Env , public Key, channelList , ( ) => { } ) ;
removePinned ( Env , safe Key, oldChannels , ( ) => {
addPinned ( Env , safe Key, channelList , ( ) => { } ) ;
} ) ;
// update in-memory cache IFF the reset was allowed.
session . channels = pins ;
getHash ( Env , public Key, function ( e , hash ) {
getHash ( Env , safe Key, function ( e , hash ) {
cb ( e , hash ) ;
} ) ;
} ) ;
@ -429,35 +486,74 @@ Pinning.getDeletedPads = function (Env, channels, cb) {
} ) ;
} ;
const answerNoConclusively = function ( Env ) {
const pending = Env . pendingPinInquiries ;
for ( var channel in pending ) {
answerDeferred ( Env , channel , false ) ;
}
} ;
// inform that the
Pinning . loadChannelPins = function ( Env ) {
Pins . list ( function ( err , data ) {
const stats = {
surplus : 0 ,
pinned : 0 ,
duplicated : 0 ,
users : 0 , // XXX useful for admin panel ?
} ;
const handler = function ( ref , safeKey , pinned ) {
if ( ref . surplus ) {
stats . surplus += ref . surplus ;
}
for ( var channel in ref . pins ) {
if ( ! pinned . hasOwnProperty ( channel ) ) {
answerDeferred ( Env , channel , true ) ;
stats . pinned ++ ;
} else {
stats . duplicated ++ ;
}
}
stats . users ++ ;
runUserDeferred ( Env , safeKey ) ;
} ;
Pins . list ( function ( err ) {
if ( err ) {
Env . pinsLoaded = true ;
Env . Log . error ( "LOAD_CHANNEL_PINS" , err ) ;
// FIXME not sure what should be done here instead
Env . pinnedPads = { } ;
Env . evPinnedPadsReady . fire ( ) ;
return ;
}
Env . pinnedPads = data ;
Env . evPinnedPadsReady . fire ( ) ;
Env . pinsLoaded = true ;
answerNoConclusively( Env ) ;
runRemainingDeferred( Env ) ;
} , {
pinPath : Env . paths . pin ,
handler : handler ,
pinned : Env . pinnedPads ,
workers : Env . pinWorkers ,
} ) ;
} ;
Pinning . isChannelPinned = function ( Env , channel , cb ) {
Env . evPinnedPadsReady . reg ( ( ) => {
if ( Env . pinnedPads [ channel ] && Object . keys ( Env . pinnedPads [ channel ] ) . length ) { // FIXME 'Object.keys' here is overkill. We only need to know that it isn't empty
cb ( void 0 , true ) ;
} else {
delete Env . pinnedPads [ channel ] ;
cb ( void 0 , false ) ;
}
} ) ;
const deferResponse = function ( Env , channel , cb ) {
const pending = Env . pendingPinInquiries ;
( pending [ channel ] = pending [ channel ] || [ ] ) . push ( cb ) ;
} ;
Pinning . isChannelPinned = function ( Env , channel , cb ) {
// if the pins are fully loaded then you can answer yes/no definitively
if ( Env . pinsLoaded ) {
return void cb ( void 0 , ! isEmpty ( Env . pinnedPads [ channel ] ) ) ;
}
// you may already know that a channel is pinned
// even if you're still loading. answer immediately if so
if ( ! isEmpty ( Env . pinnedPads [ channel ] ) ) { return cb ( void 0 , true ) ; }
// if you're still loading them then can answer 'yes' as soon
// as you learn that one account has pinned a file.
// negative responses have to wait until the end
deferResponse ( Env , channel , cb ) ;
} ;