From 44f919da9afa25ffd54e29c6f64cfba20e41f34f Mon Sep 17 00:00:00 2001 From: Ryan Leavengood Date: Sat, 13 Oct 2007 04:46:32 +0000 Subject: [PATCH] =?UTF-8?q?Adding=20=C5=81ukasz=20Zemczak's=20package=20in?= =?UTF-8?q?staller=20to=20our=20tree.=20This=20was=20his=20Google=20Summer?= =?UTF-8?q?=20of=20Code=20project.=20Thanks=20for=20your=20work=20=C5=81uk?= =?UTF-8?q?asz=20and=20I=20hope=20we=20can=20see=20more=20work=20from=20yo?= =?UTF-8?q?u.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Still to do: - Create an icon. - Add pkg files to the MIME database with PackageInstaller as the default handler. The rdef here has the MIME type name for the pkg format should anyone else choose to add it :) - Support for running scripts included with packages. - Testing various different packages. - Fixing problems in the Haiku GUI layout system which affect the code used for various parts of the installer GUI (please bear with the commented out code for now.) - Adding this to the image. Tomorrow I will add Łukasz's InstalledPackages utility which can be used to view installed packages and uninstall them. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@22525 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/apps/Jamfile | 4 + .../packageinstaller/InstalledPackageInfo.cpp | 331 +++++ .../packageinstaller/InstalledPackageInfo.h | 75 ++ src/apps/packageinstaller/Jamfile | 21 + .../packageinstaller/PackageImageViewer.cpp | 175 +++ .../packageinstaller/PackageImageViewer.h | 50 + src/apps/packageinstaller/PackageInfo.cpp | 1131 +++++++++++++++++ src/apps/packageinstaller/PackageInfo.h | 78 ++ .../packageinstaller/PackageInstaller.rdef | 22 + src/apps/packageinstaller/PackageItem.cpp | 697 ++++++++++ src/apps/packageinstaller/PackageItem.h | 114 ++ src/apps/packageinstaller/PackageStatus.cpp | 169 +++ src/apps/packageinstaller/PackageStatus.h | 53 + .../packageinstaller/PackageTextViewer.cpp | 218 ++++ src/apps/packageinstaller/PackageTextViewer.h | 37 + src/apps/packageinstaller/PackageView.cpp | 629 +++++++++ src/apps/packageinstaller/PackageView.h | 59 + src/apps/packageinstaller/PackageWindow.cpp | 47 + src/apps/packageinstaller/PackageWindow.h | 32 + src/apps/packageinstaller/main.cpp | 163 +++ 20 files changed, 4105 insertions(+) create mode 100644 src/apps/packageinstaller/InstalledPackageInfo.cpp create mode 100644 src/apps/packageinstaller/InstalledPackageInfo.h create mode 100644 src/apps/packageinstaller/Jamfile create mode 100644 src/apps/packageinstaller/PackageImageViewer.cpp create mode 100644 src/apps/packageinstaller/PackageImageViewer.h create mode 100644 src/apps/packageinstaller/PackageInfo.cpp create mode 100644 src/apps/packageinstaller/PackageInfo.h create mode 100644 src/apps/packageinstaller/PackageInstaller.rdef create mode 100644 src/apps/packageinstaller/PackageItem.cpp create mode 100644 src/apps/packageinstaller/PackageItem.h create mode 100644 src/apps/packageinstaller/PackageStatus.cpp create mode 100644 src/apps/packageinstaller/PackageStatus.h create mode 100644 src/apps/packageinstaller/PackageTextViewer.cpp create mode 100644 src/apps/packageinstaller/PackageTextViewer.h create mode 100644 src/apps/packageinstaller/PackageView.cpp create mode 100644 src/apps/packageinstaller/PackageView.h create mode 100644 src/apps/packageinstaller/PackageWindow.cpp create mode 100644 src/apps/packageinstaller/PackageWindow.h create mode 100644 src/apps/packageinstaller/main.cpp diff --git a/src/apps/Jamfile b/src/apps/Jamfile index efab9ee4a6..bc6cf6dba9 100644 --- a/src/apps/Jamfile +++ b/src/apps/Jamfile @@ -12,6 +12,7 @@ SubInclude HAIKU_TOP src apps expander ; SubInclude HAIKU_TOP src apps fontdemo ; SubInclude HAIKU_TOP src apps glteapot ; SubInclude HAIKU_TOP src apps icon-o-matic ; +SubInclude HAIKU_TOP src apps installedpackages ; SubInclude HAIKU_TOP src apps installer ; SubInclude HAIKU_TOP src apps launchbox ; SubInclude HAIKU_TOP src apps magnify ; @@ -21,6 +22,7 @@ SubInclude HAIKU_TOP src apps mediaconverter ; SubInclude HAIKU_TOP src apps mediaplayer ; SubInclude HAIKU_TOP src apps midiplayer ; SubInclude HAIKU_TOP src apps networkstatus ; +SubInclude HAIKU_TOP src apps packageinstaller ; SubInclude HAIKU_TOP src apps people ; SubInclude HAIKU_TOP src apps poorman ; SubInclude HAIKU_TOP src apps powerstatus ; @@ -35,3 +37,5 @@ SubInclude HAIKU_TOP src apps terminal ; SubInclude HAIKU_TOP src apps tracker ; SubInclude HAIKU_TOP src apps tv ; SubInclude HAIKU_TOP src apps workspaces ; + +SubInclude HAIKU_TOP src apps test_app ; diff --git a/src/apps/packageinstaller/InstalledPackageInfo.cpp b/src/apps/packageinstaller/InstalledPackageInfo.cpp new file mode 100644 index 0000000000..b5768e16ed --- /dev/null +++ b/src/apps/packageinstaller/InstalledPackageInfo.cpp @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ + + +#include "InstalledPackageInfo.h" +#include +#include +#include +#include +#include +#include + + +const char * kPackagesDir = "packages"; + + +static status_t +info_prepare(const char *filename, BFile *file, BMessage *info) +{ + if (!filename) + return B_ERROR; + + BPath path; + if (find_directory(B_USER_CONFIG_DIRECTORY, &path) != B_OK + || path.Append(kPackagesDir) != B_OK + || path.Append(filename) != B_OK) + return B_ERROR; + + file->SetTo(path.Path(), B_READ_ONLY); + if (file->InitCheck() != B_OK) + return B_ERROR; + + status_t ret = info->Unflatten(file); + if (ret != B_OK || info->what != P_PACKAGE_INFO) + return B_ERROR; + + return B_OK; +} + + +const char * +info_get_package_name(const char *filename) +{ + BFile file; + BMessage info; + if (info_prepare(filename, &file, &info) != B_OK) + return NULL; + BString name; + info.FindString("package_name", &name); + return name.String(); +} + + +const char * +info_get_package_version(const char *filename) +{ + BFile file; + BMessage info; + if (info_prepare(filename, &file, &info) != B_OK) + return NULL; + BString version; + info.FindString("package_version", &version); + return version.String(); +} + + +InstalledPackageInfo::InstalledPackageInfo() + : + fStatus(B_NO_INIT), + fIsUpToDate(false), + fCreate(false), + fInstalledItems(10) +{ +} + + +InstalledPackageInfo::InstalledPackageInfo(const char *packageName, + const char *version, bool create) + : + fStatus(B_NO_INIT), + fIsUpToDate(false), + fInstalledItems(10) +{ + SetTo(packageName, version, create); +} + + +InstalledPackageInfo::~InstalledPackageInfo() +{ + _ClearItemList(); +} + + +status_t +InstalledPackageInfo::InitCheck() +{ + return fStatus; +} + + +status_t +InstalledPackageInfo::SetTo(const char *packageName, const char *version, + bool create) +{ + _ClearItemList(); + fCreate = create; + fStatus = B_NO_INIT; + fVersion = version; + + if (!packageName) + return fStatus; + + BPath configPath; + if (find_directory(B_USER_CONFIG_DIRECTORY, &configPath) != B_OK) { + fStatus = B_ERROR; + return fStatus; + } + + if (fPathToInfo.SetTo(configPath.Path(), kPackagesDir) != B_OK) { + fStatus = B_ERROR; + return fStatus; + } + + // Check whether the directory exists + BDirectory packageDir(fPathToInfo.Path()); + fStatus = packageDir.InitCheck(); + if (fStatus == B_ENTRY_NOT_FOUND) { + // If not, create it + packageDir.SetTo(configPath.Path()); + if (packageDir.CreateDirectory(kPackagesDir, &packageDir) != B_OK) { + fStatus = B_ERROR; + return fStatus; + } + } + + BString filename = packageName; + filename << version << ".pdb"; + if (fPathToInfo.Append(filename.String()) != B_OK) { + fStatus = B_ERROR; + return fStatus; + } + + BFile package(fPathToInfo.Path(), B_READ_ONLY); + fStatus = package.InitCheck(); + if (fStatus == B_OK) { + // The given package exists, so we can unflatten the data to a message + // and then pass it further + BMessage info; + if (info.Unflatten(&package) != B_OK || info.what != P_PACKAGE_INFO) { + fStatus = B_ERROR; + return fStatus; + } + + int32 count; + fStatus = info.FindString("package_name", &fName); + fStatus |= info.FindString("package_desc", &fDescription); + fStatus |= info.FindString("package_version", &fVersion); + int64 spaceNeeded = 0; + fStatus |= info.FindInt64("package_size", &spaceNeeded); + fSpaceNeeded = static_cast(spaceNeeded); + fStatus |= info.FindInt32("file_count", &count); + if (fStatus != B_OK) { + fStatus = B_ERROR; + return fStatus; + } + + int32 i; + BString itemPath; + for (i = 0; i < count; i++) { + if (info.FindString("items", i, &itemPath) != B_OK) { + fStatus = B_ERROR; + return fStatus; + } + fInstalledItems.AddItem(new BString(itemPath)); // Or maybe BPath better? + } + fIsUpToDate = true; + } + else if (fStatus == B_ENTRY_NOT_FOUND) { + if (create) { + fStatus = B_OK; + fIsUpToDate = false; + } + } + + return fStatus; +} + + +status_t +InstalledPackageInfo::AddItem(const char *itemName) +{ + if (!itemName) + return B_ERROR; + + return fInstalledItems.AddItem(new BString(itemName)); +} + + +status_t +InstalledPackageInfo::Uninstall() +{ + if (fStatus != B_OK) + return fStatus; + + BString *iter; + uint32 i, count = fInstalledItems.CountItems(); + BEntry entry; + status_t ret; + + // Try to remove all entries that are present in the list + for (i = 0; i < count; i++) { + iter = static_cast(fInstalledItems.ItemAt(count - i - 1)); + fprintf(stderr, "Removing: %s (%d/%d)\n", iter->String(), i, count); + ret = entry.SetTo(iter->String()); + if (ret == B_BUSY) { + // The entry's directory is locked - wait a few cycles for it to + // unlock itself + int32 tries = 0; + for (tries = 0; tries < P_BUSY_TRIES; tries++) { + ret = entry.SetTo(iter->String()); + if (ret != B_BUSY) + break; + // Wait a moment + usleep(1000); + } + } + + if (ret == B_ENTRY_NOT_FOUND) + continue; + else if (ret != B_OK) { + fStatus = B_ERROR; + return fStatus; + } + + fprintf(stderr, "...we continue\n"); + + if (entry.Exists() && entry.Remove() != B_OK) { + fprintf(stderr, "\n%s\n", strerror(ret)); + fStatus = B_ERROR; + return fStatus; + } + fInstalledItems.RemoveItem(count - i - 1); + } + + if (entry.SetTo(fPathToInfo.Path()) != B_OK) { + fStatus = B_ERROR; + return fStatus; + } + if (entry.Exists() && entry.Remove() != B_OK) { + fStatus = B_ERROR; + return fStatus; + } + + return fStatus; +} + + +status_t +InstalledPackageInfo::Save() +{ + // If the package info is not up to date and everything till now was + // done correctly, we will save all data as a flattened BMessage to the + // package info file + if (fIsUpToDate || fStatus != B_OK) + return fStatus; + + BFile package; + if (fCreate) { + fStatus = package.SetTo(fPathToInfo.Path(), B_WRITE_ONLY | B_CREATE_FILE + | B_ERASE_FILE); + } + else { + fStatus = package.SetTo(fPathToInfo.Path(), B_WRITE_ONLY | B_ERASE_FILE); + } + + if (fStatus != B_OK) + return fStatus; + + status_t ret; + int32 i, count = fInstalledItems.CountItems(); + BMessage info(P_PACKAGE_INFO); + ret = info.AddString("package_name", fName); + ret |= info.AddString("package_desc", fDescription); + ret |= info.AddString("package_version", fVersion); + ret |= info.AddInt64("package_size", fSpaceNeeded); + ret |= info.AddInt32("file_count", count); + if (ret != B_OK) { + fStatus = B_ERROR; + return fStatus; + } + + BString *iter; + for (i = 0; i < count; i++) { + iter = static_cast(fInstalledItems.ItemAt(i)); + if (info.AddString("items", *iter) != B_OK) { + fStatus = B_ERROR; + return fStatus; + } + } + + if (info.Flatten(&package) != B_OK) { + fStatus = B_ERROR; + return fStatus; + } + fIsUpToDate = true; + + return fStatus; +} + + +// #pragma mark - + + +void +InstalledPackageInfo::_ClearItemList() +{ + // Clear the items list + BString *iter; + uint32 i, count = fInstalledItems.CountItems(); + for (i = 0; i < count; i++) { + iter = static_cast(fInstalledItems.ItemAt(0)); + fInstalledItems.RemoveItem((int32)0); + delete iter; + } +} + diff --git a/src/apps/packageinstaller/InstalledPackageInfo.h b/src/apps/packageinstaller/InstalledPackageInfo.h new file mode 100644 index 0000000000..d2d8bb03f7 --- /dev/null +++ b/src/apps/packageinstaller/InstalledPackageInfo.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ +#ifndef INSTALLEDPACKAGEINFO_H +#define INSTALLEDPACKAGEINFO_H + +#include +#include +#include +#include + + +#define P_BUSY_TRIES 10 + +enum { + P_PACKAGE_INFO = 'ppki' +}; + +extern const char * kPackagesDir; + + +// Useful function for fetching the package name and version without parsing all +// other data +const char * info_get_package_name(const char *filename); +const char * info_get_package_version(const char *filename); + + +class InstalledPackageInfo { + public: + InstalledPackageInfo(); + InstalledPackageInfo(const char *packageName, const char *version = NULL, + bool create = false); + ~InstalledPackageInfo(); + + status_t InitCheck(); + status_t SetTo(const char *packageName, const char *version = NULL, + bool create = false); + + void SetName(const char *name) { fName = name; } + const char *GetName() { return fName.String(); } + void SetDescription(const char *description) { fDescription = description; } + const char *GetDescription() { return fDescription.String(); } + //void SetVersion(const char *version) { fVersion = version; } + const char *GetVersion() { return fVersion.String(); } + void SetSpaceNeeded(uint64 size) { fSpaceNeeded = size; } + uint64 GetSpaceNeeded() { return fSpaceNeeded; } + + status_t AddItem(const char *itemName); + + status_t Uninstall(); + status_t Save(); + + private: + void _ClearItemList(); + + status_t fStatus; + bool fIsUpToDate; + bool fCreate; + + BString fName; + BString fDescription; + BString fVersion; + uint64 fSpaceNeeded; + BList fInstalledItems; + + BPath fPathToInfo; +}; + + +#endif + diff --git a/src/apps/packageinstaller/Jamfile b/src/apps/packageinstaller/Jamfile new file mode 100644 index 0000000000..3f7c70596d --- /dev/null +++ b/src/apps/packageinstaller/Jamfile @@ -0,0 +1,21 @@ +SubDir HAIKU_TOP src apps packageinstaller ; + +UsePrivateHeaders shared interface ; +SubDirHdrs $(HAIKU_TOP) headers libs zlib ; + +#SEARCH_SOURCE += [ FDirName $(HAIKU_TOP) src kits interface ] ; + +Application PackageInstaller : + main.cpp + PackageWindow.cpp + PackageView.cpp + PackageInfo.cpp + PackageItem.cpp + PackageStatus.cpp + PackageTextViewer.cpp + PackageImageViewer.cpp + InstalledPackageInfo.cpp + : be tracker translation z + : PackageInstaller.rdef +; + diff --git a/src/apps/packageinstaller/PackageImageViewer.cpp b/src/apps/packageinstaller/PackageImageViewer.cpp new file mode 100644 index 0000000000..dcbee6ff48 --- /dev/null +++ b/src/apps/packageinstaller/PackageImageViewer.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ + + +#include "PackageImageViewer.h" + +#include +#include +#include +#include + + +// Reserved +#define T(x) x + + +enum { + P_MSG_CLOSE = 'pmic' +}; + + + +ImageView::ImageView(BPositionIO *image) + : BView(BRect(0, 0, 1, 1), "image_view", B_FOLLOW_NONE, B_WILL_DRAW), + fSuccess(true) +{ + if (!image) { + fSuccess = false; + return; + } + // Initialize and translate the image + BTranslatorRoster *roster = BTranslatorRoster::Default(); + BBitmapStream stream; + if (roster->Translate(image, NULL, NULL, &stream, B_TRANSLATOR_BITMAP) + < B_OK) { + fSuccess = false; + return; + } + stream.DetachBitmap(&fImage); +} + + +ImageView::~ImageView() +{ +} + + +void +ImageView::AttachedToWindow() +{ + if (!fSuccess) { + ResizeTo(75, 75); + return; + } + + // We need to resize the view depending on what size has the screen and + // the image we will be viewing + BScreen screen(Window()); + BRect frame = screen.Frame(); + BRect image = fImage->Bounds(); + + if (image.Width() > (frame.Width() - 100.0f)) { + image.right = frame.Width() - 100.0f; + } + if (image.Height() > (frame.Height() - 100.0f)) { + image.bottom = frame.Height() - 100.f; + } + + ResizeTo(image.Width(), image.Height()); +} + + +void +ImageView::Draw(BRect updateRect) +{ + if (fSuccess) + DrawBitmapAsync(fImage, Bounds()); + else { + float length = StringWidth(T("Image not loaded correctly")); + DrawString(T("Image not loaded correctly"), + BPoint((Bounds().Width() - length) / 2.0f, 30.0f)); + } +} + + +void +ImageView::MouseUp(BPoint point) +{ + BWindow *parent = Window(); + if (parent) + parent->PostMessage(P_MSG_CLOSE); +} + + +// #pragma mark - + + +PackageImageViewer::PackageImageViewer(BPositionIO *image) + : BWindow(BRect(100, 100, 100, 100), "", B_MODAL_WINDOW, + B_NOT_ZOOMABLE | B_NOT_RESIZABLE | B_NOT_CLOSABLE) +{ + fBackground = new ImageView(image); + AddChild(fBackground); + + ResizeTo(fBackground->Bounds().Width(), fBackground->Bounds().Height()); + + BScreen screen(this); + BRect frame = screen.Frame(); + MoveTo((frame.Width() - Bounds().Width()) / 2.0f, + (frame.Height() - Bounds().Height()) / 2.0f); +} + + +PackageImageViewer::~PackageImageViewer() +{ +} + + +void +PackageImageViewer::Go() +{ + // Since this class can be thought of as a modified BAlert window, no use + // to reinvent a well fledged wheel. This concept has been borrowed from + // the current BAlert implementation + fSemaphore = create_sem(0, "ImageViewer"); + if (fSemaphore < B_OK) { + Quit(); + return; + } + + BWindow *parent = + dynamic_cast(BLooper::LooperForThread(find_thread(NULL))); + Show(); + + if (parent) { + status_t ret; + for (;;) { + do { + ret = acquire_sem_etc(fSemaphore, 1, B_RELATIVE_TIMEOUT, 50000); + } while (ret == B_INTERRUPTED); + + if (ret == B_BAD_SEM_ID) + break; + parent->UpdateIfNeeded(); + } + } + else { + // Since there are no spinlocks, wait until the semaphore is free + while (acquire_sem(fSemaphore) == B_INTERRUPTED) { + } + } + + if (Lock()) + Quit(); +} + + +void +PackageImageViewer::MessageReceived(BMessage *msg) +{ + if (msg->what == P_MSG_CLOSE) { + if (fSemaphore >= B_OK) { + delete_sem(fSemaphore); + fSemaphore = -1; + } + } + else + BWindow::MessageReceived(msg); +} + diff --git a/src/apps/packageinstaller/PackageImageViewer.h b/src/apps/packageinstaller/PackageImageViewer.h new file mode 100644 index 0000000000..ec6aa694cf --- /dev/null +++ b/src/apps/packageinstaller/PackageImageViewer.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ +#ifndef PACKAGEIMAGEVIEWER_H +#define PACKAGEIMAGEVIEWER_H + +#include +#include +#include +#include + + + +class ImageView : public BView { + public: + ImageView(BPositionIO *image); + ~ImageView(); + + void AttachedToWindow(); + void Draw(BRect updateRect); + void MouseUp(BPoint point); + + private: + BBitmap *fImage; + bool fSuccess; +}; + + +class PackageImageViewer : public BWindow { + public: + PackageImageViewer(BPositionIO *image); + ~PackageImageViewer(); + + void Go(); + + void MessageReceived(BMessage *msg); + + private: + ImageView *fBackground; + + sem_id fSemaphore; +}; + + +#endif + diff --git a/src/apps/packageinstaller/PackageInfo.cpp b/src/apps/packageinstaller/PackageInfo.cpp new file mode 100644 index 0000000000..ced4069fae --- /dev/null +++ b/src/apps/packageinstaller/PackageInfo.cpp @@ -0,0 +1,1131 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ + + +#include "PackageInfo.h" + +#include +#include +#include +#include +#include + + +// Macro reserved for later localization +#define T(x) x + +const uint32 kSkipOffset = 33; + +// Section constants +enum { + P_GROUPS_SECTION = 0, + P_PATH_SECTION, + P_USER_PATH_SECTION, + P_LICENSE_SECTION +}; + + +// Element constants +enum { + P_NONE = 0, + P_FILE, + P_DIRECTORY, + P_LINK +}; + + +PackageInfo::PackageInfo() + : + fStatus(B_NO_INIT), + fPackageFile(0), + fDescription(T("No package available.")), + fProfiles(2), + fHasImage(false) +{ +} + + +PackageInfo::PackageInfo(const entry_ref *ref) + : + fStatus(B_NO_INIT), + fPackageFile(new BFile(ref, B_READ_ONLY)), + fDescription(T("No package selected.")), + fProfiles(2), + fHasImage(false) +{ + fStatus = Parse(); +} + + +PackageInfo::~PackageInfo() +{ + pkg_profile *iter = 0; + while (1) { + iter = static_cast(fProfiles.RemoveItem((long int)0)); + if (iter) + delete iter; + else + break; + } + + PkgItem *file = 0; + while (1) { + file = static_cast(fFiles.RemoveItem((long int)0)); + if (file) + delete file; + else + break; + } + + if (fPackageFile) + delete fPackageFile; +} + + +status_t +PackageInfo::Parse() +{ + // TODO: Clean up + if (!fPackageFile || fPackageFile->InitCheck() != B_OK) { + fStatus = B_ERROR; + return fStatus; + } + + // Check for the presence of the first AlB tag - as the 'magic number'. + // This also ensures that the file header section is present - which + // is a crucial pkg section + char buffer[16]; + fPackageFile->Read(buffer, 8); + if (buffer[0] != 'A' || buffer[1] != 'l' || buffer[2] != 'B' + || buffer[3] != 0x1a) { + fStatus = B_ERROR; + return fStatus; + } + + fHasImage = false; + + // Parse all known parts of the given .pkg file + + uint32 i; + int8 bytesRead; + off_t actualSize = 0; + fPackageFile->GetSize(&actualSize); + uint64 fileSize = 0; + + const char padding[7] = { 0, 0, 0, 0, 0, 0, 0 }; + + system_info sysinfo; + get_system_info(&sysinfo); + + uint64 infoOffset = 0, groupsOffset = 0; + uint64 length = 0; + + // Parse the file header + while (1) { + bytesRead = fPackageFile->Read(buffer, 7); + if (bytesRead != 7) { + fStatus = B_ERROR; + return fStatus; + } + + if (!memcmp(buffer, "PhIn", 5)) { + } + else if (!memcmp(buffer, "FVer", 5)) { + // Not used right now + fPackageFile->Seek(4, SEEK_CUR); + parser_debug("FVer\n"); + } + else if (!memcmp(buffer, "AFla", 5)) { + // Not used right now TODO: Check what this tag is for + fPackageFile->Seek(8, SEEK_CUR); + parser_debug("AFla\n"); + } + else if (!memcmp(buffer, "FSiz", 5)) { + fPackageFile->Read(&fileSize, 8); + swap_data(B_UINT64_TYPE, &fileSize, sizeof(uint64), + B_SWAP_BENDIAN_TO_HOST); + parser_debug("FSiz %llu\n", fileSize); + } + else if (!memcmp(buffer, "COff", 5)) { + fPackageFile->Read(&infoOffset, 8); + swap_data(B_UINT64_TYPE, &infoOffset, sizeof(uint64), + B_SWAP_BENDIAN_TO_HOST); + parser_debug("COff %llu\n", infoOffset); + } + else if (!memcmp(buffer, "AOff", 5)) { + fPackageFile->Read(&groupsOffset, 8); + swap_data(B_UINT64_TYPE, &groupsOffset, sizeof(uint64), + B_SWAP_BENDIAN_TO_HOST); + parser_debug("AOff %llu\n", groupsOffset); + } + else if (!memcmp(buffer, padding, 7)) { + // This means the end of this section - we should move to the + // groups section. + if (groupsOffset) { + fPackageFile->Seek(groupsOffset, SEEK_SET); + } + parser_debug("End!\n"); + break; + } + else { + fStatus = B_ERROR; + return fStatus; + } + } + + fPackageFile->Read(buffer, 7); + if (memcmp(buffer, "PkgA", 5) || !groupsOffset || !infoOffset) { + fStatus = B_ERROR; + return fStatus; + } + + // Section header identifying constant byte sequences: + const char groupsMarker[7] = { 0, 0, 0, 1, 0, 0, 4 }; + const char idMarker[7] = { 0, 0, 0, 2, 0, 0, 4 }; + const char pathMarker[7] = { 0, 0, 0, 3, 0, 0, 4 }; + const char upathMarker[7] = { 0, 0, 0, 4, 0, 0, 4 }; + const char licenseMarker[7] = { 0, 0, 0, 18, 0, 0, 4 }; + const char descMarker[7] = { 0, 0, 0, 5, 0, 0, 2 }; + const char helpMarker[7] = { 0, 0, 0, 10, 0, 0, 3 }; + + const char splashScreenMarker[7] = { 0, 0, 0, 8, 0, 0, 3 }; + const char disclaimerMarker[7] = { 0, 0, 0, 7, 0, 0, 3 }; + + const char nameMarker[7] = { 0, 0, 0, 13, 0, 0, 2 }; + const char versionMarker[7] = { 0, 0, 0, 14, 0, 0, 2 }; + const char devMarker[7] = { 0, 0, 0, 15, 0, 0, 2 }; + const char shortDescMarker[7] = { 0, 0, 0, 17, 0, 0, 2 }; + + int8 section = P_GROUPS_SECTION, installDirectoryFlag = 0; + + pkg_profile group; + BList groups(3), userPaths(3), systemPaths(10); + bool groupStarted = false; + parser_debug("Package Info reached!\n"); + // TODO: Maybe checking whether the needed number of bytes are read + // everytime would be a good idea + + // Parse the package info section + while (1) { + bytesRead = fPackageFile->Read(buffer, 7); + if (bytesRead != 7) { + parser_debug("EOF!\n"); + break; + } + + if (!memcmp(buffer, groupsMarker, 7)) { + section = P_GROUPS_SECTION; + parser_debug("Got to Groups section\n"); + continue; + } + else if (!memcmp(buffer, pathMarker, 7)) { + section = P_PATH_SECTION; + parser_debug("Got to System Paths\n"); + continue; + } + else if (!memcmp(buffer, upathMarker, 7)) { + section = P_USER_PATH_SECTION; + parser_debug("Got to User Paths\n"); + continue; + } + else if (!memcmp(buffer, licenseMarker, 7)) { + section = P_LICENSE_SECTION; + parser_debug("Got to License\n"); + continue; + } // After this, non sectioned tags follow + else if (!memcmp(buffer, disclaimerMarker, 7)) { + uint64 length; + fPackageFile->Read(&length, 8); + swap_data(B_UINT64_TYPE, &length, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST); + + uint64 original; + if (fPackageFile->Read(&original, 8) != 8) { + fStatus = B_ERROR; + return fStatus; + } + swap_data(B_UINT64_TYPE, &original, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST); + + fPackageFile->Seek(4, SEEK_CUR); + + uint8 *compressed = new uint8[length]; + if (fPackageFile->Read(compressed, length) != static_cast(length)) { + fStatus = B_ERROR; + delete compressed; + return fStatus; + } + + uint8 *disclaimer = new uint8[original + 1]; + status_t ret = inflate_data(compressed, length, disclaimer, original); + disclaimer[original] = 0; + delete compressed; + if (ret != B_OK) { + fStatus = B_ERROR; + delete disclaimer; + return ret; + } + + fDisclaimer = (char *)disclaimer; + delete disclaimer; + + continue; + } + else if (!memcmp(buffer, splashScreenMarker, 7)) { + uint64 length; + fPackageFile->Read(&length, 8); + swap_data(B_UINT64_TYPE, &length, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST); + + uint64 original; + if (fPackageFile->Read(&original, 8) != 8) { + fStatus = B_ERROR; + return fStatus; + } + swap_data(B_UINT64_TYPE, &original, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST); + + fPackageFile->Seek(4, SEEK_CUR); + + uint8 *compressed = new uint8[length]; + if (fPackageFile->Read(compressed, length) != static_cast(length)) { + fStatus = B_ERROR; + delete compressed; + return fStatus; + } + + fImage.SetSize(original); + status_t ret = inflate_data(compressed, length, + static_cast(const_cast(fImage.Buffer())), original); + delete compressed; + if (ret != B_OK) { + fStatus = B_ERROR; + return ret; + } + fHasImage = true; + continue; + } + + switch (section) { + case P_PATH_SECTION: + { + if (!memcmp(buffer, "DPat", 5)) { + parser_debug("DPat\n"); + continue; + } + else if (!memcmp(buffer, "FDst", 5)) { + parser_debug("FDst - "); + directory_which dir; + if (fPackageFile->Read(&dir, 4) != 4) { + fStatus = B_ERROR; + return fStatus; + } + swap_data(B_UINT32_TYPE, &dir, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + BPath *path = new BPath(); + status_t ret = find_directory(dir, path); + if (ret != B_OK) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("%s\n", path->Path()); + + systemPaths.AddItem(path); + } + else if (!memcmp(buffer, "PaNa", 5)) { + parser_debug("PaNa\n"); + if (fPackageFile->Read(&length, 4) != 4) { + fStatus = B_ERROR; + return fStatus; + } + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + // Since its a default, system path, we can ignore the path name + // - all information needed is beside the FDst tag. + fPackageFile->Seek(length, SEEK_CUR); + } + else if (!memcmp(buffer, padding, 7)) { + parser_debug("Padding!\n"); + continue; + } + else { + fStatus = B_ERROR; + return fStatus; + } + break; + } + + case P_GROUPS_SECTION: + { + if (!memcmp(buffer, "IGrp", 5)) { + // Creata a new group + groupStarted = true; + group = pkg_profile(); + parser_debug("IGrp\n"); + } + else if (!memcmp(buffer, "GrpN", 5)) { + if (!groupStarted) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("GrpN\n"); + fPackageFile->Read(&length, 4); + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + + char *name = new char[length + 1]; + fPackageFile->Read(name, length); + name[length] = 0; + group.name = name; + delete name; + } + else if (!memcmp(buffer, "GrpD", 5)) { + if (!groupStarted) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("GrpD\n"); + fPackageFile->Read(&length, 4); + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + + char *desc = new char[length + 1]; + fPackageFile->Read(desc, length); + desc[length] = 0; + group.description = desc; + delete desc; + } + else if (!memcmp(buffer, "GrHt", 5)) { + if (!groupStarted) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("GrHt\n"); + // For now, we don't need group help + fPackageFile->Read(&length, 4); + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + fPackageFile->Seek(length, SEEK_CUR); + } + else if (!memcmp(buffer, padding, 5)) { + if (!groupStarted) { + parser_debug("No group - padding!\n"); + continue; + } + + fProfiles.AddItem(new pkg_profile(group)); + parser_debug("Group added: %s %s\n", group.name.String(), + group.description.String()); + + groupStarted = false; + } + else if (!memcmp(buffer, "GrId", 5)) { + uint32 id; + fPackageFile->Read(&id, 4); + swap_data(B_UINT32_TYPE, &id, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + + parser_debug("GrId\n"); + + if (id == 0xffffffff) + groups.AddItem(NULL); + else + groups.AddItem(fProfiles.ItemAt(id)); + } + else if (!memcmp(buffer, idMarker, 7) || + !memcmp(buffer, groupsMarker, 7)) { + parser_debug("Marker, jumping!\n"); + continue; + } + else { + fStatus = B_ERROR; + return fStatus; + } + break; + } + + case P_LICENSE_SECTION: + { + if (!memcmp(buffer, "Lic?", 5)) { + parser_debug("Lic?\n"); + // This tag informs whether a license is present in the package + // or not. Since we don't care about licenses right now, just + // skip this section + fPackageFile->Seek(4, SEEK_CUR); + } + else if (!memcmp(buffer, "LicP", 5)) { + parser_debug("LicP\n"); + fPackageFile->Read(&length, 4); + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + + fPackageFile->Seek(length, SEEK_CUR); + } + else if (!memcmp(buffer, padding, 7)) { + continue; + } + else if (!memcmp(buffer, descMarker, 7)) { + parser_debug("Description text reached\n"); + fPackageFile->Read(&length, 4); + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + + char *description = new char[length + 1]; + fPackageFile->Read(description, length); + description[length] = 0; + fDescription = description; + + // Truncate all leading newlines + for (i = 0;i < length;i++) + if (fDescription[i] != '\n') + break; + fDescription.Remove(0, i); + + delete description; + parser_debug("Description text reached\n"); + + // After this, there's a known size sequence of bytes, which meaning + // is yet to be determined. + + // One is already known. The byte (or just its least significant bit) + // at offset 21 from the description text is responsible for the + // install folder existence information. If it is 0, there is no + // install folder, if it is 1 (or the least significant bit is set) + // it means we should install all 0xffffffff files/directories to + // the first directory existing in the package + fPackageFile->Seek(21, SEEK_CUR); + if (fPackageFile->Read(&installDirectoryFlag, 1) != 1) { + fStatus = B_ERROR; + return fStatus; + } + + fPackageFile->Seek(11, SEEK_CUR); + } + else if (!memcmp(buffer, nameMarker, 7)) { + parser_debug("Package name reached\n"); + fPackageFile->Read(&length, 4); + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + + char *name = new char[length + 1]; + fPackageFile->Read(name, length); + name[length] = 0; + fName = name; + delete name; + } + else if (!memcmp(buffer, versionMarker, 7)) { + parser_debug("Package version reached\n"); + fPackageFile->Read(&length, 4); + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + + char *version = new char[length + 1]; + fPackageFile->Read(version, length); + version[length] = 0; + fVersion = version; + delete version; + } + else if (!memcmp(buffer, devMarker, 7)) { + parser_debug("Package developer reached\n"); + fPackageFile->Read(&length, 4); + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + + char *dev = new char[length + 1]; + fPackageFile->Read(dev, length); + dev[length] = 0; + fDeveloper = dev; + delete dev; + } + else if (!memcmp(buffer, shortDescMarker, 7)) { + parser_debug("Package short description reached\n"); + fPackageFile->Read(&length, 4); + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + + char *desc = new char[length + 1]; + fPackageFile->Read(desc, length); + desc[length] = 0; + fShortDesc = desc; + delete desc; + } + else if (!memcmp(buffer, helpMarker, 7)) { + // The help text is a stored in deflated state, preceded by a 64 bit + // compressed size, 64 bit inflated size and a 32 bit integer + // Since there was no discussion whether we need this help text, + // it will be skipped + parser_debug("Help text reached\n"); + //uint64 length64; + fPackageFile->Read(&length, 8); + swap_data(B_UINT64_TYPE, &length, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST); + + fPackageFile->Seek(12 + length, SEEK_CUR); + } + break; + } + + case P_USER_PATH_SECTION: + { + if (!memcmp(buffer, "DPat", 5)) { + parser_debug("DPat\n"); + continue; + } + else if (!memcmp(buffer, "PaNa", 5)) { + parser_debug("PaNa\n"); + fPackageFile->Read(&length, 4); + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + + char *pathname = new char[length + 1]; + fPackageFile->Read(pathname, length); + pathname[length] = 0; + BString *path = new BString(pathname); + userPaths.AddItem(path); + delete pathname; + } + else if (!memcmp(buffer, padding, 7)) { + parser_debug("Padding!\n"); + continue; + } + else { + fStatus = B_ERROR; + return fStatus; + } + break; + } + } + } + + BString nameString, mimeString, signatureString, linkString; + BString itemPath = "", installDirectory = ""; + uint32 directoryCount = 0; + + uint8 element = P_NONE; + uint32 itemGroups = 0, path = 0, cust = 0, ctime = 0, mtime = 0, + platform = 0xffffffff; + uint64 offset = 0, size = 0, originalSize = 0, mode = 0; + uint8 pathType = P_INSTALL_PATH; + + status_t ret; + + fPackageFile->Seek(infoOffset, SEEK_SET); + + // Parse package file data + while (1) { + bytesRead = fPackageFile->Read(buffer, 7); + if (bytesRead != 7) { + fStatus = B_ERROR; + return fStatus; + } + + // TODO: Here's the deal... there seems to be a strange ScrI tag that + // seems to mean script files (check this). It seems exaclty the same + // as a normal file (just as script files are normal files) so for + // now I'm treating those as files. Check if it's correct! + // No, it isn't and I will fix this soon. + if (!memcmp(buffer, "FilI", 5) || !memcmp(buffer, "ScrI", 5)) { + parser_debug("FilI\n"); + element = P_FILE; + + mimeString = ""; + nameString = ""; + signatureString = ""; + + itemGroups = 0; + ctime = 0; + mtime = 0; + offset = 0; + itemGroups = 0; + cust = 0; + mode = 0; + platform = 0xffffffff; + + size = 0; + originalSize = 0; + } + else if (!memcmp(buffer, "FldI", 5)) { + parser_debug("FldI\n"); + element = P_DIRECTORY; + + nameString = ""; + + itemGroups = 0; + ctime = 0; + mtime = 0; + offset = 0; + itemGroups = 0; + cust = 0; + platform = 0xffffffff; + + size = 0; + originalSize = 0; + } + else if (!memcmp(buffer, "LnkI", 5)) { + parser_debug("LnkI\n"); + element = P_LINK; + + nameString = ""; + linkString = ""; + + itemGroups = 0; + ctime = 0; + mtime = 0; + offset = 0; + itemGroups = 0; + cust = 0; + platform = 0xffffffff; + + size = 0; + originalSize = 0; + } + else if (!memcmp(buffer, "Name", 5)) { + if (element == P_NONE) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("Name\n"); + fPackageFile->Read(&length, 4); + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + + char *name = new char[length + 1]; + fPackageFile->Read(name, length); + name[length] = 0; + + nameString = name; + delete name; + } + else if (!memcmp(buffer, "Grps", 5)) { + if (element == P_NONE) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("Grps\n"); + fPackageFile->Read(&itemGroups, 4); + swap_data(B_UINT32_TYPE, &itemGroups, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + } + else if (!memcmp(buffer, "Dest", 5)) { + if (element == P_NONE) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("Dest\n"); + fPackageFile->Read(&path, 4); + swap_data(B_UINT32_TYPE, &path, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + } + else if (!memcmp(buffer, "Cust", 5)) { + if (element == P_NONE) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("Cust\n"); + fPackageFile->Read(&cust, 4); + swap_data(B_UINT32_TYPE, &cust, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + } + else if (!memcmp(buffer, "Repl", 5)) { + if (element == P_NONE) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("Repl\n"); + fPackageFile->Seek(4, SEEK_CUR); + // TODO: Should the replace philosophy depend on this flag? For now + // I always leave the decision to the user + } + else if (!memcmp(buffer, "Plat", 5)) { + if (element == P_NONE) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("Plat\n"); + fPackageFile->Read(&platform, 4); + swap_data(B_UINT32_TYPE, &platform, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + } + else if (!memcmp(buffer, "CTim", 5)) { + if (element == P_NONE) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("CTim\n"); + fPackageFile->Read(&ctime, 4); + swap_data(B_UINT32_TYPE, &ctime, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + } + else if (!memcmp(buffer, "MTim", 5)) { + if (element == P_NONE) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("MTim\n"); + fPackageFile->Read(&mtime, 4); + swap_data(B_UINT32_TYPE, &mtime, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + } + else if (!memcmp(buffer, "OffT", 5)) { + if (element == P_NONE) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("OffT\n"); + fPackageFile->Read(&offset, 8); + swap_data(B_UINT64_TYPE, &offset, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST); + } + else if (!memcmp(buffer, "Mime", 5)) { + if (element != P_FILE) { + fStatus = B_ERROR; + return fStatus; + } + + fPackageFile->Read(&length, 4); + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + + char *mime = new char[length + 1]; + fPackageFile->Read(mime, length); + mime[length] = 0; + parser_debug("Mime: %s\n", mime); + + mimeString = mime; + delete mime; + } + else if (!memcmp(buffer, "CmpS", 5)) { + if (element == P_NONE) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("CmpS\n"); + fPackageFile->Read(&size, 8); + swap_data(B_UINT64_TYPE, &size, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST); + } + else if (!memcmp(buffer, "OrgS", 5)) { + if (element != P_FILE && element != P_LINK) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("OrgS\n"); + fPackageFile->Read(&originalSize, 8); + swap_data(B_UINT64_TYPE, &originalSize, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST); + } + else if (!memcmp(buffer, "VrsI", 5)) { + if (element != P_FILE) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("VrsI\n"); + fPackageFile->Seek(24, SEEK_CUR); + // TODO + // Also, check what those empty 20 bytes mean + } + else if (!memcmp(buffer, "Mode", 5)) { + if (element != P_FILE && element != P_LINK) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("Mode\n"); + fPackageFile->Read(&mode, 4); + swap_data(B_UINT32_TYPE, &mode, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + } + else if (!memcmp(buffer, "FDat", 5)) { + if (element != P_DIRECTORY) { + fStatus = B_ERROR; + return fStatus; + } + + parser_debug("FDat\n"); + } + else if (!memcmp(buffer, "ASig", 5)) { + if (element != P_FILE) { + fStatus = B_ERROR; + return fStatus; + } + + fPackageFile->Read(&length, 4); + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + + char *signature = new char[length + 1]; + fPackageFile->Read(signature, length); + signature[length] = 0; + parser_debug("Signature: %s\n", signature); + + signatureString = signature; + delete signature; + } + else if (!memcmp(buffer, "Link", 5)) { + if (element != P_LINK) { + fStatus = B_ERROR; + return fStatus; + } + + fPackageFile->Read(&length, 4); + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + + char *link = new char[length + 1]; + fPackageFile->Read(link, length); + link[length] = 0; + parser_debug("Link: %s\n", link); + + linkString = link; + delete link; + } + else if (!memcmp(buffer, padding, 7)) { + PkgItem *item = 0; + + parser_debug("Padding!\n"); + if (platform != 0xffffffff && + static_cast(platform) != sysinfo.platform_type) { + // If the file/directory/item's platform is different than the + // target platform (or different than the 'any' constant), ignore + // this file + } + else if (element == P_FILE) { + if (itemGroups && offset && size) { + BString dest = ""; + uint8 localType = pathType; + + if (path == 0xfffffffe) + dest << itemPath << "/" << nameString.String(); + else if (path == 0xffffffff) { + localType = P_INSTALL_PATH; + dest = installDirectory; + dest << nameString; + } + else { + if (cust) { + BString *def = static_cast(userPaths.ItemAt(path)); + if (!def) { + fStatus = B_ERROR; + return fStatus; + } + if ((*def)[0] == '/') + localType = P_SYSTEM_PATH; + else + localType = P_USER_PATH; + + dest << *def << "/" << nameString; + } + else { + BPath *def = static_cast(systemPaths.ItemAt(path)); + if (!def) { + fStatus = B_ERROR; + return fStatus; + } + localType = P_SYSTEM_PATH; + + dest << def->Path() << "/" << nameString; + } + } + + item = new PkgFile(fPackageFile, dest, localType, ctime, mtime, + offset, size, originalSize, 0, mimeString, signatureString, mode); + parser_debug("Adding file: %s!\n", dest.String()); + } + } + else if (element == P_DIRECTORY) { + if (itemGroups) { + if (installDirectoryFlag != 0) { + if (installDirectoryFlag < 0) { // Normal directory + if (path == 0xfffffffe) { // Install to current directory + itemPath << "/" << nameString.String(); + directoryCount++; + } + else if (path == 0xffffffff) { // Install to install directory + pathType = P_INSTALL_PATH; + itemPath = installDirectory; + itemPath << nameString; + directoryCount = 1; + } + else { // Install to defined directory + if (cust) { + BString *def = static_cast(userPaths.ItemAt(path)); + if (!def) { + fStatus = B_ERROR; + return fStatus; + } + if ((*def)[0] == '/') + pathType = P_SYSTEM_PATH; + else + pathType = P_USER_PATH; + + itemPath = *def; + } + else { + BPath *def = static_cast(systemPaths.ItemAt(path)); + if (!def) { + fStatus = B_ERROR; + return fStatus; + } + pathType = P_SYSTEM_PATH; + + itemPath = def->Path(); + } + + itemPath << "/" << nameString; + directoryCount = 1; + } + } + else { // Install directory + if (path != 0xffffffff) { + fStatus = B_ERROR; + return fStatus; + } + + installDirectory = nameString; + installDirectory << "/"; + pathType = P_INSTALL_PATH; + itemPath = nameString; + + installDirectoryFlag = -1; + } + + parser_debug("Adding the directory %s!\n", itemPath.String()); + item = new PkgDirectory(fPackageFile, itemPath, pathType, ctime, + mtime, offset, size); + } + else { + installDirectoryFlag = -1; + } + } + } + else if (element == P_LINK) { + if (itemGroups && linkString.Length()) { + BString dest = ""; + uint8 localType = pathType; + + if (path == 0xfffffffe) + dest << itemPath << "/" << nameString.String(); + else if (path == 0xffffffff) { + localType = P_INSTALL_PATH; + dest = installDirectory; + dest << nameString; + } + else { + if (cust) { + BString *def = static_cast(userPaths.ItemAt(path)); + if (!def) { + fStatus = B_ERROR; + return fStatus; + } + if ((*def)[0] == '/') + localType = P_SYSTEM_PATH; + else + localType = P_USER_PATH; + + dest << *def << "/" << nameString; + } + else { + BPath *def = static_cast(systemPaths.ItemAt(path)); + if (!def) { + fStatus = B_ERROR; + return fStatus; + } + localType = P_SYSTEM_PATH; + + dest << def->Path() << "/" << nameString; + } + } + + parser_debug("Adding link: %s!\n", dest.String()); + item = new PkgLink(fPackageFile, dest, linkString, pathType, + ctime, mtime, mode, offset, size); + } + } + else { + // If the directory tree count is equal to zero, this means all + // directory trees have been closed and a padding sequence means the + // end of the section + if (directoryCount == 0) + break; + ret = itemPath.FindLast('/'); + if (ret == B_ERROR) { + itemPath = ""; + } + else { + itemPath.Truncate(ret); + } + directoryCount--; + } + + if (item) { + _AddItem(item, originalSize, itemGroups, path, cust); + } + + element = P_NONE; + } + else { + fStatus = B_ERROR; + return fStatus; + } + } + + if (static_cast(actualSize) != fileSize) { + // Inform the user of a possible error + int32 selection; + BAlert *warning = new BAlert(T("filesize_wrong"), + T("There seems to be a filesize mismatch in the package file. " + "The package might be corrupted or have been modified after its " + "creation. Do you still wish to continue?"), T("Yes"), T("No"), NULL, + B_WIDTH_AS_USUAL, B_WARNING_ALERT); + selection = warning->Go(); + + if (selection == 1) { + fStatus = B_ERROR; + return fStatus; + } + } + + if (!groups.IsEmpty()) + fProfiles = groups; + + return B_OK; +} + + +void +PackageInfo::_AddItem(PkgItem *item, uint64 size, uint32 groups, uint32 path, + uint32 cust) +{ + // Add the item to all groups it resides in + uint32 i, n = fProfiles.CountItems(), mask = 1; + pkg_profile *profile; + + for (i = 0;i < n;i++) { + if (groups & mask) { + profile = static_cast(fProfiles.ItemAt(i)); + profile->items.AddItem(item); + profile->space_needed += size; + // If there is at least one non-predefined destination element + // in the package, we give the user the ability to select the + // installation directory. + // If there are only predefined path files in the package, but + // such defined by the user, the user will be able to select + // the destination volume + if (path == 0xffffffff) + profile->path_type = P_INSTALL_PATH; + else if (path < 0xfffffffe && + profile->path_type != P_INSTALL_PATH) { + if (cust) { + profile->path_type = P_USER_PATH; + } + } + } + mask = mask << 1; + } +} + + +// #pragma mark - + + +pkg_profile::~pkg_profile() +{ + PkgItem *iter = 0; + while (1) { + iter = static_cast(items.RemoveItem((long int)0)); + if (iter) + delete iter; + else + break; + } +} + diff --git a/src/apps/packageinstaller/PackageInfo.h b/src/apps/packageinstaller/PackageInfo.h new file mode 100644 index 0000000000..537ec9aa3a --- /dev/null +++ b/src/apps/packageinstaller/PackageInfo.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ +#ifndef PACKAGEINFO_H +#define PACKAGEINFO_H + +#include "PackageItem.h" +#include +#include +#include +#include +#include + + +struct pkg_profile; + + +class PackageInfo { + public: + PackageInfo(); + PackageInfo(const entry_ref *ref); + ~PackageInfo(); + + const char *GetName() { return fName.String(); } + const char *GetDescription() { return fDescription.String(); } + const char *GetShortDescription() { return fShortDesc.String(); } + const char *GetVersion() { return fVersion.String(); } + const char *GetDisclaimer() { return fDisclaimer.String(); } + BMallocIO *GetSplashScreen() { return fHasImage ? &fImage : NULL; } + int32 GetProfileCount() { return fProfiles.CountItems(); } + pkg_profile *GetProfile(int32 num) { return static_cast(fProfiles.ItemAt(num)); } + + status_t Parse(); + status_t InitCheck() { return fStatus; } + + private: + void _AddItem(PkgItem *item, uint64 size, uint32 groups, uint32 path, + uint32 cust); + + status_t fStatus; + + BFile *fPackageFile; + BString fName; + BString fDescription; + BList fProfiles; + + BString fShortDesc; + BString fDeveloper; + BString fVersion; + BString fDisclaimer; + BMallocIO fImage; + bool fHasImage; + + BList fFiles; // Holds all files in the package +}; + + +// #pragma mark - + + +struct pkg_profile { + pkg_profile() : items(10), space_needed(0), path_type(P_SYSTEM_PATH) {} + ~pkg_profile(); + + BString name; + BString description; + BList items; + uint64 space_needed; + + uint8 path_type; +}; + +#endif + diff --git a/src/apps/packageinstaller/PackageInstaller.rdef b/src/apps/packageinstaller/PackageInstaller.rdef new file mode 100644 index 0000000000..b7682377c5 --- /dev/null +++ b/src/apps/packageinstaller/PackageInstaller.rdef @@ -0,0 +1,22 @@ + +resource app_signature "application/x-vnd.Haiku-PackageInstaller"; + +resource file_types message { + "types" = "application/x-scode-UPkg" +}; + +resource app_version { + major = 0, + middle = 2, + minor = 0, + + variety = B_APPV_ALPHA, + + internal = 0, + + short_info = "Package Installer", + long_info = "Package Installer ©2007 Haiku, Inc." +}; + +resource app_flags B_SINGLE_LAUNCH; + diff --git a/src/apps/packageinstaller/PackageItem.cpp b/src/apps/packageinstaller/PackageItem.cpp new file mode 100644 index 0000000000..0a2c46ed1c --- /dev/null +++ b/src/apps/packageinstaller/PackageItem.cpp @@ -0,0 +1,697 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ + + +#include "PackageItem.h" + +#include +#include +#include +#include +#include +#include + +#include +#include "zlib.h" + +// Macro reserved for later localization +#define T(x) x + +enum { + P_CHUNK_SIZE = 256 +}; + +static const uint32 kDefaultMode = 0777; +static const uint8 padding[7] = { 0, 0, 0, 0, 0, 0, 0 }; + +enum { + P_DATA = 0, + P_ATTRIBUTE +}; + + +status_t +inflate_data(uint8 *in, uint32 in_size, uint8 *out, uint32 out_size) +{ + z_stream stream; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + stream.avail_in = in_size; + stream.next_in = in; + status_t ret; + + ret = inflateInit(&stream); + if (ret != Z_OK) + return B_ERROR; + + stream.avail_out = out_size; + stream.next_out = out; + + ret = inflate(&stream, Z_NO_FLUSH); + if (ret != Z_STREAM_END) { + parser_debug("Left: %d\n", stream.avail_out); + return B_ERROR; // Uncompressed file size in package info corrupted + } + + (void)inflateEnd(&stream); + + return B_OK; +} + + +static inline int +inflate_file_to_file(BFile *in, uint64 in_size, BFile *out, uint64 out_size) +{ + z_stream stream; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + stream.avail_in = 0; + stream.next_in = Z_NULL; + status_t ret; + + uint8 buffer_out[P_CHUNK_SIZE], buffer_in[P_CHUNK_SIZE]; + uint64 bytes_read = 0, read = P_CHUNK_SIZE, write = 0; + + ret = inflateInit(&stream); + if (ret != Z_OK) + return B_ERROR; + + do { + bytes_read += P_CHUNK_SIZE; + if (bytes_read > in_size) { + read = in_size - (bytes_read - P_CHUNK_SIZE); + bytes_read = in_size; + } + + stream.avail_in = in->Read(buffer_in, read); + if (stream.avail_in != read) { + (void)inflateEnd(&stream); + return B_ERROR; + } + stream.next_in = buffer_in; + + do { + stream.avail_out = P_CHUNK_SIZE; + stream.next_out = buffer_out; + + ret = inflate(&stream, Z_NO_FLUSH); + if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) { + (void)inflateEnd(&stream); + return B_ERROR; + } + + write = P_CHUNK_SIZE - stream.avail_out; + if (static_cast(out->Write(buffer_out, write)) != write) { + (void)inflateEnd(&stream); + return B_ERROR; + } + } + while (stream.avail_out == 0); + } + while (bytes_read != in_size); + + (void)inflateEnd(&stream); + + return B_OK; +} + + +// #pragma mark - + + +PkgDirectory::PkgDirectory(BFile *parent, BString path, uint8 type, uint32 ctime, + uint32 mtime, uint64 offset, uint64 size) + : + fPath(path), + fOffset(offset), + fSize(size), + fPathType(type), + fCreationTime(ctime), + fModificationTime(mtime), + fPackage(parent) +{ +} + + +void +PkgDirectory::SetTo(BFile *parent, BString path, uint8 type, uint32 ctime, + uint32 mtime, uint64 offset, uint64 size) +{ + fPackage = parent; + fPath = path; + + fOffset = offset; + fSize = size; + fPathType = type; + fCreationTime = ctime; + fModificationTime = mtime; +} + + +PkgDirectory::~PkgDirectory() +{ +} + + +status_t +PkgDirectory::WriteToPath(const char *path, BPath *final) +{ + BPath destination; + status_t ret; + parser_debug("Directory: %s WriteToPath() called!\n", fPath.String()); + + ret = _InitPath(path, &destination); + if (ret != B_OK) + return ret; + + // Since Haiku is single-user right now, we give the newly + // created directory default permissions + ret = create_directory(destination.Path(), kDefaultMode); + if (ret != B_OK) + return ret; + BDirectory dir(destination.Path()); + parser_debug("Directory created!\n"); + + if (fCreationTime) + dir.SetCreationTime(static_cast(fCreationTime)); + + if (fModificationTime) + dir.SetModificationTime(static_cast(fModificationTime)); + + // Since directories can only have attributes in the offset section, + // we can check here whether it is necessary to continue + if (fOffset) { + ret = _HandleAttributes(&destination, &dir, "FoDa"); + } + + if (final) { + *final = destination; + } + + return ret; +} + + +int32 +PkgDirectory::_ItemExists(const char *name) +{ + BString alertString = T("The file named"); + alertString << " \'" << name << "\' "; + alertString << T("already exists in the given path. Should I replace " + "the existing file with the one from this package?"); + + BAlert *alert = new BAlert(T("file_exists"), alertString.String(), + T("Yes"), T("No"), T("Abort")); + + return alert->Go(); +} + + +status_t +PkgDirectory::_InitPath(const char *path, BPath *destination) +{ + status_t ret = B_OK; + + if (fPathType == P_INSTALL_PATH) { + if (!path) + return B_ERROR; + ret = destination->SetTo(path, fPath.String()); + } + else if (fPathType == P_SYSTEM_PATH) + ret = destination->SetTo(fPath.String()); + else { + if (!path) + return B_ERROR; + + BVolume volume(dev_for_path(path)); + ret = volume.InitCheck(); + if (ret != B_OK) + return ret; + + BDirectory temp; + ret = volume.GetRootDirectory(&temp); + if (ret != B_OK) + return ret; + + BPath mountPoint(&temp, NULL); + ret = destination->SetTo(mountPoint.Path(), fPath.String()); + } + + return ret; +} + + +status_t +PkgDirectory::_HandleAttributes(BPath *destination, BNode *node, + const char *header) +{ + status_t ret = B_OK; + + BVolume volume(dev_for_path(destination->Path())); + if (volume.KnowsAttr()) { + parser_debug("We have an offset\n"); + if (!fPackage) + return B_ERROR; + + ret = fPackage->InitCheck(); + if (ret != B_OK) + return ret; + + // We need to parse the data section now + fPackage->Seek(fOffset, SEEK_SET); + uint8 buffer[7]; + if (fPackage->Read(buffer, 7) != 7 || memcmp(buffer, header, 5)) + return B_ERROR; + parser_debug("Header validated!\n"); + + char *attrName = 0; + uint32 nameSize = 0; + uint8 *attrData = new uint8[P_CHUNK_SIZE]; + uint64 dataSize = P_CHUNK_SIZE; + uint8 *temp = new uint8[P_CHUNK_SIZE]; + uint64 tempSize = P_CHUNK_SIZE; + + uint64 attrCSize = 0, attrOSize = 0; + uint32 attrType = 0; // type_code type + bool attrStarted = false, done = false; + + while (fPackage->Read(buffer, 7) == 7) { + if (!memcmp(buffer, "FBeA", 5)) + continue; + + ret = _ParseAttribute(buffer, node, &attrName, &nameSize, &attrType, + &attrData, &dataSize, &temp, &tempSize, &attrCSize, &attrOSize, + &attrStarted, &done); + if (ret != B_OK || done) + break; + } + + if (attrData) + delete attrData; + if (temp) + delete temp; + } + + return ret; +} + + +inline status_t +PkgDirectory::_ParseAttribute(uint8 *buffer, BNode *node, char **attrName, + uint32 *nameSize, uint32 *attrType, uint8 **attrData, uint64 *dataSize, + uint8 **temp, uint64 *tempSize, uint64 *attrCSize, uint64 *attrOSize, + bool *attrStarted, bool *done) +{ + status_t ret = B_OK; + uint32 length; + + if (!memcmp(buffer, "BeAI", 5)) { + parser_debug(" Attribute started.\n"); + if (*attrName) + *attrName[0] = 0; + *attrCSize = 0; + *attrOSize = 0; + + *attrStarted = true; + } + else if (!memcmp(buffer, "BeAN", 5)) { + if (!*attrStarted) { + ret = B_ERROR; + return ret; + } + + parser_debug(" BeAN.\n"); + fPackage->Read(&length, 4); + swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST); + + if (*nameSize < (length + 1)) { + delete *attrName; + *nameSize = length + 1; + *attrName = new char[*nameSize]; + } + fPackage->Read(*attrName, length); + (*attrName)[length] = 0; + + parser_debug(" (%d) = %s\n", length, *attrName); + } + else if (!memcmp(buffer, "BeAT", 5)) { + if (!*attrStarted) { + ret = B_ERROR; + return ret; + } + + parser_debug(" BeAT.\n"); + fPackage->Read(attrType, 4); + swap_data(B_UINT32_TYPE, attrType, sizeof(*attrType), + B_SWAP_BENDIAN_TO_HOST); + } + else if (!memcmp(buffer, "BeAD", 5)) { + if (!*attrStarted) { + ret = B_ERROR; + return ret; + } + + parser_debug(" BeAD.\n"); + fPackage->Read(attrCSize, 8); + swap_data(B_UINT64_TYPE, attrCSize, sizeof(*attrCSize), + B_SWAP_BENDIAN_TO_HOST); + + fPackage->Read(attrOSize, 8); + swap_data(B_UINT64_TYPE, attrOSize, sizeof(*attrOSize), + B_SWAP_BENDIAN_TO_HOST); + + fPackage->Seek(4, SEEK_CUR); // TODO: Check what this means + + if (*tempSize < *attrCSize) { + delete *temp; + *tempSize = *attrCSize; + *temp = new uint8[*tempSize]; + } + if (*dataSize < *attrOSize) { + delete *attrData; + *dataSize = *attrOSize; + *attrData = new uint8[*dataSize]; + } + + if (fPackage->Read(*temp, *attrCSize) + != static_cast(*attrCSize)) { + ret = B_ERROR; + return ret; + } + + parser_debug(" Data read successfuly. Inflating!\n"); + ret = inflate_data(*temp, *tempSize, *attrData, *dataSize); + if (ret != B_OK) + return ret; + } + else if (!memcmp(buffer, padding, 7)) { + if (!*attrStarted) { + *done = true; + return ret; + } + + parser_debug(" Padding.\n"); + ssize_t wrote = node->WriteAttr(*attrName, *attrType, 0, *attrData, + *attrOSize); + if(wrote != static_cast(*attrOSize)) { + ret = B_ERROR; + return ret; + } + + *attrStarted = false; + if (*attrName) + *attrName[0] = 0; + *attrCSize = 0; + *attrOSize = 0; + + parser_debug(" > Attribute added.\n"); + } + else { + ret = B_ERROR; + } + + return ret; +} + + +inline status_t +PkgDirectory::_ParseData(uint8 *buffer, BFile *file, uint64 originalSize, + bool *done) +{ + status_t ret = B_OK; + + if (!memcmp(buffer, "FiMF", 5)) { + parser_debug(" Found file data.\n"); + uint64 compressed, original; + fPackage->Read(&compressed, 8); + swap_data(B_UINT64_TYPE, &compressed, sizeof(uint64), + B_SWAP_BENDIAN_TO_HOST); + + fPackage->Read(&original, 8); + swap_data(B_UINT64_TYPE, &original, sizeof(uint64), + B_SWAP_BENDIAN_TO_HOST); + parser_debug(" Still good... (%llu : %llu)\n", original, + originalSize); + + if (original != originalSize) { + ret = B_ERROR; // File size missmatch + return ret; + } + parser_debug(" Still good...\n"); + + if (fPackage->Read(buffer, 4) != 4) { + ret = B_ERROR; + return ret; + } + parser_debug(" Still good...\n"); + + ret = inflate_file_to_file(fPackage, compressed, file, original); + if (ret != B_OK) + return ret; + parser_debug(" File data inflation complete!\n"); + } + else if (!memcmp(buffer, padding, 7)) { + *done = true; + return ret; + } + else { + ret = B_ERROR; + } + + return ret; +} + + + +PkgFile::PkgFile(BFile *parent, BString path, uint8 type, uint32 ctime, + uint32 mtime, uint64 offset, uint64 size, uint64 originalSize, + uint32 platform, BString mime, BString signature, uint32 mode) + : PkgItem(parent, path, type, ctime, mtime, offset, size), + fOriginalSize(originalSize), + fPlatform(platform), + fMode(mode), + fMimeType(mime), + fSignature(signature) +{ +} + + +PkgFile::~PkgFile() +{ +} + + +status_t +PkgFile::WriteToPath(const char *path, BPath *final) +{ + BPath destination; + status_t ret; + parser_debug("File: %s WriteToPath() called!\n", fPath.String()); + + ret = _InitPath(path, &destination); + if (ret != B_OK) + return ret; + + BFile file(destination.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS); + ret = file.InitCheck(); + if (ret == B_FILE_EXISTS) { + int32 selection = _ItemExists(destination.Leaf()); + switch (selection) { + case 0: + ret = file.SetTo(destination.Path(), B_WRITE_ONLY | B_ERASE_FILE); + if (ret != B_OK) + return ret; + break; + case 1: + return B_OK; + default: + return B_FILE_EXISTS; + } + } + else if (ret == B_ENTRY_NOT_FOUND) { + BPath directory; + destination.GetParent(&directory); + if (create_directory(directory.Path(), kDefaultMode) != B_OK) + return B_ERROR; + + ret = file.SetTo(destination.Path(), B_WRITE_ONLY | B_CREATE_FILE); + if (ret != B_OK) + return ret; + } + else if (ret != B_OK) + return ret; + + parser_debug(" File created!\n"); + + // Set the file permissions, creation and modification times + ret = file.SetPermissions(static_cast(fMode)); + if (fCreationTime) + ret |= file.SetCreationTime(static_cast(fCreationTime)); + if (fModificationTime) + ret |= file.SetModificationTime(static_cast(fModificationTime)); + + if (ret != B_OK) + return ret; + + // Set the mimetype and application signature if present + BNodeInfo info(&file); + if (fMimeType.Length() > 0) { + ret = info.SetType(fMimeType.String()); + if (ret != B_OK) + return ret; + } + if (fSignature.Length() > 0) { + ret = info.SetPreferredApp(fSignature.String()); + if (ret != B_OK) + return ret; + } + + if (fOffset) { + parser_debug("We have an offset\n"); + if (!fPackage) + return B_ERROR; + + ret = fPackage->InitCheck(); + if (ret != B_OK) + return ret; + + // We need to parse the data section now + fPackage->Seek(fOffset, SEEK_SET); + uint8 buffer[7]; + + char *attrName = 0; + uint32 nameSize = 0; + uint8 *attrData = new uint8[P_CHUNK_SIZE]; + uint64 dataSize = P_CHUNK_SIZE; + uint8 *temp = new uint8[P_CHUNK_SIZE]; + uint64 tempSize = P_CHUNK_SIZE; + + uint64 attrCSize = 0, attrOSize = 0; + uint32 attrType = 0; // type_code type + bool attrStarted = false, done = false; + + uint8 section = P_ATTRIBUTE; + + while (fPackage->Read(buffer, 7) == 7) { + if (!memcmp(buffer, "FBeA", 5)) { + parser_debug("-> Attribute\n"); + section = P_ATTRIBUTE; + continue; + } + else if (!memcmp(buffer, "FiDa", 5)) { + parser_debug("-> File data\n"); + section = P_DATA; + continue; + } + + switch (section) { + case P_ATTRIBUTE: + { + ret = _ParseAttribute(buffer, &file, &attrName, &nameSize, &attrType, + &attrData, &dataSize, &temp, &tempSize, &attrCSize, &attrOSize, + &attrStarted, &done); + break; + } + case P_DATA: + { + ret = _ParseData(buffer, &file, fOriginalSize, &done); + break; + } + default: + return B_ERROR; + } + + if (ret != B_OK || done) + break; + } + + if (attrData) + delete attrData; + if (temp) + delete temp; + } + + if (final) { + *final = destination; + } + + return ret; +} + + +PkgLink::PkgLink(BFile *parent, BString path, BString link, uint8 type, + uint32 ctime, uint32 mtime, uint32 mode, uint64 offset, uint64 size) + : PkgItem(parent, path, type, ctime, mtime, offset, size), + fMode(mode), + fLink(link) +{ +} + + +PkgLink::~PkgLink() +{ +} + + +status_t +PkgLink::WriteToPath(const char *path, BPath *final) +{ + BPath destination; + status_t ret; + parser_debug("Symlink: %s WriteToPath() called!\n", fPath.String()); + + ret = _InitPath(path, &destination); + if (ret != B_OK) + return ret; + + BString linkName(destination.Leaf()); + parser_debug("%s:%s:%s\n", fPath.String(), destination.Path(), linkName.String()); + BPath dirPath; + ret = destination.GetParent(&dirPath); + BDirectory dir(dirPath.Path()); + + ret = dir.InitCheck(); + if (ret == B_ENTRY_NOT_FOUND) { + if (create_directory(destination.Path(), kDefaultMode) != B_OK) + return B_ERROR; + } + if (ret != B_OK) + return ret; + + BSymLink symlink; + ret = dir.CreateSymLink(linkName.String(), fLink.String(), &symlink); + if (ret != B_OK) + return ret; + + parser_debug(" Symlink created!\n"); + + ret = symlink.SetPermissions(static_cast(fMode)); + + if (fCreationTime) + ret |= symlink.SetCreationTime(static_cast(fCreationTime)); + + if (fModificationTime) + ret |= symlink.SetModificationTime(static_cast(fModificationTime)); + + if (ret != B_OK) + return ret; + + if (fOffset) { + // Simlinks also seem to have attributes - so parse them + ret = _HandleAttributes(&destination, &dir, "LnDa"); + } + + if (final) { + *final = destination; + } + + return ret; +} + diff --git a/src/apps/packageinstaller/PackageItem.h b/src/apps/packageinstaller/PackageItem.h new file mode 100644 index 0000000000..c712478956 --- /dev/null +++ b/src/apps/packageinstaller/PackageItem.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ +#ifndef PACKAGEITEM_H +#define PACKAGEITEM_H + +#include +#include +#include +#include +#include + +//#define DEBUG_PARSER + +// Local macro for the parser debug output +#ifdef DEBUG_PARSER + #define parser_debug(format, args...) fprintf(stderr, format, ##args) +#else + #define parser_debug(format, args...) +#endif + + +class PkgDirectory; + +// Since files are derive from directories, which is not too obvious, +// we define a type PkgItem to use for base type iterations +typedef PkgDirectory PkgItem; + + +enum { + P_INSTALL_PATH = 0, + P_SYSTEM_PATH, + P_USER_PATH +}; + + +status_t inflate_data(uint8 *in, uint32 in_size, uint8 *out, uint32 out_size); + + +class PkgDirectory { + public: + PkgDirectory(BFile *parent, BString path, uint8 type, uint32 ctime, + uint32 mtime, uint64 offset = 0, uint64 size = 0); + virtual ~PkgDirectory(); + + virtual status_t WriteToPath(const char *path = NULL, BPath *final = NULL); + virtual void SetTo(BFile *parent, BString path, uint8 type, + uint32 ctime, uint32 mtime, uint64 offset = 0, uint64 size = 0); + + protected: + int32 _ItemExists(const char *name); + status_t _InitPath(const char *path, BPath *destination); + status_t _HandleAttributes(BPath *destination, BNode *node, + const char *header); + + inline status_t _ParseAttribute(uint8 *buffer, BNode *node, char **attrName, + uint32 *nameSize, uint32 *attrType, uint8 **attrData, uint64 *dataSize, + uint8 **temp, uint64 *tempSize, uint64 *attrCSize, uint64 *attrOSize, + bool *attrStarted, bool *done); + inline status_t _ParseData(uint8 *buffer, BFile *file, uint64 originalSize, + bool *done); + + BString fPath; + uint64 fOffset; + uint64 fSize; + uint8 fPathType; + uint32 fCreationTime; + uint32 fModificationTime; + + BFile *fPackage; +}; + + +class PkgFile : public PkgItem { + public: + PkgFile(BFile *parent, BString path, uint8 type, uint32 ctime, + uint32 mtime, uint64 offset, uint64 size, uint64 originalSize, + uint32 platform, BString mime, BString signature, uint32 mode); + ~PkgFile(); + + status_t WriteToPath(const char *path = NULL, BPath *final = NULL); + + private: + uint64 fOriginalSize; + uint32 fPlatform; + uint32 fMode; + + BString fMimeType; + BString fSignature; +}; + + +class PkgLink : public PkgItem { + public: + PkgLink(BFile *parent, BString path, BString link, uint8 type, + uint32 ctime, uint32 mtime, uint32 mode, uint64 offset = 0, + uint64 size = 0); + ~PkgLink(); + + status_t WriteToPath(const char *path = NULL, BPath *final = NULL); + + private: + uint32 fMode; + + BString fLink; +}; + + +#endif + diff --git a/src/apps/packageinstaller/PackageStatus.cpp b/src/apps/packageinstaller/PackageStatus.cpp new file mode 100644 index 0000000000..915e20bf45 --- /dev/null +++ b/src/apps/packageinstaller/PackageStatus.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ + + +#include "PackageStatus.h" + +#include + +#include +#include + +#include +#include + +// Macro reserved for later localization +#define T(x) x + + +StopButton::StopButton() + : BButton(BRect(0, 0, 22, 18), "stop", "", new BMessage(P_MSG_STOP)) +{ +} + + +void +StopButton::Draw(BRect updateRect) +{ + BButton::Draw(updateRect); + + updateRect = Bounds(); + updateRect.InsetBy((updateRect.Width() - 4) / 2, (updateRect.Height() - 4) / 2); + //updateRect.InsetBy(9, 7); + SetHighColor(0, 0, 0); + FillRect(updateRect); +} + + + + + +// #pragma mark - + + +/*PackageStatus::PackageStatus(BHandler *parent, const char *title, + const char *label, const char *trailing) + : BWindow(BRect(200, 200, 550, 275), title, B_TITLED_WINDOW, + B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_NOT_ZOOMABLE, 0) +{ + SetSizeLimits(0, 100000, 0, 100000); + fBackground = new BView(Bounds(), "background", B_FOLLOW_NONE, B_WILL_DRAW); + fBackground->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + BRect rect(Bounds()); + float width, height; + rect.left += 6; + rect.right -= 40; + rect.top += 6; + rect.bottom = rect.top + 15; + fStatus = new BStatusBar(rect, "status_bar", T("Installing package")); + fStatus->SetBarHeight(12); + fStatus->GetPreferredSize(&width, &height); + fStatus->ResizeTo(fStatus->Frame().Width(), height); + fBackground->AddChild(fStatus); + + font_height fontHeight; + fBackground->GetFontHeight(&fontHeight); + BRect frame = fStatus->Frame(); + fBackground->ResizeTo(Bounds().Width(), (2 * frame.top) + frame.Height() + + fontHeight.leading + fontHeight.ascent + fontHeight.descent); + + rect = Bounds(); + rect.left = rect.right - 32; + //rect.right = rect.left + 17; + rect.top += 18; + //rect.bottom = rect.top + 10; + fButton = new StopButton(); + fButton->MoveTo(BPoint(rect.left, rect.top)); + fButton->ResizeTo(22, 18); + fBackground->AddChild(fButton); + + AddChild(fBackground); + fButton->SetTarget(parent); + + ResizeTo(Bounds().Width(), fBackground->Bounds().Height()); + Run(); +}*/ + + +PackageStatus::PackageStatus(const char *title, const char *label, + const char *trailing) + : BWindow(BRect(200, 200, 550, 255), title, B_TITLED_WINDOW, + B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_NOT_ZOOMABLE, 0), + fIsStopped(false) +{ + SetLayout(new BGroupLayout(B_VERTICAL)); + + fStatus = new BStatusBar("status_bar", T("Installing package")); + fStatus->SetBarHeight(12); + + fButton = new StopButton(); + fButton->SetExplicitMaxSize(BSize(22, 18)); + + fBackground = BGroupLayoutBuilder(B_HORIZONTAL) + .AddStrut(5.0f) + .Add(fStatus) + .Add(fButton); + fBackground->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + AddChild(fBackground); + + fButton->SetTarget(this); + Run(); +} + + +PackageStatus::~PackageStatus() +{ +} + + +void +PackageStatus::MessageReceived(BMessage *msg) +{ + switch (msg->what) { + case P_MSG_STOP: + fIsStopped = true; + break; + default: + BWindow::MessageReceived(msg); + } +} + + +void +PackageStatus::Reset(uint32 stages, const char *label, const char *trailing) +{ + BAutolock lock(this); + + if (lock.IsLocked()) { + fStatus->Reset(label, trailing); + fStatus->SetMaxValue(stages); + fIsStopped = false; + } +} + + +void +PackageStatus::StageStep(uint32 count, const char *text, const char *trailing) +{ + BAutolock lock(this); + + if (lock.IsLocked()) { + fStatus->Update(count, text, trailing); + } +} + + +bool +PackageStatus::Stopped() +{ + BAutolock lock(this); + return fIsStopped; +} + diff --git a/src/apps/packageinstaller/PackageStatus.h b/src/apps/packageinstaller/PackageStatus.h new file mode 100644 index 0000000000..f3c38a1485 --- /dev/null +++ b/src/apps/packageinstaller/PackageStatus.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ +#ifndef PACKAGESTATUS_H +#define PACKAGESTATUS_H + +#include +#include +#include +#include + + +enum { + P_MSG_NEXT_STAGE = 'psne', + P_MSG_STOP, + P_MSG_RESET +}; + + +class StopButton : public BButton { + public: + StopButton(); + virtual void Draw(BRect); +}; + + +class PackageStatus : public BWindow { + public: + PackageStatus(const char *title, const char *label = NULL, + const char *trailing = NULL); + ~PackageStatus(); + + void MessageReceived(BMessage *msg); + void Reset(uint32 stages, const char *label = NULL, + const char *trailing = NULL); + void StageStep(uint32 count, const char *text = NULL, + const char *trailing = NULL); + bool Stopped(); + + private: + BView *fBackground; + BStatusBar *fStatus; + StopButton *fButton; + bool fIsStopped; +}; + + +#endif + diff --git a/src/apps/packageinstaller/PackageTextViewer.cpp b/src/apps/packageinstaller/PackageTextViewer.cpp new file mode 100644 index 0000000000..7a281e49e3 --- /dev/null +++ b/src/apps/packageinstaller/PackageTextViewer.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ + + +#include "PackageTextViewer.h" + +#include +#include + +#include +#include + + +enum { + P_MSG_ACCEPT = 'pmac', + P_MSG_DECLINE +}; + +// Reserved +#define T(x) x + + +PackageTextViewer::PackageTextViewer(const char *text, bool disclaimer) + : BWindow(BRect(125, 125, 675, 475), T("Disclaimer"), B_MODAL_WINDOW, + B_NOT_ZOOMABLE | B_NOT_RESIZABLE | B_NOT_CLOSABLE), + fValue(0) +{ + _InitView(text, disclaimer); +} + + +PackageTextViewer::~PackageTextViewer() +{ +} + + +int32 +PackageTextViewer::Go() +{ + // Since this class can be thought of as a modified BAlert window, no use + // to reinvent a well fledged wheel. This concept has been borrowed from + // the current BAlert implementation + fSemaphore = create_sem(0, "TextViewer"); + if (fSemaphore < B_OK) { + Quit(); + return B_ERROR; + } + + BWindow *parent = + dynamic_cast(BLooper::LooperForThread(find_thread(NULL))); + Show(); + + if (parent) { + status_t ret; + for (;;) { + do { + ret = acquire_sem_etc(fSemaphore, 1, B_RELATIVE_TIMEOUT, 50000); + } while (ret == B_INTERRUPTED); + + if (ret == B_BAD_SEM_ID) + break; + parent->UpdateIfNeeded(); + } + } + else { + // Since there are no spinlocks, wait until the semaphore is free + while (acquire_sem(fSemaphore) == B_INTERRUPTED) { + } + } + + int32 value = fValue; + if (Lock()) + Quit(); + + return value; +} + + +void +PackageTextViewer::MessageReceived(BMessage *msg) +{ + if (msg->what == P_MSG_ACCEPT) { + if (fSemaphore >= B_OK) { + fValue = 1; + delete_sem(fSemaphore); + fSemaphore = -1; + } + } + else if (msg->what == P_MSG_DECLINE) { + if (fSemaphore >= B_OK) { + fValue = 0; + delete_sem(fSemaphore); + fSemaphore = -1; + } + } + else + BWindow::MessageReceived(msg); +} + + +// #pragma mark - + + +void +PackageTextViewer::_InitView(const char *text, bool disclaimer) +{ + fBackground = new BView(Bounds(), "background_view", 0, 0); + fBackground->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + BRect bounds; + BRect rect = Bounds(); + if (disclaimer) { + BButton *button = new BButton(BRect(0, 0, 1, 1), "accept", T("Accept"), + new BMessage(P_MSG_ACCEPT)); + button->ResizeToPreferred(); + + bounds = button->Bounds(); + rect.top = rect.bottom - bounds.bottom - 5.0f; + rect.left = rect.right - bounds.right - 5.0f; + rect.bottom = bounds.bottom; + rect.right = bounds.right; + button->MoveTo(rect.LeftTop()); + button->MakeDefault(true); + fBackground->AddChild(button); + + button = new BButton(BRect(0, 0, 1, 1), "decline", T("Decline"), + new BMessage(P_MSG_DECLINE)); + button->ResizeToPreferred(); + + bounds = button->Bounds(); + rect.left -= bounds.right + 7.0f; + button->MoveTo(rect.LeftTop()); + fBackground->AddChild(button); + } + else { + BButton *button = new BButton(BRect(0, 0, 1, 1), "accept", T("Continue"), + new BMessage(P_MSG_ACCEPT)); + button->ResizeToPreferred(); + + bounds = button->Bounds(); + rect.top = rect.bottom - bounds.bottom - 5.0f; + rect.left = rect.right - bounds.right - 5.0f; + rect.bottom = bounds.bottom; + rect.right = bounds.right; + button->MoveTo(rect.LeftTop()); + button->MakeDefault(true); + fBackground->AddChild(button); + } + + bounds = Bounds().InsetBySelf(5.0f, 5.0f); + bounds.bottom = rect.top - 6.0f; + bounds.right -= B_V_SCROLL_BAR_WIDTH; + + fText = new BTextView(bounds, "text_view", BRect(0, 0, bounds.Width(), + bounds.Height()), B_FOLLOW_NONE, B_WILL_DRAW); + fText->MakeEditable(false); + fText->MakeSelectable(true); + fText->SetText(text); + + BScrollView *scroll = new BScrollView("scroll_view", fText, + B_FOLLOW_LEFT | B_FOLLOW_TOP, 0, false, true); + + fBackground->AddChild(scroll); + + AddChild(fBackground); +} + + +/*void +PackageTextViewer::_InitView(const char *text, bool disclaimer) +{ + SetLayout(new BGroupLayout(B_HORIZONTAL)); + + fText = new BTextView(BRect(0, 0, 1, 1), "text_view", BRect(0, 0, 1, 1), + B_FOLLOW_NONE, B_WILL_DRAW | B_SUPPORTS_LAYOUT); + fText->MakeEditable(false); + fText->MakeSelectable(true); + BScrollView *scroll = new BScrollView("scroll_view", fText, + B_FOLLOW_LEFT | B_FOLLOW_TOP, 0, false, true); + + if (disclaimer) { + BButton *accept = new BButton("accept", T("Accept"), + new BMessage(P_MSG_ACCEPT)); + + BButton *decline = new BButton("decline", T("Decline"), + new BMessage(P_MSG_DECLINE)); + + fBackground = BGroupLayoutBuilder(B_VERTICAL) + .Add(scroll) + .AddGroup(B_HORIZONTAL, 5.0f) + .AddGlue() + .Add(accept) + .Add(decline) + .End(); + } + else { + BButton *button = new BButton("accept", T("Continue"), + new BMessage(P_MSG_ACCEPT)); + + fBackground = BGroupLayoutBuilder(B_VERTICAL) + .Add(scroll) + .AddGroup(B_HORIZONTAL, 5.0f) + .AddGlue() + .Add(button) + .End(); + } + + AddChild(fBackground); + + fBackground->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + fText->SetText(text); +}*/ + diff --git a/src/apps/packageinstaller/PackageTextViewer.h b/src/apps/packageinstaller/PackageTextViewer.h new file mode 100644 index 0000000000..d677fd0fb7 --- /dev/null +++ b/src/apps/packageinstaller/PackageTextViewer.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ +#ifndef PACKAGETEXTVIEWER_H +#define PACKAGETEXTVIEWER_H + +#include +#include +#include + + +class PackageTextViewer : public BWindow { + public: + PackageTextViewer(const char *text, bool disclaimer = false); + ~PackageTextViewer(); + + int32 Go(); + + void MessageReceived(BMessage *msg); + + private: + void _InitView(const char *text, bool disclaimer); + + BView *fBackground; + BTextView *fText; + + sem_id fSemaphore; + int32 fValue; +}; + + +#endif + diff --git a/src/apps/packageinstaller/PackageView.cpp b/src/apps/packageinstaller/PackageView.cpp new file mode 100644 index 0000000000..c7660e9673 --- /dev/null +++ b/src/apps/packageinstaller/PackageView.cpp @@ -0,0 +1,629 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ + + +#include "InstalledPackageInfo.h" +#include "PackageImageViewer.h" +#include "PackageTextViewer.h" +#include "PackageView.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include // For debugging + +// Macro reserved for later localization +#define T(x) x + +const float kMaxDescHeight = 125.0f; +const uint32 kSeparatorIndex = 3; + + + +static void +convert_size(uint64 size, char *buffer, uint32 n) +{ + if (size < 1024) + snprintf(buffer, n, "%llu bytes", size); + else if (size < 1024 * 1024) + snprintf(buffer, n, "%.1f KiB", size / 1024.0f); + else if (size < 1024 * 1024 * 1024) + snprintf(buffer, n, "%.1f MiB", size / (1024.0f*1024.0f)); + else + snprintf(buffer, n, "%.1f GiB", size / (1024.0f*1024.0f*1024.0f)); +} + + + +// #pragma mark - + + +PackageView::PackageView(BRect frame, const entry_ref *ref) + : BView(frame, "package_view", B_FOLLOW_NONE, 0), + //BView("package_view", B_WILL_DRAW, new BGroupLayout(B_HORIZONTAL)), + fOpenPanel(new BFilePanel(B_OPEN_PANEL, NULL, NULL, + B_DIRECTORY_NODE, false)), + fInfo(ref) +{ + _InitView(); + + // Check whether the package has been successfuly parsed + status_t ret = fInfo.InitCheck(); + if (ret == B_OK) + _InitProfiles(); + else if (ret != B_NO_INIT) { + BAlert *warning = new BAlert(T("parsing_failed"), + T("I was unable to read the given package file.\nOne of the possible " + "reasons for this might be that the requested file is not a valid " + "BeOS .pkg package."), T("OK"), NULL, NULL, B_WIDTH_AS_USUAL, + B_WARNING_ALERT); + warning->Go(); + + BWindow *parent = Window(); + if (parent && parent->Lock()) + parent->Quit(); + } + + ResizeTo(Bounds().Width(), fInstall->Frame().bottom + 4); +} + + +PackageView::~PackageView() +{ + delete fOpenPanel; +} + + +void +PackageView::AttachedToWindow() +{ + // Set the window title + BWindow *parent = Window(); + BString title; + BString name = fInfo.GetName(); + if (name.CountChars() == 0) { + title = T("Package installer"); + } + else { + title = T("Install "); + title += name; + } + parent->SetTitle(title.String()); + fInstall->SetTarget(this); + + fOpenPanel->SetTarget(BMessenger(this)); + fInstallTypes->SetTargetForItems(this); + + if (fInfo.InitCheck() == B_OK) { + // If the package is valid, we can set up the default group and all + // other things. If not, then the application will close just after + // attaching the view to the window + _GroupChanged(0); + + fStatusWindow = new PackageStatus(T("Installation progress")); + + // Show the splash screen, if present + BMallocIO *image = fInfo.GetSplashScreen(); + if (image) { + PackageImageViewer *imageViewer = new PackageImageViewer(image); + imageViewer->Go(); + } + + // Show the disclaimer/info text popup, if present + BString disclaimer = fInfo.GetDisclaimer(); + if (disclaimer.Length() != 0) { + PackageTextViewer *text = new PackageTextViewer(disclaimer.String()); + int32 selection = text->Go(); + // The user didn't accept our disclaimer, this means we cannot continue. + if (selection == 0) { + BWindow *parent = Window(); + if (parent && parent->Lock()) + parent->Quit(); + } + } + } +} + + +void +PackageView::MessageReceived(BMessage *msg) +{ + switch (msg->what) { + case P_MSG_INSTALL: + { + fInstall->SetEnabled(false); + fStatusWindow->Show(); + BAlert *notify; + status_t ret = Install(); + if (ret == B_OK) { + notify = new BAlert("installation_success", + T("The package you requested has been successfully installed " + "on your system."), T("OK")); + + notify->Go(); + fStatusWindow->Hide(); + + BWindow *parent = Window(); + if (parent && parent->Lock()) + parent->Quit(); + } + else if (ret == B_FILE_EXISTS) + notify = new BAlert("installation_aborted", + T("The installation of the package has been aborted."), T("OK")); + else + notify = new BAlert("installation_failed", // TODO: Review this + T("The requested package failed to install on your system. This " + "might be a problem with the target package file. Please consult " + "this issue with the package distributor."), T("OK"), NULL, + NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); + notify->Go(); + fStatusWindow->Hide(); + fInstall->SetEnabled(true); + + break; + } + case P_MSG_PATH_CHANGED: + { + BString path; + if (msg->FindString("path", &path) == B_OK) { + fCurrentPath.SetTo(path.String()); + } + break; + } + case P_MSG_OPEN_PANEL: + fOpenPanel->Show(); + break; + case P_MSG_GROUP_CHANGED: + { + int32 index; + if (msg->FindInt32("index", &index) == B_OK) { + _GroupChanged(index); + } + break; + } + case B_REFS_RECEIVED: + { + entry_ref ref; + if (msg->FindRef("refs", &ref) == B_OK) { + BPath path(&ref); + + BMenuItem * item = fDestField->MenuItem(); + dev_t device = dev_for_path(path.Path()); + BVolume volume(device); + if (volume.InitCheck() != B_OK) + break; + + BString name = path.Path(); + char sizeString[32]; + + convert_size(volume.FreeBytes(), sizeString, 32); + name << " (" << sizeString << " free)"; + + item->SetLabel(name.String()); + fCurrentPath.SetTo(path.Path()); + } + break; + } + case B_SIMPLE_DATA: + if (msg->WasDropped()) { + uint32 type; + int32 count; + status_t ret = msg->GetInfo("refs", &type, &count); + // Check whether the message means someone dropped a file + // to our view + if (ret == B_OK && type == B_REF_TYPE) { + // If it is, send it along with the refs to the application + msg->what = B_REFS_RECEIVED; + be_app->PostMessage(msg); + } + } + default: + BView::MessageReceived(msg); + break; + } +} + + +status_t +PackageView::Install() +{ + pkg_profile *type = static_cast(fInfo.GetProfile(fCurrentType)); + uint32 n = type->items.CountItems(); + + fStatusWindow->Reset(n + 4); + + fStatusWindow->StageStep(1, "Preparing package"); + + InstalledPackageInfo packageInfo(fInfo.GetName(), fInfo.GetVersion()); + + status_t err = packageInfo.InitCheck(); + if (err == B_OK) { + // The package is already installed, inform the user + BAlert *reinstall = new BAlert("reinstall", + T("The given package seems to be already installed on your system. " + "Would you like to uninstall the existing one and continue the " + "installation?"), T("Yes"), T("No")); + + if (reinstall->Go() == 0) { + // Uninstall the package + err = packageInfo.Uninstall(); + if (err != B_OK) + return err; + + err = packageInfo.SetTo(fInfo.GetName(), fInfo.GetVersion(), true); + if (err != B_OK) + return err; + } + else { + // Abort the installation + return B_FILE_EXISTS; + } + } + else if (err == B_ENTRY_NOT_FOUND) { + err = packageInfo.SetTo(fInfo.GetName(), fInfo.GetVersion(), true); + if (err != B_OK) + return err; + } + else if (fStatusWindow->Stopped()) + return B_FILE_EXISTS; + else + return err; + + fStatusWindow->StageStep(1, "Installing files and directories"); + + // Install files and directories + PkgItem *iter; + BPath installedTo; + uint32 i; + BString label; + + packageInfo.SetName(fInfo.GetName()); + // TODO: Here's a small problem, since right now it's not quite sure + // which description is really used as such. The one displayed on + // the installer is mostly package installation description, but + // most people use it for describing the application in more detail + // then in the short description. + // For now, we'll use the short description if possible. + BString description = fInfo.GetShortDescription(); + if (description.Length() <= 0) + description = fInfo.GetDescription(); + packageInfo.SetDescription(description.String()); + packageInfo.SetSpaceNeeded(type->space_needed); + + for (i = 0;i < n;i++) { + iter = static_cast(type->items.ItemAt(i)); + err = iter->WriteToPath(fCurrentPath.Path(), &installedTo); + if (err != B_OK) + return err; + if (fStatusWindow->Stopped()) + return B_FILE_EXISTS; + label = ""; + label << (uint32)(i + 1) << " of " << (uint32)n; + fStatusWindow->StageStep(1, NULL, label.String()); + + packageInfo.AddItem(installedTo.Path()); + } + + fStatusWindow->StageStep(1, "Finishing installation", ""); + + err = packageInfo.Save(); + if (err != B_OK) + return err; + + fStatusWindow->StageStep(1, "Done"); + + return B_OK; +} + + +/* +void +PackageView::_InitView() +{ + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + BTextView *description = new BTextView(BRect(0, 0, 20, 20), "description", + BRect(4, 4, 16, 16), B_FOLLOW_NONE, B_WILL_DRAW); + description->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + description->SetText(fInfo.GetDescription()); + description->MakeEditable(false); + description->MakeSelectable(false); + + fInstallTypes = new BPopUpMenu("none"); + + BMenuField *installType = new BMenuField("install_type", + T("Installation type:"), fInstallTypes, 0); + installType->SetAlignment(B_ALIGN_RIGHT); + installType->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_MIDDLE)); + + fInstallDesc = new BTextView(BRect(0, 0, 10, 10), "install_desc", + BRect(2, 2, 8, 8), B_FOLLOW_NONE, B_WILL_DRAW); + fInstallDesc->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + fInstallDesc->MakeEditable(false); + fInstallDesc->MakeSelectable(false); + fInstallDesc->SetText(T("No installation type selected")); + fInstallDesc->TextHeight(0, fInstallDesc->TextLength()); + + fInstall = new BButton("install_button", T("Install"), + new BMessage(P_MSG_INSTALL)); + + BView *installField = BGroupLayoutBuilder(B_VERTICAL, 5.0f) + .AddGroup(B_HORIZONTAL) + .Add(installType) + .AddGlue() + .End() + .Add(fInstallDesc); + + BBox *installBox = new BBox("install_box"); + installBox->AddChild(installField); + + BView *root = BGroupLayoutBuilder(B_VERTICAL, 3.0f) + .Add(description) + .Add(installBox) + .AddGroup(B_HORIZONTAL) + .AddGlue() + .Add(fInstall) + .End(); + + AddChild(root); + + fInstall->MakeDefault(true); +}*/ + + +void +PackageView::_InitView() +{ + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + BRect rect = Bounds(); + BTextView *description = new BTextView(rect, "description", + rect.InsetByCopy(5, 5), B_FOLLOW_NONE, B_WILL_DRAW); + description->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + description->SetText(fInfo.GetDescription()); + description->MakeEditable(false); + description->MakeSelectable(false); + + float length = description->TextHeight(0, description->TextLength()) + 5; + if (length > kMaxDescHeight) { + // Set a scroller for the description. + description->ResizeTo(rect.Width() - B_V_SCROLL_BAR_WIDTH, kMaxDescHeight); + BScrollView *scroller = new BScrollView("desciption_view", description, + B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS, false, true, B_NO_BORDER); + + AddChild(scroller); + rect = scroller->Frame(); + } + else { + description->ResizeTo(rect.Width(), length); + AddChild(description); + rect = description->Frame(); + } + + rect.top = rect.bottom + 2; + rect.bottom += 100; + BBox *installBox = new BBox(rect.InsetByCopy(2, 2), "install_box"); + + fInstallTypes = new BPopUpMenu("none"); + + BMenuField *installType = new BMenuField(BRect(2, 2, 100, 50), "install_type", + T("Installation type:"), fInstallTypes, false); + installType->SetDivider(installType->StringWidth(installType->Label()) + 8); + installType->SetAlignment(B_ALIGN_RIGHT); + installType->ResizeToPreferred(); + + installBox->AddChild(installType); + + rect = installBox->Bounds().InsetBySelf(4, 4); + rect.top = installType->Frame().bottom; + fInstallDesc = new BTextView(rect, "install_desc", + BRect(2, 2, rect.Width() - 2, rect.Height() - 2), B_FOLLOW_NONE, + B_WILL_DRAW); + fInstallDesc->MakeEditable(false); + fInstallDesc->MakeSelectable(false); + fInstallDesc->SetText(T("No installation type selected")); + fInstallDesc->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + fInstallDesc->ResizeTo(rect.Width() - B_V_SCROLL_BAR_WIDTH, 60); + BScrollView *scroller = new BScrollView("desciption_view", fInstallDesc, + B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS, false, true, B_NO_BORDER); + + installBox->ResizeTo(installBox->Bounds().Width(), + scroller->Frame().bottom + 10); + + installBox->AddChild(scroller); + + AddChild(installBox); + + fDestination = new BPopUpMenu("none"); + + rect = installBox->Frame(); + rect.top = rect.bottom + 5; + rect.bottom += 35; + fDestField = new BMenuField(rect, "install_to", T("Install to:"), + fDestination, false); + fDestField->SetDivider(fDestField->StringWidth(fDestField->Label()) + 8); + fDestField->SetAlignment(B_ALIGN_RIGHT); + fDestField->ResizeToPreferred(); + + AddChild(fDestField); + + fInstall = new BButton(rect, "install_button", T("Install"), + new BMessage(P_MSG_INSTALL)); + fInstall->ResizeToPreferred(); + AddChild(fInstall); + fInstall->MoveTo(Bounds().Width() - fInstall->Bounds().Width() - 10, rect.top + 2); + fInstall->MakeDefault(true); +} + + +void +PackageView::_InitProfiles() +{ + // Set all profiles + int i = 0, num = fInfo.GetProfileCount(); + pkg_profile *prof; + BMenuItem *item = 0; + char sizeString[32]; + BString name = ""; + BMessage *message; + + if (num > 0) { // Add the default item + prof = fInfo.GetProfile(0); + convert_size(prof->space_needed, sizeString, 32); + name << prof->name << " (" << sizeString << ")"; + + message = new BMessage(P_MSG_GROUP_CHANGED); + message->AddInt32("index", 0); + item = new BMenuItem(name.String(), message); + fInstallTypes->AddItem(item); + item->SetMarked(true); + fCurrentType = 0; + } + + for (i = 1; i < num; i++) { + prof = fInfo.GetProfile(i); + + if (prof) { + convert_size(prof->space_needed, sizeString, 32); + name = prof->name; + name << " (" << sizeString << ")"; + + message = new BMessage(P_MSG_GROUP_CHANGED); + message->AddInt32("index", i); + item = new BMenuItem(name.String(), message); + fInstallTypes->AddItem(item); + } + else + fInstallTypes->AddSeparatorItem(); + } +} + + +status_t +PackageView::_GroupChanged(int32 index) +{ + if (index < 0) + return B_ERROR; + + BMenuItem *iter; + int32 i, num = fDestination->CountItems(); + + // Clear the choice list + for (i = 0;i < num;i++) { + iter = fDestination->RemoveItem((int32)0); + delete iter; + } + + fCurrentType = index; + pkg_profile *prof = fInfo.GetProfile(index); + BString test; + fInstallDesc->SetText(prof->description.String()); + + if (prof) { + BMenuItem *item = 0; + BPath path; + BMessage *temp; + BVolume volume; + + if (prof->path_type == P_INSTALL_PATH) { + dev_t device; + BString name; + char sizeString[32]; + + if (find_directory(B_BEOS_APPS_DIRECTORY, &path) == B_OK) { + device = dev_for_path(path.Path()); + if (volume.SetTo(device) == B_OK && !volume.IsReadOnly()) { + temp = new BMessage(P_MSG_PATH_CHANGED); + temp->AddString("path", BString(path.Path())); + + convert_size(volume.FreeBytes(), sizeString, 32); + name = path.Path(); + name << " (" << sizeString << " free)"; + item = new BMenuItem(name.String(), temp); + item->SetTarget(this); + fDestination->AddItem(item); + } + } + if (find_directory(B_APPS_DIRECTORY, &path) == B_OK) { + device = dev_for_path(path.Path()); + if (volume.SetTo(device) == B_OK && !volume.IsReadOnly()) { + temp = new BMessage(P_MSG_PATH_CHANGED); + temp->AddString("path", BString(path.Path())); + + convert_size(volume.FreeBytes(), sizeString, 32); + name = path.Path(); + name << " (" << sizeString << " free)"; + item = new BMenuItem(name.String(), temp); + item->SetTarget(this); + fDestination->AddItem(item); + } + } + + if (item) { + item->SetMarked(true); + fCurrentPath.SetTo(path.Path()); + } + fDestination->AddSeparatorItem(); + + item = new BMenuItem("Other...", new BMessage(P_MSG_OPEN_PANEL)); + item->SetTarget(this); + fDestination->AddItem(item); + + fDestField->SetEnabled(true); + } + else if (prof->path_type == P_USER_PATH) { + BString name; + char sizeString[32], volumeName[B_FILE_NAME_LENGTH]; + BVolumeRoster roster; + BDirectory mountPoint; + + while (roster.GetNextVolume(&volume) != B_BAD_VALUE) { + if (volume.IsReadOnly() || + volume.GetRootDirectory(&mountPoint) != B_OK) + continue; + + if (path.SetTo(&mountPoint, NULL) != B_OK) + continue; + + temp = new BMessage(P_MSG_PATH_CHANGED); + temp->AddString("path", BString(path.Path())); + + convert_size(volume.FreeBytes(), sizeString, 32); + volume.GetName(volumeName); + name = volumeName; + name << " (" << sizeString << " free)"; + item = new BMenuItem(name.String(), temp); + item->SetTarget(this); + fDestination->AddItem(item); + } + + fDestField->SetEnabled(true); + } + else + fDestField->SetEnabled(false); + } + + return B_OK; +} + diff --git a/src/apps/packageinstaller/PackageView.h b/src/apps/packageinstaller/PackageView.h new file mode 100644 index 0000000000..68cab1f35a --- /dev/null +++ b/src/apps/packageinstaller/PackageView.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ +#ifndef PACKAGEVIEW_H +#define PACKAGEVIEW_H + +#include "PackageInfo.h" +#include "PackageStatus.h" +#include +#include +#include +#include +#include + +enum { + P_MSG_GROUP_CHANGED = 'gpch', + P_MSG_PATH_CHANGED, + P_MSG_OPEN_PANEL, + P_MSG_INSTALL +}; + + +class PackageView : public BView { + public: + PackageView(BRect frame, const entry_ref *ref); + ~PackageView(); + + void AttachedToWindow(); + void MessageReceived(BMessage *msg); + + status_t Install(); + + private: + void _InitView(); + void _InitProfiles(); + + status_t _GroupChanged(int32 index); + + BPopUpMenu *fInstallTypes; + BTextView *fInstallDesc; + BPopUpMenu *fDestination; + BMenuField *fDestField; + BButton *fInstall; + + BFilePanel *fOpenPanel; + BPath fCurrentPath; + uint32 fCurrentType; + + PackageInfo fInfo; + PackageStatus *fStatusWindow; +}; + + +#endif + diff --git a/src/apps/packageinstaller/PackageWindow.cpp b/src/apps/packageinstaller/PackageWindow.cpp new file mode 100644 index 0000000000..05a7541f35 --- /dev/null +++ b/src/apps/packageinstaller/PackageWindow.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ + + +#include "PackageWindow.h" + +#include + +#include + +// Macro reserved for later localization +#define T(x) x + + +PackageWindow::PackageWindow(const entry_ref *ref) + : BWindow(BRect(100, 100, 600, 300), T("Package Installer"), B_TITLED_WINDOW, + B_NOT_ZOOMABLE | B_NOT_RESIZABLE) +{ + //SetLayout(new BGroupLayout(B_HORIZONTAL)); + + fBackground = new PackageView(Bounds(), ref); + AddChild(fBackground); + + ResizeTo(Bounds().Width(), fBackground->Bounds().Height()); +} + + +PackageWindow::~PackageWindow() +{ + RemoveChild(fBackground); + + delete fBackground; +} + + +void +PackageWindow::Quit() +{ + be_app->PostMessage(P_WINDOW_QUIT); + BWindow::Quit(); +} + diff --git a/src/apps/packageinstaller/PackageWindow.h b/src/apps/packageinstaller/PackageWindow.h new file mode 100644 index 0000000000..833c37be7c --- /dev/null +++ b/src/apps/packageinstaller/PackageWindow.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ +#ifndef PACKAGEWINDOW_H +#define PACKAGEWINDOW_H + +#include "PackageView.h" +#include +#include + + +const uint32 P_WINDOW_QUIT = 'PiWq'; + + +class PackageWindow : public BWindow { + public: + PackageWindow(const entry_ref *refs); + virtual ~PackageWindow(); + + virtual void Quit(); + + private: + PackageView *fBackground; +}; + + +#endif + diff --git a/src/apps/packageinstaller/main.cpp b/src/apps/packageinstaller/main.cpp new file mode 100644 index 0000000000..4d46d99574 --- /dev/null +++ b/src/apps/packageinstaller/main.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2007, Haiku, Inc. + * Distributed under the terms of the MIT license. + * + * Author: + * Łukasz 'Sil2100' Zemczak + */ + + +#include "PackageWindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +class PackageInstaller : public BApplication { + public: + PackageInstaller(); + ~PackageInstaller(); + + void RefsReceived(BMessage *msg); + void ArgvReceived(int32 argc, char **argv); + void ReadyToRun(); + + void MessageReceived(BMessage *msg); + + void AboutRequested(); + + private: + BFilePanel *fOpen; + uint32 fWindowCount; +}; + + +PackageInstaller::PackageInstaller() + : BApplication("application/x-vnd.Haiku-PackageInstaller"), + fWindowCount(0) +{ + fOpen = new BFilePanel(B_OPEN_PANEL); +} + + +PackageInstaller::~PackageInstaller() +{ +} + + +void +PackageInstaller::ReadyToRun() +{ + // We're ready to run - if no windows are yet visible, this means that + // we should show a open panel + if (fWindowCount == 0) { + fOpen->Show(); + } +} + + +void +PackageInstaller::RefsReceived(BMessage *msg) +{ + uint32 type; + int32 i, count; + status_t ret = msg->GetInfo("refs", &type, &count); + if (ret != B_OK || type != B_REF_TYPE) + return; + + entry_ref ref; + PackageWindow *iter; + for (i = 0; i < count; i++) { + if (msg->FindRef("refs", i, &ref) == B_OK) { + iter = new PackageWindow(&ref); + fWindowCount++; + iter->Show(); + } + } +} + + +void +PackageInstaller::ArgvReceived(int32 argc, char **argv) +{ + int i; + BPath path; + entry_ref ref; + status_t ret = B_OK; + PackageWindow *iter = 0; + + for (i = 1; i < argc; i++) { + if (path.SetTo(argv[i]) != B_OK) { + fprintf(stderr, "Error! \"%s\" is not a valid path.\n", argv[i]); + continue; + } + + ret = get_ref_for_path(path.Path(), &ref); + if (ret != B_OK) { + fprintf(stderr, "Error (%s)! Could not open \"%s\".\n", strerror(ret), + argv[i]); + continue; + } + + iter = new PackageWindow(&ref); + fWindowCount++; + iter->Show(); + } +} + + +void +PackageInstaller::MessageReceived(BMessage *msg) +{ + switch (msg->what) { + case P_WINDOW_QUIT: + fWindowCount--; + if (fWindowCount == 0) { + BAutolock lock(this); + if (lock.IsLocked()) + Quit(); + } + break; + default: + BApplication::MessageReceived(msg); + } +} + + +void +PackageInstaller::AboutRequested() +{ + BAlert *about = new BAlert("about", + "PackageInstaller\n" + "BeOS legacy .pkg file installer for Haiku.\n\n" + "Copyright 2007,\nŁukasz 'Sil2100' Zemczak\n\n" + "Copyright (c) 2007 Haiku, Inc. \n", + "Close"); + + BTextView *view = about->TextView(); + BFont font; + view->SetStylable(true); + view->GetFont(&font); + font.SetFace(B_BOLD_FACE); + font.SetSize(font.Size() * 1.5); + view->SetFontAndColor(0, 17, &font); + + about->Go(); +} + + +int +main(void) +{ + PackageInstaller app; + app.Run(); + + return 0; +} +