source: trunk/beos/TRWindow.cpp @ 4182

Last change on this file since 4182 was 4182, checked in by charles, 15 years ago

add explicit BSD licensing and copyright ownership by Bryan Varner, so the Debian packagers will be satisfied about is free software status. (#528)

  • Property svn:keywords set to Date Rev Author Id
File size: 17.6 KB
Line 
1/**
2 * Copyright (C) 2007 Bryan Varner
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 *
23 * $Id:$
24 */
25
26#include "TRWindow.h"
27
28#include <stdio.h>
29
30#include <Alert.h>
31#include <Application.h>
32#include <File.h>
33#include <MenuBar.h>
34#include <MenuItem.h>
35#include <NodeMonitor.h>
36#include <ScrollView.h>
37#include <String.h>
38
39#include <malloc.h>
40#include <time.h> /* for time() */
41#include <unistd.h> /* for sleep() */
42
43#include "Prefs.h"
44#include "TRApplication.h"
45#include "TRTransfer.h"
46#include "TRInfoWindow.h"
47
48BListView *TRWindow::transfers = NULL;
49
50/**
51 * The Transmission Window! Yay!
52 */
53TRWindow::TRWindow() : BWindow(BRect(10, 40, 350, 110), "Transmission", B_TITLED_WINDOW,
54                               B_ASYNCHRONOUS_CONTROLS , B_CURRENT_WORKSPACE)
55{
56        engine = NULL;
57        stopping = false;
58        quitter = NULL;
59        Prefs prefs(TRANSMISSION_SETTINGS);
60       
61        BRect *rectFrame = new BRect();
62        if (prefs.FindRect("window.frame", rectFrame) == B_OK) {
63                MoveTo(rectFrame->LeftTop());
64                ResizeTo(rectFrame->Width(), rectFrame->Height());
65        } else {
66                rectFrame->Set(10, 40, 350, 110);
67        }
68        Lock();
69       
70        BRect viewRect(0, 0, rectFrame->Width(), rectFrame->Height());
71       
72        BMenuBar *menubar = new BMenuBar(viewRect, "MenuBar");
73        BMenu *menu = new BMenu("File");
74        menu->AddItem(new BMenuItem("Open", new BMessage(TR_OPEN), 'O', B_COMMAND_KEY));
75        menu->FindItem(TR_OPEN)->SetTarget(be_app_messenger); // send OPEN to the be_app.
76        menu->AddSeparatorItem();
77        menu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED), 'Q', B_COMMAND_KEY));
78        menubar->AddItem(menu);
79       
80        menu = new BMenu("Torrent");
81        menu->AddItem(new BMenuItem("Get Info", new BMessage(TR_INFO), 'I', B_COMMAND_KEY));
82        menu->FindItem(TR_INFO)->SetEnabled(false);
83        menu->AddSeparatorItem();
84        menu->AddItem(new BMenuItem("Resume", new BMessage(TR_RESUME)));
85        menu->AddItem(new BMenuItem("Pause", new BMessage(TR_PAUSE)));
86        menu->AddItem(new BMenuItem("Remove", new BMessage(TR_REMOVE)));
87        menubar->AddItem(menu);
88       
89        menu = new BMenu("Tools");
90        menu->AddItem(new BMenuItem("Settings", new BMessage(TR_SETTINGS)));
91        menu->FindItem(TR_SETTINGS)->SetTarget(be_app_messenger);
92        menu->AddSeparatorItem();
93        menu->AddItem(new BMenuItem("About Transmission", new BMessage(B_ABOUT_REQUESTED)));
94        menu->FindItem(B_ABOUT_REQUESTED)->SetTarget(be_app_messenger);
95        menubar->AddItem(menu);
96       
97        AddChild(menubar);
98        SetKeyMenuBar(menubar);
99       
100        // TODO: Tool Bar? (Well after everything is working based on Menus)
101       
102        // Setup the transfers ListView
103        viewRect.Set(2, menubar->Frame().bottom + 3, rectFrame->Width() - 2 - B_V_SCROLL_BAR_WIDTH, rectFrame->Height() - 2);
104        TRWindow::transfers = new BListView(viewRect, "TorrentList", B_SINGLE_SELECTION_LIST, B_FOLLOW_ALL);
105        TRWindow::transfers->SetSelectionMessage(new BMessage(TR_SELECT));
106        AddChild(new BScrollView("TransferScroller", transfers, B_FOLLOW_ALL, 0, false, true));
107       
108        Unlock();
109        delete rectFrame;
110       
111        // Bring up the Transmission Engine
112        engine = tr_init( "beos" );
113        LoadSettings();
114       
115        UpdateList(-1, true);
116       
117        // Start the message loop without showing the window.
118        Hide();
119        Show();
120}
121
122static void torrentclose(tr_torrent_t *torrent, void *)
123{
124        tr_torrentClose(torrent);
125}
126
127TRWindow::~TRWindow() {
128        tr_torrentIterate(engine, torrentclose, NULL);
129        const int MAX_EXIT_WAIT_SECS = 10;
130        const time_t deadline = time(0) + MAX_EXIT_WAIT_SECS;
131        while (tr_torrentCount(engine) && time(NULL) < deadline) {
132                snooze(100000);
133        }
134        /* XXX there's no way to make sure the torrent threads are running so this might crash */
135        tr_close(engine);
136        stop_watching(this);
137        delete quitter;
138}
139
140
141void TRWindow::LoadSettings() {
142        if (engine != NULL) {
143                Prefs prefs(TRANSMISSION_SETTINGS);
144               
145                int32 bindPort;
146                if (prefs.FindInt32("transmission.bindPort", &bindPort) != B_OK) {
147                        bindPort = 9000;
148                        prefs.SetInt32("transmission.bindPort", bindPort);
149                }
150                tr_setBindPort(engine, (int)bindPort);
151               
152                int32 uploadLimit;
153                if (prefs.FindInt32("transmission.uploadLimit", &uploadLimit) != B_OK) {
154                        uploadLimit = 20;
155                        prefs.SetInt32("transmission.uploadLimit", uploadLimit);
156                }
157                tr_setGlobalSpeedLimit(engine, TR_UP, (int)uploadLimit);
158        }
159}
160
161
162/**
163 * Rescans the active Torrents folder, and will add all the torrents there to the
164 * engine. Called during initial Application Start & Stop.
165 */
166void TRWindow::RescanTorrents() {
167        if (Lock()) {
168                TRApplication *app = dynamic_cast<TRApplication*>(be_app);
169                BEntry *torrentEntry = new BEntry();
170                status_t err;
171               
172                if (app->TorrentDir()->InitCheck() == B_OK) {
173                        err = app->TorrentDir()->Rewind();
174                        while (err == B_OK) {
175                                err = app->TorrentDir()->GetNextEntry(torrentEntry, true);
176                                if (err != B_ENTRY_NOT_FOUND) {
177                                        AddEntry(torrentEntry);
178                                }
179                        }
180                }
181                delete torrentEntry;
182                Unlock();
183        }
184}
185
186
187/**
188 * Adds the file specified by *torrent to the Transmission engine.
189 * Then adds a new TRTransfer item in the transfers list.
190 * This item holds cached information about the torrent entry and node.
191 * These TRTransmission items are _NOT_ guaranteed to render the entry
192 * they were created from.
193 */
194void TRWindow::AddEntry(BEntry *torrent) {
195        node_ref node;
196        if (torrent->GetNodeRef(&node) == B_OK) {
197                if (watch_node(&node, B_WATCH_NAME, this) == B_OK) {
198                        BPath path;
199                        torrent->GetPath(&path);
200                       
201                        // Try adding the torrent to the engine.
202                        int error;
203                        tr_torrent_t *nTorrent;
204                        nTorrent = tr_torrentInit(engine, path.Path(), GetFolder().String(),
205                                TR_FLAG_PAUSED, &error);
206                        if (nTorrent != NULL && Lock()) { // Success. Add the TRTorrent item.
207                                transfers->AddItem(new TRTransfer(path.Path(), node, nTorrent));
208                               
209                                bool autoStart = true;
210                               
211                                // Decide if we should auto-start this torrent or not.
212                                BString prefName("download.");
213                                prefName << path.Path() << ".running";
214                               
215                                Prefs *prefs = new Prefs(TRANSMISSION_SETTINGS);
216                                if (prefs->FindBool(prefName.String(), &autoStart) != B_OK) {
217                                        autoStart = true;
218                                }
219                                delete prefs;
220                               
221                                if (autoStart) {
222                                        // Start the newly added torrent.
223                                        worker_info *startData = (worker_info*)calloc(1, sizeof(worker_info));
224                                        startData->window = this;
225                                        startData->torrent = nTorrent;
226                                        thread_id start_thread = spawn_thread(TRWindow::AsynchStartTorrent, "BirthCanal",
227                                                                              B_NORMAL_PRIORITY, (void *)startData);
228                                        if (!((start_thread) < B_OK)) {
229                                                resume_thread(start_thread);
230                                        } else { // Fallback and start the old way.
231                                                StartTorrent(startData->torrent);
232                                                free(startData);
233                                        }
234                                }
235                                Unlock();
236                        } else {
237                                bool duplicate = false;
238                                TRTransfer* tr;
239                                for (int32 i = 0; i < transfers->CountItems(); i++) {
240                                        tr = (TRTransfer*)transfers->ItemAt(i);
241                                        if (tr->GetCachedNodeRef() == node) {
242                                                duplicate = true;
243                                        }
244                                }
245                                if (!duplicate) {
246                                        BString errmsg("An error occurred trying to read ");
247                                        char namebuf[B_FILE_NAME_LENGTH];
248                                        torrent->GetName(namebuf);
249                                        errmsg << namebuf;
250                                        errmsg << ".";
251                                       
252                                        BAlert *error = new BAlert("Error Opening Torrent", 
253                                               errmsg.String(), 
254                                               "Ok", NULL, NULL, 
255                                               B_WIDTH_AS_USUAL, B_WARNING_ALERT);
256                                        error->Go();
257                                        torrent->Remove();
258                                }
259                        }
260                }
261        }
262}
263
264
265void TRWindow::MessageReceived(BMessage *msg) {
266        /*
267         * The only messages we receive from the node_monitor are if we need to
268         * stop watching the node. Basically, if it's been moved or removed we stop.
269         */
270        if (msg->what == B_NODE_MONITOR) {
271                node_ref node;
272                ino_t fromDir;
273                ino_t toDir;
274                int32 opcode;
275               
276                if ((msg->FindInt32("opcode", &opcode) == B_OK) &&
277                        (msg->FindInt64("node", &node.node) == B_OK) &&
278                    (msg->FindInt32("device", &node.device) == B_OK))
279                {
280                        bool stop = (opcode == B_ENTRY_REMOVED);
281                       
282                        if (stop) {
283                                msg->FindInt64("directory", &toDir);
284                        } else { // It must have moved.
285                                stop = ((msg->FindInt64("from directory", &fromDir) == B_OK) &&
286                                        (msg->FindInt64("to directory", &toDir) == B_OK) &&
287                                        (toDir != fromDir));
288                        }
289                       
290                        if (stop) {
291                                watch_node(&node, B_STOP_WATCHING, this);
292                               
293                                /* Find the full path from the TRTorrents.
294                                 * The index of the TRTorrent that is caching the information
295                                 * IS NOT the index of the torrent in the engine. These are
296                                 * Totally decoupled, due to the way transmission is written.
297                                 */
298                                remove_info *removeData = (remove_info*)calloc(1, sizeof(remove_info));
299                                removeData->window = this;
300                                TRTransfer* item;
301                                for (int32 i = 0; i < transfers->CountItems(); i++) {
302                                        item = (TRTransfer*)transfers->ItemAt(i);
303                                        if (item->GetCachedNodeRef() == node) {
304                                                strcpy(removeData->path, item->GetCachedPath());
305                                        }
306                                }
307                               
308                                transfers->DoForEach(TRWindow::RemovePath, (void *)removeData);
309                               
310                                free(removeData);
311                        }
312                }
313        } else if (msg->what == TR_INFO) {
314                // Display an Info Window.
315                TRTransfer *transfer = dynamic_cast<TRTransfer*>(transfers->ItemAt(transfers->CurrentSelection()));
316                const tr_stat_t *s = tr_torrentStat(transfer->GetTorrent());
317                const tr_info_t *i = tr_torrentInfo(transfer->GetTorrent());
318               
319                TRInfoWindow *info = new TRInfoWindow(s, i, tr_torrentGetFolder(transfer->GetTorrent()));
320                info->MoveTo(Frame().LeftTop() + BPoint(20, 25));
321                info->Show();
322        } else if (msg->what == TR_SELECT) {
323                // Setup the Torrent Menu enabled / disabled state.
324                int32 selection;
325                msg->FindInt32("index", &selection);
326                UpdateList(selection, true);
327        } else if (msg->what == TR_RESUME) {
328                worker_info *startData = (worker_info*)calloc(1, sizeof(worker_info));
329                startData->window = this;
330                startData->torrent = (dynamic_cast<TRTransfer*>(transfers->ItemAt(transfers->CurrentSelection())))->GetTorrent();
331                thread_id start_thread = spawn_thread(TRWindow::AsynchStartTorrent, "BirthCanal",
332                                                      B_NORMAL_PRIORITY, (void *)startData);
333                if (!((start_thread) < B_OK)) {
334                        resume_thread(start_thread);
335                } else { // Fallback and start the old way.
336                        StartTorrent(startData->torrent);
337                        free(startData);
338                }
339        } else if (msg->what == TR_PAUSE) {
340                worker_info *stopData = (worker_info*)calloc(1, sizeof(worker_info));
341                stopData->window = this;
342                stopData->torrent = (dynamic_cast<TRTransfer*>(transfers->ItemAt(transfers->CurrentSelection())))->GetTorrent();
343                thread_id stop_thread = spawn_thread(TRWindow::AsynchStopTorrent, "InUtero",
344                                                     B_NORMAL_PRIORITY, (void *)stopData);
345                if (!((stop_thread) < B_OK)) {
346                        resume_thread(stop_thread);
347                } else { // Fallback and stop it the old way.
348                        StopTorrent(stopData->torrent);
349                        free(stopData);
350                }
351        } else if (msg->what == TR_REMOVE) {
352                int32 index = transfers->CurrentSelection();
353               
354                // Remove the file from the filesystem.
355                TRTransfer *item = (TRTransfer*)transfers->RemoveItem(index);
356                tr_torrentClose(item->GetTorrent());
357                BEntry *entry = new BEntry(item->GetCachedPath(), true);
358                entry->Remove();
359                delete entry;
360                delete item;
361               
362               
363               
364                UpdateList(transfers->CurrentSelection(), true);
365        } else if (msg->what == B_SIMPLE_DATA) {
366                be_app->RefsReceived(msg);
367        }
368               
369        BWindow::MessageReceived(msg);
370}
371
372/**
373 * Handles QuitRequests.
374 * Displays a BAlert asking if the user really wants to quit if torrents are running.
375 * If affimative, then we'll stop all the running torrents.
376 */
377bool TRWindow::QuitRequested() {
378        if (stopping)
379                return true;
380
381        bool quit = false;
382        int running;
383       
384        quit_info *quitData = (quit_info*)calloc(1, sizeof(quit_info));
385        quitData->running = 0;
386        transfers->DoForEach(TRWindow::CheckQuitStatus, (void *)quitData);
387        running = quitData->running;
388        free(quitData);
389       
390        if (running > 0) {
391                BString quitMsg("");
392                quitMsg << "There's " << running << " torrent";
393                if (running > 1) {
394                        quitMsg << "s";
395                }
396                quitMsg << " currently running.\n"
397                        << "What would you like to do?";
398               
399                BAlert *confirmQuit = new BAlert("Confirm Quit", quitMsg.String(),
400                                                 "Cancel", "Quit", NULL,
401                                                 B_WIDTH_AS_USUAL, B_WARNING_ALERT);
402                quit = (confirmQuit->Go() == 1);
403        } else {
404                quit = true;
405        }
406       
407        if (quit) {
408                Prefs *prefs = new Prefs(TRANSMISSION_SETTINGS);
409                prefs->SetRect("window.frame", Frame());
410               
411                BString strItem("");
412                for (int i = 0; i < transfers->CountItems(); i++) {
413                        strItem = "download.";
414                        tr_torrent_t *torrent = (dynamic_cast<TRTransfer*>(transfers->ItemAt(i)))->GetTorrent(); 
415                        const tr_info_t *info = tr_torrentInfo(torrent);
416                        const tr_stat_t *stat = tr_torrentStat(torrent);
417                       
418                        strItem << info->torrent << ".running";
419                        if (stat->status & (TR_STATUS_CHECK_WAIT | TR_STATUS_CHECK | TR_STATUS_DOWNLOAD | TR_STATUS_SEED)) {
420                                prefs->SetBool(strItem.String(), true);
421                                tr_torrentStop(torrent);
422                        } else {
423                                prefs->SetBool(strItem.String(), false);
424                        }
425                }
426                delete prefs;
427               
428                if (running > 0) {
429                        stopping = true;
430                        BAlert *waiting = new BAlert("Stopping Torrents", "Waiting for torrents to stop...", "Quit");
431                        quitter = new BInvoker(new BMessage(B_QUIT_REQUESTED), BMessenger(be_app));
432                        waiting->Go(quitter);
433                        quit = false;
434                } else {
435                        be_app->PostMessage(new BMessage(B_QUIT_REQUESTED));
436                }
437        }
438        return quit;
439}
440
441void TRWindow::FrameResized(float, float) {
442        transfers->Invalidate();
443}
444
445
446/**
447 * Called from the StopTorrent thread.
448 */
449void TRWindow::StopTorrent(tr_torrent_t *torrent) {
450        tr_torrentStop(torrent);
451       
452        UpdateList(transfers->CurrentSelection(), true);
453}
454
455BString TRWindow::GetFolder(void) {
456        // Read the settings.
457        BString folder("");
458        Prefs *prefs = new Prefs(TRANSMISSION_SETTINGS);
459        if (prefs->FindString("download.folder", &folder) != B_OK) {
460                prefs->SetString("download.folder", "/boot/home/Downloads");
461                folder << "/boot/home/Downloads";
462        }
463        delete prefs;
464        return folder;
465}
466
467/**
468 * Called from StartTorrent thread.
469 */
470void TRWindow::StartTorrent(tr_torrent_t *torrent) {
471        tr_torrentSetFolder(torrent, GetFolder().String());
472        tr_torrentStart(torrent);
473       
474        if (transfers->CurrentSelection() >= 0) {
475                UpdateList(transfers->CurrentSelection(), true);
476        }
477}
478
479/**
480 * Called from the be_app Pulse();
481 * This will update the data structures that the TRTorrents use to render,
482 * and invalidate the view.
483 */
484void TRWindow::UpdateList(int32 selection = -1, bool menus = true) {
485        if (stopping)
486        {
487                quit_info *quitData = (quit_info*)calloc(1, sizeof(quit_info));
488                quitData->running = 0;
489                transfers->DoForEach(TRWindow::CheckQuitStatus, (void *)quitData);
490                if (quitData->running == 0)
491                        be_app->PostMessage(new BMessage(B_QUIT_REQUESTED));
492                free(quitData);
493        }
494
495        update_info *upData = (update_info*)calloc(1, sizeof(update_info));
496        upData->running = false;
497        upData->selected = selection;
498        upData->invalid = 0;
499       
500        transfers->DoForEach(TRWindow::UpdateStats, (void *)upData);
501       
502        if (menus) {
503                KeyMenuBar()->FindItem(TR_INFO)->SetEnabled(selection >= 0);
504                KeyMenuBar()->FindItem(TR_RESUME)->SetEnabled(selection >= 0 && !upData->running);
505                KeyMenuBar()->FindItem(TR_PAUSE)->SetEnabled(selection >= 0 && upData->running);
506                KeyMenuBar()->FindItem(TR_REMOVE)->SetEnabled(selection >= 0 && !upData->running);
507        }
508       
509        if (upData->invalid > 0 && transfers->LockLooper()) {
510                transfers->Invalidate();
511                transfers->UnlockLooper();
512        }
513       
514        free(upData);
515}
516
517
518
519/**
520 * Thread Function to stop Torrents. This can be expensive and causes the event loop to
521 * choke.
522 */
523int32 TRWindow::AsynchStopTorrent(void *data) {
524        worker_info* stopData = (worker_info*)data;
525        stopData->window->StopTorrent(stopData->torrent);
526        free(stopData);
527        return B_OK;
528}
529
530/**
531 * Thread Function to start Torrents. This can be expensive and causes the event loop to
532 * choke.
533 */
534int32 TRWindow::AsynchStartTorrent(void *data) {
535        worker_info* startData = (worker_info*)data;
536        startData->window->StartTorrent(startData->torrent);
537        free(startData);
538        return B_OK;
539}
540
541/**
542 * Invoked by DoForEach upon the transfers list. This will
543 * remove the item that is caching the path specified by
544 * path.
545 */
546bool TRWindow::RemovePath(BListItem *item, void *data) {
547        remove_info* removeData = (remove_info*)data;
548        TRTransfer *transfer = dynamic_cast<TRTransfer*>(item);
549       
550        if (strcmp(transfer->GetCachedPath(), removeData->path) == 0) {
551                removeData->window->transfers->RemoveItem(transfer);
552                return true;
553        }
554        return false;
555}
556
557/**
558 * Invoked during QuitRequested, iterates all Transfers and
559 * checks to see how many are running.
560 */
561bool TRWindow::CheckQuitStatus(BListItem *item, void *data) {
562        quit_info* quitData = (quit_info*)data;
563        TRTransfer *transfer = dynamic_cast<TRTransfer*>(item);
564       
565        if (transfer->IsRunning()) {
566                quitData->running++;
567        }
568        return false;
569}
570
571/**
572 * Invoked during UpdateList()
573 */
574bool TRWindow::UpdateStats(BListItem *item, void *data) {
575        update_info* upData = (update_info*)data;
576        TRTransfer *transfer = dynamic_cast<TRTransfer*>(item);
577       
578        int32 index = transfers->IndexOf(transfer);
579        if (transfer->UpdateStatus(tr_torrentStat(transfer->GetTorrent()), index % 2 == 0)) {
580                upData->invalid++;
581        }
582       
583        if (index == upData->selected) {
584                upData->running = transfer->IsRunning();
585        }
586       
587        return false;
588}
Note: See TracBrowser for help on using the repository browser.