6 "github.com/anacrolix/torrent/bencode"
15 //----------------------------------------------------------------------------
17 //----------------------------------------------------------------------------
19 // The Builder type is responsible for .torrent files construction. Just
20 // instantiate it, call necessary methods and then call the .Build method. While
21 // waiting for completion you can use 'status' channel to get status reports.
24 filesmap map[string]bool
27 // Adds a file to the builder queue. You may add one or more files.
28 func (b *Builder) AddFile(filename string) {
29 if b.filesmap == nil {
30 b.filesmap = make(map[string]bool)
33 filename, err := filepath.Abs(filename)
37 b.filesmap[filename] = true
40 // Defines a name of the future torrent file. For single file torrents it's the
41 // recommended name of the contained file. For multiple files torrents it's the
42 // recommended name of the directory in which all of them will be
43 // stored. Calling this function is not required. In case if no name was
44 // specified, the builder will try to automatically assign it. It will use the
45 // name of the file if there is only one file in the queue or it will try to
46 // find the rightmost common directory of all the queued files and use its name as
47 // a torrent name. In case if name cannot be assigned automatically, it will use
48 // "unknown" as a torrent name.
49 func (b *Builder) SetName(name string) {
53 // Sets the length of a piece in the torrent file in bytes. The default is
55 func (b *Builder) SetPieceLength(length int64) {
56 b.piece_length = length
59 // Sets the "private" flag. The default is false.
60 func (b *Builder) SetPrivate(v bool) {
64 // Add announce URL group. TODO: better explanation.
65 func (b *Builder) AddAnnounceGroup(group []string) {
66 b.announce_list = append(b.announce_list, group)
69 // Sets creation date. The default is time.Now() when the .Build method was
71 func (b *Builder) SetCreationDate(date time.Time) {
72 b.creation_date = date
75 // Sets the comment. The default is no comment.
76 func (b *Builder) SetComment(comment string) {
80 // Sets the "created by" parameter. The default is "libtorgo".
81 func (b *Builder) SetCreatedBy(createdby string) {
82 b.created_by = createdby
85 // Sets the "encoding" parameter. The default is "UTF-8".
86 func (b *Builder) SetEncoding(encoding string) {
90 // Add WebSeed URL to the list.
91 func (b *Builder) AddWebSeedURL(url string) {
92 b.urls = append(b.urls, url)
95 // Finalizes the Builder state and makes a Batch out of it. After calling that
96 // method, Builder becomes empty and you can use it to create another Batch if
98 func (b *Builder) Submit() (*Batch, error) {
99 err := b.check_parameters()
106 batch_state: b.batch_state,
109 const non_regular = os.ModeDir | os.ModeSymlink |
110 os.ModeDevice | os.ModeNamedPipe | os.ModeSocket
112 // convert a map to a slice, calculate sizes and split paths
114 batch.files = make([]file, 0, 10)
115 for f, _ := range b.filesmap {
117 fi, err := os.Stat(f)
122 if fi.Mode()&non_regular != 0 {
123 return nil, errors.New(f + " is not a regular file")
127 file.splitpath = split_path(f)
128 file.size = fi.Size()
129 batch.files = append(batch.files, file)
130 batch.total_size += file.size
133 // find the rightmost common directory
134 if len(batch.files) == 1 {
135 sp := batch.files[0].splitpath
136 batch.default_name = sp[len(sp)-1]
138 common := batch.files[0].splitpath
139 for _, f := range batch.files {
140 if len(common) > len(f.splitpath) {
141 common = common[:len(f.splitpath)]
144 for i, n := 0, len(common); i < n; i++ {
145 if common[i] != f.splitpath[i] {
151 if len(common) == 0 {
156 if len(common) == 0 {
157 return nil, errors.New("no common rightmost folder was found for a set of queued files")
160 // found the common folder, let's strip that part from splitpath
161 // and setup the default name
162 batch.default_name = common[len(common)-1]
164 lcommon := len(common)
165 for i := range batch.files {
167 f.splitpath = f.splitpath[lcommon:]
170 // and finally sort the files
171 sort.Sort(file_slice(batch.files))
174 // reset the builder state
175 b.batch_state = batch_state{}
181 func (b *Builder) set_defaults() {
182 if b.piece_length == 0 {
183 b.piece_length = 256 * 1024
186 if b.creation_date.IsZero() {
187 b.creation_date = time.Now()
190 if b.created_by == "" {
191 b.created_by = "libtorgo"
194 if b.encoding == "" {
199 func (b *Builder) check_parameters() error {
200 // should be at least one file
201 if len(b.filesmap) == 0 {
202 return errors.New("no files were queued")
205 // let's clean up the announce_list
206 newal := make([][]string, 0, len(b.announce_list))
207 for _, ag := range b.announce_list {
208 ag = remove_empty_strings(ag)
210 // discard empty announce groups
214 newal = append(newal, ag)
216 b.announce_list = newal
217 if len(b.announce_list) == 0 {
218 return errors.New("no announce groups were specified")
221 // and clean up the urls
222 b.urls = remove_empty_strings(b.urls)
227 //----------------------------------------------------------------------------
229 //----------------------------------------------------------------------------
231 // Batch represents a snapshot of a builder state, ready for transforming it
232 // into a torrent file. Note that Batch contains two accessor methods you might
233 // be interested in. The TotalSize is the total size of all the files queued for
234 // hashing, you will use it for status reporting. The DefaultName is an
235 // automatically determined name of the torrent metainfo, you might want to use
236 // it for naming the .torrent file itself.
244 // Get a total size of all the files queued for hashing. Useful in conjunction
245 // with status reports.
246 func (b *Batch) TotalSize() int64 {
250 // Get an automatically determined name of the future torrent metainfo. You can
251 // use it for a .torrent file in case user hasn't provided it specifically.
252 func (b *Batch) DefaultName() string {
253 return b.default_name
256 // Starts a process of building the torrent file. This function does everything
257 // in a separate goroutine and uses up to 'nworkers' of goroutines to perform
258 // SHA1 hashing. Therefore it will return almost immedately. It returns two
259 // channels, the first one is for completion awaiting, the second one is for
260 // getting status reports. Status report is a number of bytes hashed, you can
261 // get the total amount of bytes by inspecting the Batch.TotalSize method return
263 func (b *Batch) Start(w io.Writer, nworkers int) (<-chan error, <-chan int64) {
268 completion := make(chan error)
269 status := make(chan int64)
273 workers := make([]*worker, nworkers)
274 free_workers := make(chan *worker, nworkers)
275 for i := 0; i < nworkers; i++ {
276 workers[i] = new_worker(free_workers)
278 stop_workers := func() {
279 for _, w := range workers {
282 for _, w := range workers {
287 // prepare files for reading
288 fr := files_reader{files: b.files}
289 npieces := b.total_size/b.piece_length + 1
290 b.pieces = make([]byte, 20*npieces)
293 // read all the pieces passing them to workers for hashing
295 for i := int64(0); i < npieces; i++ {
297 data = make([]byte, b.piece_length)
300 nr, err := fr.Read(data)
302 // EOF is not an eror if it was the last piece
316 // cut the data slice to the amount of actual data read
319 data = w.queue(data, b.pieces[20*i:20*i+20])
321 // update and try to send the status report
323 hashed += int64(len(data))
324 data = data[:cap(data)]
327 case status <- hashed:
334 // at this point the hash was calculated and we're ready to
335 // write the torrent file
336 err := b.write_torrent(w)
343 return completion, status
346 func (b *Batch) write_torrent(w io.Writer) error {
348 td.Announce = b.announce_list[0][0]
349 if len(b.announce_list) != 1 || len(b.announce_list[0]) != 1 {
350 td.AnnounceList = b.announce_list
352 td.CreationDate = b.creation_date.Unix()
353 td.Comment = b.comment
354 td.CreatedBy = b.created_by
355 td.Encoding = b.encoding
357 case len(b.urls) == 0:
358 case len(b.urls) == 1:
359 td.URLList = b.urls[0]
364 td.Info.PieceLength = b.piece_length
365 td.Info.Pieces = b.pieces
367 td.Info.Name = b.default_name
369 td.Info.Name = b.name
371 if len(b.files) == 1 {
372 td.Info.Length = b.files[0].size
374 td.Info.Files = make([]FileInfo, len(b.files))
375 for i, f := range b.files {
376 td.Info.Files[i] = FileInfo{
382 td.Info.Private = b.private
384 e := bencode.NewEncoder(w)
388 //----------------------------------------------------------------------------
390 //----------------------------------------------------------------------------
392 // splits path into components (dirs and files), works only on absolute paths
393 func split_path(path string) []string {
395 s := make([]string, 0, 5)
399 dir, file = filepath.Split(filepath.Clean(dir))
407 for i, n := 0, len(s)/2; i < n; i++ {
409 s[i], s[i2] = s[i2], s[i]
415 // just a common data between the Builder and the Batch
416 type batch_state struct {
421 announce_list [][]string
422 creation_date time.Time
435 type file_slice []file
437 func (s file_slice) Len() int { return len(s) }
438 func (s file_slice) Less(i, j int) bool { return s[i].abspath < s[j].abspath }
439 func (s file_slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
441 func remove_empty_strings(slice []string) []string {
443 for i, n := 0, len(slice); i < n; i++ {
453 //----------------------------------------------------------------------------
455 //----------------------------------------------------------------------------
466 // returns existing 'data'
467 func (w *worker) queue(data, sha1 []byte) []byte {
475 func (w *worker) stop() {
479 func (w *worker) wait_for_stop() {
483 func new_worker(out chan<- *worker) *worker {
485 msgbox: make(chan bool),
498 copy(w.sha1, sha1[:])
506 //----------------------------------------------------------------------------
508 //----------------------------------------------------------------------------
510 type files_reader struct {
517 func (f *files_reader) Read(data []byte) (int, error) {
518 if f.cur >= len(f.files) {
528 file := &f.files[f.cur]
529 if f.curfile == nil {
531 f.curfile, err = os.Open(file.abspath)
537 // we need to read up to 'len(data)' bytes from current file
538 n := int64(len(data))
540 // unless there is not enough data in this file
541 if file.size-f.off < n {
542 n = file.size - f.off
545 // if there is no data in this file, try next one
547 err := f.curfile.Close()
555 if f.cur >= len(f.files) {
561 // read, handle errors
562 nr, err := f.curfile.Read(data[:n])
569 // ok, we've read nr bytes out of len(data), cut the data slice