chunk 2: remaining non-audio non-NewImport assets

This commit is contained in:
2026-04-06 11:25:19 +03:00
parent 0d11a097d8
commit ed675b9f4a
3742 changed files with 1640416 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 82d84541ddbac4dc283e6dd0f45a76e3
timeCreated: 1540331105
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c7b0cb9288a1142bb9fdbab1795abe9e
timeCreated: 1540331105
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c96ff1b4e1fa04e52835b8b54a861bf4
timeCreated: 1540331105
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: e0bee5c147382431ba11a2a4b3a3210b
timeCreated: 1540331105
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4021c9fd6b6874777be4455f9addd6e8
timeCreated: 1540331105
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a11063dc084564ec8a2c318ffe18709f
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 0d4bc874171bf46818df8b8c647622d4
timeCreated: 1540331104
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: