1

I am launching rsync from inside my app. Right now it works fine, it opens a console window, prompts me for my password, and shows file progress as the files copy.

public static int SyncFolder(string sourceFolder, string destFolder)
{

    Process rsync = new Process();
    rsync.StartInfo.FileName = Path.Combine(RsyncPath, "rsync.exe");
    rsync.StartInfo.UseShellExecute = true;
    rsync.StartInfo.Arguments = String.Join(" ", new[] { "-rltzh --progress --chmod=a=rw,Da+x", FileUtils.EncodeParameterArgument(sourceFolder), FileUtils.EncodeParameterArgument(destFolder) });
    rsync.Start();
    rsync.WaitForExit();

    return rsync.ExitCode;
}

The problem is I don't want a separate console window to be opened. I would like to display the text progress inside a control of some type and respond to any prompts (like entering the password) from inside my program itself.

public int SyncFolder(string sourceFolder, string destFolder)
{

    Process rsync = new Process();
    rsync.StartInfo.FileName = Path.Combine(RsyncPath, "rsync.exe");
    rsync.StartInfo.UseShellExecute = false;
    rsync.StartInfo.RedirectStandardInput = true;
    rsync.StartInfo.RedirectStandardOutput = true;
    rsync.StartInfo.RedirectStandardError = true;
    rsync.StartInfo.Arguments = String.Join(" ", new[] { "-rltzh --progress --chmod=a=rw,Da+x", FileUtils.EncodeParameterArgument(sourceFolder), FileUtils.EncodeParameterArgument(destFolder) });

    rsync.ErrorDataReceived += rsync_ErrorDataReceived;
    rsync.OutputDataReceived += rsync_OutputDataReceived;

    rsync.Start();
    BindToUiConrol(rsync.StandardInput);

    rsync.WaitForExit();

    return rsync.ExitCode;
}

private void rsync_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
    //Magic!
}

private void rsync_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
    //Even more Magic!
}

private void BindToUiConrol(StreamWriter standardInput)
{
   if(this.InvokeRequired)
   {
       this.BeginInvoke(new Action<StreamWriter>(BindToUiConrol), standardInput);
       return;
   }

   //Hook up events here so a single line text box dumps it's text in when you hit enter.
}

And this is where I am stuck. If I did not have the %'s that kept updating on the same line I would just have it keep dumping new lines as they come in, but how do I handle the line being re-used with a new value put in to it?

My dream solution would be to just have kind of a console window embedded inside the form itself, similar to what HyperTerminal did in older versions of windows, but I have no idea how do do that in .NET.


Here is a example program to show the issue

public class Program
{

    private static void Main(string[] args)
    {
        Console.Write("1");
        Console.WriteLine();

        Console.Write("2");
        Console.CursorLeft = 0;
        Console.Write("3");
        Console.WriteLine();

        Console.Write("4");
        Console.WriteLine();
    }
}

How do I write a Forms application that will display

1
3
4

in a control after it runs the process.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • I'm a little confused about your comment about "updating the same line". I think you mean that rsync is sending '\r' characters, which repositions the cursor to the start of the current line. However, once data is sent to standard output, it doesn't get "reused" or replaced. It is only the console that gives the illusion that the data is being reused. It is simply a stream of "1%\r2%\r3%..." – Mark Lakata Aug 26 '13 at 16:58

2 Answers2

1

This is not a trivial problem to solve in general. It has to do with the fact that Windows/Console applications don't really write to Standard Out and Standard Error, they write to the "Console". There are various hacks and things that make Standard Out and Standard Error work ok when you just care about all of the text and all of the error text at once, but nothing works well interactively, because the way things are buffered.

Follow this discussion for more information:

Writing a console wrapper in C#?

Community
  • 1
  • 1
Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
  • btw, I've been working on the 1-click solution to this problem, by turning Console2 and the `Process` class into one C#/WinForms solution, but haven't gotten there yet. – Mark Lakata Aug 23 '13 at 23:19
-1

What you are looking to do is append the terminal text to a multiline textbox or a RichTextBox.

You will also need to capture the keyboard events on that control.

One concern you might run into is the program input and the GUI operating on separate threads.

How to update textbox on GUI from another thread in C#

Fast Append Text to text box

After original question was clarified What you are looking for is to detect the '\r' character which is carriage return and then you will need to move back to the last '\n' line feed in your textbox content.

You could do this in several ways.

  1. First keep a List that would contain each line and just delete the last line. And append the text after the previous line.

  2. You could use a Regex that would match the textbox content to the final linefeed in the textbox. I believe the Regex would be "^.*\n" http://msdn.microsoft.com/en-us/library/system.text.regularexpressions.regex.aspx

  3. Simply truncate the textbox contents to the String.LastIndexOf('\n') http://msdn.microsoft.com/en-us/library/system.string.lastindexof.aspx

Edit #2:

The question seems directed at determining the triggers for cursor movement.
Microsoft has their reference at the following link: http://msdn.microsoft.com/en-us/library/h21280bw.aspx

The second example you gave with Console.Left = 0 seems to throw an exception. Since it is a contrived answer for this solution I am assuming oyu really only care about rsync which I would venture to guess only uses '\r', '\n', and '\b'. The only other issue would be if it uses ANSI escape codes http://en.wikipedia.org/wiki/ANSI_escape_code

Finally a loop must read the input and parse the appropriate characters. I would recommend a BackgroundWorker as the GUI will block waiting on the loop.

Community
  • 1
  • 1
bjaminn
  • 121
  • 5
  • No, that is not what I am looking to do, I am not trying to just **append** any new text. If that is all I wanted to do the problem would be extremely easy to solve. The problem I am having is **detecting when the process wants to overwrite a previously printed value in the text instead of appending a new line**. – Scott Chamberlain Aug 23 '13 at 20:58
  • @ScottChamberlain I updated my answer closer to what I think you were looking for. Let me know if there is something else. – bjaminn Aug 23 '13 at 21:17
  • You still are not understanding the issue. Please look at my edit to my original question, use that program to test any answer you want to give. It should show you what problem I am trying to solve and why just detecting `\r` would not work. – Scott Chamberlain Aug 23 '13 at 21:24
  • Are you ok with using a BackgroundWorker to handle the Output and the streaming? – bjaminn Aug 23 '13 at 21:31
  • I will be willing to use anything the solution requires as long as it solves the problem of replacing updated text. – Scott Chamberlain Aug 23 '13 at 21:32
  • Is your issue with not knowing the console escape sequences for cursor movement? Or the fact that `rsync_OutputDataReceived` only is called at end of line? – bjaminn Aug 23 '13 at 21:34
  • Not knowing how to detect cursor movement. I just used those two event's for examples if I have to read using `rsync.StandardOutput` that is perfectly fine too. – Scott Chamberlain Aug 23 '13 at 21:35
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/36136/discussion-between-bjaminn-and-scott-chamberlain) – bjaminn Aug 23 '13 at 21:56