示例#1
0
        async Task DownloadAll(string cacheDirectory, List <PartialZipDownload> parts)
        {
            // Get the parts all grouped by their URL so we can batch requests with multiple ranges
            // instead of making a request to the same url for each part
            // also only grab the parts that don't already locally exist
            var uniqueUrls = parts
                             .Where(p => !File.Exists(Path.Combine(cacheDirectory, p.Id, p.ToFile)))
                             .GroupBy(p => p.Url);

            // For each unique url...
            foreach (var partsByUrl in uniqueUrls)
            {
                var downloadUrl = partsByUrl.Key;

                LogMessage("Downloading Partial Zip parts from: " + downloadUrl);

                try {
                    // Create a lock file based on the hash of the URL we are downloading from
                    // Since we could download a multipart request, we are locking on the url from any other process downloading from it
                    var lockFile = Path.Combine(cacheDirectory, DownloadUtils.Crc64(downloadUrl) + ".locked");

                    using (var lockStream = DownloadUtils.ObtainExclusiveFileLock(lockFile, base.Token, TimeSpan.FromSeconds(30))) {
                        if (lockStream == null)
                        {
                            LogCodedError(ErrorCodes.ExclusiveLockTimeout, "Timed out waiting for exclusive file lock on: {0}", lockFile);
                            LogMessage("Timed out waiting for an exclusive file lock on: " + lockFile, MessageImportance.High);

                            // Log Custom error if one was specified in the partial download info
                            var firstPart = partsByUrl.FirstOrDefault();
                            if (!string.IsNullOrEmpty(firstPart?.CustomErrorCode) && !string.IsNullOrEmpty(firstPart?.CustomErrorMessage))
                            {
                                LogCodedError(firstPart.CustomErrorCode, firstPart.CustomErrorMessage);
                            }

                            return;
                        }

                        try {
                            await Download(cacheDirectory, partsByUrl.Key, partsByUrl.ToList()).ConfigureAwait(false);
                        } catch (Exception ex) {
                            LogCodedError(ErrorCodes.PartialDownloadFailed, "Partial Download Failed for one or more parts");
                            LogErrorFromException(ex);

                            // Log Custom error if one was specified in the partial download info
                            var firstPart = partsByUrl.FirstOrDefault();
                            if (!string.IsNullOrEmpty(firstPart?.CustomErrorCode) && !string.IsNullOrEmpty(firstPart?.CustomErrorMessage))
                            {
                                LogCodedError(firstPart.CustomErrorCode, firstPart.CustomErrorMessage);
                            }
                        }
                    }

                    try {
                        if (File.Exists(lockFile))
                        {
                            File.Delete(lockFile);
                        }
                    } catch { }
                } catch (Exception ex) {
                    LogCodedError(ErrorCodes.PartialDownloadFailed, "Partial Download Failed for one or more parts");
                    LogErrorFromException(ex);

                    // Log Custom error if one was specified in the partial download info
                    var firstPart = partsByUrl.FirstOrDefault();
                    if (!string.IsNullOrEmpty(firstPart?.CustomErrorCode) && !string.IsNullOrEmpty(firstPart?.CustomErrorMessage))
                    {
                        LogCodedError(firstPart.CustomErrorCode, firstPart.CustomErrorMessage);
                    }
                }
            }
        }
        internal static void FixupAar(string filename, bool androidFixManifests, TaskLoggingHelper loggingHelper)
        {
            using (var fileStream = new FileStream(filename, FileMode.Open))
                using (var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Update, true))
                {
                    var entryNames = zipArchive.Entries.Select(zae => zae.FullName).ToList();

                    loggingHelper.LogMessage("Found {0} entries in {1}", entryNames.Count, filename);

                    foreach (var entryName in entryNames)
                    {
                        var newName = entryName;

                        // Open the old entry
                        var oldEntry = zipArchive.GetEntry(entryName);

                        // Some .aars contain an AndroidManifest.xml in the aapt folder which is essentially a duplicate of the main one
                        // but a sanitized version with placeholders like ${applicationId} being escaped to _dollar blah
                        // we don't care about these for xamarin.android, which picks up both manifests and merges both
                        // This will ensure the 'sanitized' version doesn't get packaged
                        if (entryName.TrimStart('/').Equals("aapt/AndroidManifest.xml", StringComparison.InvariantCultureIgnoreCase))
                        {
                            loggingHelper.LogMessage("Found aapt/AndroidManifest.xml, skipping...");
                            // Delete the entry entirely and continue
                            oldEntry.Delete();
                            continue;
                        }

                        // We are only re-adding non empty folders, otherwise we end up with a corrupt zip in mono
                        if (!string.IsNullOrEmpty(oldEntry.Name))
                        {
                            // SPOILER ALERT: UGLY WORKAROUND
                            // In the Android Support libraries, there exist multiple .aar files which have a `libs/internal_impl-25.0.0` file.
                            // In Xamarin.Android, there is a Task "CheckDuplicateJavaLibraries" which inspects jar files being pulled in from .aar files
                            // in assemblies to see if there exist any files with the same name but different content, and will throw an error if it finds any.
                            // However, for us, it is perfectly valid to have this scenario and we should not see an error.
                            // We are working around this by detecting files named like this, and renaming them to some unique value
                            // in this case, a part of the hash of the assembly name.
                            var newFile = Path.GetFileName(newName);
                            var newDir  = Path.GetDirectoryName(newName);

                            if (newFile.StartsWith("internal_impl", StringComparison.InvariantCulture))
                            {
                                newName = Path.Combine(newDir, "internal_impl-" + DownloadUtils.Crc64(filename).Substring(0, 6) + ".jar");
                            }

                            loggingHelper.LogMessage("Renaming: {0} to {1}", entryName, newName);

                            // Create a new entry based on our new name
                            var newEntry = zipArchive.CreateEntry(newName);

                            // Since Xamarin.Android's AndoridManifest.xml merging code is not as sophisticated as gradle's yet, we may need
                            // to fix some things up in the manifest file to get it to merge properly into our applications
                            // Here we will check to see if Fixing manifests was enabled, and if the entry we are on is the AndroidManifest.xml file
                            if (androidFixManifests && oldEntry.Length > 0 && newName.EndsWith("AndroidManifest.xml", StringComparison.OrdinalIgnoreCase))
                            {
                                // android: namespace
                                XNamespace xns = "http://schemas.android.com/apk/res/android";

                                using (var oldStream = oldEntry.Open())
                                    using (var xmlReader = System.Xml.XmlReader.Create(oldStream))
                                    {
                                        var xdoc = XDocument.Load(xmlReader);

                                        // BEGIN FIXUP #1
                                        // Some `android:name` attributes will start with a . indicating, that the `package` value of the `manifest` element
                                        // should be prefixed dynamically/at merge to this attribute value.  Xamarin.Android doesn't handle this case yet
                                        // so we are going to manually take care of it.

                                        // Get the package name from the manifest node
                                        var packageName = xdoc.Document.Descendants("manifest")?.FirstOrDefault()?.Attribute("package")?.Value;

                                        if (!string.IsNullOrEmpty(packageName))
                                        {
                                            // Find all elements in the xml document that have a `android:name` attribute which starts with a .
                                            // Select all of them, and then change the `android:name` attribute value to be the
                                            // package name we found in the `manifest` element previously + the original attribute value
                                            xdoc.Document.Descendants()
                                            .Where(elem => elem.Attribute(xns + "name")?.Value?.StartsWith(".", StringComparison.Ordinal) ?? false)
                                            .ToList()
                                            .ForEach(elem => elem.SetAttributeValue(xns + "name", packageName + elem.Attribute(xns + "name").Value));
                                        }
                                        // END FIXUP #1

                                        using (var newStream = newEntry.Open())
                                            using (var xmlWriter = System.Xml.XmlWriter.Create(newStream))
                                            {
                                                xdoc.WriteTo(xmlWriter);
                                            }
                                    }
                            }
                            else
                            {
                                // Copy file contents over if they exist
                                if (oldEntry.Length > 0)
                                {
                                    using (var oldStream = oldEntry.Open())
                                        using (var newStream = newEntry.Open())
                                        {
                                            oldStream.CopyTo(newStream);
                                        }
                                }
                            }
                        }

                        // Delete the old entry regardless of if it's a folder or not
                        oldEntry.Delete();
                    }
                }
        }