Mise en place de la récupération de fichier (todo: vérification fingerprint)
This commit is contained in:
parent
986e06b073
commit
d6e14085a8
@ -61,13 +61,13 @@ public class App {
|
||||
protocolRep.addReader(new FilelistRule(protocolRep));
|
||||
protocolRep.addReader(new SavefileRule(protocolRep, repositoryStorage.getStoragePath()));
|
||||
protocolRep.addReader(new SendOkRule(protocolRep));
|
||||
protocolRep.addReader(new SendErrorRule(protocolRep, repositoryStorage.getStoragePath()));
|
||||
protocolRep.addReader(new SendErrorRule(protocolRep));
|
||||
protocolRep.addReader(new GetFileRule(protocolRep));
|
||||
protocolRep.addReader(new EraseErrorRule(protocolRep));
|
||||
protocolRep.addReader(new EraseOkRule(protocolRep));
|
||||
protocolRep.addReader(new RemoveFileRule(protocolRep));
|
||||
protocolRep.addReader(new RetrieveErrorRule());
|
||||
protocolRep.addReader(new RetrieveOkRule());
|
||||
protocolRep.addReader(new RetrieveErrorRule(protocolRep));
|
||||
protocolRep.addReader(new RetrieveOkRule(protocolRep, repositoryStorage.getStoragePath()));
|
||||
}
|
||||
|
||||
private static void initWritersProtocols(Repository repositoryStorage, ProtocolRepository protocolRep) {
|
||||
@ -81,7 +81,7 @@ public class App {
|
||||
protocolRep.addWriter(new EraseFileRule());
|
||||
protocolRep.addWriter(new RemoveFileErrorRule());
|
||||
protocolRep.addWriter(new RemoveFileOkRule());
|
||||
protocolRep.addWriter(new GetFileOkRule());
|
||||
protocolRep.addWriter(new GetFileOkRule(repositoryStorage.getStoragePath()));
|
||||
protocolRep.addWriter(new RetrieveFileRule());
|
||||
}
|
||||
|
||||
|
@ -116,25 +116,24 @@ public class ClientHandler implements Runnable, AutoCloseable {
|
||||
ProtocolWriter.ProtocolResult writerCommand = ruleResult.getResultCommand();
|
||||
// TODO : Vérifier que le StorBackEnd demandé (Pas toujours demandé) est disponible
|
||||
if (ruleResult.getReceiver() == ProtocolReader.ResultCmdReceiver.STOREBACKEND && !fileFrontEnd.canExecuteCommand(ruleResult.getRequestDomain())) {
|
||||
System.out.println("ICI");
|
||||
System.out.println(context.getLogin() + " - " + 2);
|
||||
writer.print(ruleResult.onNotExecutable(context)); // Renvoie au client
|
||||
writer.flush();
|
||||
} else if (ruleResult.getReceiver() == ProtocolReader.ResultCmdReceiver.STOREBACKEND) {
|
||||
|
||||
fileFrontEnd.newCommand(context, writerCommand); // Envoie dans la file de tâche FileFrontEnd en attente d'un traitement d'un StorBackEnd
|
||||
|
||||
System.out.println(context.getLogin() + " - " + 3);
|
||||
// Attend la fin de la réalisation de la tâche
|
||||
waitTaskResponse();
|
||||
System.out.println(context.getLogin() + " - " + 4);
|
||||
|
||||
writer.write(response.getCommand()); // Renvoie au client
|
||||
writer.flush();
|
||||
response.write(this.client.getOutputStream()); // Ecrit au client si nécessaire
|
||||
System.out.println(context.getLogin() + " - " + 5);
|
||||
if (response != null) {
|
||||
writer.write(response.getCommand()); // Renvoie au client
|
||||
writer.flush();
|
||||
response.write(this.client.getOutputStream()); // Ecrit au client si nécessaire
|
||||
} else {
|
||||
writer.print(ruleResult.onNotExecutable(context)); // Renvoie au client
|
||||
writer.flush();
|
||||
}
|
||||
} else {
|
||||
System.out.println(context.getLogin() + " - " + 6);
|
||||
writer.print(writerCommand.getCommand()); // Renvoie au client
|
||||
writer.flush();
|
||||
}
|
||||
|
@ -96,16 +96,25 @@ public class StoreProcessor extends Thread implements AutoCloseable {
|
||||
|
||||
|
||||
// Response
|
||||
String responseCommand = this.reader.readLine() + "\r\n";
|
||||
if (responseCommand != null)
|
||||
System.out.println("StoreBackEnd: " + responseCommand);
|
||||
ProtocolReader.ProtocolResult responseResult = protocolRep.executeReader(context, responseCommand);
|
||||
responseResult.read(
|
||||
this.store.getInputStream()
|
||||
);
|
||||
System.out.println("StoreBackEnd response to client: " + responseResult.getResultCommand());
|
||||
String responseCommand = this.reader.readLine();
|
||||
if (responseCommand != null && !responseCommand.isBlank()) {
|
||||
responseCommand += "\r\n";
|
||||
ProtocolReader.ProtocolResult responseResult = protocolRep.executeReader(context, responseCommand);
|
||||
if (responseResult != null) {
|
||||
System.out.println("StoreBackEnd response to client: " + responseResult.getResultCommand());
|
||||
responseResult.read(
|
||||
this.store.getInputStream()
|
||||
);
|
||||
|
||||
alertAvalaible(responseResult.getResultCommand());
|
||||
alertAvalaible(responseResult.getResultCommand());
|
||||
} else {
|
||||
System.out.println("StoreBackEnd result: Commande null");
|
||||
alertAvalaible(null);
|
||||
}
|
||||
} else {
|
||||
System.out.println("StoreBackEnd: Commande null");
|
||||
alertAvalaible(null);
|
||||
}
|
||||
|
||||
} catch (IOException ignore) {
|
||||
// TODO : Gérer déconnection
|
||||
|
@ -95,8 +95,7 @@ public abstract class ProtocolReader {
|
||||
*
|
||||
* @param reader Buffer rempli du fichier
|
||||
*/
|
||||
public void read(InputStream reader) {
|
||||
}
|
||||
public void read(InputStream reader) { }
|
||||
|
||||
/**
|
||||
* Permet de récupérer le context courant
|
||||
@ -111,7 +110,7 @@ public abstract class ProtocolReader {
|
||||
/**
|
||||
* Cette méthode est appelée lorsque la commande à exécuter ne peut pas être exécutée
|
||||
*/
|
||||
public final String onNotExecutable(Context context) {
|
||||
public final String onNotExecutable(Context context) {
|
||||
ProtocolResult cmdResult = onError(context);
|
||||
return cmdResult == null ? null : cmdResult.getResultCommand().getCommand();
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import lightcontainer.domains.client.Context;
|
||||
import lightcontainer.interfaces.ProtocolRepository;
|
||||
import lightcontainer.protocol.ProtocolReader;
|
||||
import lightcontainer.protocol.rules.writer.GetFileErrorRule;
|
||||
import lightcontainer.protocol.rules.writer.RemoveFileErrorRule;
|
||||
import lightcontainer.protocol.rules.writer.RetrieveFileRule;
|
||||
import lightcontainer.storage.ReadOnlyFile;
|
||||
|
||||
import java.util.Iterator;
|
||||
@ -36,7 +38,6 @@ public class GetFileRule extends ProtocolReader {
|
||||
super(context);
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -51,16 +52,21 @@ public class GetFileRule extends ProtocolReader {
|
||||
|
||||
ReadOnlyFile file = context.getFile(data[FILE_NAME]);
|
||||
|
||||
// Précision du store back end demandé pour traiter cette commande.
|
||||
String requestDomain = extractRequestDomain(file.getStorageIterator());
|
||||
result.setRequestDomain(requestDomain);
|
||||
if (file != null) {
|
||||
// Précision du store back end demandé pour traiter cette commande.
|
||||
String requestDomain = extractRequestDomain(file.getStorageIterator());
|
||||
result.setRequestDomain(requestDomain);
|
||||
|
||||
if (true) {
|
||||
// Save into bundle
|
||||
context.putDataString("fileName", file.getName());
|
||||
context.putDataInt("fileSize", file.getSize());
|
||||
context.putDataString("fileIV", file.getIv());
|
||||
|
||||
// Create cmd for storebacked
|
||||
result.setResultCommand(protocolRep.executeWriter(context, RetrieveFileRule.NAME, context.getHashedFileName(file.getName())), ResultCmdReceiver.STOREBACKEND);
|
||||
} else {
|
||||
|
||||
result.setResultCommand(protocolRep.executeWriter(context, GetFileErrorRule.NAME), ResultCmdReceiver.CLIENT);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -74,8 +80,8 @@ public class GetFileRule extends ProtocolReader {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO : But futur est de pouvoir en avoir plusieurs
|
||||
/** TMP
|
||||
* TODO : But futur est de pouvoir en avoir plusieurs (sbe)
|
||||
* Cette méthode permet de choisir le domaine voulu.
|
||||
* @param storageIterator Les domaines
|
||||
* @return Le domain choisi
|
||||
|
@ -1,16 +1,22 @@
|
||||
package lightcontainer.protocol.rules.reader;
|
||||
|
||||
import lightcontainer.domains.client.Context;
|
||||
import lightcontainer.interfaces.ProtocolRepository;
|
||||
import lightcontainer.protocol.ProtocolReader;
|
||||
import lightcontainer.protocol.rules.writer.GetFileErrorRule;
|
||||
|
||||
public class RetrieveErrorRule extends ProtocolReader {
|
||||
// Constants
|
||||
private static final String PATTERN = "^RETRIEVE_ERROR\r\n$";
|
||||
private static final String NAME = "RETRIEVE_ERROR";
|
||||
|
||||
// Variables
|
||||
private final ProtocolRepository protocolRep;
|
||||
|
||||
// Constructor
|
||||
public RetrieveErrorRule() {
|
||||
public RetrieveErrorRule(ProtocolRepository protocolRep) {
|
||||
super(NAME, PATTERN);
|
||||
this.protocolRep = protocolRep;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -20,7 +26,9 @@ public class RetrieveErrorRule extends ProtocolReader {
|
||||
* @param data Paramètres pour créer la commande.
|
||||
*/
|
||||
@Override
|
||||
protected <T extends ProtocolResult> T onExecuted(Context context, String... data) {
|
||||
return null;
|
||||
protected ProtocolReader.ProtocolResult onExecuted(Context context, String... data) {
|
||||
ProtocolReader.ProtocolResult result = new ProtocolReader.ProtocolResult(context);
|
||||
result.setResultCommand(protocolRep.executeWriter(context, GetFileErrorRule.NAME), ResultCmdReceiver.CLIENT);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,18 @@
|
||||
package lightcontainer.protocol.rules.reader;
|
||||
|
||||
import lightcontainer.domains.client.Context;
|
||||
import lightcontainer.interfaces.ProtocolRepository;
|
||||
import lightcontainer.protocol.ProtocolReader;
|
||||
import lightcontainer.protocol.rules.writer.GetFileOkRule;
|
||||
import lightcontainer.protocol.rules.writer.SaveFileErrorRule;
|
||||
import lightcontainer.utils.FileReceiver;
|
||||
import lightcontainer.utils.FileSender;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class RetrieveOkRule extends ProtocolReader {
|
||||
// Constants
|
||||
private static final String PATTERN = "^RETRIEVE_OK ([A-Za-z0-9.]{50,200} [0-9]{1,10} [A-Za-z0-9.]{50,200})\r\n$";
|
||||
private static final String PATTERN = "^RETRIEVE_OK ([A-Za-z0-9.]{50,200}) ([0-9]{1,10}) ([A-Za-z0-9.]{50,200})\r\n$";
|
||||
private static final String NAME = "RETRIEVE_OK";
|
||||
// -- arguments
|
||||
private static final int HASHED_FILE_NAME = 0; // Index hashed filename
|
||||
@ -13,8 +20,43 @@ public class RetrieveOkRule extends ProtocolReader {
|
||||
private static final int HASHED_FILE_CONTENT = 2; // Index hashed file content
|
||||
|
||||
// Variables
|
||||
public RetrieveOkRule() {
|
||||
private ProtocolRepository protocolRep;
|
||||
private String storagePath;
|
||||
|
||||
// Variables
|
||||
public RetrieveOkRule(ProtocolRepository protocolRep, String storagePath) {
|
||||
super(NAME, PATTERN);
|
||||
this.protocolRep = protocolRep;
|
||||
this.storagePath = storagePath;
|
||||
}
|
||||
|
||||
public class Result extends ProtocolResult {
|
||||
// Variables
|
||||
private String filename;
|
||||
private int filesize;
|
||||
private String hashedFileContent;
|
||||
|
||||
public Result(Context context, String filename, int filesize, String hashedFileContent) {
|
||||
super(context);
|
||||
this.filename = filename;
|
||||
this.filesize = filesize;
|
||||
this.hashedFileContent = hashedFileContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitch has bettern than my money
|
||||
* @param reader Buffer rempli du fichier
|
||||
*/
|
||||
@Override
|
||||
public void read(InputStream reader) {
|
||||
super.read(reader);
|
||||
System.out.println("Récupération du fichier du SBE");
|
||||
|
||||
FileReceiver fileReceiver = new FileReceiver(storagePath);
|
||||
fileReceiver.receiveFile(reader, this.filename, this.filesize);
|
||||
|
||||
// TODO fingerprint
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -24,12 +66,16 @@ public class RetrieveOkRule extends ProtocolReader {
|
||||
* @param data Paramètres pour créer la commande.
|
||||
*/
|
||||
@Override
|
||||
protected <T extends ProtocolResult> T onExecuted(Context context, String... data) {
|
||||
return null;
|
||||
protected RetrieveOkRule.Result onExecuted(Context context, String... data) {
|
||||
RetrieveOkRule.Result result = new RetrieveOkRule.Result(context, data[HASHED_FILE_NAME], Integer.parseInt(data[FILE_SIZE]), data[HASHED_FILE_CONTENT]);
|
||||
result.setResultCommand(protocolRep.executeWriter(context, GetFileOkRule.NAME, context.getDataString("fileName"), String.valueOf(context.getDataInt("fileSize"))), ResultCmdReceiver.CLIENT);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T extends ProtocolResult> T onError(Context context) {
|
||||
return super.onError(context);
|
||||
protected ProtocolReader.ProtocolResult onError(Context context) {
|
||||
ProtocolResult result = new ProtocolResult(context);
|
||||
result.setResultCommand(protocolRep.executeWriter(context, SaveFileErrorRule.NAME), ResultCmdReceiver.CLIENT);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -14,29 +14,23 @@ import java.nio.file.Path;
|
||||
* Règle permettant de de confirmer la sauvegrade d'un fichier.
|
||||
*/
|
||||
public class SendErrorRule extends ProtocolReader {
|
||||
|
||||
|
||||
// Constants
|
||||
private static final String PATTERN = "^SEND_ERROR\r\n$";
|
||||
|
||||
private static final String NAME = "SEND_ERROR";
|
||||
|
||||
private ProtocolRepository protocolRep;
|
||||
private final String storagePath;
|
||||
|
||||
// Constructor
|
||||
public SendErrorRule(ProtocolRepository protocolRep, String storagePath) {
|
||||
public SendErrorRule(ProtocolRepository protocolRep) {
|
||||
super(NAME, PATTERN);
|
||||
this.protocolRep = protocolRep;
|
||||
this.storagePath = storagePath;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected ProtocolResult onExecuted(Context context, String... data) {
|
||||
ProtocolResult result = new ProtocolResult(context);
|
||||
result.setResultCommand(protocolRep.executeWriter(context, SaveFileErrorRule.NAME), ResultCmdReceiver.CLIENT);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,56 @@
|
||||
package lightcontainer.protocol.rules.writer;
|
||||
|
||||
import lightcontainer.domains.client.Context;
|
||||
import lightcontainer.protocol.ProtocolWriter;
|
||||
import lightcontainer.utils.FileSender;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class GetFileOkRule extends ProtocolWriter {
|
||||
// Constants
|
||||
private static final String PATTERN = "^GETFILE_OK (^ !]{1,20}) ([0-9]{1,10})\r\n$";
|
||||
private static final String PATTERN = "^GETFILE_OK ([^ !]{1,20}) ([0-9]{1,10})\r\n$";
|
||||
public static final String NAME = "GETFILE_OK";
|
||||
// -- params
|
||||
private static final int FILE_NAME = 0; // Index file name hashed
|
||||
private static final int FILE_SIZE = 1; // Index file size
|
||||
|
||||
// Variables
|
||||
private String storagePath;
|
||||
|
||||
// Constructors
|
||||
public GetFileOkRule() {
|
||||
public GetFileOkRule(String storagePath) {
|
||||
super(NAME, PATTERN);
|
||||
this.storagePath = storagePath;
|
||||
}
|
||||
|
||||
public class Result extends ProtocolWriter.ProtocolResult {
|
||||
// Variables
|
||||
private String filename;
|
||||
private int filesize;
|
||||
|
||||
// Constructors
|
||||
public Result(Context context, String filename, int filesize) {
|
||||
super(context);
|
||||
this.filename = filename;
|
||||
this.filesize = filesize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Oh yeahh baby
|
||||
* @param writer Buffer à remplir qui sera envoyer via le réseau
|
||||
*/
|
||||
@Override
|
||||
public void write(OutputStream writer) {
|
||||
super.write(writer);
|
||||
System.out.printf("Sauvegarde du fichier : %s %d\n", this.filename, this.filesize);
|
||||
|
||||
FileSender fileSender = new FileSender(storagePath);
|
||||
fileSender.sendFile(getContext().getHashedFileName(this.filename), writer, this.filesize, getContext().getAesKey(), getContext().getDataString("fileIV"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GetFileOkRule.Result onExecuted(Context context, String... data) {
|
||||
return new GetFileOkRule.Result(context, data[FILE_NAME], Integer.parseInt(data[FILE_SIZE]));
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,6 @@ public class RetrieveFileRule extends ProtocolWriter {
|
||||
// Constants
|
||||
private static final String PATTERN = "^RETRIEVEFILE ([A-Za-z0-9.]{50,200})\r\n$";
|
||||
public static final String NAME = "RETRIEVEFILE";
|
||||
// -- params
|
||||
private static final int HASHED_FILE_NAME = 0; // Index hashed filename
|
||||
|
||||
// Constructor
|
||||
public RetrieveFileRule() {
|
||||
|
@ -44,10 +44,8 @@ public class SendfileRule extends ProtocolWriter {
|
||||
super.write(writer);
|
||||
System.out.println("Envoie du fichier au SBE");
|
||||
|
||||
|
||||
FileSender fileSender = new FileSender(storagePath);
|
||||
fileSender.sendFile(hashedFileName, writer);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,17 +48,15 @@ public class FileFrontEnd implements ClientHandlerFFE, StoreProcessorFFE {
|
||||
@Override
|
||||
public void onStoreAvailable(StoreProcessor store, ProtocolWriter.ProtocolResult response) {
|
||||
// TODO : Chercher une tâche appropriée
|
||||
if (response != null) {
|
||||
Iterator<Task> it = tasks.iterator();
|
||||
while (it.hasNext()) {
|
||||
Task task = it.next();
|
||||
System.out.println("Cherche");
|
||||
if (task.isResponseOfClient(store.getDomain())) {
|
||||
System.out.println("Task trouvée");
|
||||
clientRepository.respondToClient(task.getClient(), response);
|
||||
it.remove(); // Suppression de la tâche
|
||||
break;
|
||||
}
|
||||
Iterator<Task> it = tasks.iterator();
|
||||
while (it.hasNext()) {
|
||||
Task task = it.next();
|
||||
System.out.println("Cherche");
|
||||
if (task.isResponseOfClient(store.getDomain())) {
|
||||
System.out.println("Task trouvée");
|
||||
clientRepository.respondToClient(task.getClient(), response);
|
||||
it.remove(); // Suppression de la tâche
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,4 +27,28 @@ public class FileReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean receiveFile(InputStream input, String fileName, long fileSize) {
|
||||
int bytesReceived = 0;
|
||||
BufferedOutputStream bosFile = null;
|
||||
|
||||
try {
|
||||
byte[] buffer = new byte[1024];
|
||||
bosFile = new BufferedOutputStream(new FileOutputStream(String.format("%s/%s", path, fileName)));
|
||||
long currentOffset = 0;
|
||||
|
||||
while((currentOffset < fileSize) && ((bytesReceived = input.read(buffer)) > 0)) {
|
||||
bosFile.write(buffer, 0, bytesReceived);
|
||||
currentOffset += bytesReceived;
|
||||
}
|
||||
bosFile.flush();
|
||||
bosFile.close();
|
||||
|
||||
return true;
|
||||
} catch(Exception ex) {
|
||||
ex.printStackTrace();
|
||||
if(bosFile != null) { try { bosFile.close(); } catch(Exception e) {} }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1 +1,27 @@
|
||||
{"unicast_port":8000,"multicast_ip":"224.66.66.1","multicast_port":15502,"network_interface":"wlp1s0","tls":true,"storagePath":"/home/benjamin/ffe","users":[{"name":"aaaaa","password":"5d628c274ebb008324f1e199d3bfff0a3fe839730a7f2355e82850d7acca5e5ca64db9071abf3d91034295695f84a617","aes_key":"qlTH6TijnfMRnrS0Qf+k6IPKGp5LoRMXGxCq16e+mF4=","passwordSalt":"Ns8Al6DpqPsIDlCSRBVTEg==","files":[{"name":"ca.crt","fileNameSalt":"rTiE/FXNglnzbGBUGTDuJA==","size":4207,"iv":"0lg3QNhJ/eN292dbOvjShQ==","storage":["lightcontainerSB01"]},{"name":"main.py","fileNameSalt":"A3e6pNPkjZd4Rp2z9Nfzsg==","size":854,"iv":"rBqMLyPxtOQWJYasKFmrkA==","storage":["lightcontainerSB01"]},{"name":"README.md","fileNameSalt":"cDY6LMq13iqKknRS9OVBPw==","size":17,"iv":"jxc7hkIAoH64M8JF7FvNFw==","storage":["lightcontainerSB01"]}]}]}
|
||||
{
|
||||
"unicast_port": 8000,
|
||||
"multicast_ip": "224.66.66.1",
|
||||
"multicast_port": 15502,
|
||||
"network_interface": "Software Loopback Interface 1",
|
||||
"tls": true,
|
||||
"storagePath": "D:\\ffe",
|
||||
"users": [
|
||||
{
|
||||
"name": "aaaaa",
|
||||
"password": "5d628c274ebb008324f1e199d3bfff0a3fe839730a7f2355e82850d7acca5e5ca64db9071abf3d91034295695f84a617",
|
||||
"aes_key": "qlTH6TijnfMRnrS0Qf+k6IPKGp5LoRMXGxCq16e+mF4=",
|
||||
"passwordSalt": "Ns8Al6DpqPsIDlCSRBVTEg==",
|
||||
"files": [
|
||||
{
|
||||
"name": "loader.gif",
|
||||
"fileNameSalt": "qIM0UIgtc4c9I+K8iLeObg==",
|
||||
"size": 89594,
|
||||
"iv": "WHJj4TMCmlRnCIQqgGjrnw==",
|
||||
"storage": [
|
||||
"lightcontainerSB01"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user