add ts to address key
This commit is contained in:
parent
c9c8a8739f
commit
e3c7440cf3
|
@ -53,7 +53,7 @@ exports.show = function(req, res, next) {
|
||||||
} else {
|
} else {
|
||||||
return res.jsonp(a.getObj());
|
return res.jsonp(a.getObj());
|
||||||
}
|
}
|
||||||
}, {noTxList: req.query.noTxList});
|
}, {txLimit: req.query.noTxList?0:-1});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -104,13 +104,13 @@ Address.prototype._addTxItem = function(txItem, txList) {
|
||||||
add=1;
|
add=1;
|
||||||
|
|
||||||
if (txList)
|
if (txList)
|
||||||
txList.push({txid: txItem.txid, ts: txItem.ts});
|
txList.push(txItem.txid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spent tx
|
// Spent tx
|
||||||
if (txItem.spentTxId && !seen[txItem.spentTxId] ) {
|
if (txItem.spentTxId && !seen[txItem.spentTxId] ) {
|
||||||
if (txList) {
|
if (txList) {
|
||||||
txList.push({txid: txItem.spentTxId, ts: txItem.spentTs});
|
txList.push(txItem.spentTxId);
|
||||||
}
|
}
|
||||||
seen[txItem.spentTxId]=1;
|
seen[txItem.spentTxId]=1;
|
||||||
addSpend=1;
|
addSpend=1;
|
||||||
|
@ -140,29 +140,16 @@ Address.prototype._addTxItem = function(txItem, txList) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Address.prototype._setTxs = function(txs) {
|
|
||||||
|
|
||||||
// sort input and outputs togheter
|
|
||||||
txs.sort(
|
|
||||||
function compare(a,b) {
|
|
||||||
if (a.ts < b.ts) return 1;
|
|
||||||
if (a.ts > b.ts) return -1;
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.transactions = txs.map(function(i) { return i.txid; } );
|
|
||||||
};
|
|
||||||
|
|
||||||
// opts are
|
// opts are
|
||||||
// .noTxList
|
|
||||||
// .onlyUnspent
|
// .onlyUnspent
|
||||||
// .noSortTxs
|
// .txLimit (=0 -> no txs, => -1 no limit)
|
||||||
|
//
|
||||||
Address.prototype.update = function(next, opts) {
|
Address.prototype.update = function(next, opts) {
|
||||||
var self = this;
|
var self = this;
|
||||||
if (!self.addrStr) return next();
|
if (!self.addrStr) return next();
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|
||||||
var txList = opts.noTxList ? null : [];
|
var txList = opts.txLimit === 0 ? null: [];
|
||||||
var tDb = TransactionDb;
|
var tDb = TransactionDb;
|
||||||
var bDb = BlockDb;
|
var bDb = BlockDb;
|
||||||
tDb.fromAddr(self.addrStr, function(err,txOut){
|
tDb.fromAddr(self.addrStr, function(err,txOut){
|
||||||
|
@ -172,8 +159,8 @@ Address.prototype.update = function(next, opts) {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
tDb.cacheConfirmations(txOut, function(err) {
|
tDb.cacheConfirmations(txOut, function(err) {
|
||||||
|
// console.log('[Address.js.161:txOut:]',txOut); //TODO
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
if (opts.onlyUnspent) {
|
if (opts.onlyUnspent) {
|
||||||
txOut = txOut.filter(function(x){
|
txOut = txOut.filter(function(x){
|
||||||
return !x.spentTxId;
|
return !x.spentTxId;
|
||||||
|
@ -197,32 +184,13 @@ Address.prototype.update = function(next, opts) {
|
||||||
txOut.forEach(function(txItem){
|
txOut.forEach(function(txItem){
|
||||||
self._addTxItem(txItem, txList);
|
self._addTxItem(txItem, txList);
|
||||||
});
|
});
|
||||||
|
if (txList) self.transactions = txList;
|
||||||
if (txList && !opts.noSortTxs)
|
|
||||||
self._setTxs(txList);
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
Address.prototype.getUtxo = function(next) {
|
|
||||||
var self = this;
|
|
||||||
var tDb = TransactionDb;
|
|
||||||
var bDb = BlockDb;
|
|
||||||
var ret;
|
|
||||||
if (!self.addrStr) return next(new Error('no error'));
|
|
||||||
|
|
||||||
tDb.fromAddr(self.addrStr, function(err,txOut){
|
|
||||||
if (err) return next(err);
|
|
||||||
var unspent = txOut.filter(function(x){
|
|
||||||
return !x.spentTxId;
|
|
||||||
});
|
|
||||||
|
|
||||||
bDb.fillConfirmations(unspent, function() {
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = require('soop')(Address);
|
module.exports = require('soop')(Address);
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,18 @@
|
||||||
var imports = require('soop').imports();
|
var imports = require('soop').imports();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// to show tx outs
|
// to show tx outs
|
||||||
var OUTS_PREFIX = 'txo-'; //txo-<txid>-<n> => [addr, btc_sat]
|
var OUTS_PREFIX = 'txo-'; //txo-<txid>-<n> => [addr, btc_sat]
|
||||||
var SPENT_PREFIX = 'txs-'; //txs-<txid(out)>-<n(out)>-<txid(in)>-<n(in)> = ts
|
var SPENT_PREFIX = 'txs-'; //txs-<txid(out)>-<n(out)>-<txid(in)>-<n(in)> = ts
|
||||||
|
|
||||||
// to sum up addr balance (only outs, spents are gotten later)
|
// to sum up addr balance (only outs, spents are gotten later)
|
||||||
var ADDR_PREFIX = 'txa-'; //txa-<addr>-<txid>-<n>
|
var ADDR_PREFIX = 'txa2-'; //txa-<addr>-<tsr>-<txid>-<n>
|
||||||
// => + btc_sat:ts [:isConfirmed:[scriptPubKey|isSpendConfirmed:SpentTxid:SpentVout:SpentTs]
|
// tsr = 1e13-js_timestamp
|
||||||
|
|
||||||
|
// => + btc_sat [:isConfirmed:[scriptPubKey|isSpendConfirmed:SpentTxid:SpentVout:SpentTs]
|
||||||
|
// |balance:txApperances
|
||||||
|
|
||||||
|
|
||||||
// TODO: use bitcore networks module
|
// TODO: use bitcore networks module
|
||||||
var genesisTXID = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b';
|
var genesisTXID = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b';
|
||||||
|
@ -17,6 +22,7 @@ var CONCURRENCY = 10;
|
||||||
var DEFAULT_SAFE_CONFIRMATIONS = 6;
|
var DEFAULT_SAFE_CONFIRMATIONS = 6;
|
||||||
|
|
||||||
var MAX_OPEN_FILES = 500;
|
var MAX_OPEN_FILES = 500;
|
||||||
|
var END_OF_WORLD_TS = 1e13;
|
||||||
// var CONFIRMATION_NR_TO_NOT_CHECK = 10; //Spend
|
// var CONFIRMATION_NR_TO_NOT_CHECK = 10; //Spend
|
||||||
/**
|
/**
|
||||||
* Module dependencies.
|
* Module dependencies.
|
||||||
|
@ -189,42 +195,12 @@ TransactionDb.prototype._fillOutpoints = function(txInfo, cb) {
|
||||||
return c_in(); // error not scalated
|
return c_in(); // error not scalated
|
||||||
}
|
}
|
||||||
|
|
||||||
txInfo.firstSeenTs = ret.spentTs;
|
txInfo.firstSeenTs = ret.ts;
|
||||||
i.unconfirmedInput = i.unconfirmedInput;
|
i.unconfirmedInput = i.unconfirmedInput;
|
||||||
i.addr = ret.addr;
|
i.addr = ret.addr;
|
||||||
i.valueSat = ret.valueSat;
|
i.valueSat = ret.valueSat;
|
||||||
i.value = ret.valueSat / util.COIN;
|
i.value = ret.valueSat / util.COIN;
|
||||||
valueIn += i.valueSat;
|
valueIn += i.valueSat;
|
||||||
|
|
||||||
/*
|
|
||||||
* If confirmed by bitcoind, we could not check for double spents
|
|
||||||
* but we prefer to keep the flag of double spent attempt
|
|
||||||
*
|
|
||||||
if (txInfo.confirmations
|
|
||||||
&& txInfo.confirmations >= CONFIRMATION_NR_TO_NOT_CHECK)
|
|
||||||
return c_in();
|
|
||||||
isspent
|
|
||||||
*/
|
|
||||||
// Double spent?
|
|
||||||
if (ret.multipleSpentAttempt || !ret.spentTxId ||
|
|
||||||
(ret.spentTxId && ret.spentTxId !== txInfo.txid)
|
|
||||||
) {
|
|
||||||
if (ret.multipleSpentAttempts) {
|
|
||||||
ret.multipleSpentAttempts.forEach(function(mul) {
|
|
||||||
if (mul.spentTxId !== txInfo.txid) {
|
|
||||||
i.doubleSpentTxID = ret.spentTxId;
|
|
||||||
i.doubleSpentIndex = ret.spentIndex;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (!ret.spentTxId) {
|
|
||||||
i.dbError = 'Input spent not registered';
|
|
||||||
} else {
|
|
||||||
i.doubleSpentTxID = ret.spentTxId;
|
|
||||||
i.doubleSpentIndex = ret.spentIndex;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
i.doubleSpentTxID = null;
|
|
||||||
}
|
|
||||||
return c_in();
|
return c_in();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -275,39 +251,27 @@ TransactionDb.prototype.fromIdWithInfo = function(txid, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Gets address info from an outpoint
|
||||||
TransactionDb.prototype.fromTxIdN = function(txid, n, cb) {
|
TransactionDb.prototype.fromTxIdN = function(txid, n, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var k = OUTS_PREFIX + txid + '-' + n;
|
var k = OUTS_PREFIX + txid + '-' + n;
|
||||||
|
|
||||||
db.get(k, function(err, val) {
|
db.get(k, function(err, val) {
|
||||||
if (!val || (err && err.notFound)) {
|
var ret;
|
||||||
return cb(null, {
|
|
||||||
unconfirmedInput: 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!val || (err && err.notFound)) {
|
||||||
|
err=null;
|
||||||
|
ret= { unconfirmedInput: 1 };
|
||||||
|
}
|
||||||
|
else {
|
||||||
var a = val.split(':');
|
var a = val.split(':');
|
||||||
var ret = {
|
ret = {
|
||||||
addr: a[0],
|
addr: a[0],
|
||||||
valueSat: parseInt(a[1]),
|
valueSat: parseInt(a[1]),
|
||||||
|
// ts: parseInt(a[2]), // TODO
|
||||||
};
|
};
|
||||||
|
}
|
||||||
// spent?
|
return cb(err, ret);
|
||||||
var k = SPENT_PREFIX + txid + '-' + n + '-';
|
|
||||||
db.createReadStream({
|
|
||||||
start: k,
|
|
||||||
end: k + '~'
|
|
||||||
})
|
|
||||||
.on('data', function(data) {
|
|
||||||
var k = data.key.split('-');
|
|
||||||
self._addSpentInfo(ret, k[3], k[4], data.value);
|
|
||||||
})
|
|
||||||
.on('error', function(error) {
|
|
||||||
return cb(error);
|
|
||||||
})
|
|
||||||
.on('end', function() {
|
|
||||||
return cb(null, ret);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -324,7 +288,7 @@ TransactionDb.prototype.deleteCacheForAddress = function(addr,cb) {
|
||||||
dbScript.push({
|
dbScript.push({
|
||||||
type: 'put',
|
type: 'put',
|
||||||
key: data.key,
|
key: data.key,
|
||||||
value: v.slice(0,2).join(':'),
|
value: v[0],
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.on('error', function(err) {
|
.on('error', function(err) {
|
||||||
|
@ -362,7 +326,7 @@ TransactionDb.prototype.cacheConfirmations = function(txouts,cb) {
|
||||||
//console.log('[TransactionDb.js.352:infoToCache:]',infoToCache); //TODO
|
//console.log('[TransactionDb.js.352:infoToCache:]',infoToCache); //TODO
|
||||||
if (infoToCache.length){
|
if (infoToCache.length){
|
||||||
|
|
||||||
infoToCache.unshift(txout.value_sat,txout.ts);
|
infoToCache.unshift(txout.value_sat);
|
||||||
//console.log('[BlockDb.js.373:txs:]' ,txout.key, infoToCache.join(':')); //TODO
|
//console.log('[BlockDb.js.373:txs:]' ,txout.key, infoToCache.join(':')); //TODO
|
||||||
dbScript.push({
|
dbScript.push({
|
||||||
type: 'put',
|
type: 'put',
|
||||||
|
@ -409,50 +373,51 @@ TransactionDb.prototype.cacheScriptPubKey = function(txouts,cb) {
|
||||||
TransactionDb.prototype._parseAddrData = function(data) {
|
TransactionDb.prototype._parseAddrData = function(data) {
|
||||||
var k = data.key.split('-');
|
var k = data.key.split('-');
|
||||||
var v = data.value.split(':');
|
var v = data.value.split(':');
|
||||||
// console.log('[TransactionDb.js.410]',v); //TODO
|
// console.log('[TransactionDb.js.375]',data.key,data.value); //TODO
|
||||||
var item = {
|
var item = {
|
||||||
key: data.key,
|
key: data.key,
|
||||||
txid: k[2],
|
ts: parseInt(k[2]),
|
||||||
index: parseInt(k[3]),
|
txid: k[3],
|
||||||
|
index: parseInt(k[4]),
|
||||||
value_sat: parseInt(v[0]),
|
value_sat: parseInt(v[0]),
|
||||||
ts: parseInt(v[1]),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cache:
|
// Cache:
|
||||||
// v[2]== isConfirmedCached
|
// v[1]== isConfirmedCached
|
||||||
// v[3]=== '1' -> is SpendCached -> [4]=spendTxId [5]=spentIndex [6]=spendTs
|
// v[2]=== '1' -> is SpendCached -> [4]=spendTxId [5]=spentIndex [6]=spendTs
|
||||||
// v[4]!== '1' -> is ScriptPubkey -> [[3] = scriptPubkey
|
// v[3]!== '1' -> is ScriptPubkey -> [[3] = scriptPubkey
|
||||||
if (v[2]){
|
if (v[1]){
|
||||||
item.isConfirmed = 1;
|
item.isConfirmed = 1;
|
||||||
item.isConfirmedCached = 1;
|
item.isConfirmedCached = 1;
|
||||||
// console.log('[TransactionDb.js.356] CACHE HIT CONF:', item.key); //TODO
|
// console.log('[TransactionDb.js.356] CACHE HIT CONF:', item.key); //TODO
|
||||||
// Sent, confirmed
|
// Sent, confirmed
|
||||||
if (v[3] === '1'){
|
if (v[2] === '1'){
|
||||||
// console.log('[TransactionDb.js.356] CACHE HIT SPENT:', item.key); //TODO
|
// console.log('[TransactionDb.js.356] CACHE HIT SPENT:', item.key); //TODO
|
||||||
item.spentIsConfirmed = 1;
|
item.spentIsConfirmed = 1;
|
||||||
item.spentIsConfirmedCached = 1;
|
item.spentIsConfirmedCached = 1;
|
||||||
item.spentTxId = v[4];
|
item.spentTxId = v[3];
|
||||||
item.spentIndex = parseInt(v[5]);
|
item.spentIndex = parseInt(v[4]);
|
||||||
item.spentTs = parseInt(v[6]);
|
item.spentTs = parseInt(v[5]);
|
||||||
}
|
}
|
||||||
// Scriptpubkey cached
|
// Scriptpubkey cached
|
||||||
else if (v[3]) {
|
else if (v[2]) {
|
||||||
// console.log('[TransactionDb.js.356] CACHE HIT SCRIPTPUBKEY:', item.key); //TODO
|
// console.log('[TransactionDb.js.356] CACHE HIT SCRIPTPUBKEY:', item.key); //TODO
|
||||||
item.scriptPubKey = v[3];
|
item.scriptPubKey = v[2];
|
||||||
item.scriptPubKeyCached = 1;
|
item.scriptPubKeyCached = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
};
|
};
|
||||||
|
|
||||||
TransactionDb.prototype.fromAddr = function(addr, cb) {
|
TransactionDb.prototype.fromAddr = function(addr, cb, txLimit) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var k = ADDR_PREFIX + addr + '-';
|
var k = ADDR_PREFIX + addr + '-';
|
||||||
var ret = [];
|
var ret = [];
|
||||||
|
|
||||||
db.createReadStream({
|
db.createReadStream({
|
||||||
start: k,
|
start: k,
|
||||||
end: k + '~'
|
end: k + '~',
|
||||||
|
limit: txLimit>0 ? txLimit: -1, // -1 means not limit
|
||||||
})
|
})
|
||||||
.on('data', function(data) {
|
.on('data', function(data) {
|
||||||
ret.push(self._parseAddrData(data));
|
ret.push(self._parseAddrData(data));
|
||||||
|
@ -597,14 +562,15 @@ TransactionDb.prototype._addScript = function(tx, relatedAddrs) {
|
||||||
|
|
||||||
if (relatedAddrs) relatedAddrs[addr]=1;
|
if (relatedAddrs) relatedAddrs[addr]=1;
|
||||||
var k = OUTS_PREFIX + txid + '-' + o.n;
|
var k = OUTS_PREFIX + txid + '-' + o.n;
|
||||||
|
var tsr = END_OF_WORLD_TS - ts;
|
||||||
dbScript.push({
|
dbScript.push({
|
||||||
type: 'put',
|
type: 'put',
|
||||||
key: k,
|
key: k,
|
||||||
value: addr + ':' + sat,
|
value: addr + ':' + sat,
|
||||||
},{
|
},{
|
||||||
type: 'put',
|
type: 'put',
|
||||||
key: ADDR_PREFIX + addr + '-' + txid + '-' + o.n,
|
key: ADDR_PREFIX + addr + '-' + tsr + '-'+ txid + '-' + o.n,
|
||||||
value: sat + ':' + ts,
|
value: sat,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -673,12 +639,44 @@ TransactionDb.prototype.getPoolInfo = function(txid, cb) {
|
||||||
|
|
||||||
|
|
||||||
TransactionDb.prototype.checkVersion02 = function(cb) {
|
TransactionDb.prototype.checkVersion02 = function(cb) {
|
||||||
var k = 'txb-f0315ffc38709d70ad5647e22048358dd3745f3ce3874223c80a7c92fab0c8ba-00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206';
|
var k = 'txa-';
|
||||||
db.get(k, function(err, val) {
|
db.createReadStream({
|
||||||
|
start: k,
|
||||||
|
end: k + '~'
|
||||||
|
})
|
||||||
|
.on('data', function(data) {
|
||||||
|
console.log('[TransactionDb.js.689]',data); //TODO
|
||||||
|
return cb(0);
|
||||||
|
})
|
||||||
|
.on('end', function (){
|
||||||
|
return cb(1);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return cb(!val);
|
TransactionDb.prototype.migrateV02 = function(cb) {
|
||||||
|
var k = 'txa-';
|
||||||
|
var dbScript = [];
|
||||||
|
db.createReadStream({
|
||||||
|
start: k,
|
||||||
|
end: k + '~'
|
||||||
|
})
|
||||||
|
.on('data', function(data) {
|
||||||
|
var k = data.key.split('-');
|
||||||
|
var v = data.value.split(':');
|
||||||
|
dbScript.push({
|
||||||
|
type: 'put',
|
||||||
|
key: ADDR_PREFIX + k[1] + '-' + (END_OF_WORLD_TS - parseInt(v[1])) + '-' + k[3] + '-' + [4],
|
||||||
|
value: v[0],
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.on('error', function(err) {
|
||||||
|
return cb(err);
|
||||||
|
})
|
||||||
|
.on('end', function (){
|
||||||
|
db.batch(dbScript,cb);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = require('soop')(TransactionDb);
|
module.exports = require('soop')(TransactionDb);
|
||||||
|
|
|
@ -19,7 +19,14 @@ describe('Address balances', function() {
|
||||||
|
|
||||||
before(function(c) {
|
before(function(c) {
|
||||||
txDb = TransactionDb;
|
txDb = TransactionDb;
|
||||||
return c();
|
|
||||||
|
var l =addrValid.length;
|
||||||
|
var i =0;
|
||||||
|
addrValid.forEach(function(v) {
|
||||||
|
TransactionDb.deleteCacheForAddress(v.addr, function() {
|
||||||
|
if (++i===l) return c();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
addrValid.forEach(function(v) {
|
addrValid.forEach(function(v) {
|
||||||
|
@ -47,7 +54,7 @@ describe('Address balances', function() {
|
||||||
if (v.transactions) {
|
if (v.transactions) {
|
||||||
|
|
||||||
v.transactions.forEach(function(tx) {
|
v.transactions.forEach(function(tx) {
|
||||||
assert(a.transactions.indexOf(tx) > -1, 'have tx ' + tx);
|
a.transactions.should.include(tx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
done();
|
done();
|
||||||
|
@ -68,7 +75,7 @@ describe('Address balances', function() {
|
||||||
if (v.totalSent) assert.equal(v.totalSent, a.totalSent, 'send: ' + a.totalSent);
|
if (v.totalSent) assert.equal(v.totalSent, a.totalSent, 'send: ' + a.totalSent);
|
||||||
if (v.balance) assert.equal(v.balance, a.balance, 'balance: ' + a.balance);
|
if (v.balance) assert.equal(v.balance, a.balance, 'balance: ' + a.balance);
|
||||||
done();
|
done();
|
||||||
},{noTxList:1});
|
},{txLimit:0});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,6 +22,9 @@ async.series([
|
||||||
return c(err);
|
return c(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
function(c){
|
||||||
|
txDb.migrateV02(c);
|
||||||
|
},
|
||||||
function(c){
|
function(c){
|
||||||
var script=[];
|
var script=[];
|
||||||
async.whilst(
|
async.whilst(
|
||||||
|
|
Loading…
Reference in New Issue