github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/clients/android/src/org/camlistore/UploadThread.java (about)

     1  /*
     2  Copyright 2011 Google Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15   */
    16  
    17  package org.camlistore;
    18  
    19  import java.io.BufferedReader;
    20  import java.io.BufferedWriter;
    21  import java.io.IOException;
    22  import java.io.InputStream;
    23  import java.io.InputStreamReader;
    24  import java.io.OutputStream;
    25  import java.io.OutputStreamWriter;
    26  import java.util.HashMap;
    27  import java.util.ListIterator;
    28  import java.util.concurrent.LinkedBlockingQueue;
    29  import java.util.concurrent.TimeUnit;
    30  import java.util.concurrent.atomic.AtomicReference;
    31  import java.util.regex.Matcher;
    32  import java.util.regex.Pattern;
    33  
    34  import android.util.Log;
    35  
    36  public class UploadThread extends Thread {
    37      private static final String TAG = "UploadThread";
    38  
    39      private final UploadService mService;
    40      private final HostPort mHostPort;
    41      private final String mTrustedCert;
    42      private final String mUsername;
    43      private final String mPassword;
    44      private final LinkedBlockingQueue<UploadThreadMessage> msgCh = new LinkedBlockingQueue<UploadThreadMessage>();
    45  
    46      AtomicReference<Process> goProcess = new AtomicReference<Process>();
    47      AtomicReference<OutputStream> toChildRef = new AtomicReference<OutputStream>();
    48      HashMap<String, QueuedFile> mQueuedFile = new HashMap<String, QueuedFile>(); // guarded
    49                                                                                   // by
    50                                                                                   // itself
    51  
    52      private final Object stdinLock = new Object(); // guards setting and writing
    53                                                     // to stdinWriter
    54      private BufferedWriter stdinWriter;
    55  
    56      public UploadThread(UploadService uploadService, HostPort hp, String trustedCert, String username, String password) {
    57          mService = uploadService;
    58          mHostPort = hp;
    59          mTrustedCert = trustedCert != null ? trustedCert.toLowerCase().trim() : "";
    60          mUsername = username;
    61          mPassword = password;
    62      }
    63  
    64      public void stopUploads() {
    65          Process p = goProcess.get();
    66          if (p == null) {
    67              return;
    68          }
    69          synchronized (stdinLock) {
    70              if (stdinWriter == null) {
    71                  // force kill. confused.
    72                  p.destroy();
    73                  goProcess.set(null);
    74                  return;
    75              }
    76              try {
    77                  stdinWriter.close();
    78                  Log.d(TAG, "Closed camput's stdin");
    79                  stdinWriter = null;
    80              } catch (IOException e) {
    81                  p.destroy(); // force kill
    82                  goProcess.set(null);
    83                  return;
    84              }
    85  
    86              // Unnecessary paranoia, never seen in practice:
    87              new Thread() {
    88                  @Override
    89                  public void run() {
    90                      try {
    91                          Thread.sleep(750, 0);
    92                          stopUploads(); // force kill if still alive.
    93                      } catch (InterruptedException e) {
    94                      }
    95  
    96                  }
    97              }.start();
    98          }
    99      }
   100  
   101      private String binaryPath(String suffix) {
   102          return mService.getBaseContext().getFilesDir().getAbsolutePath() + "/" + suffix;
   103      }
   104  
   105      private void status(String st) {
   106          Log.d(TAG, st);
   107          mService.setUploadStatusText(st);
   108      }
   109  
   110      // An UploadThreadMessage can be sent on msgCh and read by the run() method
   111      // in
   112      // until it's time to quit the thread.
   113      private static class UploadThreadMessage {
   114      }
   115  
   116      private static class ProcessExitedMessage extends UploadThreadMessage {
   117          public int code;
   118  
   119          public ProcessExitedMessage(int code) {
   120              this.code = code;
   121          }
   122      }
   123  
   124      public boolean enqueueFile(QueuedFile qf) {
   125          String diskPath = qf.getDiskPath();
   126          if (diskPath == null) {
   127              Log.d(TAG, "file has no disk path: " + qf);
   128              return false;
   129          }
   130          synchronized (stdinLock) {
   131              if (stdinWriter == null) {
   132                  return false;
   133              }
   134              synchronized (mQueuedFile) {
   135                  mQueuedFile.put(diskPath, qf);
   136              }
   137              try {
   138                  stdinWriter.write(diskPath + "\n");
   139                  stdinWriter.flush();
   140              } catch (IOException e) {
   141                  Log.d(TAG, "Failed to write " + diskPath + " to camput stdin: " + e);
   142                  return false;
   143              }
   144          }
   145          return true;
   146      }
   147  
   148      @Override
   149      public void run() {
   150          Log.d(TAG, "Running");
   151          if (!mHostPort.isValid()) {
   152              Log.d(TAG, "host/port is invalid");
   153              return;
   154          }
   155          status("Running UploadThread for " + mHostPort);
   156  
   157          mService.onStatReceived(null, 0);
   158  
   159          Process process = null;
   160          try {
   161              ProcessBuilder pb = new ProcessBuilder();
   162              pb.command(binaryPath("camput.bin"), "--server=" + mHostPort.urlPrefix(), "file", "-stdinargs", "-vivify");
   163              pb.redirectErrorStream(false);
   164              pb.environment().put("CAMLI_AUTH", "userpass:" + mUsername + ":" + mPassword);
   165              pb.environment().put("CAMLI_TRUSTED_CERT", mTrustedCert);
   166              pb.environment().put("CAMLI_CACHE_DIR", mService.getCacheDir().getAbsolutePath());
   167              pb.environment().put("CAMPUT_ANDROID_OUTPUT", "1");
   168              process = pb.start();
   169              goProcess.set(process);
   170              synchronized (stdinLock) {
   171                  stdinWriter = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), "UTF-8"));
   172              }
   173              new CopyToAndroidLogThread("stderr", process.getErrorStream()).start();
   174              new ParseCamputOutputThread(process, mService).start();
   175              new WaitForProcessThread(process).start();
   176          } catch (IOException e) {
   177              throw new RuntimeException(e);
   178          }
   179  
   180          ListIterator<QueuedFile> iter = mService.uploadQueue().listIterator();
   181          while (iter.hasNext()) {
   182              enqueueFile(iter.next());
   183          }
   184  
   185          // Loop forever reading from msgCh
   186          while (true) {
   187              UploadThreadMessage msg = null;
   188              try {
   189                  msg = msgCh.poll(10, TimeUnit.SECONDS);
   190              } catch (InterruptedException e) {
   191                  continue;
   192              }
   193              if (msg instanceof ProcessExitedMessage) {
   194                  status("Upload process ended.");
   195                  ProcessExitedMessage pem = (ProcessExitedMessage) msg;
   196                  Log.d(TAG, "Loop exiting; code was = " + pem.code);
   197                  return;
   198              }
   199          }
   200      }
   201  
   202      // "CHUNK_UPLOADED %d %s %s\n", sb.Size, blob, asr.path
   203      private final static Pattern chunkUploadedPattern = Pattern.compile("^CHUNK_UPLOADED (\\d+) (\\S+) (.+)");
   204  
   205      public class CamputChunkUploadedMessage {
   206          private final String mFilename;
   207          private final long mSize;
   208  
   209          public CamputChunkUploadedMessage(String line) {
   210              Matcher m = chunkUploadedPattern.matcher(line);
   211              if (!m.matches()) {
   212                  throw new RuntimeException("bogus CamputChunkMessage: " + line);
   213              }
   214              mSize = Long.parseLong(m.group(1));
   215              mFilename = m.group(3);
   216          }
   217  
   218          public QueuedFile queuedFile() {
   219              synchronized (mQueuedFile) {
   220                  return mQueuedFile.get(mFilename);
   221              }
   222          }
   223  
   224          public long size() {
   225              return mSize;
   226          }
   227      }
   228  
   229      // STAT %s %d\n
   230      private final static Pattern statPattern = Pattern.compile("^STAT (\\S+) (\\d+)\\b");
   231  
   232      public class CamputStatMessage {
   233          private final Matcher mm;
   234  
   235          public CamputStatMessage(String line) {
   236              mm = statPattern.matcher(line);
   237              if (!mm.matches()) {
   238                  throw new RuntimeException("bogus CamputStatMessage: " + line);
   239              }
   240          }
   241  
   242          public String name() {
   243              return mm.group(1);
   244          }
   245  
   246          public long value() {
   247              return Long.parseLong(mm.group(2));
   248          }
   249      }
   250  
   251      // STATS nfile=%d nbyte=%d skfile=%d skbyte=%d upfile=%d upbyte=%d\n
   252      private final static Pattern statsPattern = Pattern.compile("^STATS nfile=(\\d+) nbyte=(\\d+) skfile=(\\d+) skbyte=(\\d+) upfile=(\\d+) upbyte=(\\d+)");
   253  
   254      public class CamputStatsMessage {
   255          private final Matcher mm;
   256  
   257          public CamputStatsMessage(String line) {
   258              mm = statsPattern.matcher(line);
   259              if (!mm.matches()) {
   260                  throw new RuntimeException("bogus CamputStatsMessage: " + line);
   261              }
   262          }
   263  
   264          private long field(int n) {
   265              return Long.parseLong(mm.group(n));
   266          }
   267  
   268          public long totalFiles() {
   269              return field(1);
   270          }
   271  
   272          public long totalBytes() {
   273              return field(2);
   274          }
   275  
   276          public long skippedFiles() {
   277              return field(3);
   278          }
   279  
   280          public long skippedBytes() {
   281              return field(4);
   282          }
   283  
   284          public long uploadedFiles() {
   285              return field(5);
   286          }
   287  
   288          public long uploadedBytes() {
   289              return field(6);
   290          }
   291      }
   292  
   293      private class ParseCamputOutputThread extends Thread {
   294          private final BufferedReader mBufIn;
   295          private final UploadService mService;
   296          private final static String TAG = UploadThread.TAG + "/camput-out";
   297          private final static boolean DEBUG_CAMPUT_ACTIVITY = false;
   298  
   299          public ParseCamputOutputThread(Process process, UploadService service) {
   300              mService = service;
   301              mBufIn = new BufferedReader(new InputStreamReader(process.getInputStream()));
   302          }
   303  
   304          @Override
   305          public void run() {
   306              while (true) {
   307                  String line = null;
   308                  try {
   309                      line = mBufIn.readLine();
   310                  } catch (IOException e) {
   311                      Log.d(TAG, "Exception reading camput's stdout: " + e.toString());
   312                      return;
   313                  }
   314                  if (DEBUG_CAMPUT_ACTIVITY) {
   315                      Log.d(TAG, "camput: " + line);
   316                  }
   317                  if (line == null) {
   318                      // EOF
   319                      return;
   320                  }
   321                  if (line.startsWith("CHUNK_UPLOADED ")) {
   322                      CamputChunkUploadedMessage msg = new CamputChunkUploadedMessage(line);
   323                      mService.onChunkUploaded(msg);
   324                      continue;
   325                  }
   326                  if (line.startsWith("STATS ")) {
   327                      CamputStatsMessage msg = new CamputStatsMessage(line);
   328                      mService.onStatsReceived(msg);
   329                      continue;
   330                  }
   331                  if (line.startsWith("STAT ")) {
   332                      CamputStatMessage msg = new CamputStatMessage(line);
   333                      mService.onStatReceived(msg.name(), msg.value());
   334                      continue;
   335                  }
   336                  if (line.startsWith("FILE_UPLOADED ")) {
   337                      String filename = line.substring(14).trim();
   338                      QueuedFile qf = null;
   339                      synchronized (mQueuedFile) {
   340                          qf = mQueuedFile.get(filename);
   341                          if (qf != null) {
   342                              mQueuedFile.remove(filename);
   343                          }
   344                      }
   345                      if (qf != null) {
   346                          mService.onUploadComplete(qf);
   347                      }
   348                      continue;
   349                  }
   350                  Log.d(TAG, "camput said unknown line: " + line);
   351              }
   352  
   353          }
   354      }
   355  
   356      private class WaitForProcessThread extends Thread {
   357          private final Process mProcess;
   358  
   359          public WaitForProcessThread(Process p) {
   360              mProcess = p;
   361          }
   362  
   363          @Override
   364          public void run() {
   365              Log.d(TAG, "Waiting for camput process.");
   366              try {
   367                  mProcess.waitFor();
   368              } catch (InterruptedException e) {
   369                  Log.d(TAG, "Interrupted waiting for camput");
   370                  msgCh.offer(new ProcessExitedMessage(-1));
   371                  return;
   372              }
   373              Log.d(TAG, "Exit status of camput = " + mProcess.exitValue());
   374              msgCh.offer(new ProcessExitedMessage(mProcess.exitValue()));
   375          }
   376      }
   377  
   378      // CopyToAndroidLogThread copies the camput child process's stderr
   379      // to Android's log.
   380      private static class CopyToAndroidLogThread extends Thread {
   381          private final BufferedReader mBufIn;
   382          private final String mStream;
   383  
   384          public CopyToAndroidLogThread(String stream, InputStream in) {
   385              mBufIn = new BufferedReader(new InputStreamReader(in));
   386              mStream = stream;
   387          }
   388  
   389          @Override
   390          public void run() {
   391              String tag = TAG + "/" + mStream + "-child";
   392              while (true) {
   393                  String line = null;
   394                  try {
   395                      line = mBufIn.readLine();
   396                  } catch (IOException e) {
   397                      Log.d(tag, "Exception: " + e.toString());
   398                      return;
   399                  }
   400                  if (line == null) {
   401                      // EOF
   402                      return;
   403                  }
   404                  Log.d(tag, line);
   405              }
   406          }
   407      }
   408  
   409  }