/** * Copyright (C) 2007 Bryan Varner * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * $Id:$ */ #include "TRApplication.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __BEOS__ // work-around to get rid of this stupid find_directory_r() on Zeta #ifdef find_directory #undef find_directory #endif #endif int main(int, char**) { TRApplication *app = new TRApplication(); if (app->InitCheck() == B_OK) { app->Run(); } else { BString errMsg(""); errMsg << "The following error occurred loading Transmission:\n" << strerror(app->InitCheck()); BAlert *ohNo = new BAlert("Transmission Error", errMsg.String(), "OK", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); ohNo->Go(); } delete app; return (0); } TRApplication::TRApplication() : BApplication(APP_SIG) { status_t err = B_OK; torrentDir = NULL; // Install Mime types if necessary. BMimeType torrentType("application/x-bittorrent"); if (torrentType.InitCheck() == B_OK) { if (!torrentType.IsInstalled()) { fprintf(stderr, "Installing mime type...\n"); // Icon app_info runningInfo; GetAppInfo(&runningInfo); BFile appFile(&(runningInfo.ref), B_READ_ONLY); BResources res(&appFile, false); size_t len = 0; BBitmap *icon = NULL; void *iconBits = NULL; iconBits = res.FindResource('ICON', "BEOS:L:application/x-bittorrent", &len); if (iconBits) { icon = new BBitmap(BRect(0, 0, 31, 31), B_CMAP8); icon->SetBits(iconBits, len, 0, B_CMAP8); torrentType.SetIcon(icon, B_LARGE_ICON); delete icon; icon = NULL; len = 0; } iconBits = res.FindResource('ICON', "BEOS:M:application/x-bittorrent", &len); if (iconBits) { icon = new BBitmap(BRect(0, 0, 15, 15), B_CMAP8); icon->SetBits(iconBits, len, 0, B_CMAP8); torrentType.SetIcon(icon, B_MINI_ICON); delete icon; } // Extensions BMessage extensions; extensions.AddString("extensions", "torrent"); torrentType.SetFileExtensions(&extensions); torrentType.SetShortDescription("BitTorrent Meta File"); torrentType.SetLongDescription("BitTorrent Protocol Meta File (http://www.bittorrent.com)"); // Set Preferred Application torrentType.SetPreferredApp(APP_SIG, B_OPEN); torrentType.Install(); } } // Create Settings folder and structure BPath settingsPath; err = find_directory(B_USER_SETTINGS_DIRECTORY, &settingsPath, false); if (err == B_OK) { BDirectory settings(settingsPath.Path()); BDirectory *trSettings = new BDirectory(); err = settings.CreateDirectory("Transmission", trSettings); if (err == B_FILE_EXISTS) { err = trSettings->SetTo(&settings, "Transmission"); } if (err == B_OK) { torrentDir = new BDirectory(); err = trSettings->CreateDirectory("Torrents", torrentDir); if (err != B_OK) { torrentDir->SetTo(trSettings, "Torrents"); } } delete trSettings; } // Create window window = new TRWindow(); window->RescanTorrents(); settings = new TRPrefsWindow(); openPanel = new BFilePanel(); openPanel->SetRefFilter(new TRFilter()); } TRApplication::~TRApplication() { if (window->Lock()) { window->Quit(); } if (settings->Lock()) { settings->Quit(); } if (openPanel != NULL) { delete openPanel; } if (torrentDir != NULL) { delete torrentDir; } } /** * Checks to make sure our Settings directory structure is in place. * If this fails, then we need to display an alert and vomit. */ status_t TRApplication::InitCheck() { if (torrentDir != NULL) { return torrentDir->InitCheck(); } return B_ERROR; } void TRApplication::AboutRequested() { // Read the application version info app_info runningInfo; GetAppInfo(&runningInfo); BFile appFile(&(runningInfo.ref), B_READ_ONLY); BAppFileInfo appInfo(&appFile); version_info vInfo; appInfo.GetVersionInfo(&vInfo, B_APP_VERSION_KIND); BString aboutMsg(""); aboutMsg << "Transmission : " << "Version " << LONG_VERSION_STRING << '\n' << "GUI : " << "Version " << vInfo.major << "." << vInfo.middle << "." << vInfo.minor << "\n" << "\nMade for BeOS/Haiku/Zeta by\n" << "===========================\n" << "Eric Petit & Bryan Varner\n"; BAlert *aboutBox = new BAlert("About Transmission", aboutMsg.String(), "Close"); aboutBox->Go(); } void TRApplication::ReadyToRun() { SetPulseRate(500000); window->Show(); } void TRApplication::Pulse() { window->UpdateList(-1, false); } /** * When a ref is received, we copy each file that is to be opened into * B_USER_SETTINGS/Transmission/Torrents * * Our window node_monitors this folder for added / removed torrent meta files. * * Each copy is performed in a seperate thread to avoid blocking / locking the app. */ void TRApplication::RefsReceived(BMessage *message) { int32 count; type_code code; message->GetInfo("refs", &code, &count); for (int i = 0; i < count; i++) { entry_ref *ref = (entry_ref*)calloc(sizeof(entry_ref), 1); if (message->FindRef("refs", i, ref) == B_OK) { thread_id cp_thread = spawn_thread(TRApplication::Copy, "TorrentDuper", B_NORMAL_PRIORITY, (void *)ref); if (!((cp_thread) < B_OK)) { resume_thread(cp_thread); } } } } /** * Needed for browsers or command line interaction */ void TRApplication::ArgvReceived(int32 _argc, char** _argv) { entry_ref ref; BMessage refs(B_REFS_RECEIVED); for( int32 i = 0; i < _argc; ++i ) { if( B_OK == get_ref_for_path(_argv[i], &ref) ) { refs.AddRef("refs", &ref); } } be_app_messenger.SendMessage(&refs); } /** * BMessage handling. */ void TRApplication::MessageReceived(BMessage *message) { BApplication::MessageReceived(message); /* * When the copy of a torrent file is complete, we get a message * signaling that we should add the now copied .torrent file to our Transfer Window. */ if (message->what == TR_ADD) { // Add the torrent to the window. entry_ref torrentRef; if (message->FindRef("torrent", &torrentRef) == B_OK) { BEntry *entry = new BEntry(&torrentRef, true); window->AddEntry(entry); delete entry; } /* Show the Open Dialog */ } else if (message->what == TR_OPEN) { openPanel->Show(); } else if (message->what == TR_SETTINGS) { settings->MoveTo(window->Frame().left + (window->Frame().Width() - settings->Frame().Width()) / 2, window->Frame().top + (window->Frame().Height() - settings->Frame().Height()) / 2); settings->Show(); } else if (message->what == TR_RELOAD_SETTINGS) { window->LoadSettings(); } } bool TRApplication::QuitRequested() { return BApplication::QuitRequested(); } /** * Thread Function. * The thread copies the original .torrent file into the torrents folder, * then signals the window to load the torrent meta info from the copy. * The torrent window will then node_monitor the file so that if it's removed * the torrent will cease. * * Keeping the file copy in a separate thread keeps us from blocking up the * rest of our program. * * This behavior lets us kill the original .torrent download from disc (which * speaking as a user I have a tendency to do) but keep the meta around for * Transmission to use later. * * If the user whacks the .torrent file from our settings folder, then we'll * remove it from the list (node_monitor!) and if they remove it from our list * we'll remove it from the file system. */ int32 TRApplication::Copy(void *data) { entry_ref *ref = (entry_ref*)data; BFile source(ref, B_READ_ONLY); BFile target(((TRApplication*)be_app)->TorrentDir(), ref->name, B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS); BEntry targetEntry(((TRApplication*)be_app)->TorrentDir(), ref->name); entry_ref targetRef; targetEntry.GetRef(&targetRef); // Only perform the copy if the target is freshly created. Everything is B_OK! if (source.InitCheck() == B_OK && target.InitCheck() == B_OK) { if (target.Lock() == B_OK) { char *buffer = (char*)calloc(1, 4096); // 4k data buffer. ssize_t read = 0; while ((read = source.Read(buffer, 4096)) > 0) { target.Write(buffer, read); } free(buffer); target.Unlock(); } } BMessage msg(TR_ADD); msg.AddRef("torrent", &targetRef); BMessenger messenger(be_app); messenger.SendMessage(&msg); free(ref); return B_OK; } /** * Filters the FilePanel for torrent files and directories. */ bool TRFilter::Filter(const entry_ref *ref, BNode *node, struct stat *, const char *mimetype) { return (node->IsDirectory() || (strcmp(mimetype, "application/x-bittorrent") == 0) || (strstr(ref->name, ".torrent") != NULL)); }