package org.yamcs.ui; import java.awt.Component; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.CharArrayReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.prefs.Preferences; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.ProgressMonitor; import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.yamcs.api.YamcsApiException; import org.yamcs.api.YamcsConnectionProperties; import org.yamcs.api.rest.BulkRestDataReceiver; import org.yamcs.api.rest.RestClient; import org.yamcs.protobuf.Pvalue.ParameterData; import org.yamcs.protobuf.Pvalue.ParameterValue; import org.yamcs.protobuf.Rest.BulkDownloadParameterValueRequest; import org.yamcs.protobuf.Yamcs.NamedObjectId; import org.yamcs.utils.ParameterFormatter; import org.yamcs.utils.TimeEncoding; import org.yamcs.xtce.MdbMappings; import com.google.protobuf.InvalidProtocolBufferException; /** * @author nm * GUI for requesting parameter dumps */ public class ParameterRetrievalGui extends JFrame implements ParameterSelectDialogListener { JCheckBox printTime, printRaw, printUnique, keepValues, ignoreInvalidParameters, printFullLines, timeWindow; JTextField timeWindowSize; JButton startButton; JFileChooser loadfileChooser, outfileChooser; long start, stop; String archiveInstance; Component parent; ProgressMonitor progressMonitor; JTextArea paramText; JTextField fileNameTextField; private BufferedWriter writer; private Preferences prefs; final int maxRecent = 10; ArrayList<String> recentFiles; JPopupMenu recentPopup; JButton recentButton; YamcsConnectionProperties connectionParams; private ParameterFormatter parameterFormatter; ParameterSelectDialog selectDialog; private CompletableFuture<Void> completableFuture; /** * Creates a new window that requests parameter deliveries * @param parent */ public ParameterRetrievalGui(YamcsConnectionProperties connectionParameters, Component parent) { super("Parameter Retrieval"); setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); prefs = Preferences.userNodeForPackage(getClass()); recentFiles = new ArrayList<String>(); for (int i = 1; ; ++i) { String s = prefs.get("recentFile" + i, ""); if (s.equalsIgnoreCase("")) { break; } else { recentFiles.add(s); } } this.connectionParams = connectionParameters; this.parent=parent; final String home = System.getProperties().getProperty("user.home"); loadfileChooser=new JFileChooser(); loadfileChooser.setApproveButtonText("Load"); loadfileChooser.setDialogTitle("Select a file containing parameter names"); loadfileChooser.setCurrentDirectory(new File(prefs.get("loadPath", home))); outfileChooser=new JFileChooser(); outfileChooser.setApproveButtonText("Choose"); outfileChooser.setDialogTitle("Select output file"); outfileChooser.setCurrentDirectory(new File(prefs.get("outputPath", home))); GridBagLayout gridbag = new GridBagLayout(); setLayout(gridbag); GridBagConstraints gbc = new GridBagConstraints(); gbc.fill = GridBagConstraints.BOTH;gbc.anchor=GridBagConstraints.NORTH; JLabel label=new JLabel("Dump the selected telemetry parameters into a file."); gbc.gridwidth=GridBagConstraints.REMAINDER; //gbc.ipadx=5; gbc.ipady=5; gbc.insets=new Insets(5,5,5,5); getContentPane().add(label,gbc); //options Box optionsPanel=Box.createVerticalBox(); optionsPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Options"),BorderFactory.createEmptyBorder(5,5,5,5))); printTime=new JCheckBox("Print the generation time"); printTime.setSelected(true); optionsPanel.add(printTime); printRaw=new JCheckBox("Print the raw value"); optionsPanel.add(printRaw); printUnique=new JCheckBox("Print only the unique lines"); optionsPanel.add(printUnique); printFullLines=new JCheckBox("Print only the full lines"); optionsPanel.add(printFullLines); keepValues=new JCheckBox("Keep previous values"); optionsPanel.add(keepValues); Box timeWindowPanel=Box.createHorizontalBox(); timeWindow=new JCheckBox("Merging time window of"); timeWindowPanel.add(timeWindow); timeWindowSize=new JTextField(5); timeWindowSize.setText("500"); timeWindowSize.setMaximumSize(timeWindowSize.getPreferredSize()); timeWindowPanel.add(timeWindowSize); JLabel l=new JLabel("ms"); timeWindowPanel.add(l); timeWindowPanel.setAlignmentX(LEFT_ALIGNMENT); optionsPanel.add(timeWindowPanel); ignoreInvalidParameters=new JCheckBox("Ignore Invalid Parameters"); optionsPanel.add(ignoreInvalidParameters); gbc.gridwidth=1;gbc.weighty = 1.0;gbc.weightx = 0.0; getContentPane().add(optionsPanel,gbc); Box paramPanel=Box.createVerticalBox(); paramPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Parameters"),BorderFactory.createEmptyBorder(5,5,5,5))); paramText = new JTextArea(15, 30); TextAreaListener listener = new TextAreaListener() { @Override public void updated() { startButton.setEnabled( (!paramText.getText().equalsIgnoreCase("")) && !fileNameTextField.getText().equalsIgnoreCase("") ); } }; paramText.getDocument().addDocumentListener(listener); JScrollPane scrollPane = new JScrollPane(paramText); paramPanel.add(scrollPane); JPanel parambuttonPanel=new JPanel(); paramPanel.add(parambuttonPanel); JButton button=new JButton("Open"); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { loadParameters(); } }); parambuttonPanel.add(button); button=new JButton("Save"); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { saveParameters(); } }); parambuttonPanel.add(button); recentPopup = new JPopupMenu(); recentButton = new JButton("Recent"); populateRecentMenu(); recentButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { recentPopup.show(recentButton, 0, recentButton.getSize().height); } }); parambuttonPanel.add(recentButton); // Provide method to select parameters from a hierarchy JButton selectParamButton = new JButton( "Select" ); selectParamButton.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { selectParameters(); } }); parambuttonPanel.add( selectParamButton ); gbc.weighty = 1.0;gbc.weightx = 1.0;gbc.gridwidth=GridBagConstraints.REMAINDER; getContentPane().add(paramPanel,gbc); //gbc.insets=new Insets(2,2,2,2); Box outputPanel=Box.createHorizontalBox(); outputPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Output File"),BorderFactory.createEmptyBorder(5,5,5,5))); fileNameTextField=new JTextField(); outputPanel.add(fileNameTextField); button=new JButton("Choose"); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { chooseOutputFile(); } }); outputPanel.add(button); gbc.gridwidth=GridBagConstraints.REMAINDER; gbc.weighty = 0.0;gbc.weightx = 1.0;gbc.anchor=GridBagConstraints.CENTER; getContentPane().add(outputPanel,gbc); JPanel actionbuttonPanel=new JPanel(); startButton=new JButton("Start Retrieval"); startButton.setEnabled(false); actionbuttonPanel.add(startButton); startButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { startRetrieval(); } }); button=new JButton("Close"); actionbuttonPanel.add(button); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { setVisible(false); } }); gbc.gridwidth=GridBagConstraints.REMAINDER; gbc.weighty = 0.0;gbc.weightx = 1.0;gbc.anchor=GridBagConstraints.CENTER; getContentPane().add(actionbuttonPanel,gbc); pack(); } /** * Uses blocking method to get list of parameters using a hierarchical display. */ private void selectParameters() { YamcsConnectionProperties ycd = connectionParams.clone(); ycd.setInstance(archiveInstance); if( selectDialog == null ) { selectDialog = new ParameterSelectDialog(this, ycd); selectDialog.addListener( this ); } List<String> params = selectDialog.showDialog(); if( params != null ) { setParameters( params ); } } @Override public void parametersAdded( List<String> opsnames ) { addParameters( opsnames ); } /** * Convenience method to add opsnames to the displayed list without * duplicating any existing entries. * * @param opsnames */ public void addParameters( List<String> opsnames ) { List<String> currentOpsnames = Arrays.asList( paramText.getText().split( "\n" ) ); for( String opsname : opsnames ) { if( ! currentOpsnames.contains( opsname ) ) { paramText.append( opsname ); paramText.append( "\n" ); } } } /** * Convenience method to set current parameter list. * * @param opsnames */ public void setParameters( List<String> opsnames ) { paramText.setText(""); for( String opsname : opsnames ) { paramText.append( opsname ); paramText.append( "\n" ); } } private void populateRecentMenu() { recentPopup.removeAll(); for (int i = 0; i < recentFiles.size(); ++i) { final String s = recentFiles.get(i); recentPopup.add(new AbstractAction(s) { @Override public void actionPerformed(ActionEvent arg0) { loadParameters(new File(s)); } }); } recentPopup.pack(); recentButton.setEnabled(recentPopup.getComponentCount() > 0); } private void addRecentFile(File file) { final String filename = file.getAbsolutePath(); if (recentFiles.indexOf(filename) == -1) { while (recentFiles.size() > maxRecent) { recentFiles.remove(0); } recentFiles.add(filename); for (int i = 0; i < recentFiles.size(); ++i) { prefs.put("recentFile" + (i + 1), recentFiles.get(i)); } populateRecentMenu(); } } public void setValues(String archiveInstance, long start, long stop) { this.start=start; this.stop=stop; this.archiveInstance=archiveInstance; String startWinCompatibleDateTime = TimeEncoding.toWinCompatibleDateTime(start); String stopWinCompatibleDateTime = TimeEncoding.toWinCompatibleDateTime(stop); fileNameTextField.setText(String.format("parameters_%s_%s.dump" ,startWinCompatibleDateTime ,stopWinCompatibleDateTime)); } private void showErrorMessage(final String s) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { JOptionPane.showMessageDialog(getParent(), s, getTitle(), JOptionPane.ERROR_MESSAGE); } }); } private void showMessage(String s) { JOptionPane.showMessageDialog(getParent(), s, getTitle(), JOptionPane.INFORMATION_MESSAGE); } private void loadParameters() { int returnVal=loadfileChooser.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { File f = loadfileChooser.getSelectedFile(); prefs.put("loadPath", loadfileChooser.getCurrentDirectory().getAbsolutePath()); loadParameters(f); } } private void loadParameters(File f) { try { startButton.setEnabled(false); paramText.setText(""); List<NamedObjectId> paraList=loadParameters(new BufferedReader(new FileReader(f))); for(NamedObjectId id:paraList) { if(MdbMappings.MDB_OPSNAME.equals(id.getNamespace())) { paramText.append(id.getName()); } else { paramText.append(id.getNamespace()+":"+id.getName()); } paramText.append("\n"); } addRecentFile(f); } catch (IOException e) { showErrorMessage("Cannot load parameters: "+e.getMessage()); } } public static List<NamedObjectId> loadParameters(BufferedReader reader) throws IOException { List<NamedObjectId> paramList=new ArrayList<NamedObjectId>(); String line=reader.readLine(); if(line==null) return paramList; //hmm, empty file if(line.contains("<")) { //read an xml file Pattern p1 = Pattern.compile(".*\\<TelemetryName context=\"CGS_ALIAS\"\\>(\\w+)\\<\\/TelemetryName\\>.*"); Pattern p2 = Pattern.compile(".*<string>Opsname</string>.*"); Pattern p3 = Pattern.compile(".*\\<string\\>(\\w+)\\<\\/string\\>.*"); do { Matcher m1=p1.matcher(line); if(m1.matches()) { paramList.add(NamedObjectId.newBuilder() .setNamespace(MdbMappings.MDB_OPSNAME) .setName(m1.group(1)).build()); } else { Matcher m2=p2.matcher(line); if(m2.matches()) { line=reader.readLine(); if (line==null) break; Matcher m3=p3.matcher(line); if(m3.matches()) { paramList.add(NamedObjectId.newBuilder() .setNamespace(MdbMappings.MDB_OPSNAME) .setName(m3.group(1)).build()); } } } } while ((line=reader.readLine())!=null); } else { //parameters separated by new lines, spaces or tabs do { String[] params=line.split("[ \t]+"); for(String p:params) { if(p.length()>0) { paramList.add(NamedObjectId.newBuilder() .setNamespace(MdbMappings.MDB_OPSNAME) .setName(line).build()); } } } while ((line=reader.readLine())!=null); } return paramList; } private void saveParameters() { int returnVal=loadfileChooser.showSaveDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { File f=loadfileChooser.getSelectedFile(); try { PrintWriter w = new PrintWriter(f); w.print(paramText.getText()); w.close(); addRecentFile(f); } catch (IOException e) { showErrorMessage("Cannot save parameters file: "+e.getMessage()); } } } private void chooseOutputFile() { outfileChooser.setSelectedFile(new File(outfileChooser.getCurrentDirectory(), fileNameTextField.getText())); int returnVal=outfileChooser.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { File outputFile=outfileChooser.getSelectedFile(); if(outputFile.exists()) { if(JOptionPane.showConfirmDialog(this, "Are you sure you want to overwrite "+outputFile,getTitle(),JOptionPane.YES_NO_OPTION,JOptionPane.WARNING_MESSAGE) ==JOptionPane.NO_OPTION) { return; } } fileNameTextField.setText(outputFile.getAbsolutePath()); prefs.put("outputPath", outfileChooser.getCurrentDirectory().getAbsolutePath()); if ((!paramText.getText().equalsIgnoreCase("")) && !fileNameTextField.getText().equalsIgnoreCase("")) { startButton.setEnabled(true); } } } private void startRetrieval() { final List<NamedObjectId> paramList; try { paramList=loadParameters(new BufferedReader(new CharArrayReader(paramText.getText().toCharArray()))); } catch (IOException e) { showErrorMessage("Cannot apply parameters: "+e.getMessage()); return; } setVisible(false); progressMonitor = new ProgressMonitor(parent, "Saving parameters","0 lines saved", 0, (int)((stop-start)/1000)); try { writer=new BufferedWriter(new FileWriter(fileNameTextField.getText())); parameterFormatter=new ParameterFormatter(writer, paramList); parameterFormatter.setPrintTime(printTime.isSelected()); parameterFormatter.setPrintRaw(printRaw.isSelected()); parameterFormatter.setPrintUnique(printUnique.isSelected()); if(timeWindow.isSelected()) { try { parameterFormatter.setTimeWindow(Integer.parseInt(timeWindowSize.getText())); } catch (NumberFormatException e) { showErrorMessage("Cannot parse number: "+e.getMessage()+". Please make sure that the number is integer"); } } else { parameterFormatter.resetTimeWindow(); } parameterFormatter.setAllParametersPresent(printFullLines.isSelected()); parameterFormatter.setKeepValues(keepValues.isSelected()); YamcsConnectionProperties ycd = connectionParams.clone(); ycd.setInstance(archiveInstance); RestClient restClient = new RestClient(ycd); BulkDownloadParameterValueRequest prr = BulkDownloadParameterValueRequest.newBuilder().addAllId(paramList).setStart(TimeEncoding.toString(start)).setStop(TimeEncoding.toString(stop)).build(); completableFuture = restClient.doBulkGetRequest("/archive/"+archiveInstance+"/downloads/parameters", prr.toByteArray(), new BulkRestDataReceiver() { @Override public void receiveData(byte[] data) throws YamcsApiException { ParameterData pd; try { pd = ParameterData.parseFrom(data); updateParameters(pd.getParameterList()); } catch (InvalidProtocolBufferException e) { gotException(new Exception("Exception decoding data: "+e.getMessage())); } } }); completableFuture.whenComplete((v, t) ->{ if((t==null) || (t instanceof CancellationException)) { replayFinished(); } else { gotException(t); } }); } catch (Exception e) { gotException(e); } } private void updateParameters(List<ParameterValue> paramList) { if(completableFuture.isCancelled()) return; try { parameterFormatter.writeParameters(paramList); int linesReceived = parameterFormatter.getLinesReceived(); long time=paramList.get(0).getGenerationTime(); int progr=(int)((time-start)/1000); if(linesReceived%100==0) progressMonitor.setNote(getNote()); progressMonitor.setProgress(progr); if(progressMonitor.isCanceled()) completableFuture.cancel(true); } catch (IOException e) { gotException(e); } } private void replayFinished() { try { parameterFormatter.close(); } catch (Exception e) { gotException(e); } SwingUtilities.invokeLater( new Runnable() { @Override public void run() { if(progressMonitor.isCanceled()) { showMessage("Retrieval cancelled. "+getNote()); } else { progressMonitor.close(); showMessage("The parameter retrieval finished successfully. "+getNote()+" in "+fileNameTextField.getText()); } } }); } private String getNote() { int linesSaved=parameterFormatter.getLinesSaved(); int linesReceived=parameterFormatter.getLinesReceived(); if(linesReceived!=linesSaved) { return linesReceived+" lines received, "+linesSaved+" lines saved"; } else { return linesReceived+" lines received"; } } public void gotException(final Throwable e) { final String message; e.printStackTrace(); message="Error when retrieving parameters: "+e; showErrorMessage(message); } public static void main(String[] args) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { new ParameterRetrievalGui(null,null); } }); } abstract class TextAreaListener implements DocumentListener { @Override public void changedUpdate(DocumentEvent e) { updated(); } @Override public void insertUpdate(DocumentEvent e) { updated(); } @Override public void removeUpdate(DocumentEvent e) { updated(); } abstract public void updated(); } }