@fto-consult/common
Version:
Un ensemble de bibliothèques et d'utilistaires communs pour le développement d'applications javascript
411 lines (403 loc) • 19.2 kB
JavaScript
import notify from "$notify";
import {getLabel,getAll as getAllDataFiles,sanitizeName} from "../../../dataFileManager/utils";
import isCommonDataFile from "../../../dataFileManager/isCommon";
import Background from "../../background";
import getDB,{PouchDB} from "$pouchdb/getDB";
import APP from "$capp/instance";
import { getSyncOptions,normalizeSyncDirection,syncDirections } from "../../../sync/utils";
import {open as showPreloader,close as hidePreloader} from "$preloader";
import {getSyncProgressPreloaderProps} from "$active-platform/pouchdb";
import SignIn2SignOut,{getLoggedUser} from "$cauth/utils";
import isCommon from "../../../dataFileManager/isCommon";
import dataFile from "../../../dataFileManager/dataFile";
import fetch from "../../../dataFileManager/fetch";
import {isObjOrArray} from "$cutils";
/*** retourne une base de données couchdb :
* @param : {mixted : string | object}
* {
* remote : si c'est une base distante, à utilisation internet
* local : si c'est une base locale, pas besoin d'internet pour l'accessibilité au server
* }
*/
export const getCouchDB = function(dbName){
const arg = Array.prototype.slice.call(arguments,0);
let opts = {};
if(isObj(dbName)){
opts = dbName;
} else if(isNonNullString(dbName)){
opts = {dbName};
}
arg[0] = opts;
return new Promise((resolve,reject)=>{
//on compte 10 fois jusqu'à ce que l'application soit en ligne et on essaye à nouveau
const success = x => {
getDB.apply(getDB,arg).then((r)=>{
resolve(r);
r.db.initialize().then(x=>resolve(r)).catch((e)=>{
console.log(e,' unable to iniitialize couchdb',opts);
reject({status:false,msg:"Impossible d'initialiser la base "+dbName,error:e,result:r});
});
}).catch((e)=>{
console.log(e,' unable to get couchdb ',dbName,arg);
reject({status:false,msg:'Impossible de récupérer la base '+dbName,error:e});
});
}
if(opts.local === true || opts.remote === false || APP.isOnline()){
success();
} else {
APP.checkOnline().then(success).catch((e)=>{
console.log(e,' missing network connexion');
reject({status:false,code:500,msg:'Connexion internet manquante',error:e});
});
}
})
}
const dbSyncManager = Background.getDBSyncManager();
export const replicate = ({source,onError,onActive,onResumed,onPaused,onChange,onComplete,target,cancel,method,preloaderContent,syncTypeText,preloaderTitle,...rest})=>{
rest = getSyncOptions(rest);
let pendingMax = 0;
const getProgress = (pending) => {
let progress;
pendingMax = pendingMax < pending ? pending + rest.batch_size : pendingMax; // first time capture
if (pendingMax > 0) {
progress = 1 - pending/pendingMax;
if (pending === 0) {
pendingMax = 0; // reset for live/next replication
}
} else if(pendingMax == 0){
progress = 0;
} else {
progress = 1; // 100%
}
return Math.ceil(progress*100);
}
method = defaultStr(method,'replicate').toLowerCase();
if(!arrayValueExists(['sync','replicate'],method)){
method = 'replicate';
}
let scT = undefined;
onChange = defaultFunc(onChange);
onError = defaultFunc(onError);
onComplete = defaultFunc(onComplete)
onPaused = defaultFunc(onPaused);
onActive = defaultFunc(onActive,onResumed)
const showP = (info)=>{
if(Background.isDBSyncBackground()) return;
if(preloaderContent){
showPreloader(getSyncProgressPreloaderProps({
pending:getProgress(info.pending),
title : preloaderTitle,
content : preloaderContent,
footer : [
{
text : 'Arrière plan',
title : 'Exécuter en arrière plan',
icon : 'arrange-send-backward',
action : ()=>{
hidePreloader();
Background.setDBSyncBackground(false);
}
},
cancel === false ? null: {
text :'Annuler',
icon : 'cancel',
action : ()=>{
if(scT && isFunction(scT.cancel)){
scT.cancel();
}
hidePreloader();
scT = undefined;
}
}
],
}));
}
}
scT = PouchDB[method](source,target,rest);
showP({pending:0});
scT.on('denied', onError)
.on('error', onError)
.on('complete',onComplete)
.on('paused', onPaused)
.on('active',onActive)
.on('change', (info)=>{
let pending = info.pending;
if(isObj(info) && isObj(info.change) && isNumber(info.change.pending)){
info = info.change;
pending = info.pending;
}
if(!isDecimal(pending)){
scT.cancel();
return onComplete(info, source.getName());
}
showP({pending});
onChange(info);
return info;
})
return scT;
}
const cMsg = (d,title,db)=>{
let str = "";
if(isObj(d)){
if(isDecimal(d.docs_read) && d.docs_read){
str+=" [Lectures : "+(d.docs_read.formatNumber())+"]";
}
if(isDecimal(d.docs_written) && d.docs_written){
///le nombre de document écrits
str+=" [Ecritures : "+(d.docs_written.formatNumber())+"]";
}
if(str){
str = defaultStr(title)+str
str = getLabel(db.getName()).toUpperCase()+" "+str
}
}
return str;
}
export const syncDB = (args)=>{
let {code,_id,changedDatabases,syncID,syncFilter,isCommon,local,url,dbName,dbNameStr,syncType,allDatabasesSync,...rest} = args;
rest = defaultObj(rest)
let remoteDB = undefined;
let localDB = undefined;
let hasSyncChanged = false;
changedDatabases = defaultObj(changedDatabases);
const triggerAfterSync = (trigger)=>{
if(localDB){
if(typeof localDB.unlock =="function"){
localDB.unlock(trigger);
}
/** à la fin de la synchronisation, on actualise la liste des fichiers de données de l'application, et on met à jour les informations sur la base de données */
if(typeof localDB.getInfos =="function"){
localDB.getInfos();
}
}
}
if(dbNameStr ===false){
dbNameStr = "";
} else {
dbNameStr = defaultStr(dbNameStr,dbName);
}
let method = 'sync';
let serverCode = defaultStr(code,_id);
if(serverCode){
serverCode = "sync serveur ["+serverCode+"]";
}
local = defaultVal(local,false)? true : false;
return new Promise((_resolve,_reject)=>{
let timeout = undefined;
let syncContext = undefined;
const errorF = (err)=>{
clearTimeout(timeout);
if(err){
if(Background.isNotDBSyncBackground() && !dbSyncManager[syncID] && (isNonNullString(err) || (isObj(err) && (isNonNullString(err.msg) || isNonNullString(err.message))))){
dbSyncManager[syncID] = true;
notify.error(err);
}
_reject(err);
}
if(syncContext && isFunction(syncContext.cancel)){
syncContext.cancel();
}
triggerAfterSync(false);
}
if(!APP.isOnline() && (local !== true && rest.remote !== false)){
errorF("Veuillez vérifier votre connexion internet car celle-ci semble être instable")
return;
}
const successF = (info)=>{
clearTimeout(timeout);
_resolve(info);
if(isObj(allDatabasesSync)){
allDatabasesSync[dbName]={syncType,date : new Date().toFormat("yyyy-mm-dd HH:MM:ss")};
}
triggerAfterSync(hasSyncChanged);
if(syncContext && isFunction(syncContext.cancel)){
syncContext.cancel();
}
APP.trigger(APP.EVENTS.SYNC_POUCHDB_DATABASE,{
isCommon,
dbNameStr,
...defaultObj(info),
dbName,
});
}
getCouchDB({dbName,server:url,serverCode:defaultStr(code,_id),serverSettings:rest,local,...rest}).then(({db})=>{
remoteDB = db;
const onComplete = function(completeArgs){
let r = completeArgs.result;
let textMsg = "";
if(isObj(r) && dbNameStr){
textMsg +=textMsg?" ":"";
if(r.push){
textMsg+= cMsg(r.push,'Ajouts : ',db);
} else if(r.pull){
textMsg+= cMsg(r.pull,' Retraits : ',db);
} else if(isDecimal(r.docs_written) && isDecimal(r.docs_read)){
textMsg+= cMsg(r.pull,' Resultat : ',db);
} else textMsg = '';
}
textMsg = textMsg.trim();
if(textMsg && Background.isNotDBSyncBackground()){
notify.success(textMsg);
}
successF(r);
if(completeArgs.localDB && isFunction(completeArgs.localDB.getName) && isFunction(completeArgs.localDB.removeOldDocs) && isNonNullString(completeArgs.syncType)){
if(!defaultStr(completeArgs.localDB.getName()).toLowerCase().contains("online-devices")){
completeArgs.localDB.removeOldDocs({...rest,archived:true,syncType}).catch((e)=>{
if(e && !e.removingError){
console.log(e,' removing old data db=',dbName,", sync type ",syncType)
}
});
}
}
}
return remoteDB.info().then(()=>{
return getDB({dbName}).then(({db})=>{
if(typeof db?.lock =="function"){
db.lock();//on vérouille la base de données
}
localDB = db;
let target = remoteDB, source = localDB,
filter = undefined;
syncFilter = defaultFunc(syncFilter,x=>true)
if(syncType =="desc"){
filter = syncFilter;
} else {
filter = (doc,req)=>{
if(!doc._deleted) return syncFilter(doc,req);
return doc.table || doc.uuid || doc.createdBy || doc.createdDate? syncFilter(doc,req) : false;
}
}
let syncTypeText = "Complète";
switch(syncType){
case 'asc':
method = 'replicate'
syncTypeText = 'Asc';
break;
case 'desc':
method = 'replicate'
syncTypeText = 'Desc';
target = localDB;
source = remoteDB;
break;
}
const preloaderTitle = serverCode;
const preloaderContent = (dbNameStr||'')+" ["+syncTypeText+"] :";
return replicate({...rest,
onError:errorF,
onChange:(info)=>{
hasSyncChanged = true;
changedDatabases[dbName] = true;
clearTimeout(timeout);
},
onPaused : ()=> clearTimeout(timeout),
onActive : ()=> clearTimeout(timeout),
onComplete : (args)=>{
clearTimeout(timeout);
onComplete({localDB,syncType,remoteDB,result:args});
},
live:false,retry:false,serverCode,filter,source,target,syncTypeText,preloaderTitle,method,preloaderContent,
});
})
}).catch((er)=>{
console.log(er,' error on get remote db info for sync ',dbName," sync id is ",syncID);
if(Background.isNotDBSyncBackground() && !dbSyncManager[syncID]){
dbSyncManager[syncID] = true;
notify.error("Impossible de synchroniser la base ["+dbNameStr+"]"+(isNonNullString(serverCode)? (", "+serverCode+""):"")+" : Vérifiez "+(local !==true?"votre connexion internet où les paramètres de ce serveur":"les paramètres de connexion à ce serveur")+" SVP!!!")
}
errorF(er);
})
}).catch((e)=>{
console.log(e,' getting coudDB to sync ',dbName)
errorF(e);
})
})
}
export default {
init : ()=>{
return this;
},
getDB : getCouchDB,
/**** synchronise vers un serveur de données
* @param {object} arg, de la forme :
* {
* syncDataTypes {Array|string}, tableau où chaine de caractère avec les éléments séparés par ##, représentant à gauche, le type de fichier de données et à droite, la direction de synchronisation
* databases {Array|string}, idem à syncDataTypes sauf qu'il s'agit des bases de données, à gauche, le nom du fichier de données et à droite la direction de synchronisation
* filter {function} ({dbName,dataFile,syncDataTypes,hasSyncTypes,hasDatabases,isCommon,dataFileType,dataFileType,syncType})=><Boolean>, la fonction de filtre à appliquer pour filtrer les bases de données à synchroniser
}
*/
sync : (o)=>{
const {filter:dbFilter,...arg} = defaultObj(o);
const allDatabasesSync = {};
const syncDataTypes = normalizeSyncDirection(arg.syncDataTypes);
const databases = isObjOrArray(arg.databases) && arg.databases || [];
const isDatabasesArray = Array.isArray(databases);
const databasesIncludes = x=> isDatabasesArray ? databases.includes(x) : databases[x];
const hasSyncTypes = Object.size(syncDataTypes,true);
const hasDataBases = Object.size(databases,true);
const normalizedDatabases = {};
const filter = typeof dbFilter =="function"? dbFilter : x=>true;
const promises = [];
arg.syncID = uniqid("database-syncId")
const allExistingDataFiles = {},commonDataFiles = {};
const dataFilesByTypes = {};
getAllDataFiles((dF)=>{
const type = defaultStr(dF.type);
if(hasDataBases) {
if(!databasesIncludes(dF.code)) return false;
if(!hasSyncTypes || !syncDataTypes[type]){
console.error(`Impossible de synchroniser la base [${dF.code} ${dF.label}], car aucun type de synchronisation n'a été définie pour le type[${type}] la base de donnée : [${dF.code} ${dF.label}]`," Bases de données : ",databases,", Types de synchronisations : ",syncDataTypes);
return false;
}
normalizedDatabases[dF.code] = syncDataTypes[type];
} else if(hasSyncTypes > 0 && !(syncDataTypes[type])){
return false;
}
dataFilesByTypes[type] = defaultObj(dataFilesByTypes[type]);
dataFilesByTypes[type][dF.code] = dF;
allExistingDataFiles[dF.code] = dF;
if(isCommon(dF)){
commonDataFiles[dF.code] = dF;
}
return true;
});
const changedDatabases = allDatabasesSync.changes = {};
/*** si aucun des choix n'est définit, c'est à dire, aucune base de données où aucun type de base de données n'est selectionné
* alors rien ne sera opérationnel
*/
if(hasDataBases){
Object.map(normalizedDatabases,(syncType,dbName)=>{
if(!syncDirections[syncType] || !allExistingDataFiles[dbName]) return null;
const isC = !!commonDataFiles[dbName];
const dF = allExistingDataFiles[dbName];
const syncOpts = {...arg,dataFilesByTypes,hasDataBases,hasSyncTypes,normalizedDatabases,syncDataTypes,isCommon:isC,changedDatabases,dataFilteType:dF.type,dbName:dF.code,dF,dataFile:dF,syncType,dbNameStr:dF.label,allDatabasesSync};
if(filter(syncOpts) === false) return;
promises.push(syncDB(syncOpts));
});
} else if(hasSyncTypes){
Object.map(syncDataTypes,(syncType,dataFileType)=>{
///on synchronise les bases pour lesquelles les types ont été sélectionnées, y compris avec leur direction
if(!isNonNullString(dataFileType) || !syncType || !syncDirections[syncType] || !dataFilesByTypes[dataFileType]) return null;
////seules les bases de données dont le type a été spécifié, peuvent être synchronisées
Object.map(dataFilesByTypes[dataFileType],(dF,i)=>{
const syncOpts = {...arg,dataFilesByTypes,normalizedDatabases,syncDataTypes,hasDataBases,hasSyncTypes,isCommon:commonDataFiles[dF.code],dataFile:dF,dataFileType:dF.type,dF,changedDatabases,syncType,dbName:dF.code,dbNameStr:dF.label,allDatabasesSync};
if(filter(syncOpts) === false) return;
promises.push(syncDB(syncOpts));
});
})
}
///always sync datafile's manager as full sync
promises.push(syncDB({...arg,isCommon:true,isDataFileManager:true,changedDatabases,syncType:syncDirections.full,dbName:dataFile.code,dbNameStr:dataFile.label,allDatabasesSync}).then((r)=>{
fetch();
return r;
}));
return Promise.all(promises).then((r)=>{
return allDatabasesSync;
}).catch((e)=>{
return e;
}).finally((f)=>{
delete dbSyncManager[arg.syncID]
if(Background.isNotDBSyncBackground()) hidePreloader();
return allDatabasesSync;
});
}
}