blob: 4074bef1cfa95b2b92804611a9bbba894637be83 [file] [log] [blame]
/**
*
* Copyright (c) 2020 Silicon Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This file provides the functionality that reads the ZAP data from a database
* and exports it into a file.
*/
/**
* This module provides the functionality that reads the ZAP data from a database
* and exports it into a file.
*
* @module Export API: Exports Data into a file.
*/
const os = require('os')
const fs = require('fs')
const fsp = fs.promises
const path = require('path')
const env = require('../util/env')
const querySession = require('../db/query-session.js')
const queryImpExp = require('../db/query-impexp.js')
const dbEnum = require('../../src-shared/db-enum.js')
const ff = require('./file-format')
/**
* Export endpoint type information.
*
* @param {*} db
* @param {*} endpointType
* @returns Promise of an export of endpoint type and its entities
*/
async function exportEndpointType(db, endpointType) {
let data = await queryImpExp.exportClustersFromEndpointType(
db,
endpointType.endpointTypeId
)
endpointType.clusters = data
let ps = data.map(async (endpointCluster) => {
let endpointClusterId = endpointCluster.endpointClusterId
delete endpointCluster.endpointClusterId
endpointCluster.commands =
await queryImpExp.exportCommandsFromEndpointTypeCluster(
db,
endpointType.endpointTypeId,
endpointClusterId
)
if (endpointCluster.commands.length == 0) {
delete endpointCluster.commands
}
endpointCluster.attributes =
await queryImpExp.exportAttributesFromEndpointTypeCluster(
db,
endpointClusterId
)
if (endpointCluster.attributes.length == 0) {
delete endpointCluster.attributes
}
endpointCluster.events =
await queryImpExp.exportEventsFromEndpointTypeCluster(
db,
endpointClusterId
)
if (endpointCluster.events.length == 0) {
delete endpointCluster.events
}
})
return Promise.all(ps)
}
/**
* Resolves to an array of endpoint types.
*
* @export
* @param {*} db
* @param {*} sessionId
* @returns Promise to retrieve all endpoint types.
*/
async function exportEndpointTypes(db, sessionId) {
let endpointTypes = await queryImpExp.exportEndpointTypes(db, sessionId)
let promises = endpointTypes.map((endpointType) =>
exportEndpointType(db, endpointType)
)
await Promise.all(promises)
let endpoints = await queryImpExp.exportEndpoints(
db,
sessionId,
endpointTypes
)
endpointTypes.forEach((ept) => {
delete ept.endpointTypeId
})
endpoints.forEach((ep) => {
delete ep.parentRef
delete ep.endpointTypeRef
})
return { endpointTypes: endpointTypes, endpoints: endpoints }
}
/**
* Resolves with data for packages.
*
* @param {*} db
* @param {*} sessionId
*/
async function exportSessionPackages(db, sessionId, zapProjectFileLocation) {
let packages = await queryImpExp.exportPackagesFromSession(db, sessionId)
return packages.map((p) => {
let pathRelativity = dbEnum.pathRelativity.relativeToUserHome
let relativePath = path.relative(os.homedir(), p.path)
if (zapProjectFileLocation != null) {
let rel = path.relative(path.dirname(zapProjectFileLocation), p.path)
if (rel.length > 0) {
relativePath = rel
pathRelativity = dbEnum.pathRelativity.relativeToZap
}
}
if (path.isAbsolute(relativePath) && /^[A-Z]:\\/i.test(relativePath)) {
// Handling Windows path when package is on different drive than zapProjectFile or user home
pathRelativity = dbEnum.pathRelativity.absolute
}
let ret = {
pathRelativity: pathRelativity,
path: relativePath,
type: p.type
}
if (p.category != null) {
ret.category = p.category
}
if (p.version != null) {
ret.version = p.version
}
if (p.description != null) {
ret.description = p.description
}
return ret
})
}
/**
* Toplevel file that takes a given session ID and exports the data into the file
*
* @export
* @param {*} db
* @param {*} sessionId
* @param {*} filePath
* @returns A promise that resolves with the path of the file written.
*/
async function exportDataIntoFile(
db,
sessionId,
filePath,
options = {
removeLog: false,
createBackup: false
}
) {
let fileFormat = env.defaultFileFormat()
env.logDebug(`Writing state from session ${sessionId} into file ${filePath}`)
let state = await createStateFromDatabase(db, sessionId)
if (fileFormat > 0) {
state.fileFormat = fileFormat
} else {
delete state.fileFormat
}
// avoid unncessary Studio integration id from being saved in file.
if (state.keyValuePairs) {
state.keyValuePairs = state.keyValuePairs.filter(
(x) => x.key != dbEnum.sessionKey.ideProjectPath
)
}
state = ff.convertToFile(state)
if (options.removeLog) {
delete state.log
} else if (state.log != null && state.log.length == 0) {
delete state.log
}
if (fs.existsSync(filePath)) {
fs.copyFileSync(filePath, filePath + '~')
}
await fsp.writeFile(filePath, JSON.stringify(state, null, 2))
await querySession.setSessionClean(db, sessionId)
await querySession.deleteSessionNotificationsForDuplicateEndpointTypeMetaData(
db,
sessionId
)
return filePath
}
/**
* Get session key values.
*
* @param {*} db
* @param {*} sessionId
* @param {*} excludedKeys
* @returns session key values
*/
async function getSessionKeyValues(db, sessionId, excludedKeys) {
let keyValues = await querySession.getAllSessionKeyValues(db, sessionId)
env.logDebug(`Retrieved session keys: ${keyValues.length}`)
let zapFilePath = null
let storedKeyValuePairs = keyValues.filter(
(datum) => !excludedKeys.includes(datum.key)
)
let x = keyValues.filter((datum) => datum.key == dbEnum.sessionKey.filePath)
if (x.length > 0) zapFilePath = x[0].value
let exportedKeyValues = {
key: 'keyValuePairs',
data: storedKeyValuePairs,
zapFilePath: zapFilePath
}
let d = await exportSessionPackages(
db,
sessionId,
exportedKeyValues.zapFilePath
)
return [exportedKeyValues, { key: 'package', data: d }]
}
/**
* Given a database and a session id, this method returns a promise that
* resolves with a state object that needs to be saved into a file.
*
* @export
* @param {*} db
* @param {*} sessionId
* @returns state object that needs to be saved into a file.
*/
async function createStateFromDatabase(db, sessionId) {
let state = {
fileFormat: 0,
featureLevel: env.zapVersion().featureLevel,
creator: 'zap'
}
let promises = []
let excludedKeys = [dbEnum.sessionKey.filePath]
env.logInfo(`Exporting data for session: ${sessionId}`)
promises.push(getSessionKeyValues(db, sessionId, excludedKeys))
let allEndpointTypes = await exportEndpointTypes(db, sessionId)
let parseEndpointTypes = Promise.resolve({
key: 'endpointTypes',
data: allEndpointTypes.endpointTypes
})
let parseEndpoints = Promise.resolve({
key: 'endpoints',
data: allEndpointTypes.endpoints
})
let appendLogPromise = querySession.readLog(db, sessionId).then((log) => {
return { key: 'log', data: log }
})
promises.push(parseEndpointTypes)
promises.push(parseEndpoints)
promises.push(appendLogPromise)
let data = await Promise.all(promises)
data.flat().forEach((keyDataPair) => {
state[keyDataPair.key] = keyDataPair.data
})
return state
}
// exports
exports.exportDataIntoFile = exportDataIntoFile
exports.createStateFromDatabase = createStateFromDatabase