package org.yamcs.cli; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import org.yamcs.api.YamcsApiException; import org.yamcs.api.YamcsConnectionProperties; import org.yamcs.api.rest.BulkRestDataSender; import org.yamcs.api.rest.RestClient; import org.yamcs.protobuf.Archive.TableData.TableRecord; import org.yamcs.protobuf.Table.Row; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.HttpMethod; /** * Command line utility for doing operations with tables (we preferred this to having tables a sub-command of the archive, otherwise the commadn line was getting too long) * * @author nm * */ @Parameters(commandDescription = "Tables operations") public class TablesCli extends Command { public TablesCli(Command parent) { super("tables", parent); addSubCommand(new TablesList()); addSubCommand(new TablesDump()); addSubCommand(new TablesLoad()); setYcpRequired(true, true); } @Parameters(commandDescription = "List existing tables") class TablesList extends Command { public TablesList() { super("list", TablesCli.this); } @Override public void execute() throws Exception { YamcsConnectionProperties ycp = getYamcsConnectionProperties(); RestClient restClient = new RestClient(ycp); String resp = new String(restClient.doRequest("/api/archive/"+ycp.getInstance()+"/tables", HttpMethod.GET).get()); console.print(resp); } } @Parameters(commandDescription = "Dumps table data to file, Connection to a live Yamcs server (-y option) is required.") class TablesDump extends Command { @Parameter(names="-d", description="Name of the output directory. If not specified, the current directory will be used. The directory has to exist.") String dir; @Parameter(description="table1 table2...", required=true) List<String> tableList; public TablesDump() { super("dump", TablesCli.this); } private void dumpTable(String tableName) throws Exception { String fileName = tableName+".dump"; if(dir!=null) { fileName = dir+"/"+fileName; } AtomicInteger count = new AtomicInteger(); System.out.println("Dumping data from "+tableName+" table to "+fileName); OutputStream outputs = new GZIPOutputStream(new FileOutputStream(fileName)); YamcsConnectionProperties ycp = getYamcsConnectionProperties(); RestClient restClient = new RestClient(ycp); CompletableFuture<Void> cf = restClient.doBulkGetRequest("/archive/"+ycp.getInstance()+"/downloads/tables/"+tableName+"?format=dump", (data) -> { Row tr; try { tr = Row.parseFrom(data); tr.writeDelimitedTo(outputs); int x = count.incrementAndGet(); if(x%100==0) { System.out.print("\r"+count); System.out.flush(); } } catch (IOException e) { System.err.println("Error receiving table row: "+e.getMessage()); throw new YamcsApiException("error decoding or writing table row: "+e.getMessage(), e); } }); cf.get(); outputs.close(); restClient.close(); System.out.println("\rsaved "+count.get()+" rows"); } @Override public void execute() throws Exception { for(String tableName:tableList) { dumpTable(tableName); } } } @Parameters(commandDescription = "Load data to table") class TablesLoad extends Command { @Parameter(names="-d", description="Name of the directory to load files from. If not specified, the current directory will be used. The directory has to contain <tableName>.dump.") String dir; public TablesLoad() { super("load", TablesCli.this); } @Parameter(description="table1 table2...", required=true) List<String> tableList; @SuppressWarnings("squid:S2095") private void loadTable(String tableName) throws Exception { String fileName = tableName+".dump"; if(dir!=null) { tableName = dir+"/"+tableName+".dump"; } InputStream is = new GZIPInputStream(new FileInputStream(fileName)); System.out.println("Loading "+fileName+" into table "+tableName); YamcsConnectionProperties ycp = getYamcsConnectionProperties(); RestClient restClient = new RestClient(ycp); try { CompletableFuture<BulkRestDataSender> cf = restClient.doBulkSendRequest("/archive/"+ycp.getInstance()+"/tables/"+tableName+"/data", HttpMethod.POST); BulkRestDataSender bds = cf.get(); ByteBuf buf = Unpooled.buffer(); ByteBufOutputStream bufstream = new ByteBufOutputStream(buf); int c = 0; while(true) { Row tr = Row.parseDelimitedFrom(is); if(tr==null) { break; } tr.writeDelimitedTo(bufstream); if(((c++)&0xF)==0) { bufstream.close(); bds.sendData(buf); buf = Unpooled.buffer(); bufstream = new ByteBufOutputStream(buf); } if(c%100==0) { System.out.print("\r"+c); System.out.flush(); } } bufstream.close(); if(buf.readableBytes()>0) { bds.sendData(buf); } is.close(); String resp = new String(bds.completeRequest().get()); System.out.println("\r "+resp); } finally { restClient.close(); } } @Override public void execute() throws Exception { for(String tn:tableList) { loadTable(tn); } } } }