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