Skip to content

Commit e547b1c

Browse files
committed
Fix networkHelper.DownloadFileHttp and IoTHelper.UnpackFromZip
1 parent 9fce30c commit e547b1c

File tree

8 files changed

+227
-129
lines changed

8 files changed

+227
-129
lines changed

src/Entity/EntityBase.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ export abstract class EntityBase<T extends EntityBaseAttribute> {
151151
public Recovery():IotResult
152152
{
153153
let result:IotResult;
154-
const fileZipPath=`${this.RecoverySourcePath}\\${this.RootNameDir}.zip`;
155-
result= IoTHelper.UnpackFromZip(fileZipPath,path.dirname(this.RootDir));
154+
const fileZipPath= path.join(this.RecoverySourcePath ?? "non", `${this.RootNameDir}.zip`);
155+
result= IoTHelper.UnpackFromZip(fileZipPath,this.RootDir);
156156
if(result.Status==StatusResult.Error) result.AddMessage(`${this._entityLabel} restore error`);
157157
//result
158158
return result;

src/Entity/EntityCollection.ts

Lines changed: 71 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -141,57 +141,11 @@ export abstract class EntityCollection <A extends EntityBaseAttribute, T extends
141141
return Promise.resolve(result);
142142
}
143143
//checking all folders
144-
listFolders.forEach(dir => {
145-
const filePath=`${dir}\\${this._entityLabel.toLowerCase()}.fastiot.yaml`;
146-
let entity = new this.TCreator(this.Config.schemasFolderPath);
147-
entity.Init(type,filePath,this.Config.recoverySourcePath);
148-
if(!entity.IsValid&&type==EntityType.system) {
149-
//Recovery
150-
this.CreateEvent(`${this._entityLabel} recovery: ${path.dirname(filePath)}`,LogLevel.Debug);
151-
result= entity.Recovery();
152-
if(result.Status==StatusResult.Ok) {
153-
entity.Init(type,filePath,this.Config.recoverySourcePath);}
154-
else {
155-
this.CreateEvent(result,LogLevel.Debug);
156-
}
157-
}
158-
//main
159-
if(entity.IsValid) {
160-
this.CreateEvent(`${this._entityLabel} is valid: [${entity.Attributes.Id}]`,LogLevel.Debug);
161-
if(this.IsCompatibleByVersionExtAndPlatform(entity)) {
162-
const isContains=this.Contains(entity);
163-
switch(isContains) {
164-
case ContainsType.no: {
165-
this.Add(entity);
166-
this.CreateEvent(`${this._entityLabel} added: [${entity.Attributes.Id}] ${entity.RootDir}`,LogLevel.Debug);
167-
break;
168-
}
169-
case ContainsType.yesVersionSmaller: {
170-
this.Update(entity);
171-
this.CreateEvent(`${this._entityLabel} updated: [${entity.Attributes.Id}] ${entity.RootDir}`,LogLevel.Debug);
172-
break;
173-
}
174-
default: {
175-
this.CreateEvent(`Adding a ${this._entityLabel} was skipped because already in the collection: [${entity.Attributes.Id}] ${entity.RootDir}`,LogLevel.Debug);
176-
break;
177-
}
178-
}
179-
}else{
180-
result = new IotResult(StatusResult.Error,`The ${this._entityLabel} ${entity.RootDir} is for a newer version of the extension. ` +
181-
`Update the extension.`);
182-
this.CreateEvent(result);
183-
}
184-
}else{
185-
result = new IotResult(StatusResult.Error,`The ${this._entityLabel} ${entity.RootDir} has not been validated.`);
186-
this.CreateEvent(result);
187-
this.CreateEvent(entity.ValidationErrorsToString,LogLevel.Debug);
188-
//delete system entity
189-
if(type==EntityType.system) {
190-
result= entity.Remove();
191-
this.CreateEvent(result,LogLevel.Debug);
192-
}
193-
}
194-
});
144+
let dir:string;
145+
for (let i = 0; i < listFolders.length; i++) {
146+
dir=listFolders[i];
147+
await this.ImportEntity(dir,type);
148+
}
195149
//result
196150
if((this.Count-entitysCountBegin)>0) {
197151
result= new IotResult(StatusResult.Ok,`Loading ${type} ${this._entitiesLabel} from ${pathFolder} folder successfully completed`);
@@ -206,6 +160,68 @@ export abstract class EntityCollection <A extends EntityBaseAttribute, T extends
206160
return Promise.resolve(result);
207161
}
208162

163+
protected async ImportEntity(dir:string,type:EntityType):Promise<IotResult>
164+
{
165+
let result:IotResult;
166+
try {
167+
const filePath=`${dir}\\${this._entityLabel.toLowerCase()}.fastiot.yaml`;
168+
let entity = new this.TCreator(this.Config.schemasFolderPath);
169+
entity.Init(type,filePath,this.Config.recoverySourcePath);
170+
if(!entity.IsValid&&type==EntityType.system) {
171+
//Recovery
172+
this.CreateEvent(`${this._entityLabel} recovery: ${path.dirname(filePath)}`,LogLevel.Debug);
173+
result= entity.Recovery();
174+
if(result.Status==StatusResult.Ok) {
175+
entity.Init(type,filePath,this.Config.recoverySourcePath);}
176+
else {
177+
this.CreateEvent(result,LogLevel.Debug);
178+
}
179+
}
180+
//main
181+
if(entity.IsValid) {
182+
this.CreateEvent(`${this._entityLabel} is valid: [${entity.Attributes.Id}]`,LogLevel.Debug);
183+
if(this.IsCompatibleByVersionExtAndPlatform(entity)) {
184+
const isContains=this.Contains(entity);
185+
switch(isContains) {
186+
case ContainsType.no: {
187+
this.Add(entity);
188+
this.CreateEvent(`${this._entityLabel} added: [${entity.Attributes.Id}] ${entity.RootDir}`,LogLevel.Debug);
189+
result = new IotResult(StatusResult.Ok,`${this._entityLabel} added: [${entity.Attributes.Id}] ${entity.RootDir}`);
190+
break;
191+
}
192+
case ContainsType.yesVersionSmaller: {
193+
this.Update(entity);
194+
this.CreateEvent(`${this._entityLabel} updated: [${entity.Attributes.Id}] ${entity.RootDir}`,LogLevel.Debug);
195+
result = new IotResult(StatusResult.Ok,`${this._entityLabel} updated: [${entity.Attributes.Id}] ${entity.RootDir}`);
196+
break;
197+
}
198+
default: {
199+
this.CreateEvent(`Adding a ${this._entityLabel} was skipped because already in the collection: [${entity.Attributes.Id}] ${entity.RootDir}`,LogLevel.Debug);
200+
result = new IotResult(StatusResult.Error,`Adding a ${this._entityLabel} was skipped because already in the collection: [${entity.Attributes.Id}] ${entity.RootDir}`);
201+
break;
202+
}
203+
}
204+
}else{
205+
result = new IotResult(StatusResult.Error,`The ${this._entityLabel} ${entity.RootDir} is for a newer version of the extension. ` +
206+
`Update the extension.`);
207+
this.CreateEvent(result);
208+
}
209+
}else{
210+
result = new IotResult(StatusResult.Error,`The ${this._entityLabel} ${entity.RootDir} has not been validated.`);
211+
this.CreateEvent(result);
212+
this.CreateEvent(entity.ValidationErrorsToString,LogLevel.Debug);
213+
//delete system entity
214+
if(type==EntityType.system) {
215+
result= entity.Remove();
216+
this.CreateEvent(result,LogLevel.Debug);
217+
}
218+
}
219+
} catch (err: any){
220+
result= new IotResult(StatusResult.Error,`Error import ${type} ${this._entitiesLabel} dir ${dir}`,err);
221+
}
222+
return Promise.resolve(result);
223+
}
224+
209225
protected async UpdateEntitiesFromUrl(url:string,type:EntityType):Promise<IotResult>
210226
{
211227
const destPath= this.GetDirEntitiesCallback(type);
@@ -336,10 +352,6 @@ export abstract class EntityCollection <A extends EntityBaseAttribute, T extends
336352
//Preparing
337353
result= new IotResult(StatusResult.None);
338354
this.Clear();
339-
//To get the number of hours since Unix epoch, i.e. Unix timestamp:
340-
const dateNow=Math.floor(Date.now() / 1000/ 3600);
341-
const TimeHasPassedHours=dateNow-this.Config.builtInConfig.LastUpdateTemplatesHours;
342-
const isNeedUpdate = ()=>this.Config.isUpdate&&(TimeHasPassedHours>=this.Config.updateIntervalHours);
343355
//main code
344356
this.CreateEvent(`-------- Loading ${this._entitiesLabel} -------`,
345357
LogLevel.Information);
@@ -348,6 +360,10 @@ export abstract class EntityCollection <A extends EntityBaseAttribute, T extends
348360
await this.LoadEntities(EntityType.system);
349361
//Updating system entities
350362
this.CreateEvent(`Updating system ${this._entitiesLabel}`,undefined,15); //30
363+
//To get the number of hours since Unix epoch, i.e. Unix timestamp:
364+
const dateNow=Math.floor(Date.now() / 1000/ 3600);
365+
const TimeHasPassedHours=dateNow-this.Config.builtInConfig.LastUpdateTemplatesHours;
366+
const isNeedUpdate = ()=>this.Config.isUpdate&&(TimeHasPassedHours>=this.Config.updateIntervalHours);
351367
if(force||isNeedUpdate()){
352368
//system
353369
this.CreateEvent(`☑️ Updating system ${this._entitiesLabel}`,LogLevel.Debug);

src/Entity/EntityDownloader.ts

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as vscode from 'vscode';
22
import * as fs from 'fs-extra';
33
import * as path from 'path';
44
import YAML from 'yaml';
5-
import axios from 'axios';
65
import { IotResult,StatusResult } from '../IotResult';
76
import { IoTHelper } from '../Helper/IoTHelper';
87
import { networkHelper } from '../Helper/networkHelper';
@@ -16,24 +15,17 @@ export class EntityDownloader {
1615
let result:IotResult;
1716
try {
1817
//download *.zip
19-
const fileZipPath=`${destPath}\\${item.Id}.zip`;
20-
if (fs.existsSync(fileZipPath)) fs.removeSync(fileZipPath);
21-
await networkHelper.DownloadFileHttp(item.Url,fileZipPath);
18+
const fileZipPath = path.join(destPath, `${item.Id}.zip`);
19+
result=await networkHelper.DownloadFileHttp(item.Url,fileZipPath);
20+
if(result.Status!=StatusResult.Ok) return Promise.resolve(result);
2221
//unpack
23-
let unpackPath=`${destPath}\\${item.Id}`;
24-
//delete
25-
if (fs.existsSync(unpackPath)) {
26-
fs.emptyDirSync(unpackPath);
27-
fs.removeSync(unpackPath);
28-
}
29-
var AdmZip = require("adm-zip");
30-
var zip = new AdmZip(fileZipPath);
31-
// extracts everything
32-
zip.extractAllTo(/*target path*/ unpackPath, /*overwrite*/ true);
22+
const unpackDir = path.join(destPath, item.Id);
23+
result=IoTHelper.UnpackFromZip(fileZipPath,unpackDir);
24+
if(result.Status!=StatusResult.Ok) return Promise.resolve(result);
3325
//delete zip
3426
fs.removeSync(fileZipPath);
3527
result = new IotResult(StatusResult.Ok);
36-
result.returnObject=unpackPath;
28+
result.returnObject=unpackDir;
3729
} catch (err: any){
3830
result = new IotResult(StatusResult.Error,`Unable to download file ${item.Url}.`,err);
3931
}
@@ -47,13 +39,10 @@ export class EntityDownloader {
4739
let listDownload:Array<EntityDownload>=[];
4840
try {
4941
//download templatelist.fastiot.yaml
50-
const response = await axios.get(url);
51-
if(response.status!=200){
52-
result = new IotResult(StatusResult.Error,`Unable to download file ${url}. Server response http code ${response.status}`,`${response.statusText}`);
53-
return Promise.resolve(result);
54-
}
42+
result=await networkHelper.DownloadFileHttp(url);
43+
if(result.Status!=StatusResult.Ok) return Promise.resolve(result);
5544
//parse templatelist.fastiot.yaml
56-
const obj=YAML.parse(response.data);
45+
const obj=YAML.parse(result.returnObject);
5746
//entity download
5847
let index=0;
5948
do {

src/Helper/IoTHelper.ts

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -130,31 +130,22 @@ export class IoTHelper {
130130
static UnpackFromZip(fileZipPath:string, unpackDir:string):IotResult {
131131
let result:IotResult;
132132
try {
133-
let filename = path.basename(fileZipPath);
134-
filename=filename.substring(0,filename.length-4);
135-
unpackDir=unpackDir+"\\"+filename;
136133
//clear
137-
if (fs.existsSync(unpackDir)) fs.emptyDirSync(unpackDir);
138-
//mkdir
139-
IoTHelper.MakeDirSync(unpackDir);
134+
if (fs.existsSync(unpackDir)) {
135+
fs.emptyDirSync(unpackDir);
136+
fs.removeSync(unpackDir);
137+
}
138+
//create root dir
139+
const rootDir = path.dirname(unpackDir);
140+
IoTHelper.MakeDirSync(rootDir);
140141
//unpack
141142
var AdmZip = require("adm-zip");
142143
var zip = new AdmZip(fileZipPath);
143144
// extracts everything
144145
zip.extractAllTo(/*target path*/ unpackDir, /*overwrite*/ true);
145-
/*
146-
const zip = new StreamZip.async({ file: fileZipPath });
147-
const entriesCount = await zip.entriesCount;
148-
console.log(`Entries read: ${entriesCount}`);
149-
150-
const count = await zip.extract(null, unpackDir);
151-
console.log(`Extracted ${count} entries`);
152-
await zip.close();
153-
*/
154146
result = new IotResult(StatusResult.Ok);
155-
result.returnObject=unpackDir;
156147
} catch (err: any){
157-
result = new IotResult(StatusResult.Error,`Error while unpacking file ${fileZipPath}`,err);
148+
result = new IotResult(StatusResult.Error,`Error while unpacking file ${fileZipPath} to dir ${unpackDir}`,err);
158149
}
159150
//result
160151
return result;

src/Helper/networkHelper.ts

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import * as dns from 'dns';
1111
import tcpPortUsed from 'tcp-port-used';
1212
import ping from 'pingman';
1313
import {extendedPingOptions, pingOptions, pingResponse} from '@lolpants/pingman';
14+
import * as platformFolders from 'platform-folders';
15+
import { promisify } from 'util';
1416

1517
import {IoTHelper} from './IoTHelper';
1618
import {IotResult,StatusResult } from '../IotResult';
@@ -92,18 +94,59 @@ export class networkHelper {
9294
return Promise.resolve(result);
9395
}
9496

95-
static finished = util.promisify(stream.finished);
96-
97-
static async DownloadFileHttp(fileUrl: string, outputLocationPath: string): Promise<any> {
98-
const writer = fs.createWriteStream(outputLocationPath);
99-
return axios({
100-
method: 'get',
101-
url: fileUrl,
102-
responseType: 'stream',
103-
}).then(response => {
104-
response.data.pipe(writer);
105-
return this.finished(writer); //this is a Promise
106-
});
97+
/**
98+
* File download.
99+
*
100+
* @param {string} fileUrl - File download link URL. Ex: https://devdotnet.org/wp-content/media/code-title.png
101+
* @param {string=} outputLocationPath - The disk path to the save file.
102+
* Ex: If the path is `D:\temp`, then the path to save the file is `D:\temp\code-title.png`.
103+
* If the path is `D:\temp\image.png`, then the path to save the file is `D:\temp\image.png`.
104+
* If the path is `undefined`, then the `IotResult.returnObject` response will contain the file itself.
105+
* @returns {IotResult} `IotResult.tag` contain is HTTP response code. `IotResult.returnObject` contain is path to the file.
106+
* Ex: 200 (Ok), 404 (Not Found).
107+
*/
108+
static async DownloadFileHttp(fileUrl: string, outputLocationPath?: string): Promise<IotResult> {
109+
let result:IotResult;
110+
try {
111+
if(outputLocationPath && fs.existsSync(outputLocationPath) && fs.lstatSync(outputLocationPath).isDirectory()) {
112+
//Directory
113+
const fileName=vscode.Uri.parse(fileUrl).path.split(`/`).pop() ?? "file.tmp";
114+
outputLocationPath=path.join(outputLocationPath, fileName);
115+
}
116+
const finishedDownload = promisify(stream.finished);
117+
result = new IotResult(StatusResult.Ok,"File downloaded");
118+
if(outputLocationPath) {
119+
const response = await axios({
120+
method: 'GET',
121+
url: fileUrl,
122+
responseType: 'stream',
123+
timeout: 10000 // 10 seconds
124+
});
125+
const writer = fs.createWriteStream(outputLocationPath);
126+
response.data.pipe(writer);
127+
await finishedDownload(writer);
128+
writer.close();
129+
result.returnObject=outputLocationPath;
130+
result.tag=response.status.toString();
131+
}else {
132+
const response = await axios({
133+
method: 'GET',
134+
url: fileUrl,
135+
timeout: 5000 // 5 seconds
136+
});
137+
result.returnObject=response.data;
138+
result.tag=response.status.toString();
139+
}
140+
} catch (err: any) {
141+
let errMsg:string;
142+
errMsg=`Unable to download file ${fileUrl}.`;
143+
if(err.response)
144+
errMsg=`Unable to download file ${fileUrl}. Server response http code ${err.response.status}. statusText ${err.response.statusText}`;
145+
result = new IotResult(StatusResult.Error,errMsg,err);
146+
if(err.response) result.tag=err.response.status.toString();
147+
}
148+
//result
149+
return Promise.resolve(result);
107150
}
108151

109152
static GetLocalIPaddress(): Map<string,string> {

src/actionsDevice/exportImportDevices.ts

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,26 +40,24 @@ export async function importDevices(treeData: TreeDataDevicesProvider,contextUI:
4040
}
4141
};
4242
const file = await vscode.window.showOpenDialog(options);
43-
let result = new IotResult(StatusResult.None);
44-
if(file)
45-
{
46-
contextUI.Output(`Action: Import devices from file ${file[0].fsPath}`);
47-
try
48-
{
49-
const jsonObj = JSON.parse(fs.readFileSync(file[0].fsPath, 'utf-8'));
50-
result = await treeData.FromJSON(jsonObj);
51-
} catch (err:any) {
52-
result = new IotResult(StatusResult.Error,err);
53-
}
54-
//Output
55-
contextUI.Output(result.toStringWithHead());
56-
//Message
57-
if(result.Status==StatusResult.Ok) {
58-
vscode.window.showInformationMessage(`Devices imported successfully. ${result.Message}`);
59-
treeData.RefreshsFull();
60-
treeData.SaveDevices();
61-
} else {
62-
vscode.window.showErrorMessage(`Error. Device import failed! \n${result.Message}`);
63-
}
43+
if(!file) return;
44+
let result:IotResult;
45+
contextUI.Output(`Action: Import devices from file ${file[0].fsPath}`);
46+
try {
47+
const jsonObj = JSON.parse(fs.readFileSync(file[0].fsPath, 'utf-8'));
48+
result = await treeData.FromJSON(jsonObj);
49+
}catch (err:any) {
50+
result = new IotResult(StatusResult.Error,err);
51+
}
52+
//Output
53+
contextUI.Output(result.toStringWithHead());
54+
//Message
55+
if(result.Status==StatusResult.Ok) {
56+
vscode.window.showInformationMessage(`Devices imported successfully. ${result.Message}`);
57+
treeData.RefreshsFull();
58+
treeData.SaveDevices();
59+
} else {
60+
vscode.window.showErrorMessage(`Error. Device import failed! \n${result.Message}`);
6461
}
62+
6563
}

0 commit comments

Comments
 (0)