/* * Copyright 2009, Stephan Aßmus * All rights reserved. Distributed under the terms of the MIT License. */ #include "UnzipEngine.h" #include #include #include #include #include #include #include #include #include #include "CommandPipe.h" #include "SemaphoreLocker.h" #include "ProgressReporter.h" using std::nothrow; UnzipEngine::UnzipEngine(ProgressReporter* reporter, sem_id cancelSemaphore) : fPackage(""), fRetrievingListing(false), fBytesToUncompress(0), fBytesUncompressed(0), fLastBytesUncompressed(0), fItemsToUncompress(0), fItemsUncompressed(0), fLastItemsUncompressed(0), fProgressReporter(reporter), fCancelSemaphore(cancelSemaphore) { } UnzipEngine::~UnzipEngine() { } status_t UnzipEngine::SetTo(const char* pathToPackage, const char* destinationFolder) { fPackage = pathToPackage; fDestinationFolder = destinationFolder; fEntrySizeMap.Clear(); fBytesToUncompress = 0; fBytesUncompressed = 0; fLastBytesUncompressed = 0; fItemsToUncompress = 0; fItemsUncompressed = 0; fLastItemsUncompressed = 0; BPrivate::BCommandPipe commandPipe; status_t ret = commandPipe.AddArg("unzip"); if (ret == B_OK) ret = commandPipe.AddArg("-l"); if (ret == B_OK) ret = commandPipe.AddArg(fPackage.String()); if (ret != B_OK) return ret; // Launch the unzip thread and start reading the stdout and stderr output. FILE* stdOutAndErrPipe = NULL; thread_id unzipThread = commandPipe.PipeInto(&stdOutAndErrPipe); if (unzipThread < 0) return (status_t)unzipThread; fRetrievingListing = true; ret = commandPipe.ReadLines(stdOutAndErrPipe, this); fRetrievingListing = false; printf("%" B_PRIu64 " items in %" B_PRIdOFF " bytes\n", fItemsToUncompress, fBytesToUncompress); return ret; } status_t UnzipEngine::UnzipPackage() { if (fItemsToUncompress == 0) return B_NO_INIT; BPrivate::BCommandPipe commandPipe; status_t ret = commandPipe.AddArg("unzip"); if (ret == B_OK) ret = commandPipe.AddArg("-o"); if (ret == B_OK) ret = commandPipe.AddArg(fPackage.String()); if (ret == B_OK) ret = commandPipe.AddArg("-d"); if (ret == B_OK) ret = commandPipe.AddArg(fDestinationFolder.String()); if (ret != B_OK) { fprintf(stderr, "Faild to construct argument list for unzip " "process: %s\n", strerror(ret)); return ret; } // Launch the unzip thread and start reading the stdout and stderr output. FILE* stdOutAndErrPipe = NULL; thread_id unzipThread = commandPipe.PipeInto(&stdOutAndErrPipe); if (unzipThread < 0) return (status_t)unzipThread; ret = commandPipe.ReadLines(stdOutAndErrPipe, this); if (ret != B_OK) { fprintf(stderr, "Piping the unzip process failed: %s\n", strerror(ret)); return ret; } // Add the contents of a potentially existing .OptionalPackageDescription // to the COPYRIGHTS attribute of AboutSystem. BPath descriptionPath(fDestinationFolder.String(), ".OptionalPackageDescription"); ret = descriptionPath.InitCheck(); if (ret != B_OK) { fprintf(stderr, "Failed to construct path to " ".OptionalPackageDescription: %s\n", strerror(ret)); return ret; } BEntry descriptionEntry(descriptionPath.Path()); if (!descriptionEntry.Exists()) return B_OK; BFile descriptionFile(&descriptionEntry, B_READ_ONLY); ret = descriptionFile.InitCheck(); if (ret != B_OK) { fprintf(stderr, "Failed to construct file to " ".OptionalPackageDescription: %s\n", strerror(ret)); return ret; } BPath aboutSystemPath(fDestinationFolder.String(), "system/apps/AboutSystem"); ret = aboutSystemPath.InitCheck(); if (ret != B_OK) { fprintf(stderr, "Failed to construct path to AboutSystem: %s\n", strerror(ret)); return ret; } BNode aboutSystemNode(aboutSystemPath.Path()); ret = aboutSystemNode.InitCheck(); if (ret != B_OK) { fprintf(stderr, "Failed to construct node to AboutSystem: %s\n", strerror(ret)); return ret; } const char* kCopyrightsAttrName = "COPYRIGHTS"; BString copyrightAttr; ret = aboutSystemNode.ReadAttrString(kCopyrightsAttrName, ©rightAttr); if (ret != B_OK && ret != B_ENTRY_NOT_FOUND) { fprintf(stderr, "Failed to read current COPYRIGHTS attribute from " "AboutSystem: %s\n", strerror(ret)); return ret; } // Append the contents of the current optional package description to // the existing COPYRIGHTS attribute from AboutSystem size_t bufferSize = 2048; char buffer[bufferSize + 1]; buffer[bufferSize] = '\0'; while (true) { ssize_t read = descriptionFile.Read(buffer, bufferSize); if (read > 0) { int32 length = copyrightAttr.Length(); if (read < (ssize_t)bufferSize) buffer[read] = '\0'; int32 bufferLength = strlen(buffer); // Should be "read", but maybe we have a zero in the // buffer in which case the next check would be fooled. copyrightAttr << buffer; if (copyrightAttr.Length() != length + bufferLength) { fprintf(stderr, "Failed to append buffer to COPYRIGHTS " "attribute.\n"); return B_NO_MEMORY; } } else break; } if (copyrightAttr[copyrightAttr.Length() - 1] != '\n') copyrightAttr << '\n\n'; else copyrightAttr << '\n'; ret = aboutSystemNode.WriteAttrString(kCopyrightsAttrName, ©rightAttr); if (ret != B_OK && ret != B_ENTRY_NOT_FOUND) { fprintf(stderr, "Failed to read current COPYRIGHTS attribute from " "AboutSystem: %s\n", strerror(ret)); return ret; } // Don't leave the .OptionalPackageDescription behind. descriptionFile.Unset(); descriptionEntry.Remove(); return B_OK; } // #pragma mark - bool UnzipEngine::IsCanceled() { if (fCancelSemaphore < 0) return false; SemaphoreLocker locker(fCancelSemaphore); return !locker.IsLocked(); } status_t UnzipEngine::ReadLine(const BString& line) { if (fRetrievingListing) return _ReadLineListing(line); else return _ReadLineExtract(line); } status_t UnzipEngine::_ReadLineListing(const BString& line) { static const char* kListingFormat = "%llu %s %s %s\n"; const char* string = line.String(); while (string[0] == ' ') string++; uint64 bytes; char date[16]; char time[16]; char path[1024]; if (sscanf(string, kListingFormat, &bytes, &date, &time, &path) == 4) { fBytesToUncompress += bytes; BString itemPath(path); BString itemName(path); int leafPos = itemPath.FindLast('/'); if (leafPos >= 0) itemName = itemPath.String() + leafPos + 1; // We check if the target folder exists and don't increment // the item count in that case. Unzip won't report on folders that did // not need to be created. This may mess up our current item count. uint32 itemCount = 1; if (bytes == 0 && itemName.Length() == 0) { // a folder? BPath destination(fDestinationFolder.String()); if (destination.Append(itemPath.String()) == B_OK) { BEntry test(destination.Path()); if (test.Exists() && test.IsDirectory()) { // printf("ignoring %s\n", itemPath.String()); itemCount = 0; } } } fItemsToUncompress += itemCount; // printf("item %s with %llu bytes to %s\n", itemName.String(), // bytes, itemPath.String()); fEntrySizeMap.Put(itemName.String(), bytes); } else { // printf("listing not understood: %s", string); } return B_OK; } status_t UnzipEngine::_ReadLineExtract(const BString& line) { const char* kCreatingFormat = " creating:"; const char* kInflatingFormat = " inflating:"; const char* kLinkingFormat = " linking:"; if (line.FindFirst(kCreatingFormat) == 0 || line.FindFirst(kInflatingFormat) == 0 || line.FindFirst(kLinkingFormat) == 0) { fItemsUncompressed++; BString itemPath; int pos = line.FindLast(" -> "); if (pos > 0) line.CopyInto(itemPath, 13, pos - 13); else line.CopyInto(itemPath, 13, line.CountChars() - 14); itemPath.Trim(); pos = itemPath.FindLast('/'); BString itemName = itemPath.String() + pos + 1; itemPath.Truncate(pos); off_t bytes = 0; if (fEntrySizeMap.ContainsKey(itemName.String())) { bytes = fEntrySizeMap.Get(itemName.String()); fBytesUncompressed += bytes; } // printf("%llu extracted %s to %s (%llu)\n", fItemsUncompressed, // itemName.String(), itemPath.String(), bytes); _UpdateProgress(itemName.String(), itemPath.String()); } else { // printf("ignored: '%s'\n", line.String()); } return B_OK; } void UnzipEngine::_UpdateProgress(const char* item, const char* targetFolder) { if (fProgressReporter == NULL) return; uint64 items = 0; if (fLastItemsUncompressed < fItemsUncompressed) { items = fItemsUncompressed - fLastItemsUncompressed; fLastItemsUncompressed = fItemsUncompressed; } off_t bytes = 0; if (fLastBytesUncompressed < fBytesUncompressed) { bytes = fBytesUncompressed - fLastBytesUncompressed; fLastBytesUncompressed = fBytesUncompressed; } fProgressReporter->ItemsWritten(items, bytes, item, targetFolder); }