chunk 2: remaining non-audio non-NewImport assets
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82d84541ddbac4dc283e6dd0f45a76e3
|
||||
timeCreated: 1540331105
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,737 @@
|
||||
// <copyright file="CommandLine.cs" company="Google Inc.">
|
||||
// Copyright (C) 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.PlayServices
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
public static class CommandLine
|
||||
{
|
||||
// Constants used for StreamData's handle field.
|
||||
public const int StandardOutputStreamDataHandle = 0;
|
||||
public const int StandardErrorStreamDataHandle = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Result from Run().
|
||||
/// </summary>
|
||||
public class Result
|
||||
{
|
||||
/// String containing the standard output stream of the tool.
|
||||
public string stdout;
|
||||
/// String containing the standard error stream of the tool.
|
||||
public string stderr;
|
||||
/// Exit code returned by the tool when execution is complete.
|
||||
public int exitCode;
|
||||
/// String that can be used in an error message.
|
||||
/// This contains:
|
||||
/// * The command executed.
|
||||
/// * Standard output string.
|
||||
/// * Standard error string.
|
||||
/// * Exit code.
|
||||
public string message;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Called when a RunAsync() completes.
|
||||
/// </summary>
|
||||
public delegate void CompletionHandler(Result result);
|
||||
|
||||
/// <summary>
|
||||
/// Text and byte representations of an array of data.
|
||||
/// </summary>
|
||||
public class StreamData
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle to the stream this was read from.
|
||||
/// e.g 0 for stdout, 1 for stderr.
|
||||
/// </summary>
|
||||
public int handle = 0;
|
||||
/// <summary>
|
||||
/// Text representation of "data".
|
||||
/// </summary>
|
||||
public string text = "";
|
||||
/// <summary>
|
||||
/// Array of bytes or "null" if no data is present.
|
||||
/// </summary>
|
||||
public byte[] data = null;
|
||||
/// <summary>
|
||||
/// Whether this marks the end of the stream.
|
||||
/// </summary>
|
||||
public bool end;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize this instance.
|
||||
/// </summary>
|
||||
/// <param name="handle">Stream identifier.</param>
|
||||
/// <param name="text">String</param>
|
||||
/// <param name="data">Bytes</param>
|
||||
/// <param name="end">Whether this is the end of the stream.</param>
|
||||
public StreamData(int handle, string text, byte[] data, bool end)
|
||||
{
|
||||
this.handle = handle;
|
||||
this.text = text;
|
||||
this.data = data;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an empty StreamData instance.
|
||||
/// </summary>
|
||||
public static StreamData Empty
|
||||
{
|
||||
get { return new StreamData(0, "", null, false); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when data is received from either the standard output or standard error
|
||||
/// streams with a reference to the current standard input stream to enable simulated
|
||||
/// interactive input.
|
||||
/// </summary>
|
||||
/// <param name="process">Executing process.</param>
|
||||
/// <param name="stdin">Standard input stream.</param>
|
||||
/// <param name="stream">Data read from the standard output or error streams.</param>
|
||||
public delegate void IOHandler(Process process, StreamWriter stdin, StreamData streamData);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously execute a command line tool, calling the specified delegate on
|
||||
/// completion.
|
||||
/// </summary>
|
||||
/// <param name="toolPath">Tool to execute.</param>
|
||||
/// <param name="arguments">String to pass to the tools' command line.</param>
|
||||
/// <param name="completionDelegate">Called when the tool completes.</param>
|
||||
/// <param name="workingDirectory">Directory to execute the tool from.</param>
|
||||
/// <param name="envVars">Additional environment variables to set for the command.</param>
|
||||
/// <param name="ioHandler">Allows a caller to provide interactive input and also handle
|
||||
/// both output and error streams from a single delegate.</param>
|
||||
public static void RunAsync(
|
||||
string toolPath, string arguments, CompletionHandler completionDelegate,
|
||||
string workingDirectory = null,
|
||||
Dictionary<string, string> envVars = null,
|
||||
IOHandler ioHandler = null)
|
||||
{
|
||||
Thread thread = new Thread(new ThreadStart(() => {
|
||||
Result result = Run(toolPath, arguments, workingDirectory, envVars: envVars,
|
||||
ioHandler: ioHandler);
|
||||
completionDelegate(result);
|
||||
}));
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously reads binary data from a stream using a configurable buffer.
|
||||
/// </summary>
|
||||
private class AsyncStreamReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate called when data is read from the stream.
|
||||
/// <param name="streamData">Data read from the stream.</param>
|
||||
/// </summary>
|
||||
public delegate void Handler(StreamData streamData);
|
||||
/// <summary>
|
||||
/// Event which is signalled when data is received.
|
||||
/// </summary>
|
||||
public event Handler DataReceived;
|
||||
|
||||
// Signalled when a read completes.
|
||||
private AutoResetEvent readEvent = null;
|
||||
// Handle to the stream.
|
||||
private int handle;
|
||||
// Stream to read.
|
||||
private Stream stream;
|
||||
// Buffer used to read data from the stream.
|
||||
private byte[] buffer;
|
||||
// Whether reading is complete.
|
||||
volatile bool complete = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the reader.
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream to read.</param>
|
||||
/// <param name="bufferSize">Size of the buffer to read.</param>
|
||||
public AsyncStreamReader(int handle, Stream stream, int bufferSize)
|
||||
{
|
||||
readEvent = new AutoResetEvent(false);
|
||||
this.handle = handle;
|
||||
this.stream = stream;
|
||||
buffer = new byte[bufferSize];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the handle of the stream associated with this reader.
|
||||
/// </summary>
|
||||
public int Handle
|
||||
{
|
||||
get
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Start reading.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
if (!complete) (new Thread(new ThreadStart(Read))).Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read from the stream until the end is reached.
|
||||
/// </summary>
|
||||
private void Read()
|
||||
{
|
||||
while (!complete)
|
||||
{
|
||||
stream.BeginRead(
|
||||
buffer, 0, buffer.Length, (asyncResult) => {
|
||||
int bytesRead = stream.EndRead(asyncResult);
|
||||
if (!complete)
|
||||
{
|
||||
complete = bytesRead == 0;
|
||||
if (DataReceived != null)
|
||||
{
|
||||
byte[] copy = new byte[bytesRead];
|
||||
Array.Copy(buffer, copy, copy.Length);
|
||||
DataReceived(new StreamData(
|
||||
handle, System.Text.Encoding.UTF8.GetString(copy), copy,
|
||||
complete));
|
||||
}
|
||||
}
|
||||
readEvent.Set();
|
||||
}, null);
|
||||
readEvent.WaitOne();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a set of readers to read the specified streams, handles are assigned
|
||||
/// based upon the index of each stream in the provided array.
|
||||
/// </summary>
|
||||
/// <param name="streams">Streams to read.</param>
|
||||
/// <param name="bufferSize">Size of the buffer to use to read each stream.</param>
|
||||
public static AsyncStreamReader[] CreateFromStreams(Stream[] streams, int bufferSize)
|
||||
{
|
||||
AsyncStreamReader[] readers = new AsyncStreamReader[streams.Length];
|
||||
for (int i = 0; i < streams.Length; i++)
|
||||
{
|
||||
readers[i] = new AsyncStreamReader(i, streams[i], bufferSize);
|
||||
}
|
||||
return readers;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplexes data read from multiple AsyncStreamReaders onto a single thread.
|
||||
/// </summary>
|
||||
private class AsyncStreamReaderMultiplexer
|
||||
{
|
||||
/// Used to wait on items in the queue.
|
||||
private AutoResetEvent queuedItem = null;
|
||||
/// Queue of Data read from the readers.
|
||||
private System.Collections.Queue queue = null;
|
||||
/// Active stream handles.
|
||||
private HashSet<int> activeStreams;
|
||||
|
||||
/// <summary>
|
||||
/// Called when all streams reach the end or the reader is shut down.
|
||||
/// </summary>
|
||||
public delegate void CompletionHandler();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when all streams reach the end or the reader is shut down.
|
||||
/// </summary>
|
||||
public event CompletionHandler Complete;
|
||||
|
||||
/// <summary>
|
||||
/// Handler called from the multiplexer's thread.
|
||||
/// </summary>
|
||||
public event AsyncStreamReader.Handler DataReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Create the multiplexer and attach it to the specified handler.
|
||||
/// </summary>
|
||||
/// <param name="readers">Readers to read.</param>
|
||||
/// <param name="handler">Called for queued data item.</param>
|
||||
/// <param name="complete">Called when all readers complete.</param>
|
||||
public AsyncStreamReaderMultiplexer(AsyncStreamReader[] readers,
|
||||
AsyncStreamReader.Handler handler,
|
||||
CompletionHandler complete = null)
|
||||
{
|
||||
queuedItem = new AutoResetEvent(false);
|
||||
queue = System.Collections.Queue.Synchronized(new System.Collections.Queue());
|
||||
activeStreams = new HashSet<int>();
|
||||
foreach (AsyncStreamReader reader in readers)
|
||||
{
|
||||
reader.DataReceived += HandleRead;
|
||||
activeStreams.Add(reader.Handle);
|
||||
}
|
||||
DataReceived += handler;
|
||||
if (complete != null) Complete += complete;
|
||||
(new Thread(new ThreadStart(PollQueue))).Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shutdown the multiplexer.
|
||||
/// </summary>
|
||||
public void Shutdown()
|
||||
{
|
||||
lock (activeStreams)
|
||||
{
|
||||
activeStreams.Clear();
|
||||
}
|
||||
queuedItem.Set();
|
||||
}
|
||||
|
||||
// Handle stream read notification.
|
||||
private void HandleRead(StreamData streamData)
|
||||
{
|
||||
queue.Enqueue(streamData);
|
||||
queuedItem.Set();
|
||||
}
|
||||
|
||||
// Poll the queue.
|
||||
private void PollQueue()
|
||||
{
|
||||
while (activeStreams.Count > 0)
|
||||
{
|
||||
queuedItem.WaitOne();
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
StreamData data = (StreamData)queue.Dequeue();
|
||||
if (data.end)
|
||||
{
|
||||
lock(activeStreams)
|
||||
{
|
||||
activeStreams.Remove(data.handle);
|
||||
}
|
||||
}
|
||||
if (DataReceived != null) DataReceived(data);
|
||||
}
|
||||
}
|
||||
if (Complete != null) Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aggregates lines read by AsyncStreamReader.
|
||||
/// </summary>
|
||||
public class LineReader
|
||||
{
|
||||
// List of data keyed by stream handle.
|
||||
private Dictionary<int, List<StreamData>> streamDataByHandle =
|
||||
new Dictionary<int, List<StreamData>>();
|
||||
|
||||
/// <summary>
|
||||
/// Event called with a new line of data.
|
||||
/// </summary>
|
||||
public event IOHandler LineHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Event called for each piece of data received.
|
||||
/// </summary>
|
||||
public event IOHandler DataHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the instance.
|
||||
/// </summary>
|
||||
/// <param name="handler">Called for each line read.</param>
|
||||
public LineReader(IOHandler handler = null)
|
||||
{
|
||||
if (handler != null) LineHandler += handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the currently buffered set of data.
|
||||
/// This allows the user to retrieve data before the end of a stream when
|
||||
/// a newline isn't present.
|
||||
/// </summary>
|
||||
/// <param name="handle">Handle of the stream to query.</param>
|
||||
/// <returns>List of data for the requested stream.</return>
|
||||
public List<StreamData> GetBufferedData(int handle)
|
||||
{
|
||||
List<StreamData> handleData;
|
||||
return streamDataByHandle.TryGetValue(handle, out handleData) ?
|
||||
new List<StreamData>(handleData) : new List<StreamData>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush the internal buffer.
|
||||
/// </summary>
|
||||
public void Flush()
|
||||
{
|
||||
foreach (List<StreamData> handleData in streamDataByHandle.Values)
|
||||
{
|
||||
handleData.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aggregate the specified list of StringBytes into a single structure.
|
||||
/// </summary>
|
||||
/// <param name="handle">Stream handle.</param>
|
||||
/// <param name="dataStream">Data to aggregate.</param>
|
||||
public static StreamData Aggregate(List<StreamData> dataStream)
|
||||
{
|
||||
// Build a list of strings up to the newline.
|
||||
List<string> stringList = new List<string>();
|
||||
int byteArraySize = 0;
|
||||
int handle = 0;
|
||||
bool end = false;
|
||||
foreach (StreamData sd in dataStream)
|
||||
{
|
||||
stringList.Add(sd.text);
|
||||
byteArraySize += sd.data.Length;
|
||||
handle = sd.handle;
|
||||
end |= sd.end;
|
||||
}
|
||||
string concatenatedString = String.Join("", stringList.ToArray());
|
||||
|
||||
// Concatenate the list of bytes up to the StringBytes before the
|
||||
// newline.
|
||||
byte[] byteArray = new byte[byteArraySize];
|
||||
int byteArrayOffset = 0;
|
||||
foreach (StreamData sd in dataStream)
|
||||
{
|
||||
Array.Copy(sd.data, 0, byteArray, byteArrayOffset, sd.data.Length);
|
||||
byteArrayOffset += sd.data.Length;
|
||||
}
|
||||
return new StreamData(handle, concatenatedString, byteArray, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate method which can be attached to AsyncStreamReader.DataReceived to
|
||||
/// aggregate data until a new line is found before calling LineHandler.
|
||||
/// </summary>
|
||||
public void AggregateLine(Process process, StreamWriter stdin, StreamData data)
|
||||
{
|
||||
if (DataHandler != null) DataHandler(process, stdin, data);
|
||||
bool linesProcessed = false;
|
||||
|
||||
if (data.data != null)
|
||||
{
|
||||
// Simplify line tracking by converting linefeeds to newlines.
|
||||
data.text = data.text.Replace("\r\n", "\n").Replace("\r", "\n");
|
||||
|
||||
List<StreamData> aggregateList = GetBufferedData(data.handle);
|
||||
aggregateList.Add(data);
|
||||
bool complete = false;
|
||||
while (!complete)
|
||||
{
|
||||
List<StreamData> newAggregateList = new List<StreamData>();
|
||||
int textDataIndex = 0;
|
||||
int aggregateListSize = aggregateList.Count;
|
||||
complete = true;
|
||||
|
||||
foreach (StreamData textData in aggregateList)
|
||||
{
|
||||
bool end = data.end && (++textDataIndex == aggregateListSize);
|
||||
newAggregateList.Add(textData);
|
||||
int newlineOffset = textData.text.Length;
|
||||
if (!end)
|
||||
{
|
||||
newlineOffset = textData.text.IndexOf("\n");
|
||||
if (newlineOffset < 0) continue;
|
||||
newAggregateList.Remove(textData);
|
||||
}
|
||||
|
||||
StreamData concatenated = Aggregate(newAggregateList);
|
||||
|
||||
// Flush the aggregation list and store the overflow.
|
||||
newAggregateList.Clear();
|
||||
if (!end)
|
||||
{
|
||||
// Add the remaining line to the concatenated data.
|
||||
concatenated.text += textData.text.Substring(0, newlineOffset + 1);
|
||||
// Save the line after the newline in the buffer.
|
||||
newAggregateList.Add(new StreamData(
|
||||
data.handle, textData.text.Substring(newlineOffset + 1),
|
||||
textData.data, false));
|
||||
complete = false;
|
||||
}
|
||||
|
||||
// Report the data.
|
||||
if (LineHandler != null) LineHandler(process, stdin, concatenated);
|
||||
linesProcessed = true;
|
||||
}
|
||||
aggregateList = newAggregateList;
|
||||
}
|
||||
streamDataByHandle[data.handle] = aggregateList;
|
||||
}
|
||||
// If no lines were processed call the handle to allow it to look ahead.
|
||||
if (!linesProcessed && LineHandler != null)
|
||||
{
|
||||
LineHandler(process, stdin, StreamData.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a command line tool.
|
||||
/// </summary>
|
||||
/// <param name="toolPath">Tool to execute.</param>
|
||||
/// <param name="arguments">String to pass to the tools' command line.</param>
|
||||
/// <param name="workingDirectory">Directory to execute the tool from.</param>
|
||||
/// <param name="envVars">Additional environment variables to set for the command.</param>
|
||||
/// <param name="ioHandler">Allows a caller to provide interactive input and also handle
|
||||
/// both output and error streams from a single delegate.</param>
|
||||
/// <returns>CommandLineTool result if successful, raises an exception if it's not
|
||||
/// possible to execute the tool.</returns>
|
||||
public static Result Run(string toolPath, string arguments, string workingDirectory = null,
|
||||
Dictionary<string, string> envVars = null,
|
||||
IOHandler ioHandler = null) {
|
||||
return RunViaShell(toolPath, arguments, workingDirectory: workingDirectory,
|
||||
envVars: envVars, ioHandler: ioHandler, useShellExecution: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a command line tool.
|
||||
/// </summary>
|
||||
/// <param name="toolPath">Tool to execute. On Windows, if the path to this tool contains
|
||||
/// single quotes (apostrophes) the tool will be executed via the shell.</param>
|
||||
/// <param name="arguments">String to pass to the tools' command line.</param>
|
||||
/// <param name="workingDirectory">Directory to execute the tool from.</param>
|
||||
/// <param name="envVars">Additional environment variables to set for the command.</param>
|
||||
/// <param name="ioHandler">Allows a caller to provide interactive input and also handle
|
||||
/// both output and error streams from a single delegate. NOTE: This is ignored if
|
||||
/// shell execution is enabled.</param>
|
||||
/// <param name="useShellExecution">Execute the command via the shell. This disables
|
||||
/// I/O redirection and causes a window to be popped up when the command is executed.
|
||||
/// This uses file redirection to retrieve the standard output stream.
|
||||
/// </param>
|
||||
/// <param name="stdoutRedirectionInShellMode">Enables stdout and stderr redirection when
|
||||
/// executing a command via the shell. This requires:
|
||||
/// * cmd.exe (on Windows) or bash (on OSX / Linux) are in the path.
|
||||
/// * Arguments containing whitespace are quoted.</param>
|
||||
/// <returns>CommandLineTool result if successful, raises an exception if it's not
|
||||
/// possible to execute the tool.</returns>
|
||||
public static Result RunViaShell(
|
||||
string toolPath, string arguments, string workingDirectory = null,
|
||||
Dictionary<string, string> envVars = null,
|
||||
IOHandler ioHandler = null, bool useShellExecution = false,
|
||||
bool stdoutRedirectionInShellMode = true) {
|
||||
var inputEncoding = Console.InputEncoding;
|
||||
var outputEncoding = Console.OutputEncoding;
|
||||
// Set encoderShouldEmitUTF8Identifier to false to prevent writing a Byte Order Marker (BOM).
|
||||
Console.InputEncoding = new UTF8Encoding(false);
|
||||
Console.OutputEncoding = new UTF8Encoding(false);
|
||||
try
|
||||
{
|
||||
return RunViaShellInternal(toolPath, arguments, workingDirectory, envVars,
|
||||
ioHandler, useShellExecution, stdoutRedirectionInShellMode);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.InputEncoding = inputEncoding;
|
||||
Console.OutputEncoding = outputEncoding;
|
||||
}
|
||||
}
|
||||
|
||||
private static Result RunViaShellInternal(
|
||||
string toolPath, string arguments, string workingDirectory, Dictionary<string, string> envVars,
|
||||
IOHandler ioHandler, bool useShellExecution, bool stdoutRedirectionInShellMode) {
|
||||
// Mono 3.x on Windows can't execute tools with single quotes (apostrophes) in the path.
|
||||
// The following checks for this condition and forces shell execution of tools in these
|
||||
// paths which works fine as the shell tool should be in the system PATH.
|
||||
if (UnityEngine.RuntimePlatform.WindowsEditor == UnityEngine.Application.platform &&
|
||||
toolPath.Contains("\'")) {
|
||||
useShellExecution = true;
|
||||
stdoutRedirectionInShellMode = true;
|
||||
}
|
||||
|
||||
string stdoutFileName = null;
|
||||
string stderrFileName = null;
|
||||
if (useShellExecution && stdoutRedirectionInShellMode) {
|
||||
stdoutFileName = Path.GetTempFileName();
|
||||
stderrFileName = Path.GetTempFileName();
|
||||
string shellCmd ;
|
||||
string shellArgPrefix;
|
||||
string shellArgPostfix;
|
||||
string escapedToolPath = toolPath;
|
||||
if (UnityEngine.RuntimePlatform.WindowsEditor == UnityEngine.Application.platform) {
|
||||
shellCmd = "cmd.exe";
|
||||
shellArgPrefix = "/c \"";
|
||||
shellArgPostfix = "\"";
|
||||
} else {
|
||||
shellCmd = "bash";
|
||||
shellArgPrefix = "-l -c '";
|
||||
shellArgPostfix = "'";
|
||||
escapedToolPath = toolPath.Replace("'", "'\\''");
|
||||
}
|
||||
arguments = String.Format("{0}\"{1}\" {2} 1> {3} 2> {4}{5}", shellArgPrefix,
|
||||
escapedToolPath, arguments, stdoutFileName,
|
||||
stderrFileName, shellArgPostfix);
|
||||
toolPath = shellCmd;
|
||||
}
|
||||
|
||||
Process process = new Process();
|
||||
process.StartInfo.UseShellExecute = useShellExecution;
|
||||
process.StartInfo.Arguments = arguments;
|
||||
if (useShellExecution) {
|
||||
process.StartInfo.CreateNoWindow = false;
|
||||
process.StartInfo.RedirectStandardOutput = false;
|
||||
process.StartInfo.RedirectStandardError = false;
|
||||
} else {
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
if (envVars != null) {
|
||||
foreach (var env in envVars) {
|
||||
process.StartInfo.EnvironmentVariables[env.Key] = env.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
process.StartInfo.RedirectStandardInput = !useShellExecution && (ioHandler != null);
|
||||
process.StartInfo.FileName = toolPath;
|
||||
process.StartInfo.WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory;
|
||||
|
||||
var started = process.Start();
|
||||
if (!started) {
|
||||
UnityEngine.Debug.LogErrorFormat("Failed to start {0}", process);
|
||||
return new Result {exitCode = -1};
|
||||
}
|
||||
|
||||
// If an I/O handler was specified, call it with no data to provide a process and stdin
|
||||
// handle before output data is sent to it.
|
||||
if (ioHandler != null) ioHandler(process, process.StandardInput, StreamData.Empty);
|
||||
|
||||
List<string>[] stdouterr = new List<string>[] {
|
||||
new List<string>(), new List<string>() };
|
||||
|
||||
if (useShellExecution) {
|
||||
process.WaitForExit();
|
||||
if (stdoutRedirectionInShellMode) {
|
||||
stdouterr[0].Add(File.ReadAllText(stdoutFileName));
|
||||
stdouterr[1].Add(File.ReadAllText(stderrFileName));
|
||||
File.Delete(stdoutFileName);
|
||||
File.Delete(stderrFileName);
|
||||
}
|
||||
} else {
|
||||
AutoResetEvent complete = new AutoResetEvent(false);
|
||||
// Read raw output from the process.
|
||||
AsyncStreamReader[] readers = AsyncStreamReader.CreateFromStreams(
|
||||
new Stream[] { process.StandardOutput.BaseStream,
|
||||
process.StandardError.BaseStream }, 1);
|
||||
new AsyncStreamReaderMultiplexer(
|
||||
readers,
|
||||
(StreamData data) => {
|
||||
stdouterr[data.handle].Add(data.text);
|
||||
if (ioHandler != null) ioHandler(process, process.StandardInput, data);
|
||||
},
|
||||
complete: () => { complete.Set(); });
|
||||
foreach (AsyncStreamReader reader in readers) reader.Start();
|
||||
|
||||
process.WaitForExit();
|
||||
// Wait for the reading threads to complete.
|
||||
complete.WaitOne();
|
||||
}
|
||||
|
||||
Result result = new Result();
|
||||
result.stdout = String.Join("", stdouterr[StandardOutputStreamDataHandle].ToArray());
|
||||
result.stderr = String.Join("", stdouterr[StandardErrorStreamDataHandle].ToArray());
|
||||
result.exitCode = process.ExitCode;
|
||||
result.message = FormatResultMessage(toolPath, arguments, result.stdout,
|
||||
result.stderr, result.exitCode);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Split a string into lines using newline, carriage return or a combination of both.
|
||||
/// </summary>
|
||||
/// <param name="multilineString">String to split into lines</param>
|
||||
public static string[] SplitLines(string multilineString)
|
||||
{
|
||||
return Regex.Split(multilineString, "\r\n|\r|\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format a command excecution error message.
|
||||
/// </summary>
|
||||
/// <param name="toolPath">Tool executed.</param>
|
||||
/// <param name="arguments">Arguments used to execute the tool.</param>
|
||||
/// <param name="stdout">Standard output stream from tool execution.</param>
|
||||
/// <param name="stderr">Standard error stream from tool execution.</param>
|
||||
/// <param name="exitCode">Result of the tool.</param>
|
||||
private static string FormatResultMessage(string toolPath, string arguments,
|
||||
string stdout, string stderr,
|
||||
int exitCode) {
|
||||
return String.Format(
|
||||
"{0} '{1} {2}'\n" +
|
||||
"stdout:\n" +
|
||||
"{3}\n" +
|
||||
"stderr:\n" +
|
||||
"{4}\n" +
|
||||
"exit code: {5}\n",
|
||||
exitCode == 0 ? "Successfully executed" : "Failed to run",
|
||||
toolPath, arguments, stdout, stderr, exitCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the specified argument in double quotes.
|
||||
/// </summary>
|
||||
public static string QuotePath(string path)
|
||||
{
|
||||
return string.Format("\"{0}\"", path);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Get an executable extension.
|
||||
/// </summary>
|
||||
/// <returns>Platform specific extension for executables.</returns>
|
||||
public static string GetExecutableExtension()
|
||||
{
|
||||
return (UnityEngine.RuntimePlatform.WindowsEditor ==
|
||||
UnityEngine.Application.platform) ? ".exe" : "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Locate an executable in the system path.
|
||||
/// </summary>
|
||||
/// <param name="exeName">Executable name without a platform specific extension like
|
||||
/// .exe</param>
|
||||
/// <returns>A string to the executable path if it's found, null otherwise.</returns>
|
||||
public static string FindExecutable(string executable)
|
||||
{
|
||||
string which = (UnityEngine.RuntimePlatform.WindowsEditor ==
|
||||
UnityEngine.Application.platform) ? "where" : "which";
|
||||
try
|
||||
{
|
||||
Result result = Run(which, executable, Environment.CurrentDirectory);
|
||||
if (result.exitCode == 0)
|
||||
{
|
||||
return SplitLines(result.stdout)[0];
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
UnityEngine.Debug.Log("'" + which + "' command is not on path. " +
|
||||
"Unable to find executable '" + executable +
|
||||
"' (" + e.ToString() + ")");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7b0cb9288a1142bb9fdbab1795abe9e
|
||||
timeCreated: 1540331105
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,255 @@
|
||||
// <copyright file="CommandLineDialog.cs" company="Google Inc.">
|
||||
// Copyright (C) 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.PlayServices
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
public class CommandLineDialog : TextAreaDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Forwards the output of the currently executing command to a CommandLineDialog window.
|
||||
/// </summary>
|
||||
public class ProgressReporter : CommandLine.LineReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to scale the progress bar by the number of lines reported by the command
|
||||
/// line tool.
|
||||
/// </summary>
|
||||
public int maxProgressLines;
|
||||
|
||||
// Queue of command line output lines to send to the main / UI thread.
|
||||
private System.Collections.Queue textQueue = null;
|
||||
// Number of lines reported by the command line tool.
|
||||
private volatile int linesReported;
|
||||
// Command line tool result, set when command line execution is complete.
|
||||
private volatile CommandLine.Result result = null;
|
||||
|
||||
/// <summary>
|
||||
/// Event called on the main / UI thread when the outstanding command line tool
|
||||
/// completes.
|
||||
/// </summary>
|
||||
public event CommandLine.CompletionHandler Complete;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new reporter.
|
||||
/// </summary>
|
||||
public ProgressReporter(CommandLine.IOHandler handler = null)
|
||||
{
|
||||
textQueue = System.Collections.Queue.Synchronized(new System.Collections.Queue());
|
||||
maxProgressLines = 0;
|
||||
linesReported = 0;
|
||||
LineHandler += CommandLineIOHandler;
|
||||
Complete = null;
|
||||
}
|
||||
|
||||
// Count the number of newlines and carriage returns in a string.
|
||||
private int CountLines(string str)
|
||||
{
|
||||
return str.Split(new char[] { '\n', '\r' }).Length - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called from RunCommandLine() tool to report the output of the currently
|
||||
/// executing commmand.
|
||||
/// </summary>
|
||||
/// <param name="process">Executing process.</param>
|
||||
/// <param name="stdin">Standard input stream.</param>
|
||||
/// <param name="data">Data read from the standard output or error streams.</param>
|
||||
private void CommandLineIOHandler(Process process, StreamWriter stdin,
|
||||
CommandLine.StreamData data)
|
||||
{
|
||||
// Note: ignoring process.HasExited to print errors that were emitted during shutdown.
|
||||
if (data.data == null) return;
|
||||
// Count lines in stdout.
|
||||
if (data.handle == 0) linesReported += CountLines(data.text);
|
||||
// Enqueue data for the text view.
|
||||
textQueue.Enqueue(System.Text.Encoding.UTF8.GetString(data.data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the currently executing command completes.
|
||||
/// </summary>
|
||||
public void CommandLineToolCompletion(CommandLine.Result result)
|
||||
{
|
||||
PlayServicesResolver.Log(
|
||||
string.Format("Command completed with exit code {0}: {1}", result.exitCode, result.message),
|
||||
result.exitCode == 0 ? LogLevel.Info: LogLevel.Error);
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called from CommandLineDialog in the context of the main / UI thread.
|
||||
/// </summary>
|
||||
public void Update(CommandLineDialog window)
|
||||
{
|
||||
if (textQueue.Count > 0)
|
||||
{
|
||||
List<string> textList = new List<string>();
|
||||
while (textQueue.Count > 0) textList.Add((string)textQueue.Dequeue());
|
||||
string bodyText = window.bodyText + String.Join("", textList.ToArray());
|
||||
// Really weak handling of carriage returns. Truncates to the previous
|
||||
// line for each newline detected.
|
||||
while (true)
|
||||
{
|
||||
// Really weak handling carriage returns for progress style updates.
|
||||
int carriageReturn = bodyText.LastIndexOf("\r");
|
||||
if (carriageReturn < 0 || bodyText.Substring(carriageReturn, 1) == "\n")
|
||||
{
|
||||
break;
|
||||
}
|
||||
string bodyTextHead = "";
|
||||
int previousNewline = bodyText.LastIndexOf("\n", carriageReturn,
|
||||
carriageReturn);
|
||||
if (previousNewline >= 0)
|
||||
{
|
||||
bodyTextHead = bodyText.Substring(0, previousNewline + 1);
|
||||
}
|
||||
bodyText = bodyTextHead + bodyText.Substring(carriageReturn + 1);
|
||||
}
|
||||
window.bodyText = bodyText;
|
||||
if (window.autoScrollToBottom)
|
||||
{
|
||||
window.scrollPosition.y = Mathf.Infinity;
|
||||
}
|
||||
window.Repaint();
|
||||
}
|
||||
if (maxProgressLines > 0)
|
||||
{
|
||||
window.progress = (float)linesReported / (float)maxProgressLines;
|
||||
}
|
||||
if (result != null)
|
||||
{
|
||||
window.progressTitle = "";
|
||||
if (Complete != null)
|
||||
{
|
||||
Complete(result);
|
||||
Complete = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public volatile float progress;
|
||||
public string progressTitle;
|
||||
public string progressSummary;
|
||||
public volatile bool autoScrollToBottom;
|
||||
|
||||
/// <summary>
|
||||
/// Event delegate called from the Update() method of the window.
|
||||
/// </summary>
|
||||
public delegate void UpdateDelegate(CommandLineDialog window);
|
||||
|
||||
public event UpdateDelegate UpdateEvent;
|
||||
|
||||
private bool progressBarVisible;
|
||||
|
||||
/// <summary>
|
||||
/// Create a dialog box which can display command line output.
|
||||
/// </summary>
|
||||
/// <returns>Reference to the new window.</returns>
|
||||
public static CommandLineDialog CreateCommandLineDialog(string title)
|
||||
{
|
||||
CommandLineDialog window = (CommandLineDialog)EditorWindow.GetWindow(
|
||||
typeof(CommandLineDialog), true, title);
|
||||
window.Initialize();
|
||||
return window;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize all members of the window.
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
progress = 0.0f;
|
||||
progressTitle = "";
|
||||
progressSummary = "";
|
||||
UpdateEvent = null;
|
||||
progressBarVisible = false;
|
||||
autoScrollToBottom = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously execute a command line tool in this window, showing progress
|
||||
/// and finally calling the specified delegate on completion from the main / UI thread.
|
||||
/// </summary>
|
||||
/// <param name="toolPath">Tool to execute.</param>
|
||||
/// <param name="arguments">String to pass to the tools' command line.</param>
|
||||
/// <param name="completionDelegate">Called when the tool completes.</param>
|
||||
/// <param name="workingDirectory">Directory to execute the tool from.</param>
|
||||
/// <param name="ioHandler">Allows a caller to provide interactive input and also handle
|
||||
/// both output and error streams from a single delegate.</param>
|
||||
/// <param name="maxProgressLines">Specifies the number of lines output by the
|
||||
/// command line that results in a 100% value on a progress bar.</param>
|
||||
/// <returns>Reference to the new window.</returns>
|
||||
public void RunAsync(
|
||||
string toolPath, string arguments,
|
||||
CommandLine.CompletionHandler completionDelegate,
|
||||
string workingDirectory = null, Dictionary<string, string> envVars = null,
|
||||
CommandLine.IOHandler ioHandler = null, int maxProgressLines = 0)
|
||||
{
|
||||
CommandLineDialog.ProgressReporter reporter = new CommandLineDialog.ProgressReporter();
|
||||
reporter.maxProgressLines = maxProgressLines;
|
||||
// Call the reporter from the UI thread from this window.
|
||||
UpdateEvent += reporter.Update;
|
||||
// Connect the user's delegate to the reporter's completion method.
|
||||
reporter.Complete += completionDelegate;
|
||||
// Connect the caller's IoHandler delegate to the reporter.
|
||||
reporter.DataHandler += ioHandler;
|
||||
// Disconnect the reporter when the command completes.
|
||||
CommandLine.CompletionHandler reporterUpdateDisable =
|
||||
(CommandLine.Result unusedResult) => { this.UpdateEvent -= reporter.Update; };
|
||||
reporter.Complete += reporterUpdateDisable;
|
||||
PlayServicesResolver.Log(String.Format(
|
||||
"Executing command: {0} {1}", toolPath, arguments), level: LogLevel.Verbose);
|
||||
CommandLine.RunAsync(toolPath, arguments, reporter.CommandLineToolCompletion,
|
||||
workingDirectory: workingDirectory, envVars: envVars,
|
||||
ioHandler: reporter.AggregateLine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call the update event from the UI thread, optionally display / hide the progress bar.
|
||||
/// </summary>
|
||||
protected virtual void Update()
|
||||
{
|
||||
if (UpdateEvent != null) UpdateEvent(this);
|
||||
if (progressTitle != "")
|
||||
{
|
||||
progressBarVisible = true;
|
||||
EditorUtility.DisplayProgressBar(progressTitle, progressSummary,
|
||||
progress);
|
||||
}
|
||||
else if (progressBarVisible)
|
||||
{
|
||||
progressBarVisible = false;
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
}
|
||||
|
||||
// Hide the progress bar if the window is closed.
|
||||
protected override void OnDestroy() {
|
||||
if (progressBarVisible) EditorUtility.ClearProgressBar();
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c96ff1b4e1fa04e52835b8b54a861bf4
|
||||
timeCreated: 1540331105
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,98 @@
|
||||
// <copyright file="Logger.cs" company="Google Inc.">
|
||||
// Copyright (C) 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.PlayServices {
|
||||
|
||||
/// <summary>
|
||||
/// Log severity.
|
||||
/// </summary>
|
||||
internal enum LogLevel {
|
||||
Debug,
|
||||
Verbose,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Writes filtered logs to the Unity log.
|
||||
/// </summary>
|
||||
internal class Logger {
|
||||
|
||||
/// <summary>
|
||||
/// Filter the log level.
|
||||
/// </summary>
|
||||
internal LogLevel Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable / disable verbose logging.
|
||||
/// This toggles between Info vs. Verbose levels.
|
||||
/// </summary>
|
||||
internal bool Verbose {
|
||||
set { Level = value ? LogLevel.Verbose : LogLevel.Info; }
|
||||
get { return Level <= LogLevel.Verbose; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of the file to log to, if this is null this will not log to a file.
|
||||
/// </summary>
|
||||
internal string LogFilename { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a logger.
|
||||
/// </summary>
|
||||
internal Logger() {}
|
||||
|
||||
/// <summary>
|
||||
/// Write a message to the log file.
|
||||
/// </summary>
|
||||
/// <param name="message">Message to log.</param>
|
||||
private void LogToFile(string message) {
|
||||
if (LogFilename != null) {
|
||||
using (var file = new System.IO.StreamWriter(LogFilename, true)) {
|
||||
file.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a filtered message to Unity log and optionally to a file specified by LogFilename.
|
||||
/// </summary>
|
||||
/// <param name="message">String to write to the log.</param>
|
||||
/// <param name="level">Severity of the message, if this is below the currently selected
|
||||
/// Level property the message will not be logged.</param>
|
||||
internal virtual void Log(string message, LogLevel level = LogLevel.Info) {
|
||||
if (level >= Level) {
|
||||
switch (level) {
|
||||
case LogLevel.Debug:
|
||||
case LogLevel.Verbose:
|
||||
case LogLevel.Info:
|
||||
UnityEngine.Debug.Log(message);
|
||||
LogToFile(message);
|
||||
break;
|
||||
case LogLevel.Warning:
|
||||
UnityEngine.Debug.LogWarning(message);
|
||||
LogToFile("WARNING: " + message);
|
||||
break;
|
||||
case LogLevel.Error:
|
||||
UnityEngine.Debug.LogError(message);
|
||||
LogToFile("ERROR: " + message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0bee5c147382431ba11a2a4b3a3210b
|
||||
timeCreated: 1540331105
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
// <copyright file="PlayServicesResolver.cs" company="Google Inc.">
|
||||
// Copyright (C) 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.PlayServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Stub Play Services resolver for compatibility with the Play Instant plugin.
|
||||
/// </summary>
|
||||
public static class PlayServicesResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Logger for this module.
|
||||
/// </summary>
|
||||
private static readonly Logger Logger = new Logger();
|
||||
|
||||
/// <summary>
|
||||
/// Log a filtered message to Unity log, error messages are stored in
|
||||
/// PlayServicesSupport.lastError.
|
||||
/// </summary>
|
||||
/// <param name="message">String to write to the log.</param>
|
||||
/// <param name="level">Severity of the message, if this is below the currently selected
|
||||
/// Level property the message will not be logged.</param>
|
||||
internal static void Log(string message, LogLevel level = LogLevel.Info) {
|
||||
Logger.Log(message, level);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4021c9fd6b6874777be4455f9addd6e8
|
||||
timeCreated: 1540331105
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a11063dc084564ec8a2c318ffe18709f
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,188 @@
|
||||
// <copyright file="TextAreaDialog.cs" company="Google Inc.">
|
||||
// Copyright (C) 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.PlayServices
|
||||
{
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Window which displays a scrollable text area and two buttons at the bottom.
|
||||
/// </summary>
|
||||
public class TextAreaDialog : EditorWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate type, called when a button is clicked.
|
||||
/// </summary>
|
||||
public delegate void ButtonClicked(TextAreaDialog dialog);
|
||||
|
||||
/// <summary>
|
||||
/// Delegate called when a button is clicked.
|
||||
/// </summary>
|
||||
public ButtonClicked buttonClicked;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this window should be modal.
|
||||
/// NOTE: This emulates modal behavior by re-acquiring focus when it's lost.
|
||||
/// </summary>
|
||||
public bool modal = true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the text to display in the summary area of the window.
|
||||
/// </summary>
|
||||
public string summaryText = "";
|
||||
|
||||
/// <summary>
|
||||
/// Set the text to display on the "yes" (left-most) button.
|
||||
/// </summary>
|
||||
public string yesText = "";
|
||||
|
||||
/// <summary>
|
||||
/// Set the text to display on the "no" (right-most) button.
|
||||
/// </summary>
|
||||
public string noText = "";
|
||||
|
||||
/// <summary>
|
||||
/// Set the text to display in the scrollable text area.
|
||||
/// </summary>
|
||||
public string bodyText = "";
|
||||
|
||||
/// <summary>
|
||||
/// Result of yes / no button press. true if the "yes" button was pressed, false if the
|
||||
/// "no" button was pressed. Defaults to "false".
|
||||
/// </summary>
|
||||
public bool result = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether either button was clicked.
|
||||
/// </summary>
|
||||
private bool yesNoClicked = false;
|
||||
|
||||
/// <summary>
|
||||
/// Current position of the scrollbar.
|
||||
/// </summary>
|
||||
public Vector2 scrollPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Get the existing text area window or create a new one.
|
||||
/// </summary>
|
||||
/// <param name="title">Title to display on the window.</param>
|
||||
/// <returns>Reference to this class</returns>
|
||||
public static TextAreaDialog CreateTextAreaDialog(string title)
|
||||
{
|
||||
TextAreaDialog window = (TextAreaDialog)EditorWindow.GetWindow(typeof(TextAreaDialog),
|
||||
true, title, true);
|
||||
window.Initialize();
|
||||
return window;
|
||||
}
|
||||
|
||||
public virtual void Initialize()
|
||||
{
|
||||
yesText = "";
|
||||
noText = "";
|
||||
summaryText = "";
|
||||
bodyText = "";
|
||||
result = false;
|
||||
yesNoClicked = false;
|
||||
scrollPosition = new Vector2(0, 0);
|
||||
minSize = new Vector2(300, 200);
|
||||
position = new Rect(UnityEngine.Screen.width / 3, UnityEngine.Screen.height / 3,
|
||||
minSize.x * 2, minSize.y * 2);
|
||||
}
|
||||
|
||||
// Draw the GUI.
|
||||
protected virtual void OnGUI()
|
||||
{
|
||||
GUILayout.BeginVertical();
|
||||
|
||||
GUILayout.Label(summaryText, EditorStyles.boldLabel);
|
||||
|
||||
scrollPosition = GUILayout.BeginScrollView(scrollPosition);
|
||||
// Unity text elements can only display up to a small X number of characters (rumors
|
||||
// are ~65k) so generate a set of labels one for each subset of the text being
|
||||
// displayed.
|
||||
int bodyTextOffset = 0;
|
||||
System.Collections.Generic.List<string> bodyTextList =
|
||||
new System.Collections.Generic.List<string>();
|
||||
const int chunkSize = 5000; // Conservative chunk size < 65k characters.
|
||||
while (bodyTextOffset < bodyText.Length)
|
||||
{
|
||||
int readSize = chunkSize;
|
||||
readSize = bodyTextOffset + readSize >= bodyText.Length ?
|
||||
bodyText.Length - bodyTextOffset : readSize;
|
||||
bodyTextList.Add(bodyText.Substring(bodyTextOffset, readSize));
|
||||
bodyTextOffset += readSize;
|
||||
}
|
||||
foreach (string bodyTextChunk in bodyTextList)
|
||||
{
|
||||
float pixelHeight = EditorStyles.wordWrappedLabel.CalcHeight(
|
||||
new GUIContent(bodyTextChunk), position.width);
|
||||
EditorGUILayout.SelectableLabel(bodyTextChunk,
|
||||
EditorStyles.wordWrappedLabel,
|
||||
GUILayout.Height(pixelHeight));
|
||||
}
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
bool yesPressed = false;
|
||||
bool noPressed = false;
|
||||
GUILayout.BeginHorizontal();
|
||||
if (yesText != "") yesPressed = GUILayout.Button(yesText);
|
||||
if (noText != "") noPressed = GUILayout.Button(noText);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.EndVertical();
|
||||
|
||||
// If yes or no buttons were pressed, call the buttonClicked delegate.
|
||||
if (yesPressed || noPressed)
|
||||
{
|
||||
yesNoClicked = true;
|
||||
if (yesPressed)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
else if (noPressed)
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
|
||||
// TODO: figure out why the click delegate wasn't being called (need to force Close)
|
||||
if (buttonClicked == null)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
buttonClicked(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optionally make the dialog modal.
|
||||
protected virtual void OnLostFocus()
|
||||
{
|
||||
if (modal) Focus();
|
||||
}
|
||||
|
||||
// If the window is destroyed click the no button if a listener is attached.
|
||||
protected virtual void OnDestroy() {
|
||||
if (!yesNoClicked) {
|
||||
result = false;
|
||||
if (buttonClicked != null) buttonClicked(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d4bc874171bf46818df8b8c647622d4
|
||||
timeCreated: 1540331104
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user