Completed
Pull Request — master (#32)
by Jace
03:15
created

Application.init()   C

Complexity

Conditions 7

Size

Total Lines 98

Duplication

Lines 43
Ratio 43.88 %

Code Coverage

Tests 64
CRAP Score 7

Importance

Changes 10
Bugs 0 Features 0
Metric Value
cc 7
c 10
b 0
f 0
dl 43
loc 98
rs 5.3111
ccs 64
cts 64
cp 1
crap 7

4 Methods

Rating   Name   Duplication   Size   Complexity  
A Application.frame_settings() 0 19 1
A Application.frame_incoming() 21 21 2
A Application.separator() 0 3 1
A Application.frame_outgoing() 22 22 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
#!/usr/bin/env python
2
3 1
"""Graphical interface for DropTheBeat."""
4
5 1
import sys
6 1
from unittest.mock import Mock
7 1
try:
8 1
    import tkinter as tk
9 1
    from tkinter import ttk
10 1
    from tkinter import messagebox, simpledialog, filedialog
11
except ImportError as err:
12
    sys.stderr.write("WARNING: {}\n".format(err))
13
    tk = Mock()
14
    ttk = Mock()
15
16 1
import os
17 1
import argparse
18 1
from itertools import chain
19 1
import logging
20
21 1
from dtb import GUI, __version__
22 1
from dtb import share, user
23 1
from dtb.common import SHARED, WarningFormatter
24 1
from dtb import settings
25
26 1
_LAUNCH = True
27
28
29 1
class Application(ttk.Frame):  # pylint: disable=too-many-instance-attributes
30
    """Tkinter application for DropTheBeat."""
31
32 1
    def __init__(self, master=None, root=None, home=None, name=None):
33 1
        ttk.Frame.__init__(self, master)
34
35
        # Load the root sharing directory
36 1
        self.root = root or share.find(home)
37
38
        # Load the user
39 1
        self.user = user.User(os.path.join(self.root, name)) if name else None
40 1
        try:
41 1
            self.user = self.user or user.get_current(self.root)
42
        except EnvironmentError:
43
44
            while True:
45
46
                msg = "Enter your name in the form 'First Last':"
47
                text = simpledialog.askstring("Create a User", msg)
48
                logging.debug("text: {}".format(repr(text)))
49
                name = text.strip(" '") if text else None
50
                if not name:
51
                    raise KeyboardInterrupt("no user specified")
52
                try:
53
                    self.user = user.User.new(self.root, name)
54
                except EnvironmentError:
55
                    existing = user.User(os.path.join(self.root, name))
56
                    msg = "Is this you:"
57
                    for info in existing.info:
58
                        msg += "\n\n'{}' on '{}'".format(info[1], info[0])
59
                    if not existing.info or \
60
                            messagebox.askyesno("Add to Existing User", msg):
61
                        self.user = user.User.add(self.root, name)
62
                        break
63
                else:
64
                    break
65
66
        # Create variables
67 1
        self.path_root = tk.StringVar(value=self.root)
68 1
        self.path_downloads = tk.StringVar(value=self.user.path_downloads)
69 1
        self.outgoing = []
70 1
        self.incoming = []
71
72
        # Initialize the GUI
73 1
        self.listbox_outgoing = None
74 1
        self.listbox_incoming = None
75 1
        frame = self.init(master)
76 1
        frame.pack(fill=tk.BOTH, expand=1)
77
78
        # Show the GUI
79 1
        master.deiconify()
80 1
        self.update()
81
82 1
    def init(self, root):
83
        """Initialize frames and widgets."""
84
85
        # pylint: disable=line-too-long
86
87 1
        mac = sys.platform == 'darwin'
88
89
        # Shared keyword arguments
90 1
        kw_f = {'padding': 5}  # constructor arguments for frames
91 1
        kw_gp = {'padx': 5, 'pady': 5}  # grid arguments for padded widgets
92 1
        kw_gs = {'sticky': tk.NSEW}  # grid arguments for sticky widgets
93 1
        kw_gsp = dict(chain(kw_gs.items(), kw_gp.items()))  # grid arguments for sticky padded widgets
94
95
        # Configure grid
96 1
        frame = ttk.Frame(root, **kw_f)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
97 1
        frame.rowconfigure(0, weight=0)
98 1
        frame.rowconfigure(2, weight=1)
99 1
        frame.rowconfigure(4, weight=1)
100 1
        frame.columnconfigure(0, weight=1)
101
102
        # Create widgets
103 1
        def frame_settings(master):
104
            """Frame for the settings."""
105 1
            frame = ttk.Frame(master, **kw_f)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
106
107
            # Configure grid
108 1
            frame.rowconfigure(0, weight=1)
109 1
            frame.rowconfigure(1, weight=1)
110 1
            frame.columnconfigure(0, weight=0)
111 1
            frame.columnconfigure(1, weight=1)
112 1
            frame.columnconfigure(2, weight=0)
113
114
            # Place widgets
115 1
            ttk.Label(frame, text="Shared:").grid(row=0, column=0, sticky=tk.W, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
116 1
            ttk.Entry(frame, state='readonly', textvariable=self.path_root).grid(row=0, column=1, columnspan=2, **kw_gsp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
117 1
            ttk.Label(frame, text="Downloads:").grid(row=1, column=0, sticky=tk.W, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
118 1
            ttk.Entry(frame, state='readonly', textvariable=self.path_downloads).grid(row=1, column=1, **kw_gsp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
119 1
            ttk.Button(frame, text="...", width=0, command=self.browse_downloads).grid(row=1, column=2, ipadx=5, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
120
121 1
            return frame
122
123 1 View Code Duplication
        def frame_incoming(master):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
124
            """Frame for incoming songs."""
125 1
            frame = ttk.Frame(master, **kw_f)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
126
127
            # Configure grid
128 1
            frame.rowconfigure(0, weight=1)
129 1
            frame.rowconfigure(1, weight=0)
130 1
            frame.columnconfigure(0, weight=0)
131 1
            frame.columnconfigure(1, weight=1)
132 1
            frame.columnconfigure(2, weight=1)
133
134
            # Place widgets
135 1
            self.listbox_incoming = tk.Listbox(frame, selectmode=tk.EXTENDED if mac else tk.MULTIPLE)
136 1
            self.listbox_incoming.grid(row=0, column=0, columnspan=3, **kw_gsp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
137 1
            scroll_incoming = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=self.listbox_incoming.yview)
138 1
            self.listbox_incoming.configure(yscrollcommand=scroll_incoming.set)
139 1
            scroll_incoming.grid(row=0, column=2, sticky=(tk.N, tk.E, tk.S))
140 1
            ttk.Button(frame, text="\u21BB", width=0, command=self.update).grid(row=1, column=0, sticky=tk.SW, ipadx=5, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
141 1
            ttk.Button(frame, text="Ignore Selected", command=self.do_ignore).grid(row=1, column=1, sticky=tk.SW, ipadx=5, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
142 1
            ttk.Button(frame, text="Download Selected", command=self.do_download).grid(row=1, column=2, sticky=tk.SE, ipadx=5, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
143 1
            return frame
144
145 1 View Code Duplication
        def frame_outgoing(master):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
146
            """Frame for outgoing songs."""
147 1
            frame = ttk.Frame(master, **kw_f)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
148
149
            # Configure grid
150 1
            frame.rowconfigure(0, weight=1)
151 1
            frame.rowconfigure(1, weight=0)
152 1
            frame.columnconfigure(0, weight=0)
153 1
            frame.columnconfigure(1, weight=1)
154 1
            frame.columnconfigure(2, weight=1)
155
156
            # Place widgets
157 1
            self.listbox_outgoing = tk.Listbox(frame, selectmode=tk.EXTENDED if mac else tk.MULTIPLE)
158 1
            self.listbox_outgoing.grid(row=0, column=0, columnspan=3, **kw_gsp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
159 1
            scroll_outgoing = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=self.listbox_outgoing.yview)
160 1
            self.listbox_outgoing.configure(yscrollcommand=scroll_outgoing.set)
161 1
            scroll_outgoing.grid(row=0, column=2, sticky=(tk.N, tk.E, tk.S))
162 1
            ttk.Button(frame, text="\u21BB", width=0, command=self.update).grid(row=1, column=0, sticky=tk.SW, ipadx=5, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
163 1
            ttk.Button(frame, text="Remove Selected", command=self.do_remove).grid(row=1, column=1, sticky=tk.SW, ipadx=5, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
164 1
            ttk.Button(frame, text="Share Songs...", command=self.do_share).grid(row=1, column=2, sticky=tk.SE, ipadx=5, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
165
166 1
            return frame
167
168 1
        def separator(master):
169
            """Widget to separate frames."""
170 1
            return ttk.Separator(master)
171
172
        # Place widgets
173 1
        frame_settings(frame).grid(row=0, **kw_gs)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
174 1
        separator(frame).grid(row=1, padx=10, pady=5, **kw_gs)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
175 1
        frame_outgoing(frame).grid(row=2, **kw_gs)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
176 1
        separator(frame).grid(row=3, padx=10, pady=5, **kw_gs)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
177 1
        frame_incoming(frame).grid(row=4, **kw_gs)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
178
179 1
        return frame
180
181 1
    def browse_downloads(self):
182
        """Browser for a new downloads directory."""
183
        path = filedialog.askdirectory()
184
        logging.debug("path: {}".format(path))
185
        if path:
186
            self.user.path_downloads = path
187
            self.path_downloads.set(self.user.path_downloads)
188
189 1
    def do_remove(self):
190
        """Remove selected songs."""
191
        for index in (int(s) for s in self.listbox_outgoing.curselection()):
192
            self.outgoing[index].ignore()
193
        self.update()
194
195 1
    def do_share(self):
196
        """Share songs."""
197
        paths = filedialog.askopenfilenames()
198
        if isinstance(paths, str):  # http://bugs.python.org/issue5712
199
            paths = self.master.tk.splitlist(paths)
200
        logging.debug("paths: {}".format(paths))
201
        for path in paths:
202
            self.user.recommend(path)
203
        self.update()
204
205 1
    def do_ignore(self):
206
        """Ignore selected songs."""
207
        for index in (int(s) for s in self.listbox_incoming.curselection()):
208
            song = self.incoming[index]
209
            song.ignore()
210
        self.update()
211
212 1
    def do_download(self):
213
        """Download selected songs."""
214
        indicies = (int(s) for s in self.listbox_incoming.curselection())
215
        try:
216
            for index in indicies:
217
                song = self.incoming[index]
218
                song.download(catch=False)
219
        except IOError as exc:
220
            self.show_error_from_exception(exc, "Download Error")
221
        self.update()
222
223 1
    def update(self):
224
        """Update the list of outgoing and incoming songs."""
225
226
        # Cleanup outgoing songs
227 1
        self.user.cleanup()
228
229
        # Update outgoing songs list
230 1
        logging.info("updating outgoing songs...")
231 1
        self.outgoing = list(self.user.outgoing)
232 1
        self.listbox_outgoing.delete(0, tk.END)
233 1
        for song in self.outgoing:
234
            self.listbox_outgoing.insert(tk.END, song.out_string)
235
        # Update incoming songs list
236
237 1
        logging.info("updating incoming songs...")
238 1
        self.incoming = list(self.user.incoming)
239 1
        self.listbox_incoming.delete(0, tk.END)
240 1
        for song in self.incoming:
241
            self.listbox_incoming.insert(tk.END, song.in_string)
242
243 1
    @staticmethod
244 1
    def show_error_from_exception(exception, title="Error"):
245
        """Convert an exception to an error dialog."""
246
        message = str(exception).capitalize().replace(": ", ":\n\n")
247
        messagebox.showerror(title, message)
248
249
250 1
def main(args=None):
251
    """Process command-line arguments and run the program."""
252
253
    # Main parser
254 1
    parser = argparse.ArgumentParser(prog=GUI, description=__doc__, **SHARED)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
255 1
    parser.add_argument('--home', metavar='PATH', help="path to home directory")
256
    # Hidden argument to override the root sharing directory path
257 1
    parser.add_argument('--root', metavar="PATH", help=argparse.SUPPRESS)
258
    # Hidden argument to run the program as a different user
259 1
    parser.add_argument('--test', metavar='"First Last"',
260
                        help=argparse.SUPPRESS)
261
262
    # Parse arguments
263 1
    args = parser.parse_args(args=args)
264
265
    # Configure logging
266 1
    _configure_logging(args.verbose)
267
268
    # Run the program
269 1
    try:
270 1
        success = run(args)
271 1
    except KeyboardInterrupt:
272 1
        logging.debug("program manually closed")
273
    else:
274 1
        if success:
275 1
            logging.debug("program exited")
276
        else:
277 1
            logging.debug("program exited with error")
278 1
            sys.exit(1)
279
280
281 1
def _configure_logging(verbosity=0):
282
    """Configure logging using the provided verbosity level (0+)."""
283
284
    # Configure the logging level and format
285 1
    if verbosity == 0:
286 1
        level = settings.VERBOSE_LOGGING_LEVEL
287 1
        default_format = settings.DEFAULT_LOGGING_FORMAT
288 1
        verbose_format = settings.VERBOSE_LOGGING_FORMAT
289
    else:
290 1
        level = settings.VERBOSE2_LOGGING_LEVEL
291 1
        default_format = verbose_format = settings.VERBOSE_LOGGING_FORMAT
292
293
    # Set a custom formatter
294 1
    logging.basicConfig(level=level)
295 1
    formatter = WarningFormatter(default_format, verbose_format)
296 1
    logging.root.handlers[0].setFormatter(formatter)
297
298
299 1
def run(args):
300
    """Start the GUI."""
301
302
    # Exit if tkinter is not available
303 1
    if isinstance(tk, Mock) or isinstance(ttk, Mock):
304 1
        logging.error("tkinter is not available")
305 1
        return False
306
307
    else:
308
309 1
        root = tk.Tk()
310 1
        root.title("{} (v{})".format(GUI, __version__))
311 1
        root.minsize(500, 500)
312
313
        # Map the Mac 'command' key to 'control'
314 1
        if sys.platform == 'darwin':
315
            root.bind_class('Listbox', '<Command-Button-1>',
316
                            root.bind_class('Listbox', '<Control-Button-1>'))
317
318
        # Temporarily hide the window for other dialogs
319 1
        root.withdraw()
320
321
        # Start the application
322 1
        app = Application(master=root, home=args.home,
323
                          root=args.root, name=args.test)
324 1
        if _LAUNCH:
325
            app.mainloop()
326
327 1
        return True
328
329
330
if __name__ == '__main__':  # pragma: no cover (manual test)
331
    main()
332