source: trunk/src/halTorrentInternal.hpp @ 686

Revision 686, 38.2 KB checked in by Eoin, 11 years ago (diff)

More advanced state machine being implemented for torrent_internal.

Line 
1
2//         Copyright Eóin O'Callaghan 2006 - 2008.
3// Distributed under the Boost Software License, Version 1.0.
4//    (See accompanying file LICENSE_1_0.txt or copy at
5//          http://www.boost.org/LICENSE_1_0.txt)
6
7#pragma once
8
9#include "halTorrentDefines.hpp"
10
11#ifndef HAL_TORRENT_STATE_LOGGING
12#       define TORRENT_STATE_LOG(s)
13#else
14#       include "../halEvent.hpp"
15#       define TORRENT_STATE_LOG(msg) \
16        hal::event_log.post(boost::shared_ptr<hal::EventDetail>( \
17                        new hal::EventMsg(msg, hal::event_logger::torrent_dev)))
18#endif
19
20#pragma warning (push, 1)
21#       include <libtorrent/file.hpp>
22#       include <libtorrent/hasher.hpp>
23#       include <libtorrent/storage.hpp>
24#       include <libtorrent/file_pool.hpp>
25#       include <libtorrent/alert_types.hpp>
26#       include <libtorrent/entry.hpp>
27#       include <libtorrent/bencode.hpp>
28#       include <libtorrent/session.hpp>
29#       include <libtorrent/ip_filter.hpp>
30#       include <libtorrent/torrent_handle.hpp>
31#       include <libtorrent/peer_connection.hpp>
32#       include <libtorrent/extensions/metadata_transfer.hpp>
33#       include <libtorrent/extensions/ut_pex.hpp>
34#pragma warning (pop)
35
36#include <boost/tuple/tuple.hpp>
37#include <boost/enable_shared_from_this.hpp>
38#include <boost/multi_index_container.hpp>
39#include <boost/multi_index/ordered_index.hpp>
40#include <boost/multi_index/indexed_by.hpp>
41#include <boost/multi_index/identity.hpp>
42#include <boost/multi_index/member.hpp>
43#include <boost/multi_index/tag.hpp>
44
45#include <boost/statechart/event.hpp>
46#include <boost/statechart/state_machine.hpp>
47#include <boost/statechart/simple_state.hpp>
48#include <boost/statechart/transition.hpp>
49#include <boost/statechart/state.hpp>
50#include <boost/statechart/custom_reaction.hpp>
51
52#include <boost/mpl/list.hpp>
53
54#include "halIni.hpp"
55#include "halTypes.hpp"
56#include "halSignaler.hpp"
57#include "halTorrentIntEvents.hpp"
58
59namespace hal
60{
61class TorrentInternalOld;
62class torrent_internal;
63}
64
65BOOST_CLASS_VERSION(hal::TorrentInternalOld, 9)
66BOOST_CLASS_VERSION(hal::torrent_internal, 2)
67
68namespace hal
69{
70
71namespace libt = libtorrent;
72namespace sc = boost::statechart;
73namespace mpl = boost::mpl;
74
75
76inline libt::entry haldecode(const wpath &file) 
77{
78        fs::ifstream ifs(file, fs::ifstream::binary);
79        if (ifs.is_open()) 
80        {
81                ifs.unsetf(fs::ifstream::skipws);
82                return libt::bdecode(std::istream_iterator<char>(ifs), std::istream_iterator<char>());
83        }
84        else return libt::entry();
85}
86
87inline bool halencode(const wpath &file, const libt::entry &e) 
88{
89        fs::ofstream ofs(file, fs::ofstream::binary);
90
91        if (!ofs.is_open()) 
92                return false;
93       
94        libt::bencode(std::ostream_iterator<char>(ofs), e);
95        return true;
96}
97
98inline path path_to_utf8(const wpath& wp)
99{
100        return path(to_utf8(wp.string()));
101}
102
103inline wpath path_from_utf8(const path& p)
104{
105        return wpath(from_utf8(p.string()));
106}
107
108inline std::pair<std::string, std::string> extract_names(const wpath &file)
109{
110        if (fs::exists(file)) 
111        {       
112                libt::torrent_info info(path_to_utf8(file));
113
114                std::string name = info.name(); 
115                std::string filename = name;
116
117                if (!boost::find_last(filename, ".torrent")) 
118                                filename += ".torrent";
119               
120                event_log.post(shared_ptr<EventDetail>(new EventMsg(
121                        hal::wform(L"Loaded names: %1%, %2%") % from_utf8(name) % from_utf8(filename))));
122
123                return std::make_pair(name, filename);
124        }
125        else
126                return std::make_pair("", "");
127}
128
129inline libt::storage_mode_t hal_allocation_to_libt(bit::allocations alloc)
130{
131        switch (alloc)
132        {
133        case bit::full_allocation:
134                return libt::storage_mode_allocate;
135        case bit::compact_allocation:
136                return libt::storage_mode_compact;
137        case bit::sparse_allocation:
138        default:
139                return libt::storage_mode_sparse;
140        }
141}
142
143class invalid_torrent : public std::exception
144{
145public:
146        invalid_torrent(const wstring& who) :
147                who_(who)
148        {}
149       
150        virtual ~invalid_torrent() throw () {}
151
152        wstring who() const throw ()
153        {
154                return who_;
155        }       
156       
157private:
158        wstring who_;   
159};
160       
161template<typename T>
162class transfer_tracker
163{
164public:
165        transfer_tracker() :
166                total_(0),
167                total_offset_(0)
168        {}
169       
170        transfer_tracker(T total) :
171                total_(total),
172                total_offset_(0)
173        {}
174       
175        transfer_tracker(T total, T offset) :
176                total_(total),
177                total_offset_(offset)
178        {}
179       
180        void reset(T total) const
181        {
182                total_ = total;
183                total_offset_ = 0;
184        }
185       
186        T update(T rel_total) const
187        {
188                total_ += (rel_total - total_offset_);
189                total_offset_ = rel_total;
190               
191                return total_;
192        }
193       
194        void setOffset(T offset) const
195        {
196                total_offset_ = offset;
197        }
198       
199        operator T() const { return total_; }
200       
201        friend class boost::serialization::access;
202        template<class Archive>
203        void serialize(Archive& ar, const unsigned int version)
204        {
205                ar & boost::serialization::make_nvp("total", total_);
206        }
207       
208private:
209        mutable T total_;
210        mutable T total_offset_;
211};
212
213class duration_tracker
214{
215public:
216        duration_tracker() :
217                total_(boost::posix_time::time_duration(0,0,0,0), 
218                        boost::posix_time::time_duration(0,0,0,0))
219        {}
220       
221        boost::posix_time::time_duration update() const
222        {
223                if (start_.is_not_a_date_time()) 
224                        start_ = boost::posix_time::second_clock::universal_time();
225
226                if (static_cast<boost::posix_time::time_duration>(total_).is_special()) 
227                        total_.setOffset(boost::posix_time::time_duration(0,0,0,0));
228               
229                return total_.update(boost::posix_time::second_clock::universal_time() - start_);
230        }
231       
232        void reset() const
233        {
234                total_.setOffset(boost::posix_time::time_duration(0,0,0,0));
235                start_ = boost::posix_time::second_clock::universal_time();
236        }
237       
238        friend class boost::serialization::access;
239        template<class Archive>
240        void serialize(Archive& ar, const unsigned int version)
241        {
242                ar & boost::serialization::make_nvp("total", total_);
243        }
244       
245        operator boost::posix_time::time_duration() const { return total_; }
246       
247private:
248        transfer_tracker<boost::posix_time::time_duration> total_;     
249        mutable boost::posix_time::ptime start_;               
250};
251       
252struct signalers
253{
254        signaler<> torrent_finished;
255
256        boost::signal<void ()> torrent_paused;
257        boost::signal<void ()> resume_data;
258};
259
260class torrent_internal;
261typedef shared_ptr<torrent_internal> torrent_internal_ptr;
262
263struct torrent_standalone :
264        public hal::IniBase<torrent_standalone>
265{
266        typedef torrent_standalone thisClass;
267        typedef hal::IniBase<thisClass> iniClass;
268
269        torrent_standalone() :
270                iniClass("torrent")
271        {}
272
273        torrent_standalone(torrent_internal_ptr t) :
274                iniClass("torrent"),
275                torrent(t),
276                save_time(pt::second_clock::universal_time())
277        {}
278
279        torrent_internal_ptr torrent;
280        pt::ptime save_time;
281
282    friend class boost::serialization::access;
283    template<class Archive>
284    void serialize(Archive& ar, const unsigned int version)
285    {
286                ar & boost::serialization::make_nvp("torrent", torrent);
287                ar & boost::serialization::make_nvp("save_time", save_time);
288    }
289};
290
291struct out_of_session;
292
293class torrent_internal :
294        public boost::enable_shared_from_this<torrent_internal>,
295        public sc::state_machine<torrent_internal, out_of_session>
296{
297        friend class bit_impl; 
298        friend class bit::torrent::exec_around_ptr::proxy;
299
300        friend struct out_of_session;   
301        friend struct in_the_session;
302       
303        friend struct active;
304        friend struct pausing;
305        friend struct paused;
306        friend struct stopping;
307        friend struct stopped;
308        friend struct resume_data_idling;
309
310public:
311        #define TORRENT_INTERNALS_DEFAULTS \
312                original_filename_(L""), \
313                transfer_limit_(std::pair<float, float>(-1, -1)), \
314                connections_(-1), \
315                uploads_(-1), \
316                ratio_(0), \
317                resolve_countries_(true), \
318                total_uploaded_(0), \
319                total_base_(0), \
320                progress_(0), \
321                managed_(false), \
322                start_time_(boost::posix_time::second_clock::universal_time()), \
323                in_session_(false), \
324                queue_position_(0)
325               
326        torrent_internal() :   
327                TORRENT_INTERNALS_DEFAULTS,
328                allocation_(bit::sparse_allocation)
329        {
330                state(torrent_details::torrent_stopped);
331                TORRENT_STATE_LOG(L"Torrent state machine initiate");
332                initiate();
333        }
334       
335                torrent_internal(wpath filename, wpath saveDirectory, bit::allocations alloc, wpath move_to_directory=L"") :
336                TORRENT_INTERNALS_DEFAULTS,
337                save_directory_(saveDirectory.string()),
338                move_to_directory_(move_to_directory.string()),
339                allocation_(alloc)
340        {
341                state(torrent_details::torrent_stopped);
342                assert(the_session_);   
343               
344                TORRENT_STATE_LOG(L"Torrent state machine initiate");
345                initiate();
346
347                prepare(filename);
348        }
349
350        #undef TORRENT_INTERNALS_DEFAULTS
351       
352        torrent_details_ptr get_torrent_details_ptr()
353        {       
354                mutex_t::scoped_lock l(mutex_);
355
356                try
357                {
358
359                if (in_session())
360                {
361                        status_memory_ = handle_.status();
362                        progress_ = status_memory_.progress;
363
364                        queue_position_ = handle_.queue_position();
365                }
366                else
367                {
368                        // Wipe these cause they don't make sense for a non-active torrent.
369                       
370                        status_memory_.download_payload_rate = 0;
371                        status_memory_.upload_payload_rate = 0;
372                        status_memory_.next_announce = boost::posix_time::seconds(0);           
373                }
374               
375                wstring state_str;
376               
377                switch (state())
378                {
379                case torrent_details::torrent_paused:
380                        state_str = app().res_wstr(HAL_TORRENT_PAUSED);
381                        break;
382                       
383                case torrent_details::torrent_pausing:
384                        state_str = app().res_wstr(HAL_TORRENT_PAUSING);
385                        break;
386                       
387                case torrent_details::torrent_stopped:
388                        state_str = app().res_wstr(HAL_TORRENT_STOPPED);
389                        break;
390                       
391                case torrent_details::torrent_stopping:
392                        state_str = app().res_wstr(HAL_TORRENT_STOPPING);
393                        break;
394                       
395                default:
396                        switch (status_memory_.state)
397                        {
398                        case libt::torrent_status::queued_for_checking:
399                                state_str = app().res_wstr(HAL_TORRENT_QUEUED_CHECKING);
400                                break;
401                        case libt::torrent_status::checking_files:
402                                state_str = app().res_wstr(HAL_TORRENT_CHECKING_FILES);
403                                break;
404//                      case libt::torrent_status::connecting_to_tracker:
405//                              state = app().res_wstr(HAL_TORRENT_CONNECTING);
406//                              break;
407                        case libt::torrent_status::downloading_metadata:
408                                state_str = app().res_wstr(HAL_TORRENT_METADATA);
409                                break;
410                        case libt::torrent_status::downloading:
411                                state_str = app().res_wstr(HAL_TORRENT_DOWNLOADING);
412                                break;
413                        case libt::torrent_status::finished:
414                                state_str = app().res_wstr(HAL_TORRENT_FINISHED);
415                                break;
416                        case libt::torrent_status::seeding:
417                                state_str = app().res_wstr(HAL_TORRENT_SEEDING);
418                                break;
419                        case libt::torrent_status::allocating:
420                                state_str = app().res_wstr(HAL_TORRENT_ALLOCATING);
421                                break;
422                        }       
423                }
424               
425                pt::time_duration td(pt::pos_infin);
426               
427                if (status_memory_.download_payload_rate != 0)
428                {
429                        td = boost::posix_time::seconds(       
430                                long(float(status_memory_.total_wanted-status_memory_.total_wanted_done) / status_memory_.download_payload_rate));
431                }
432               
433                total_uploaded_ += (status_memory_.total_payload_upload - total_base_);
434                total_base_ = status_memory_.total_payload_upload;
435               
436                uploaded_.update(status_memory_.total_upload);
437                payload_uploaded_.update(status_memory_.total_payload_upload);
438                downloaded_.update(status_memory_.total_download);
439                payload_downloaded_.update(status_memory_.total_payload_download);
440               
441                if (is_active())
442                {
443                        active_duration_.update();
444                       
445                        if (libt::torrent_status::seeding == status_memory_.state)
446                                seeding_duration_.update();
447                }       
448               
449                boost::tuple<size_t, size_t, size_t, size_t> connections = update_peers();     
450
451                return torrent_details_ptr(new torrent_details(
452                        name_, filename_, 
453                        save_directory().string(), 
454                        state_str, 
455                        hal::from_utf8(status_memory_.current_tracker), 
456                        std::pair<float, float>(
457                                status_memory_.download_payload_rate, 
458                                status_memory_.upload_payload_rate),
459                        progress_, 
460                        status_memory_.distributed_copies, 
461                        status_memory_.total_wanted_done, 
462                        status_memory_.total_wanted, 
463                        uploaded_, payload_uploaded_,
464                        downloaded_, payload_downloaded_, 
465                        connections, 
466                        ratio_, 
467                        td, 
468                        status_memory_.next_announce, 
469                        active_duration_, seeding_duration_, 
470                        start_time_, finish_time_, 
471                        queue_position_,
472                        is_managed()));
473
474                }
475                catch (const libt::invalid_handle&)
476                {
477                        event_log.post(shared_ptr<EventDetail>(
478                                new EventInvalidTorrent(event_logger::critical, event_logger::invalid_torrent, to_utf8(name_), "get_torrent_details_ptr")));
479                }
480                catch (const std::exception& e)
481                {
482                        event_log.post(shared_ptr<EventDetail>(
483                                new EventTorrentException(event_logger::critical, event_logger::torrentException, e.what(), to_utf8(name_), "get_torrent_details_ptr")));
484                }
485               
486                return torrent_details_ptr(new torrent_details(
487                        name_, filename_, 
488                        save_directory().string(), 
489                        app().res_wstr(HAL_TORRENT_STOPPED), 
490                        app().res_wstr(HAL_NA)));
491        }
492
493        void adjust_queue_position(bit::queue_adjustments adjust)
494        {
495                if (in_session() && is_managed())
496                {
497                        switch (adjust)
498                        {
499                        case bit::move_up:
500                                handle_.queue_position_up();
501                                break;
502                        case bit::move_down:
503                                handle_.queue_position_down();
504                                break;
505                        case bit::move_to_top:
506                                handle_.queue_position_top();
507                                break;
508                        case bit::move_to_bottom:
509                                handle_.queue_position_bottom();
510                                break;
511                        };
512                }
513        }
514
515        void set_transfer_speed(float down, float up)
516        {       
517                mutex_t::scoped_lock l(mutex_);
518
519                transfer_limit_ = std::make_pair(down, up);
520               
521                apply_transfer_speed();
522        }
523
524        void set_connection_limit(int maxConn, int maxUpload)           
525        {
526                mutex_t::scoped_lock l(mutex_);
527
528                connections_ = maxConn;
529                uploads_ = maxUpload;
530               
531                apply_connection_limit();
532        }
533
534        std::pair<float, float> get_transfer_speed()
535        {
536                return transfer_limit_;
537        }
538
539        std::pair<int, int> get_connection_limit()
540        {
541                return std::make_pair(connections_, uploads_);
542        }
543       
544        const wstring& name() const { return name_; }
545       
546        void set_ratio(float ratio) 
547        { 
548                if (ratio < 0) ratio = 0;
549                ratio_ = ratio; 
550               
551                apply_ratio();
552        }
553       
554        float get_ratio()
555        {
556                return ratio_;
557        }
558
559        void set_managed(bool m)
560        {
561                mutex_t::scoped_lock l(mutex_);
562                managed_ = m;
563               
564                if (in_session()) handle_.auto_managed(managed_);
565        }
566
567        bool is_managed()
568        {
569                if (in_session())
570                {
571                        managed_ = handle_.is_auto_managed();
572                }
573
574                return managed_;
575        }
576       
577        void add_to_session(bool paused = false)
578        {
579                try
580                {
581                HAL_DEV_MSG(hal::wform(L"add_to_session() paused=%1%") % paused);
582
583                mutex_t::scoped_lock l(mutex_); 
584                assert(the_session_ != 0);
585
586                process_event( ev_add_to_session(paused) );
587               
588                assert(in_session());
589                HAL_DEV_MSG(L"Added to session");
590
591                if (handle_.is_paused())
592                        state(torrent_details::torrent_paused); 
593
594                }
595                catch(std::exception& e)
596                {
597                        hal::event_log.post(boost::shared_ptr<hal::EventDetail>(
598                                new hal::EventStdException(event_logger::critical, e, L"add_to_session"))); 
599                }
600        }
601       
602        bool remove_from_session(bool write_data=true)
603        {
604                try
605                {
606                HAL_DEV_MSG(hal::wform(L"remove_from_session() write_data=%1%") % write_data);
607
608                mutex_t::scoped_lock l(mutex_);
609
610                if (!in_session())
611                {
612                        in_session_ = false;
613                        HAL_DEV_MSG(L"Was not is session!");
614
615                        return false;
616                }
617               
618                process_event( ev_remove_from_session(write_data) );
619
620                return true;
621
622                }
623                catch(std::exception& e)
624                {
625                        hal::event_log.post(boost::shared_ptr<hal::EventDetail>(
626                                new hal::EventStdException(event_logger::critical, e, L"remove_from_session()"))); 
627                        return false;
628                }
629        }
630
631        void remove_torrent()
632        {
633                the_session_->remove_torrent(handle_);
634                in_session_ = false;
635
636                assert(!in_session()); 
637        }
638       
639        bool in_session() const
640        { 
641                mutex_t::scoped_lock l(mutex_);
642
643                return (in_session_ && the_session_ != 0 && handle_.is_valid());
644        }
645
646        void resume()
647        {
648                mutex_t::scoped_lock l(mutex_);
649                HAL_DEV_MSG(hal::wform(L"resume() - %1%") % name_);
650
651                if (state() == torrent_details::torrent_stopped)
652                {       
653                        add_to_session(false);
654                        assert(in_session());                   
655                }
656                else
657                {
658                        assert(in_session());
659                        handle_.resume();
660                }       
661               
662                state(torrent_details::torrent_active);                 
663                //assert(!handle_.is_paused());
664        }
665       
666        void pause()
667        {
668                mutex_t::scoped_lock l(mutex_);
669                HAL_DEV_MSG(hal::wform(L"pause() - %1%") % name_);
670
671                if (state() == torrent_details::torrent_stopped)
672                {       
673                        add_to_session(true);
674
675                        assert(in_session());
676                        assert(handle_.is_paused());
677                }
678                else
679                {
680                        assert(in_session());
681
682                        HAL_DEV_MSG(hal::wform(L"pause() - handle_.pause()"));
683                        handle_.pause();
684
685                        signaler_wrapper<>* sig = new signaler_wrapper<>(bind(&torrent_internal::completed_pause, this));
686                        signals().torrent_paused.connect(bind(&signaler_wrapper<>::operator(), sig));
687
688                        state(torrent_details::torrent_pausing);
689                }                       
690        }
691       
692        void stop()
693        {
694                mutex_t::scoped_lock l(mutex_);
695                HAL_DEV_MSG(hal::wform(L"stop() - %1%") % name_);
696
697                HAL_DEV_MSG(hal::wform(L"stop() requesting"));
698
699                if (state() != torrent_details::torrent_stopped)
700                {
701                        if (state() == torrent_details::torrent_active)
702                        {
703                                assert(in_session());
704                                assert(!(handle_.is_paused()));
705
706                                signaler_wrapper<>* sig = new signaler_wrapper<>(bind(&torrent_internal::completed_stop, this));
707                                signals().torrent_paused.connect(bind(&signaler_wrapper<>::operator(), sig));
708                               
709                                HAL_DEV_MSG(hal::wform(L"stop() - handle_.pause()"));
710                                handle_.pause();
711
712                                state(torrent_details::torrent_stopping);
713                        }
714                        else if (state() == torrent_details::torrent_paused)
715                        {                       
716                                remove_from_session();
717                                state(torrent_details::torrent_stopped);                               
718                        }
719                }
720        }
721
722        void set_state_stopped()
723        {
724                state(torrent_details::torrent_stopped);
725        }
726
727        void force_recheck()
728        {
729                mutex_t::scoped_lock l(mutex_);         
730                HAL_DEV_MSG(L"force_recheck()");
731
732                switch (state())
733                {
734                case torrent_details::torrent_stopped:
735                        clear_resume_data();
736                        resume();
737                        break;
738
739                case torrent_details::torrent_stopping:
740                case torrent_details::torrent_pausing:
741//                      signals().torrent_paused.disconnect_all_once();
742
743                case torrent_details::torrent_active:
744//                      signals().torrent_paused.disconnect_all_once();
745//                      signals().torrent_paused.connect_once(bind(&torrent_internal::handle_recheck, this));
746                        handle_.pause();
747                        state(torrent_details::torrent_pausing);
748                        break;
749
750                default:
751                        assert(false);
752                };
753        }
754       
755        void write_resume_data(const libt::entry& ent)
756        {                                       
757                HAL_DEV_MSG(L"write_resume_data()");
758
759                wpath resume_dir = hal::app().get_working_directory()/L"resume";
760               
761                if (!exists(resume_dir))
762                        create_directory(resume_dir);
763
764                boost::filesystem::ofstream out(resume_dir/(name_ + L".fastresume"), std::ios_base::binary);
765                out.unsetf(std::ios_base::skipws);
766                bencode(std::ostream_iterator<char>(out), ent);
767
768                HAL_DEV_MSG(L"Written!");
769        }
770
771        void save_resume_data()
772        {
773                handle_.save_resume_data();
774        }
775       
776        void clear_resume_data()
777        {
778                wpath resume_file = hal::app().get_working_directory()/L"resume"/filename_;
779               
780                if (exists(resume_file))
781                        remove(resume_file);
782
783//              resumedata_ = libt::entry();
784        }
785
786        const wpath get_save_directory()
787        {
788                return save_directory_;
789        }
790
791        void set_save_directory(wpath s, bool force=false)
792        {
793                if (in_session() && !is_finished() &&
794                                s != path_from_utf8(handle_.save_path()))
795                {
796                        handle_.move_storage(path_to_utf8(s));
797                        save_directory_ = s;
798                }
799                else if (!in_session() && force)
800                {
801                        save_directory_ = s;
802                }
803        }
804
805        const wpath get_move_to_directory()
806        {
807                return move_to_directory_;
808        }
809       
810        void set_move_to_directory(wpath m)
811        {
812                if (is_finished() && !m.empty())
813                {
814                        if (m != path_from_utf8(handle_.save_path()))
815                        {
816                                handle_.move_storage(path_to_utf8(m));
817                                save_directory_ = move_to_directory_ = m;
818                        }
819                }
820                else
821                {
822                        move_to_directory_ = m;
823                }
824        }
825
826        bool is_finished()
827        {
828                if (in_session())
829                {
830                        libt::torrent_status::state_t s = handle_.status().state;
831
832                        return (s == libt::torrent_status::seeding ||
833                                                s == libt::torrent_status::finished);
834                }
835                else return false;
836        }
837       
838        void finished()
839        {
840                if (finish_time_.is_special())
841                        finish_time_ = boost::posix_time::second_clock::universal_time();
842
843                if (is_finished())
844                {
845                        if (!move_to_directory_.empty() && 
846                                        move_to_directory_ !=  path_from_utf8(handle_.save_path()))
847                        {
848                                handle_.move_storage(path_to_utf8(move_to_directory_));
849                                save_directory_ = move_to_directory_;
850                        }
851                }
852        }
853       
854        bool is_active() const { return state() == torrent_details::torrent_active; }
855
856        unsigned get_state()
857        {
858                return state_;
859        }
860       
861        void set_tracker_login(wstring username, wstring password)
862        {
863                tracker_username_ = username;
864                tracker_password_ = password;
865               
866                apply_tracker_login();
867        }       
868       
869        std::pair<wstring, wstring> get_tracker_login() const
870        {
871                return make_pair(tracker_username_, tracker_password_);
872        }
873       
874        const wstring& filename() const { return filename_; }
875       
876        const wstring& original_filename() const { return original_filename_; }
877       
878        const libt::torrent_handle& handle() const { return handle_; }
879
880        void reset_trackers()
881        {
882                if (in_session())
883                {
884                        handle_.replace_trackers(torrent_trackers_);           
885                        trackers_.clear();
886                }
887        }
888       
889        void set_trackers(const std::vector<tracker_detail>& tracker_details)
890        {
891                trackers_.clear();
892                trackers_.assign(tracker_details.begin(), tracker_details.end());
893               
894                apply_trackers();
895        }
896       
897        const std::vector<tracker_detail>& get_trackers()
898        {
899                if (trackers_.empty() && info_memory_)
900                {
901                        std::vector<libt::announce_entry> trackers = info_memory_->trackers();
902                       
903                        foreach (const libt::announce_entry& entry, trackers)
904                        {
905                                trackers_.push_back(
906                                        tracker_detail(hal::from_utf8(entry.url), entry.tier));
907                        }
908                }               
909                return trackers_;
910        }
911       
912        void set_file_priorities(std::vector<int> fileIndices, int priority)
913        {
914                if (!file_priorities_.empty())
915                {
916                        foreach(int i, fileIndices)
917                                file_priorities_[i] = priority;
918                               
919                        apply_file_priorities();
920                }
921        }
922
923        const wpath& save_directory() { return save_directory_; }
924       
925    friend class boost::serialization::access;
926    template<class Archive>
927    void serialize(Archive& ar, const unsigned int version)
928    {
929                using boost::serialization::make_nvp;
930
931                if (version > 1) {
932                        ar & make_nvp("transfer_limits", transfer_limit_);
933                        ar & make_nvp("connection_limits", connections_);
934                        ar & make_nvp("upload_limits", uploads_);       
935
936                        ar & make_nvp("name", name_);
937                        ar & make_nvp("filename", filename_);   
938
939                        ar & make_nvp("ratio", ratio_); 
940                        ar & make_nvp("progress", progress_);
941                        ar & make_nvp("state", state_);
942//                      ar & make_nvp("compact_storage", compact_storage_);     
943                        ar & make_nvp("allocation_type", allocation_); 
944                        ar & make_nvp("resolve_countries", resolve_countries_); 
945
946                        ar & make_nvp("tracker_username", tracker_username_);
947                        ar & make_nvp("tracker_password", tracker_password_);
948                        ar & make_nvp("trackers", trackers_);
949
950                        ar & make_nvp("save_directory", save_directory_);
951                        ar & make_nvp("move_to_directory", move_to_directory_);
952                       
953                        ar & make_nvp("payload_uploaded", payload_uploaded_);
954                        ar & make_nvp("payload_downloaded", payload_downloaded_);
955                        ar & make_nvp("uploaded", uploaded_);
956                        ar & make_nvp("downloaded", downloaded_);                       
957                                       
958                        ar & make_nvp("file_priorities", file_priorities_);
959                       
960                        ar & make_nvp("start_time", start_time_);
961                        ar & make_nvp("finish_time", finish_time_);
962                        ar & make_nvp("active_duration", active_duration_);
963                        ar & make_nvp("seeding_duration", seeding_duration_);
964                        ar & make_nvp("managed", managed_);
965                                       
966                } 
967                else 
968                {
969                    ar & make_nvp("transferLimit", transfer_limit_);
970                        ar & make_nvp("connections", connections_);
971                        ar & make_nvp("uploads", uploads_);                     
972                        ar & make_nvp("filename", filename_);   
973
974                        wstring s;
975                        ar & make_nvp("saveDirectory", s);
976                        save_directory_ = s;
977
978                        if (version == 2) {
979                                wstring m;
980                                ar & make_nvp("moveToDirectory", m);
981                                move_to_directory_ = m;
982                        } else {
983                                move_to_directory_ = save_directory_;
984                        }
985                       
986                        ar & make_nvp("payload_uploaded_", payload_uploaded_);
987                        ar & make_nvp("payload_downloaded_", payload_downloaded_);
988                        ar & make_nvp("uploaded_", uploaded_);
989                        ar & make_nvp("downloaded_", downloaded_);     
990                        ar & make_nvp("ratio", ratio_); 
991                        ar & make_nvp("trackerUsername", tracker_username_);
992                        ar & make_nvp("trackerPassword", tracker_password_);
993                       
994                        ar & make_nvp("state", state_);
995                        ar & make_nvp("trackers", trackers_);
996                       
997                        ar & make_nvp("resolve_countries", resolve_countries_);
998                       
999                        ar & make_nvp("file_priorities", file_priorities_);
1000                       
1001                        ar & make_nvp("start_time", start_time_);
1002                        ar & make_nvp("activeDuration", active_duration_);
1003                        ar & make_nvp("seedingDuration", seeding_duration_);
1004                       
1005                        ar & make_nvp("name", name_);
1006                        ar & make_nvp("compactStorage", compact_storage_);
1007                        ar & make_nvp("finish_time", finish_time_);
1008                       
1009                        ar & make_nvp("progress", progress_);
1010        }
1011    }
1012
1013        void set_entry_data(boost::intrusive_ptr<libt::torrent_info> metadata, libtorrent::entry resumedata)
1014        {               
1015                info_memory_ = metadata;
1016//              resumedata_ = resumedata;
1017        }
1018
1019        std::vector<libt::peer_info>& peers() { return peers_; }
1020       
1021        boost::tuple<size_t, size_t, size_t, size_t> update_peers()
1022        {
1023                if (in_session())
1024                        handle_.get_peer_info(peers_);
1025               
1026                size_t totalPeers = 0;
1027                size_t peersConnected = 0;
1028                size_t totalSeeds = 0;
1029                size_t seedsConnected = 0;
1030               
1031                foreach (libt::peer_info& peer, peers_) 
1032                {
1033                        float speedSum = peer.down_speed + peer.up_speed;
1034                       
1035                        if (!(peer.flags & libt::peer_info::seed))
1036                        {
1037                                ++totalPeers;
1038                               
1039                                if (speedSum > 0)
1040                                        ++peersConnected;
1041                        }
1042                        else
1043                        {
1044                                ++totalSeeds;
1045                               
1046                                if (speedSum > 0)
1047                                        ++seedsConnected;
1048                        }
1049                }       
1050               
1051                return boost::make_tuple(totalPeers, peersConnected, totalSeeds, seedsConnected);
1052        }
1053       
1054        void get_peer_details(peer_details_vec& peer_details) const
1055        {
1056                if (in_session())
1057                {
1058                        foreach (libt::peer_info peer, peers_) 
1059                        {
1060                                peer_details.push_back(peer);
1061                        }       
1062                }
1063        }
1064
1065        void get_file_details(file_details_vec& files)
1066        {
1067                if (file_details_memory_.empty())
1068                {
1069                        boost::intrusive_ptr<libt::torrent_info> info = info_memory();
1070                        std::vector<libt::file_entry> files;
1071                       
1072                        std::copy(info->begin_files(), info->end_files(), 
1073                                std::back_inserter(files));                                     
1074                               
1075                        if (file_priorities_.size() != files.size())
1076                        {
1077                                file_priorities_.clear();
1078                                file_priorities_.assign(files.size(), 1);
1079                        }
1080                       
1081                        for(size_t i=0, e=files.size(); i<e; ++i)
1082                        {
1083                                wstring fullPath = hal::from_utf8(files[i].path.string());
1084                                boost::int64_t size = static_cast<boost::int64_t>(files[i].size);
1085                               
1086                                file_details_memory_.push_back(file_details(fullPath, size, 0, file_priorities_[i], i));
1087                        }       
1088                }               
1089               
1090                if (in_session())
1091                {                       
1092                        std::vector<libt::size_type> fileProgress;                     
1093                        handle_.file_progress(fileProgress);
1094                       
1095                        for(size_t i=0, e=file_details_memory_.size(); i<e; ++i)
1096                                file_details_memory_[i].progress =  fileProgress[i];                   
1097                }
1098
1099                for(size_t i=0, e=file_details_memory_.size(); i<e; ++i)
1100                        file_details_memory_[i].priority =  file_priorities_[i];
1101               
1102                files = file_details_memory_;
1103        }
1104       
1105        void prepare(wpath filename)
1106        {
1107                mutex_t::scoped_lock l(mutex_);
1108               
1109                if (fs::exists(filename)) 
1110                        info_memory_ = new libt::torrent_info(path_to_utf8(filename));
1111               
1112                extract_names(info_memory());                   
1113               
1114                const wpath resumeFile = hal::app().get_working_directory()/L"resume"/filename_;
1115                const wpath torrentFile = hal::app().get_working_directory()/L"torrents"/filename_;
1116               
1117                event_log.post(shared_ptr<EventDetail>(new EventMsg(
1118                        hal::wform(L"File: %1%, %2%.") % resumeFile % torrentFile)));
1119               
1120        //      if (exists(resumeFile))
1121        //              resumedata_ = haldecode(resumeFile);
1122
1123                if (!exists(hal::app().get_working_directory()/L"torrents"))
1124                        create_directory(hal::app().get_working_directory()/L"torrents");
1125
1126                if (!exists(torrentFile))
1127                        copy_file(filename.string(), torrentFile);
1128
1129                if (!fs::exists(save_directory_))
1130                        fs::create_directory(save_directory_);
1131
1132                // These here should not make state changes based on torrent
1133                // session status since it has not been initialized yet.
1134                if (state_ == torrent_details::torrent_stopping)
1135                        state(torrent_details::torrent_stopped);
1136                else if (state_ == torrent_details::torrent_pausing)
1137                        state(torrent_details::torrent_paused);
1138        }
1139
1140        void set_resolve_countries(bool b)
1141        {
1142                resolve_countries_ = b;
1143                apply_resolve_countries();
1144        }
1145       
1146        void extract_names(boost::intrusive_ptr<libt::torrent_info> metadata)
1147        {
1148                mutex_t::scoped_lock l(mutex_);
1149                               
1150                name_ = hal::from_utf8_safe(metadata->name());
1151               
1152                filename_ = name_;
1153                if (!boost::find_last(filename_, L".torrent")) 
1154                                filename_ += L".torrent";
1155               
1156                event_log.post(shared_ptr<EventDetail>(new EventMsg(
1157                        hal::wform(L"Loaded names: %1%, %2%") % name_ % filename_)));
1158        }
1159       
1160        boost::intrusive_ptr<libt::torrent_info> info_memory()
1161        {
1162                if (!info_memory_) 
1163                        info_memory_ = 
1164                                boost::intrusive_ptr<libt::torrent_info>(new libt::torrent_info(path_to_utf8(filename())));
1165               
1166                return info_memory_;
1167        }
1168       
1169        signalers& signals()
1170        {
1171                mutex_t::scoped_lock l(mutex_);
1172                return signals_;
1173        }
1174
1175private:       
1176        signalers signals_;
1177
1178        void apply_settings()
1179        {               
1180                apply_transfer_speed();
1181                apply_connection_limit();
1182                apply_ratio();
1183                apply_trackers();
1184                apply_tracker_login();
1185                apply_file_priorities();
1186                apply_resolve_countries();
1187        }
1188       
1189        void apply_transfer_speed()
1190        {
1191                mutex_t::scoped_lock l(mutex_);
1192                if (in_session())
1193                {
1194                        int down = (transfer_limit_.first > 0) ? static_cast<int>(transfer_limit_.first*1024) : -1;
1195                        handle_.set_download_limit(down);
1196                       
1197                        int up = (transfer_limit_.second > 0) ? static_cast<int>(transfer_limit_.second*1024) : -1;
1198                        handle_.set_upload_limit(up);
1199
1200                        HAL_DEV_MSG(hal::wform(L"Applying Transfer Speed %1% - %2%") % down % up);
1201                }
1202        }
1203
1204        void apply_connection_limit()
1205        {
1206                mutex_t::scoped_lock l(mutex_);
1207                if (in_session())
1208                {
1209                        handle_.set_max_connections(connections_);
1210                        handle_.set_max_uploads(uploads_);
1211
1212                        HAL_DEV_MSG(hal::wform(L"Applying Connection Limit %1% - %2%") % connections_ % uploads_);
1213                }
1214        }
1215       
1216        void apply_ratio()
1217        { 
1218                mutex_t::scoped_lock l(mutex_);
1219                if (in_session())
1220                {
1221                        handle_.set_ratio(ratio_);
1222
1223                        HAL_DEV_MSG(hal::wform(L"Applying Ratio %1%") % ratio_);
1224                }
1225        }
1226       
1227        void apply_trackers()
1228        {
1229                mutex_t::scoped_lock l(mutex_);
1230                if (in_session())
1231                {
1232                        if (torrent_trackers_.empty())
1233                                torrent_trackers_ = handle_.trackers();
1234                       
1235                        if (!trackers_.empty())
1236                        {
1237                                std::vector<libt::announce_entry> trackers;
1238                               
1239                                foreach (const tracker_detail& tracker, trackers_)
1240                                {
1241                                        trackers.push_back(
1242                                                libt::announce_entry(hal::to_utf8(tracker.url)));
1243                                        trackers.back().tier = tracker.tier;
1244                                }
1245                                handle_.replace_trackers(trackers);
1246                        }
1247                       
1248                        HAL_DEV_MSG(L"Applying Trackers");
1249                }
1250        }
1251       
1252        void apply_tracker_login()
1253        {
1254                mutex_t::scoped_lock l(mutex_);
1255                if (in_session())
1256                {
1257                        if (tracker_username_ != L"")
1258                        {
1259                                handle_.set_tracker_login(hal::to_utf8(tracker_username_),
1260                                        hal::to_utf8(tracker_password_));
1261                        }
1262
1263                        HAL_DEV_MSG(hal::wform(L"Applying Tracker Login User: %1%, Pass: %2%")
1264                                % tracker_username_ % tracker_password_ );
1265                }
1266        }
1267       
1268        void apply_file_priorities()
1269        {               
1270                mutex_t::scoped_lock l(mutex_);
1271                if (in_session()) 
1272                {
1273                        if (!file_priorities_.empty())
1274                                handle_.prioritize_files(file_priorities_);
1275                       
1276                        HAL_DEV_MSG(L"Applying File Priorities");
1277                }
1278        }       
1279       
1280        void apply_resolve_countries()
1281        {
1282                mutex_t::scoped_lock l(mutex_);
1283                if (in_session())
1284                {
1285                        handle_.resolve_countries(resolve_countries_);
1286                       
1287                        HAL_DEV_MSG(hal::wform(L"Applying Resolve Countries %1%") % resolve_countries_);
1288                }
1289        }
1290       
1291        bool completed_pause()
1292        {
1293                mutex_t::scoped_lock l(mutex_);
1294                assert(in_session());
1295//              assert(handle_.is_paused());   
1296
1297                HAL_DEV_MSG(L"completed_pause()");
1298
1299                save_resume_data();                             
1300                state(torrent_details::torrent_paused);
1301
1302                return true;
1303        }
1304
1305        bool completed_stop()
1306        {
1307                mutex_t::scoped_lock l(mutex_);
1308                assert(in_session());
1309//              assert(handle_.is_paused());                   
1310               
1311                if (remove_from_session())
1312                {
1313                        assert(!in_session());
1314                        HAL_DEV_MSG(L"completed_stop()");
1315                }
1316
1317                state(torrent_details::torrent_stopped);
1318
1319                return true;
1320        }
1321
1322        void handle_recheck()
1323        {
1324                mutex_t::scoped_lock l(mutex_);
1325                state(torrent_details::torrent_stopped);
1326
1327                remove_from_session(false);
1328                assert(!in_session());
1329
1330                clear_resume_data();
1331
1332                resume();
1333                assert(in_session());
1334
1335                HAL_DEV_MSG(L"handle_recheck()");
1336        }
1337
1338        void state(unsigned s)
1339        {
1340                switch (s)
1341                {
1342                case torrent_details::torrent_stopped:
1343                        HAL_DEV_MSG(L"state() - stopped");
1344                        break;
1345                case torrent_details::torrent_stopping:
1346                        HAL_DEV_MSG(L"state() - stopping");
1347                        break;
1348                case torrent_details::torrent_pausing:
1349                        HAL_DEV_MSG(L"state() - pausing");
1350                        break;
1351                case torrent_details::torrent_active:
1352                        HAL_DEV_MSG(L"state() - active");
1353                        break;
1354                case torrent_details::torrent_paused:
1355                        HAL_DEV_MSG(L"state() - paused");
1356                        break;
1357                default:
1358                        HAL_DEV_MSG(L"state() - unknown");
1359                        break;
1360                };
1361                state_ = s;
1362        }       
1363       
1364        unsigned state() const 
1365        { 
1366                if (in_session())
1367                {
1368                        if (handle_.is_paused())
1369                        {
1370                                if (state_ != torrent_details::torrent_paused)
1371                                {                       
1372                                        HAL_DEV_MSG(L"Should really be paused!");
1373                                        state_ = torrent_details::torrent_paused;
1374                                }
1375                        }
1376                        else                           
1377                        {                       
1378                                if (state_ != torrent_details::torrent_active &&
1379                                        state_ != torrent_details::torrent_pausing &&
1380                                        state_ != torrent_details::torrent_stopping)
1381                                {                       
1382                                        HAL_DEV_MSG(L"Should really be active!");
1383                                        state_ = torrent_details::torrent_active;
1384                                }
1385                        }                       
1386                }
1387                else
1388                {
1389                        if (state_ != torrent_details::torrent_stopped)
1390                        {                       
1391                                HAL_DEV_MSG(L"Should really be stopped!");
1392                                state_ = torrent_details::torrent_stopped;
1393                        }
1394                }
1395               
1396                return state_; 
1397        }
1398               
1399        static libt::session* the_session_;     
1400        mutable mutex_t mutex_;
1401
1402//      torrent_state_machine machine_;
1403       
1404        std::pair<float, float> transfer_limit_;
1405       
1406        mutable unsigned state_;
1407        int connections_;
1408        int uploads_;
1409        bool in_session_;
1410        float ratio_;
1411        bool resolve_countries_;
1412       
1413        wstring filename_;
1414        wstring name_;
1415        wpath save_directory_;
1416        wpath move_to_directory_;
1417        wstring original_filename_;
1418        libt::torrent_handle handle_;   
1419       
1420//      boost::intrusive_ptr<libt::torrent_info> metadata_;
1421//      boost::shared_ptr<libt::entry> resumedata_;
1422       
1423        wstring tracker_username_;     
1424        wstring tracker_password_;
1425       
1426        boost::int64_t total_uploaded_;
1427        boost::int64_t total_base_;
1428       
1429        transfer_tracker<boost::int64_t> payload_uploaded_;
1430        transfer_tracker<boost::int64_t> payload_downloaded_;
1431        transfer_tracker<boost::int64_t> uploaded_;
1432        transfer_tracker<boost::int64_t> downloaded_;
1433       
1434        pt::ptime start_time_;
1435        pt::ptime finish_time_;
1436        duration_tracker active_duration_;
1437        duration_tracker seeding_duration_;
1438       
1439        std::vector<tracker_detail> trackers_;
1440        std::vector<libt::announce_entry> torrent_trackers_;
1441        std::vector<libt::peer_info> peers_;   
1442        std::vector<int> file_priorities_;
1443       
1444        float progress_;
1445       
1446        boost::intrusive_ptr<libt::torrent_info> info_memory_;
1447        libt::torrent_status status_memory_;
1448        file_details_vec file_details_memory_;
1449       
1450        int queue_position_;
1451        bool compact_storage_;
1452        bool managed_;
1453        bit::allocations allocation_;
1454};
1455
1456typedef std::map<std::string, TorrentInternalOld> TorrentMap;
1457typedef std::pair<std::string, TorrentInternalOld> TorrentPair;
1458
1459class torrent_manager : 
1460        public hal::IniBase<torrent_manager>
1461{
1462        typedef torrent_manager thisClass;
1463        typedef hal::IniBase<thisClass> iniClass;
1464
1465        struct torrent_holder
1466        {
1467                mutable torrent_internal_ptr torrent;
1468               
1469                wstring filename;
1470                wstring name;           
1471               
1472                torrent_holder()
1473                {}
1474               
1475                explicit torrent_holder(torrent_internal_ptr t) :
1476                        torrent(t), filename(torrent->filename()), name(torrent->name())
1477                {}
1478                                               
1479                friend class boost::serialization::access;
1480                template<class Archive>
1481                void serialize(Archive& ar, const unsigned int version)
1482                {
1483                        using boost::serialization::make_nvp;
1484
1485                        ar & make_nvp("torrent", torrent);
1486                        ar & make_nvp("filename", filename);
1487                        ar & make_nvp("name", name);
1488                }
1489        };
1490       
1491        struct by_filename{};
1492        struct by_name{};
1493       
1494        typedef boost::multi_index_container<
1495                torrent_holder,
1496                boost::multi_index::indexed_by<
1497                        boost::multi_index::ordered_unique<
1498                                boost::multi_index::tag<by_filename>,
1499                                boost::multi_index::member<
1500                                        torrent_holder, wstring, &torrent_holder::filename> 
1501                                >,
1502                        boost::multi_index::ordered_unique<
1503                                boost::multi_index::tag<by_name>,
1504                                boost::multi_index::member<
1505                                        torrent_holder, wstring, &torrent_holder::name> 
1506                                >
1507                >
1508        > torrent_multi_index;
1509       
1510public:
1511        typedef torrent_multi_index::index<by_filename>::type torrent_by_filename;
1512        typedef torrent_multi_index::index<by_name>::type torrent_by_name;
1513       
1514        torrent_manager(ini_file& ini) :
1515                iniClass("bittorrent", "torrent_manager", ini)
1516        {}
1517
1518        std::pair<torrent_by_name::iterator, bool> insert(const torrent_holder& h)
1519        {
1520                return torrents_.get<by_name>().insert(h);
1521        }
1522       
1523        std::pair<torrent_by_name::iterator, bool> insert(torrent_internal_ptr t)
1524        {
1525                return insert(torrent_holder(t));
1526        }
1527
1528        torrent_internal_ptr get_by_file(const wstring& filename)
1529        {
1530                torrent_by_filename::iterator it = torrents_.get<by_filename>().find(filename);
1531               
1532                if (it != torrents_.get<by_filename>().end() && (*it).torrent)
1533                {
1534                        return (*it).torrent;
1535                }
1536               
1537                throw invalid_torrent(filename);
1538        }
1539       
1540        torrent_internal_ptr get(const wstring& name)
1541        {
1542                torrent_by_name::iterator it = torrents_.get<by_name>().find(name);
1543               
1544                if (it != torrents_.get<by_name>().end() && (*it).torrent)
1545                {
1546                        return (*it).torrent;
1547                }
1548               
1549                throw invalid_torrent(name);
1550        }
1551       
1552        torrent_by_name::iterator erase(torrent_by_name::iterator where)
1553        {
1554                return torrents_.get<by_name>().erase(where);
1555        }
1556       
1557        size_t size()
1558        {
1559                return torrents_.size();
1560        }
1561       
1562        size_t erase(const wstring& name)
1563        {
1564                return torrents_.get<by_name>().erase(name);
1565        }
1566       
1567        bool exists(const wstring& name)
1568        {
1569                torrent_by_name::iterator it = torrents_.get<by_name>().find(name);
1570               
1571                if (it != torrents_.get<by_name>().end())
1572                        return true;
1573                else
1574                        return false;
1575        }
1576       
1577        torrent_by_name::iterator begin() { return torrents_.get<by_name>().begin(); }
1578        torrent_by_name::iterator end() { return torrents_.get<by_name>().end(); }
1579       
1580        friend class boost::serialization::access;
1581        template<class Archive>
1582        void serialize(Archive& ar, const unsigned int version)
1583        {
1584                ar & boost::serialization::make_nvp("torrents", torrents_);
1585        }       
1586       
1587private:
1588        torrent_multi_index torrents_;
1589};
1590
1591} // namespace hal
1592
1593#include "halTorrentIntStates.hpp"
1594
1595BOOST_CLASS_VERSION(hal::torrent_manager::torrent_holder, 1)
Note: See TracBrowser for help on using the repository browser.