source: trunk/beos/TRWindow.cpp @ 17

Last change on this file since 17 was 17, checked in by root, 16 years ago

Update 2005-12-18

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