source: trunk/beos/TRWindow.cpp @ 14

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

Update 2005-12-13

File size: 12.8 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.
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                                // Start the newly added torrent.
163                                worker_info *startData = (worker_info*)calloc(1, sizeof(worker_info));
164                                startData->window = this;
165                                startData->index = tr_torrentCount(engine) - 1;
166                                thread_id start_thread = spawn_thread(TRWindow::AsynchStartTorrent, "BirthCanal",
167                                                                      B_NORMAL_PRIORITY, (void *)startData);
168                                if (!((start_thread) < B_OK)) {
169                                        resume_thread(start_thread);
170                                } else { // Fallback and start the old way.
171                                        StartTorrent(startData->index);
172                                        free(startData);
173                                }
174                                Unlock();
175                        } else {
176                                bool duplicate = false;
177                                TRTransfer* tr;
178                                for (int32 i = 0; i < transfers->CountItems(); i++) {
179                                        tr = (TRTransfer*)transfers->ItemAt(i);
180                                        if (tr->GetCachedNodeRef() == node) {
181                                                duplicate = true;
182                                        }
183                                }
184                                if (!duplicate) {
185                                        BString errmsg("An error occurred trying to read ");
186                                        char namebuf[B_FILE_NAME_LENGTH];
187                                        torrent->GetName(namebuf);
188                                        errmsg << namebuf;
189                                        errmsg << ".";
190                                       
191                                        BAlert *error = new BAlert("Error Opening Torrent", 
192                                               errmsg.String(), 
193                                               "Ok", NULL, NULL, 
194                                               B_WIDTH_AS_USUAL, B_WARNING_ALERT);
195                                        error->Go();
196                                        torrent->Remove();
197                                }
198                        }
199                }
200        }
201}
202
203
204void TRWindow::MessageReceived(BMessage *msg) {
205        /*
206         * The only messages we receive from the node_monitor are if we need to
207         * stop watching the node. Basically, if it's been moved or removed we stop.
208         */
209        if (msg->what == B_NODE_MONITOR) {
210                node_ref node;
211                ino_t fromDir;
212                ino_t toDir;
213                int32 opcode;
214               
215                if ((msg->FindInt32("opcode", &opcode) == B_OK) &&
216                        (msg->FindInt64("node", &node.node) == B_OK) &&
217                    (msg->FindInt32("device", &node.device) == B_OK))
218                {
219                        bool stop = (opcode == B_ENTRY_REMOVED);
220                       
221                        if (stop) {
222                                msg->FindInt64("directory", &toDir);
223                        } else { // It must have moved.
224                                stop = ((msg->FindInt64("from directory", &fromDir) == B_OK) &&
225                                        (msg->FindInt64("to directory", &toDir) == B_OK) &&
226                                        (toDir != fromDir));
227                        }
228                       
229                        if (stop) {
230                                watch_node(&node, B_STOP_WATCHING, this);
231                               
232                                /* Find the full path from the TRTorrents.
233                                 * The index of the TRTorrent that is caching the information
234                                 * IS NOT the index of the torrent in the engine. These are
235                                 * Totally decoupled, due to the way transmission is written.
236                                 */
237                                char path[B_FILE_NAME_LENGTH];
238                                TRTransfer* item;
239                                for (int32 i = 0; i < transfers->CountItems(); i++) {
240                                        item = (TRTransfer*)transfers->ItemAt(i);
241                                        if (item->GetCachedNodeRef() == node) {
242                                                strcpy(path, item->GetCachedPath());
243                                        }
244                                }
245                               
246                                // Look for the torrent info in the engine with the matching
247                                // path name.
248                                tr_stat_t *stats;
249                                tr_torrentStat(engine, &stats);
250                                int index;
251                                for (index = 0; index < tr_torrentCount(engine); index++) {
252                                        if (strcmp(stats[index].info.torrent, path) == 0) {
253                                                tr_torrentClose(engine, index);
254                                                transfers->RemoveItem(index);
255                                                break;
256                                        }
257                                }
258                                free(stats);
259                        }
260                }
261        } else if (msg->what == TR_INFO) {
262                // Display an Info Window.
263                tr_stat_t *s;
264                tr_torrentStat(engine, &s);
265               
266                TRInfoWindow *info = new TRInfoWindow(s[transfers->CurrentSelection()]);
267                info->MoveTo(Frame().LeftTop() + BPoint(20, 25));
268                info->Show();
269        } else if (msg->what == TR_SELECT) {
270                // Setup the Torrent Menu enabled / disabled state.
271                int32 selection;
272                msg->FindInt32("index", &selection);
273                UpdateList(selection, true);
274        } else if (msg->what == TR_RESUME) {
275                worker_info *startData = (worker_info*)calloc(1, sizeof(worker_info));
276                startData->window = this;
277                startData->index = (int)transfers->CurrentSelection();
278                thread_id start_thread = spawn_thread(TRWindow::AsynchStartTorrent, "BirthCanal",
279                                                      B_NORMAL_PRIORITY, (void *)startData);
280                if (!((start_thread) < B_OK)) {
281                        resume_thread(start_thread);
282                } else { // Fallback and start the old way.
283                        StartTorrent(startData->index);
284                        free(startData);
285                }
286        } else if (msg->what == TR_PAUSE) {
287                worker_info *stopData = (worker_info*)calloc(1, sizeof(worker_info));
288                stopData->window = this;
289                stopData->index = (int)transfers->CurrentSelection();
290                thread_id stop_thread = spawn_thread(TRWindow::AsynchStopTorrent, "InUtero",
291                                                     B_NORMAL_PRIORITY, (void *)stopData);
292                if (!((stop_thread) < B_OK)) {
293                        resume_thread(stop_thread);
294                } else { // Fallback and stop it the old way.
295                        StopTorrent(stopData->index);
296                        free(stopData);
297                }
298        } else if (msg->what == TR_REMOVE) {
299                int32 index = transfers->CurrentSelection();
300               
301                tr_torrentClose(engine, (int)index);
302               
303                // Remove the file from the filesystem.
304                TRTransfer *item = (TRTransfer*)transfers->RemoveItem(index);
305                BEntry *entry = new BEntry(item->GetCachedPath(), true);
306                entry->Remove();
307                delete entry;
308                delete item;
309               
310                UpdateList(transfers->CurrentSelection(), true);
311        } else if (msg->what == B_SIMPLE_DATA) {
312                be_app->RefsReceived(msg);
313        }
314               
315        BWindow::MessageReceived(msg);
316}
317
318
319bool TRWindow::QuitRequested() {
320        bool quit = false;
321       
322        bool running = false;
323        tr_stat_t *s;
324        int max = tr_torrentStat(engine, &s);
325        for (int i = 0; i < max && !running; i++) {
326                running = (s[i].status &
327                          (TR_STATUS_CHECK | TR_STATUS_DOWNLOAD | TR_STATUS_SEED));
328        }
329        free(s);
330       
331        if (running) {
332                BString quitMsg("");
333                quitMsg << "There's " << max << " torrent";
334                if (max > 1) {
335                        quitMsg << "s";
336                }
337                quitMsg << " currently running.\n"
338                        << "What would you like to do?";
339               
340                BAlert *confirmQuit = new BAlert("Confirm Quit", quitMsg.String(),
341                                                 "Cancel", "Quit", NULL,
342                                                 B_WIDTH_AS_USUAL, B_WARNING_ALERT);
343                quit = (confirmQuit->Go() == 1);
344        } else {
345                quit = true;
346        }
347       
348        if (quit) {
349                Prefs *prefs = new Prefs(TRANSMISSION_SETTINGS);
350                prefs->SetRect("window.frame", Frame());
351                delete prefs;
352               
353                for (int i = 0; i < tr_torrentCount(engine); i++) {
354                        tr_torrentStop(engine, i);
355                }
356               
357                be_app->PostMessage(new BMessage(B_QUIT_REQUESTED));
358        }
359        return quit;
360}
361
362void TRWindow::FrameResized(float width, float height) {
363        transfers->Invalidate();
364}
365
366
367/**
368 * Called from the StopTorrent thread.
369 */
370void TRWindow::StopTorrent(int index) {
371        tr_torrentStop(engine, index);
372       
373        UpdateList(index, true);
374}
375
376/**
377 * Called from StartTorrent thread.
378 */
379void TRWindow::StartTorrent(int index) {
380        // Read the settings.
381        BString folder("");
382        Prefs *prefs = new Prefs(TRANSMISSION_SETTINGS);
383        if (prefs->FindString("download.folder", &folder) != B_OK) {
384                prefs->SetString("download.folder", "/boot/home/Downloads");
385                folder << "/boot/home/Downloads";
386        }
387        tr_torrentSetFolder(engine, index, folder.String());
388        tr_torrentStart(engine, index);
389       
390        if (transfers->CurrentSelection() >= 0) {
391                UpdateList(index, true);
392        }
393       
394        delete prefs;
395}
396
397/**
398 * Called from the be_app Pulse();
399 * This will update the data structures that the TRTorrents use to render,
400 * and invalidate the view.
401 */
402void TRWindow::UpdateList(int32 selection = -1, bool menus = true) {
403        bool running = false;
404       
405        tr_stat_t * s;
406        int i = 0;
407        int max = tr_torrentStat(engine, &s);
408        bool invalid[max];
409       
410        for (i = 0; i < max; i++) {
411                invalid[i] = ((TRTransfer*)transfers->ItemAt(i))->SetStatus(&(s[i]), (i % 2 != 0));
412               
413                if (menus && i == (int)selection) {
414                        running = (s[selection].status & 
415                                  (TR_STATUS_CHECK | TR_STATUS_DOWNLOAD | TR_STATUS_SEED));
416                }
417        }
418        free(s);
419       
420        if (menus) {
421                KeyMenuBar()->FindItem(TR_INFO)->SetEnabled(selection >= 0);
422                KeyMenuBar()->FindItem(TR_RESUME)->SetEnabled(selection >= 0 && !running);
423                KeyMenuBar()->FindItem(TR_PAUSE)->SetEnabled(selection >= 0 && running);
424                KeyMenuBar()->FindItem(TR_REMOVE)->SetEnabled(selection >= 0 && !running);
425        }
426       
427        if (Lock()) {
428                for (i = 0; i < max; i++) {
429                        if (invalid[i]) {
430                                transfers->InvalidateItem(i);
431                        }
432                }
433                Unlock();
434        }
435}
436
437/**
438 * Thread Function to stop Torrents. This can be expensive and causes the event loop to
439 * choke.
440 */
441int32 TRWindow::AsynchStopTorrent(void *data) {
442        worker_info* stopData = (worker_info*)data;
443        stopData->window->StopTorrent(stopData->index);
444        free(stopData);
445        return B_OK;
446}
447
448/**
449 * Thread Function to start Torrents. This can be expensive and causes the event loop to
450 * choke.
451 */
452int32 TRWindow::AsynchStartTorrent(void *data) {
453        worker_info* startData = (worker_info*)data;
454        startData->window->StartTorrent(startData->index);
455        free(startData);
456        return B_OK;
457}
Note: See TracBrowser for help on using the repository browser.