Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

599

600

601

602

603

604

605

606

607

608

609

610

611

612

613

614

615

616

617

618

619

620

621

622

623

624

625

626

627

628

629

630

631

632

633

634

635

636

637

638

639

640

641

642

643

644

645

646

647

648

649

650

651

652

653

654

655

656

657

658

659

660

661

662

663

664

665

666

667

668

669

670

671

672

673

674

675

676

677

678

679

680

681

682

683

684

685

686

687

688

689

690

691

692

693

694

695

696

697

698

699

700

701

702

703

704

705

706

707

708

709

710

711

712

713

714

715

716

717

718

719

720

721

722

723

724

725

726

727

728

729

730

731

732

733

734

735

736

737

738

739

740

741

742

743

744

745

746

747

748

749

750

751

752

753

754

755

756

757

758

759

760

761

762

763

764

765

766

767

768

769

770

771

772

773

774

775

776

777

778

779

780

781

782

783

784

785

786

787

788

789

790

791

792

793

794

795

796

797

798

799

800

801

802

803

804

805

806

807

808

809

810

811

812

813

814

815

816

817

818

819

820

821

822

823

824

825

826

827

828

829

830

831

832

833

834

835

836

837

838

839

840

841

842

843

844

845

846

847

848

849

850

851

852

853

854

855

856

857

858

859

860

861

862

863

864

865

866

867

868

869

870

871

872

873

874

875

876

877

878

879

880

881

882

883

884

885

886

887

888

889

890

891

892

893

894

895

896

897

898

899

900

901

902

903

904

905

906

907

908

909

910

911

912

913

914

915

916

917

918

919

920

921

922

923

924

925

926

# -*- coding: utf-8 -*- 

# 

# Copyright (C) 2011 Nick Lanham <nick@afternight.org> 

# 

# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with 

# the additional special exception to link portions of this program with the OpenSSL library. 

# See LICENSE for more details. 

# 

 

import logging 

from collections import deque 

from sys import maxint 

 

import deluge.component as component 

from deluge.common import FILE_PRIORITY, fdate, fsize, ftime 

from deluge.ui.client import client 

from deluge.ui.console import colors 

from deluge.ui.console.modes import format_utils 

from deluge.ui.console.modes.basemode import BaseMode 

from deluge.ui.console.modes.input_popup import InputPopup 

from deluge.ui.console.modes.popup import MessagePopup, SelectablePopup 

from deluge.ui.console.modes.torrent_actions import ACTION, torrent_actions_popup 

 

try: 

    import curses 

except ImportError: 

    pass 

 

log = logging.getLogger(__name__) 

 

# Big help string that gets displayed when the user hits 'h' 

HELP_STR = """\ 

This screen shows detailed information about a torrent, and also the \ 

information about the individual files in the torrent. 

 

You can navigate the file list with the Up/Down arrows and use space to \ 

collapse/expand the file tree. 

 

All popup windows can be closed/canceled by hitting the Esc key \ 

(you might need to wait a second for an Esc to register) 

 

The actions you can perform and the keys to perform them are as follows: 

 

{!info!}'h'{!normal!} - Show this help 

 

{!info!}'a'{!normal!} - Show torrent actions popup.  Here you can do things like \ 

pause/resume, recheck, set torrent options and so on. 

 

{!info!}'r'{!normal!} - Rename currently highlighted folder or a file. You can't \ 

rename multiple files at once so you need to first clear your selection \ 

with {!info!}'c'{!normal!} 

 

{!info!}'m'{!normal!} - Mark or unmark a file or a folder 

{!info!}'c'{!normal!} - Un-mark all files 

 

{!info!}Space{!normal!} - Expand/Collapse currently selected folder 

 

{!info!}Enter{!normal!} - Show priority popup in which you can set the \ 

download priority of selected files and folders. 

 

{!info!}Left Arrow{!normal!} - Go back to torrent overview. 

""" 

 

 

class TorrentDetail(BaseMode, component.Component): 

    def __init__(self, alltorrentmode, torrentid, stdscr, console_config, encoding=None): 

 

        self.console_config = console_config 

        self.alltorrentmode = alltorrentmode 

        self.torrentid = torrentid 

        self.torrent_state = None 

        self.popup = None 

        self.messages = deque() 

        self._status_keys = ["files", "name", "state", "download_payload_rate", "upload_payload_rate", 

                             "progress", "eta", "all_time_download", "total_uploaded", "ratio", 

                             "num_seeds", "total_seeds", "num_peers", "total_peers", "active_time", 

                             "seeding_time", "time_added", "distributed_copies", "num_pieces", 

                             "piece_length", "download_location", "file_progress", "file_priorities", "message", 

                             "total_wanted", "tracker_host", "owner"] 

 

        self.file_list = None 

        self.current_file = None 

        self.current_file_idx = 0 

        self.file_limit = maxint 

        self.file_off = 0 

        self.more_to_draw = False 

        self.full_names = None 

 

        self.column_string = "" 

        self.files_sep = None 

 

        self.marked = {} 

 

        BaseMode.__init__(self, stdscr, encoding) 

        component.Component.__init__(self, "TorrentDetail", 1, depend=["SessionProxy"]) 

 

        self.column_names = ["Filename", "Size", "Progress", "Priority"] 

        self.__update_columns() 

 

        component.start(["TorrentDetail"]) 

 

        self._listing_start = self.rows // 2 

        self._listing_space = self._listing_start - self._listing_start 

 

        client.register_event_handler("TorrentFileRenamedEvent", self._on_torrentfilerenamed_event) 

        client.register_event_handler("TorrentFolderRenamedEvent", self._on_torrentfolderrenamed_event) 

        client.register_event_handler("TorrentRemovedEvent", self._on_torrentremoved_event) 

 

        curses.curs_set(0) 

        self.stdscr.notimeout(0) 

 

    # component start/update 

    def start(self): 

        component.get("SessionProxy").get_torrent_status(self.torrentid, self._status_keys).addCallback(self.set_state) 

 

    def update(self): 

        component.get("SessionProxy").get_torrent_status(self.torrentid, self._status_keys).addCallback(self.set_state) 

 

    def set_state(self, state): 

        log.debug("got state") 

 

        if state.get("files"): 

            self.full_names = dict([(x["index"], x["path"]) for x in state["files"]]) 

 

        need_prio_update = False 

        if not self.file_list: 

            # don't keep getting the files once we've got them once 

            if state.get("files"): 

                self.files_sep = "{!green,black,bold,underline!}%s" % ( 

                    ("Files (torrent has %d files)" % len(state["files"])).center(self.cols)) 

                self.file_list, self.file_dict = self.build_file_list(state["files"], state["file_progress"], 

                                                                      state["file_priorities"]) 

                self._status_keys.remove("files") 

            else: 

                self.files_sep = "{!green,black,bold,underline!}%s" % (("Files (File list unknown)").center(self.cols)) 

            need_prio_update = True 

        self.__fill_progress(self.file_list, state["file_progress"]) 

        for i, prio in enumerate(state["file_priorities"]): 

            if self.file_dict[i][6] != prio: 

                need_prio_update = True 

                self.file_dict[i][6] = prio 

        if need_prio_update: 

            self.__fill_prio(self.file_list) 

        del state["file_progress"] 

        del state["file_priorities"] 

        self.torrent_state = state 

        self.refresh() 

 

    # split file list into directory tree. this function assumes all files in a 

    # particular directory are returned together.  it won't work otherwise. 

    # returned list is a list of lists of the form: 

    # [file/dir_name,index,size,children,expanded,progress,priority] 

    # for directories index values count down from maxint (for marking usage), 

    # for files the index is the value returned in the 

    # state object for use with other libtorrent calls (i.e. setting prio) 

    # 

    # Also returns a dictionary that maps index values to the file leaves 

    # for fast updating of progress and priorities 

    def build_file_list(self, file_tuples, prog, prio): 

        ret = [] 

        retdict = {} 

        diridx = maxint 

        for f in file_tuples: 

            cur = ret 

            ps = f["path"].split("/") 

            fin = ps[-1] 

            for p in ps: 

                if not cur or p != cur[-1][0]: 

                    cl = [] 

                    if p == fin: 

                        ent = [p, f["index"], f["size"], cl, False, 

                               format_utils.format_progress(prog[f["index"]] * 100), 

                               prio[f["index"]]] 

                        retdict[f["index"]] = ent 

                    else: 

                        ent = [p, diridx, -1, cl, False, 0, -1] 

                        retdict[diridx] = ent 

                        diridx -= 1 

                    cur.append(ent) 

                    cur = cl 

                else: 

                    cur = cur[-1][3] 

        self.__build_sizes(ret) 

        self.__fill_progress(ret, prog) 

        return (ret, retdict) 

 

    # fill in the sizes of the directory entries based on their children 

    def __build_sizes(self, fs): 

        ret = 0 

        for f in fs: 

            if f[2] == -1: 

                val = self.__build_sizes(f[3]) 

                ret += val 

                f[2] = val 

            else: 

                ret += f[2] 

        return ret 

 

    # fills in progress fields in all entries based on progs 

    # returns the # of bytes complete in all the children of fs 

    def __fill_progress(self, fs, progs): 

        if not progs: 

            return 0 

        tb = 0 

        for f in fs: 

            if f[3]:  # dir, has some children 

                bd = self.__fill_progress(f[3], progs) 

                f[5] = format_utils.format_progress((bd / f[2]) * 100) 

            else:  # file, update own prog and add to total 

                bd = f[2] * progs[f[1]] 

                f[5] = format_utils.format_progress(progs[f[1]] * 100) 

            tb += bd 

        return tb 

 

    def __fill_prio(self, fs): 

        for f in fs: 

            if f[3]:  # dir, so fill in children and compute our prio 

                self.__fill_prio(f[3]) 

                child_prios = [e[6] for e in f[3]] 

                if len(child_prios) > 1: 

                    f[6] = -2  # mixed 

                else: 

                    f[6] = child_prios.pop(0) 

 

    def __update_columns(self): 

        self.column_widths = [-1, 15, 15, 20] 

        req = sum(filter(lambda x: x >= 0, self.column_widths)) 

        if (req > self.cols):  # can't satisfy requests, just spread out evenly 

            cw = int(self.cols / len(self.column_names)) 

            for i in range(0, len(self.column_widths)): 

                self.column_widths[i] = cw 

        else: 

            rem = self.cols - req 

            var_cols = len(filter(lambda x: x < 0, self.column_widths)) 

            vw = int(rem / var_cols) 

            for i in range(0, len(self.column_widths)): 

                if (self.column_widths[i] < 0): 

                    self.column_widths[i] = vw 

 

        self.column_string = "{!green,black,bold!}%s" % ("".join(["%s%s" % (self.column_names[i], " " * ( 

            self.column_widths[i] - len(self.column_names[i]))) for i in range(0, len(self.column_names))])) 

 

    def report_message(self, title, message): 

        self.messages.append((title, message)) 

 

    def clear_marks(self): 

        self.marked = {} 

 

    def set_popup(self, pu): 

        self.popup = pu 

        self.refresh() 

 

    def _on_torrentremoved_event(self, torrent_id): 

        if torrent_id == self.torrentid: 

            self.back_to_overview() 

 

    def _on_torrentfilerenamed_event(self, torrent_id, index, new_name): 

        if torrent_id == self.torrentid: 

            self.file_dict[index][0] = new_name.split("/")[-1] 

            component.get("SessionProxy").get_torrent_status( 

                self.torrentid, self._status_keys).addCallback(self.set_state) 

 

    def _on_torrentfolderrenamed_event(self, torrent_id, old_folder, new_folder): 

        if torrent_id == self.torrentid: 

            fe = None 

            fl = None 

            for i in old_folder.strip("/").split("/"): 

                if not fl: 

                    fe = fl = self.file_list 

 

                s = filter(lambda x: x[0].strip("/") == i, fl)[0] 

 

                fe = s 

                fl = s[3] 

            fe[0] = new_folder.strip("/").rpartition("/")[-1] 

 

            # self.__get_file_by_name(old_folder, self.file_list)[0] = new_folder.strip("/") 

            component.get("SessionProxy").get_torrent_status( 

                self.torrentid, self._status_keys).addCallback(self.set_state) 

 

    def draw_files(self, files, depth, off, idx): 

 

        color_selected = "blue" 

        color_partially_selected = "magenta" 

        color_highlighted = "white" 

        for fl in files: 

            # from sys import stderr 

            # print >> stderr, fl[6] 

            # kick out if we're going to draw too low on the screen 

            if (off >= self.rows - 1): 

                self.more_to_draw = True 

                return -1, -1 

 

            self.file_limit = idx 

 

            # default color values 

            fg = "white" 

            bg = "black" 

            attr = "" 

 

            if fl[6] == -2: 

                pass  # Mixed 

            elif fl[6] == 0: 

                fg = "red"  # Do Not Download 

            elif fl[6] == 1: 

                pass  # Normal 

            elif fl[6] <= 6: 

                fg = "yellow"  # High 

            elif fl[6] == 7: 

                fg = "green"  # Highest 

 

            if idx >= self.file_off: 

                # set fg/bg colors based on whether the file is selected/marked or not 

 

                if fl[1] in self.marked: 

                    bg = color_selected 

                    if fl[3]: 

                        if self.marked[fl[1]] < self.__get_contained_files_count(file_list=fl[3]): 

                            bg = color_partially_selected 

                    attr = "bold" 

 

                if idx == self.current_file_idx: 

                    self.current_file = fl 

                    bg = color_highlighted 

                    if fl[1] in self.marked: 

                        fg = color_selected 

                        if fl[3]: 

                            if self.marked[fl[1]] < self.__get_contained_files_count(file_list=fl[3]): 

                                fg = color_partially_selected 

                    else: 

                        if fg == "white": 

                            fg = "black" 

                        attr = "bold" 

 

                if attr: 

                    color_string = "{!%s,%s,%s!}" % (fg, bg, attr) 

                else: 

                    color_string = "{!%s,%s!}" % (fg, bg) 

 

                # actually draw the dir/file string 

                if fl[3] and fl[4]:  # this is an expanded directory 

                    xchar = "v" 

                elif fl[3]:  # collapsed directory 

                    xchar = ">" 

                else:  # file 

                    xchar = "-" 

 

                r = format_utils.format_row(["%s%s %s" % (" " * depth, xchar, fl[0]), 

                                            fsize(fl[2]), fl[5], 

                                            format_utils.format_priority(fl[6])], 

                                            self.column_widths) 

 

                self.add_string(off, "%s%s" % (color_string, r), trim=False) 

                off += 1 

 

            if fl[3] and fl[4]: 

                # recurse if we have children and are expanded 

                off, idx = self.draw_files(fl[3], depth + 1, off, idx + 1) 

                if off < 0: 

                    return (off, idx) 

            else: 

                idx += 1 

 

        return (off, idx) 

 

    def __get_file_list_length(self, file_list=None): 

        """ 

        Counts length of the displayed file list. 

        """ 

        if file_list is None: 

            file_list = self.file_list 

        length = 0 

        if file_list: 

            for element in file_list: 

                length += 1 

                if element[3] and element[4]: 

                    length += self.__get_file_list_length(element[3]) 

        return length 

 

    def __get_contained_files_count(self, file_list=None, idx=None): 

        length = 0 

        if file_list is None: 

            file_list = self.file_list 

        if idx is not None: 

            for element in file_list: 

                if element[1] == idx: 

                    return self.__get_contained_files_count(file_list=element[3]) 

                elif element[3]: 

                    c = self.__get_contained_files_count(file_list=element[3], idx=idx) 

                    if c > 0: 

                        return c 

        else: 

            for element in file_list: 

                length += 1 

                if element[3]: 

                    length -= 1 

                    length += self.__get_contained_files_count(element[3]) 

        return length 

 

    def on_resize(self, *args): 

        BaseMode.on_resize_norefresh(self, *args) 

 

        # Always refresh Legacy(it will also refresh AllTorrents), otherwise it will bug deluge out 

        legacy = component.get("LegacyUI") 

        legacy.on_resize(*args) 

 

        self.__update_columns() 

        if self.popup: 

            self.popup.handle_resize() 

 

        self._listing_start = self.rows / 2 

        self.refresh() 

 

    def render_header(self, off): 

        status = self.torrent_state 

 

        up_color = colors.state_color["Seeding"] 

        down_color = colors.state_color["Downloading"] 

 

        # Name 

        s = "{!info!}Name: {!input!}%s" % status["name"] 

        self.add_string(off, s) 

        off += 1 

 

        # Print DL info and ETA 

        if status["download_payload_rate"] > 0: 

            s = "%sDownloading: {!input!}" % down_color 

        else: 

            s = "{!info!}Downloaded: {!input!}" 

        s += fsize(status["all_time_download"]) 

        if status["progress"] != 100.0: 

            s += "/%s" % fsize(status["total_wanted"]) 

        if status["download_payload_rate"] > 0: 

            s += " {!yellow!}@ %s%s" % (down_color, fsize(status["download_payload_rate"])) 

            s += "{!info!} ETA: {!input!}%s" % format_utils.format_time(status["eta"]) 

        self.add_string(off, s) 

        off += 1 

 

        # Print UL info and ratio 

        if status["upload_payload_rate"] > 0: 

            s = "%sUploading: {!input!}" % up_color 

        else: 

            s = "{!info!}Uploaded: {!input!}" 

        s += fsize(status["total_uploaded"]) 

        if status["upload_payload_rate"] > 0: 

            s += " {!yellow!}@ %s%s" % (up_color, fsize(status["upload_payload_rate"])) 

        ratio_str = format_utils.format_float(status["ratio"]) 

        if ratio_str == "-": 

            ratio_str = "inf" 

        s += " {!info!}Ratio: {!input!}%s" % ratio_str 

        self.add_string(off, s) 

        off += 1 

 

        # Seed/peer info 

        s = "{!info!}Seeds:{!green!} %s {!input!}(%s)" % (status["num_seeds"], status["total_seeds"]) 

        self.add_string(off, s) 

        off += 1 

        s = "{!info!}Peers:{!red!} %s {!input!}(%s)" % (status["num_peers"], status["total_peers"]) 

        self.add_string(off, s) 

        off += 1 

 

        # Tracker 

        if status["message"] == "OK": 

            color = "{!green!}" 

        else: 

            color = "{!red!}" 

        s = "{!info!}Tracker: {!magenta!}%s{!input!} says \"%s%s{!input!}\"" % ( 

            status["tracker_host"], color, status["message"]) 

        self.add_string(off, s) 

        off += 1 

 

        # Pieces and availability 

        s = "{!info!}Pieces: {!yellow!}%s {!input!}x {!yellow!}%s" % ( 

            status["num_pieces"], fsize(status["piece_length"])) 

        if status["distributed_copies"]: 

            s += " {!info!}Availability: {!input!}%s" % format_utils.format_float(status["distributed_copies"]) 

        self.add_string(off, s) 

        off += 1 

 

        # Time added 

        s = "{!info!}Added: {!input!}%s" % fdate(status["time_added"]) 

        self.add_string(off, s) 

        off += 1 

 

        # Time active 

        s = "{!info!}Time active: {!input!}%s" % (ftime(status["active_time"])) 

        if status["seeding_time"]: 

            s += ", {!cyan!}%s{!input!} seeding" % (ftime(status["seeding_time"])) 

        self.add_string(off, s) 

        off += 1 

 

        # Download Folder 

        s = "{!info!}Download Folder: {!input!}%s" % status["download_location"] 

        self.add_string(off, s) 

        off += 1 

 

        # Owner 

        if status["owner"]: 

            s = "{!info!}Owner: {!input!}%s" % status["owner"] 

 

        return off 

 

    def refresh(self, lines=None): 

        # show a message popup if there's anything queued 

        if self.popup is None and self.messages: 

            title, msg = self.messages.popleft() 

            self.popup = MessagePopup(self, title, msg) 

 

        # Update the status bars 

        self.stdscr.erase() 

        self.add_string(0, self.statusbars.topbar) 

 

        # This will quite likely fail when switching modes 

        try: 

            rf = format_utils.remove_formatting 

            string = self.statusbars.bottombar 

            hstr = "Press {!magenta,blue,bold!}[h]{!status!} for help" 

 

            string += " " * (self.cols - len(rf(string)) - len(rf(hstr))) + hstr 

 

            self.add_string(self.rows - 1, string) 

        except: 

            pass 

 

        off = 1 

        if self.torrent_state: 

            off = self.render_header(off) 

        else: 

            self.add_string(1, "Waiting for torrent state") 

 

        off += 1 

 

        if self.files_sep: 

            self.add_string(off, self.files_sep) 

            off += 1 

 

        self._listing_start = off 

        self._listing_space = self.rows - self._listing_start 

 

        self.add_string(off, self.column_string) 

        if self.file_list: 

            off += 1 

            self.more_to_draw = False 

            self.draw_files(self.file_list, 0, off, 0) 

 

        if component.get("ConsoleUI").screen != self: 

            return 

 

        self.stdscr.noutrefresh() 

 

        if self.popup: 

            self.popup.refresh() 

 

        curses.doupdate() 

 

    def expcol_cur_file(self): 

        """ 

        Expand or collapse current file 

        """ 

        self.current_file[4] = not self.current_file[4] 

        self.refresh() 

 

    def file_list_down(self, rows=1): 

        maxlen = self.__get_file_list_length() - 1 

 

        self.current_file_idx += rows 

 

        if self.current_file_idx > maxlen: 

            self.current_file_idx = maxlen 

 

        if self.current_file_idx > self.file_off + (self._listing_space - 3): 

            self.file_off = self.current_file_idx - (self._listing_space - 3) 

 

        self.refresh() 

 

    def file_list_up(self, rows=1): 

        self.current_file_idx = max(0, self.current_file_idx - rows) 

        self.file_off = min(self.file_off, self.current_file_idx) 

        self.refresh() 

 

    def back_to_overview(self): 

        component.stop(["TorrentDetail"]) 

        component.deregister(self) 

        self.stdscr.erase() 

        component.get("ConsoleUI").set_mode(self.alltorrentmode) 

        self.alltorrentmode._go_top = False 

        self.alltorrentmode.resume() 

 

    # build list of priorities for all files in the torrent 

    # based on what is currently selected and a selected priority. 

    def build_prio_list(self, files, ret_list, parent_prio, selected_prio): 

        # has a priority been set on my parent (if so, I inherit it) 

        for f in files: 

            # Do not set priorities for the whole dir, just selected contents 

            if f[3]: 

                self.build_prio_list(f[3], ret_list, parent_prio, selected_prio) 

            else:  # file, need to add to list 

                if f[1] in self.marked or parent_prio >= 0: 

                    # selected (or parent selected), use requested priority 

                    ret_list.append((f[1], selected_prio)) 

                else: 

                    # not selected, just keep old priority 

                    ret_list.append((f[1], f[6])) 

 

    def do_priority(self, idx, data, was_empty): 

        plist = [] 

        self.build_prio_list(self.file_list, plist, -1, data) 

        plist.sort() 

        priorities = [p[1] for p in plist] 

        log.debug("priorities: %s", priorities) 

 

        client.core.set_torrent_file_priorities(self.torrentid, priorities) 

 

        if was_empty: 

            self.marked = {} 

        return True 

 

    # show popup for priority selections 

    def show_priority_popup(self, was_empty): 

        func = lambda idx, data, we=was_empty: self.do_priority(idx, data, we) 

        if self.marked: 

            self.popup = SelectablePopup(self, "Set File Priority", func) 

            self.popup.add_line("_Do Not Download", data=FILE_PRIORITY["Do Not Download"], foreground="red") 

            self.popup.add_line("_Normal Priority", data=FILE_PRIORITY["Normal Priority"]) 

            self.popup.add_line("_High Priority", data=FILE_PRIORITY["High Priority"], foreground="yellow") 

            self.popup.add_line("H_ighest Priority", data=FILE_PRIORITY["Highest Priority"], foreground="green") 

            self.popup._selected = 1 

 

    def __mark_unmark(self, idx): 

        """ 

        Selects or unselects file or a catalog(along with contained files) 

        """ 

        fc = self.__get_contained_files_count(idx=idx) 

        if idx not in self.marked: 

            # Not selected, select it 

            self.__mark_tree(self.file_list, idx) 

        elif self.marked[idx] < fc: 

            # Partially selected, unselect all contents 

            self.__unmark_tree(self.file_list, idx) 

        else: 

            # Selected, unselect it 

            self.__unmark_tree(self.file_list, idx) 

 

    def __mark_tree(self, file_list, idx, mark_all=False): 

        """ 

        Given file_list of TorrentDetail and index of file or folder, 

        recursively selects all files contained 

        as well as marks folders higher in hierarchy as partially selected 

        """ 

        total_marked = 0 

        for element in file_list: 

            marked = 0 

            # Select the file if it's the one we want or 

            # if it's inside a directory that got selected 

            if (element[1] == idx) or mark_all: 

                # If it's a folder then select everything inside 

                if element[3]: 

                    marked = self.__mark_tree(element[3], idx, True) 

                    self.marked[element[1]] = marked 

                else: 

                    marked = 1 

                    self.marked[element[1]] = 1 

            else: 

                # Does not match but the item to be selected might be inside, recurse 

                if element[3]: 

                    marked = self.__mark_tree(element[3], idx, False) 

                    # Partially select the folder if it contains files that were selected 

                    if marked > 0: 

                        self.marked[element[1]] = marked 

                else: 

                    if element[1] in self.marked: 

                        # It's not the element we want but it's marked so count it 

                        marked = 1 

            # Count and then return total amount of files selected in all subdirectories 

            total_marked += marked 

 

        return total_marked 

 

    def __get_file_by_num(self, num, file_list, idx=0): 

        for element in file_list: 

            if idx == num: 

                return element 

 

            if element[3] and element[4]: 

                i = self.__get_file_by_num(num, element[3], idx + 1) 

                if not isinstance(i, int): 

                    return i 

                else: 

                    idx = i 

            else: 

                idx += 1 

 

        return idx 

 

    def __get_file_by_name(self, name, file_list, idx=0): 

        for element in file_list: 

            if element[0].strip("/") == name.strip("/"): 

                return element 

 

            if element[3] and element[4]: 

                i = self.__get_file_by_name(name, element[3], idx + 1) 

                if not isinstance(i, int): 

                    return i 

                else: 

                    idx = i 

            else: 

                idx += 1 

 

        return idx 

 

    def __unmark_tree(self, file_list, idx, unmark_all=False): 

        """ 

        Given file_list of TorrentDetail and index of file or folder, 

        recursively deselects all files contained 

        as well as marks folders higher in hierarchy as unselected or partially selected 

        """ 

        total_marked = 0 

        for element in file_list: 

            marked = 0 

            # It's either the item we want to select or 

            # a contained item, deselect it 

            if (element[1] == idx) or unmark_all: 

                if element[1] in self.marked: 

                    del self.marked[element[1]] 

                    # Deselect all contents if it's a catalog 

                    if element[3]: 

                        self.__unmark_tree(element[3], idx, True) 

            else: 

                # Not file we wanted but it might be inside this folder, recurse inside 

                if element[3]: 

                    marked = self.__unmark_tree(element[3], idx, False) 

                    # If none of the contents remain selected, unselect this folder as well 

                    if marked == 0: 

                        if element[1] in self.marked: 

                            del self.marked[element[1]] 

                    # Otherwise update selection count 

                    else: 

                        self.marked[element[1]] = marked 

                else: 

                    if element[1] in self.marked: 

                        marked = 1 

 

            # Count and then return selection count so we can update 

            # directories higher up in the hierarchy 

            total_marked += marked 

        return total_marked 

 

    def _selection_to_file_idx(self, file_list=None, idx=0, true_idx=0, closed=False): 

        if not file_list: 

            file_list = self.file_list 

 

        for element in file_list: 

            if idx == self.current_file_idx: 

                return true_idx 

 

            # It's a folder 

            if element[3]: 

                i = self._selection_to_file_idx(element[3], idx + 1, true_idx, closed or not element[4]) 

                if isinstance(i, tuple): 

                    idx, true_idx = i 

                    if element[4]: 

                        idx, true_idx = i 

                    else: 

                        idx += 1 

                        _, true_idx = i 

                else: 

                    return i 

            else: 

                if not closed: 

                    idx += 1 

                true_idx += 1 

 

        return (idx, true_idx) 

 

    def _get_full_folder_path(self, num, file_list=None, path="", idx=0): 

        if not file_list: 

            file_list = self.file_list 

 

        for element in file_list: 

            if not element[3]: 

                idx += 1 

                continue 

 

            if num == idx: 

                return "%s%s/" % (path, element[0]) 

 

            if element[4]: 

                i = self._get_full_folder_path(num, element[3], path + element[0] + "/", idx + 1) 

                if not isinstance(i, int): 

                    return i 

                else: 

                    idx = i 

            else: 

                idx += 1 

 

        return idx 

 

    def _do_rename_folder(self, torrent_id, folder, new_folder): 

        client.core.rename_folder(torrent_id, folder, new_folder) 

 

    def _do_rename_file(self, torrent_id, file_idx, new_filename): 

        if not new_filename: 

            return 

        client.core.rename_files(torrent_id, [(file_idx, new_filename)]) 

 

    def _show_rename_popup(self): 

        # Perhaps in the future: Renaming multiple files 

        if self.marked: 

            title = "Error (Enter to close)" 

            text = "Sorry, you can't rename multiple files, please clear selection with {!info!}'c'{!normal!} key" 

            self.popup = MessagePopup(self, title, text) 

        else: 

            _file = self.__get_file_by_num(self.current_file_idx, self.file_list) 

            old_filename = _file[0] 

 

            idx = self._selection_to_file_idx() 

            tid = self.torrentid 

 

            if _file[3]: 

 

                def do_rename(result): 

                    if not result["new_foldername"]: 

                        return 

                    old_fname = self._get_full_folder_path(self.current_file_idx) 

                    new_fname = "%s/%s/" % (old_fname.strip("/").rpartition("/")[0], result["new_foldername"]) 

                    self._do_rename_folder(tid, old_fname, new_fname) 

 

                popup = InputPopup(self, "Rename folder (Esc to cancel)", close_cb=do_rename) 

                popup.add_text("{!info!}Renaming folder:{!input!}") 

                popup.add_text(" * %s\n" % old_filename) 

                popup.add_text_input("Enter new folder name:", "new_foldername", old_filename.strip("/")) 

 

                self.popup = popup 

            else: 

 

                def do_rename(result): 

                    fname = "%s/%s" % (self.full_names[idx].rpartition("/")[0], result["new_filename"]) 

                    self._do_rename_file(tid, idx, fname) 

 

                popup = InputPopup(self, "Rename file (Esc to cancel)", close_cb=do_rename) 

                popup.add_text("{!info!}Renaming file:{!input!}") 

                popup.add_text(" * %s\n" % old_filename) 

                popup.add_text_input("Enter new filename:", "new_filename", old_filename) 

 

                self.popup = popup 

 

    def read_input(self): 

        c = self.stdscr.getch() 

 

        if self.popup: 

            if self.popup.handle_read(c): 

                self.popup = None 

            self.refresh() 

            return 

 

        if c > 31 and c < 256: 

            if chr(c) == "Q": 

                from twisted.internet import reactor 

                if client.connected(): 

                    def on_disconnect(result): 

                        reactor.stop() 

                    client.disconnect().addCallback(on_disconnect) 

                else: 

                    reactor.stop() 

                return 

            elif chr(c) == "q": 

                self.back_to_overview() 

                return 

 

        if c == 27 or c == curses.KEY_LEFT: 

            self.back_to_overview() 

            return 

 

        if not self.torrent_state: 

            # actions below only make sense if there is a torrent state 

            return 

 

        # Navigate the torrent list 

        if c == curses.KEY_UP: 

            self.file_list_up() 

        elif c == curses.KEY_PPAGE: 

            self.file_list_up(self._listing_space - 2) 

        elif c == curses.KEY_HOME: 

            self.file_off = 0 

            self.current_file_idx = 0 

        elif c == curses.KEY_DOWN: 

            self.file_list_down() 

        elif c == curses.KEY_NPAGE: 

            self.file_list_down(self._listing_space - 2) 

        elif c == curses.KEY_END: 

            self.current_file_idx = self.__get_file_list_length() - 1 

            self.file_off = self.current_file_idx - (self._listing_space - 3) 

        elif c == curses.KEY_DC: 

            torrent_actions_popup(self, [self.torrentid], action=ACTION.REMOVE) 

        # Enter Key 

        elif c == curses.KEY_ENTER or c == 10: 

            was_empty = (self.marked == {}) 

            self.__mark_tree(self.file_list, self.current_file[1]) 

            self.show_priority_popup(was_empty) 

 

        # space 

        elif c == 32: 

            self.expcol_cur_file() 

        else: 

            if c > 31 and c < 256: 

                if chr(c) == "m": 

                    if self.current_file: 

                        self.__mark_unmark(self.current_file[1]) 

                elif chr(c) == "r": 

                    self._show_rename_popup() 

                elif chr(c) == "c": 

                    self.marked = {} 

                elif chr(c) == "a": 

                    torrent_actions_popup(self, [self.torrentid], details=False) 

                    return 

                elif chr(c) == "o": 

                    torrent_actions_popup(self, [self.torrentid], action=ACTION.TORRENT_OPTIONS) 

                    return 

                elif chr(c) == "h": 

                    self.popup = MessagePopup(self, "Help", HELP_STR, width_req=0.75) 

                elif chr(c) == "j": 

                    self.file_list_up() 

                if chr(c) == "k": 

                    self.file_list_down() 

 

        self.refresh()