source: trunk/beos/TRWindow.cpp @ 2633

Last change on this file since 2633 was 2633, checked in by joshe, 15 years ago

Oops, remove extra paren.

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