Application.__init__()   F
last analyzed

Complexity

Conditions 11

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 121.9122

Importance

Changes 5
Bugs 1 Features 0
Metric Value
cc 11
c 5
b 1
f 0
dl 0
loc 49
rs 3.1764
ccs 1
cts 35
cp 0.0286
crap 121.9122

How to fix   Complexity   

Complexity

Complex classes like Application.__init__() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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
        ttk.Frame.__init__(self, master)
34
35
        # Load the root sharing directory
36
        self.root = root or share.find(home)
37
38
        # Load the user
39
        self.user = user.User(os.path.join(self.root, name)) if name else None
40
        try:
41
            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
        self.path_root = tk.StringVar(value=self.root)
68
        self.path_downloads = tk.StringVar(value=self.user.path_downloads)
69
        self.outgoing = []
70
        self.incoming = []
71
72
        # Initialize the GUI
73
        self.listbox_outgoing = None
74
        self.listbox_incoming = None
75
        frame = self.init(master)
76
        frame.pack(fill=tk.BOTH, expand=1)
77
78
        # Show the GUI
79
        master.deiconify()
80
        self.update()
81
82 1
    def init(self, root):
83
        """Initialize frames and widgets."""
84
85
        # pylint: disable=line-too-long
86
87
        mac = sys.platform == 'darwin'
88
89
        # Shared keyword arguments
90
        kw_f = {'padding': 5}  # constructor arguments for frames
91
        kw_gp = {'padx': 5, 'pady': 5}  # grid arguments for padded widgets
92
        kw_gs = {'sticky': tk.NSEW}  # grid arguments for sticky widgets
93
        kw_gsp = dict(chain(kw_gs.items(), kw_gp.items()))  # grid arguments for sticky padded widgets
94
95
        # Configure grid
96
        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
        frame.rowconfigure(0, weight=0)
98
        frame.rowconfigure(2, weight=1)
99
        frame.rowconfigure(4, weight=1)
100
        frame.columnconfigure(0, weight=1)
101
102
        # Create widgets
103
        def frame_settings(master):
104
            """Frame for the settings."""
105
            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
            frame.rowconfigure(0, weight=1)
109
            frame.rowconfigure(1, weight=1)
110
            frame.columnconfigure(0, weight=0)
111
            frame.columnconfigure(1, weight=1)
112
            frame.columnconfigure(2, weight=0)
113
114
            # Place widgets
115
            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
            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
            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
            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
            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
            return frame
122
123 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
            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
            frame.rowconfigure(0, weight=1)
129
            frame.rowconfigure(1, weight=0)
130
            frame.columnconfigure(0, weight=0)
131
            frame.columnconfigure(1, weight=1)
132
            frame.columnconfigure(2, weight=1)
133
134
            # Place widgets
135
            self.listbox_incoming = tk.Listbox(frame, selectmode=tk.EXTENDED if mac else tk.MULTIPLE)
136
            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
            scroll_incoming = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=self.listbox_incoming.yview)
138
            self.listbox_incoming.configure(yscrollcommand=scroll_incoming.set)
139
            scroll_incoming.grid(row=0, column=2, sticky=(tk.N, tk.E, tk.S))
140
            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
            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
            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
            return frame
144
145 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
            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
            frame.rowconfigure(0, weight=1)
151
            frame.rowconfigure(1, weight=0)
152
            frame.columnconfigure(0, weight=0)
153
            frame.columnconfigure(1, weight=1)
154
            frame.columnconfigure(2, weight=1)
155
156
            # Place widgets
157
            self.listbox_outgoing = tk.Listbox(frame, selectmode=tk.EXTENDED if mac else tk.MULTIPLE)
158
            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
            scroll_outgoing = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=self.listbox_outgoing.yview)
160
            self.listbox_outgoing.configure(yscrollcommand=scroll_outgoing.set)
161
            scroll_outgoing.grid(row=0, column=2, sticky=(tk.N, tk.E, tk.S))
162
            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
            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
            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
            return frame
167
168
        def separator(master):
169
            """Widget to separate frames."""
170
            return ttk.Separator(master)
171
172
        # Place widgets
173
        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
        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
        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
        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
        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
        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
        self.user.cleanup()
228
229
        # Update outgoing songs list
230
        logging.info("updating outgoing songs...")
231
        self.outgoing = list(self.user.outgoing)
232
        self.listbox_outgoing.delete(0, tk.END)
233
        for song in self.outgoing:
234
            self.listbox_outgoing.insert(tk.END, song.out_string)
235
        # Update incoming songs list
236
237
        logging.info("updating incoming songs...")
238
        self.incoming = list(self.user.incoming)
239
        self.listbox_incoming.delete(0, tk.END)
240
        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)
247
        message = message[0].upper() + message[1:]
248
        if ": " in message:
249
            message.replace(": ", ":\n\n")
250
        else:
251
            message += "."
252
        messagebox.showerror(title, message)
253
254
255 1
def main(args=None):
256
    """Process command-line arguments and run the program."""
257
258
    # Main parser
259 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...
260 1
    parser.add_argument('--home', metavar='PATH', help="path to home directory")
261
    # Hidden argument to override the root sharing directory path
262 1
    parser.add_argument('--root', metavar="PATH", help=argparse.SUPPRESS)
263
    # Hidden argument to run the program as a different user
264 1
    parser.add_argument('--test', metavar='"First Last"',
265
                        help=argparse.SUPPRESS)
266
267
    # Parse arguments
268 1
    args = parser.parse_args(args=args)
269
270
    # Configure logging
271 1
    _configure_logging(args.verbose)
272
273
    # Run the program
274 1
    try:
275 1
        success = run(args)
276
    except KeyboardInterrupt:
277
        logging.debug("program manually closed")
278
    else:
279 1
        if success:
280 1
            logging.debug("program exited")
281
        else:
282
            logging.debug("program exited with error")
283
            sys.exit(1)
284
285
286 1
def _configure_logging(verbosity=0):
287
    """Configure logging using the provided verbosity level (0+)."""
288
289
    # Configure the logging level and format
290 1
    if verbosity == 0:
291 1
        level = settings.VERBOSE_LOGGING_LEVEL
292 1
        default_format = settings.DEFAULT_LOGGING_FORMAT
293 1
        verbose_format = settings.VERBOSE_LOGGING_FORMAT
294
    else:
295 1
        level = settings.VERBOSE2_LOGGING_LEVEL
296 1
        default_format = verbose_format = settings.VERBOSE_LOGGING_FORMAT
297
298
    # Set a custom formatter
299 1
    logging.basicConfig(level=level)
300 1
    formatter = WarningFormatter(default_format, verbose_format)
301 1
    logging.root.handlers[0].setFormatter(formatter)
302
303
304 1
def run(args):
305
    """Start the GUI."""
306
307
    # Exit if tkinter is not available
308 1
    if isinstance(tk, Mock) or isinstance(ttk, Mock):
309 1
        logging.error("tkinter is not available")
310 1
        return False
311
312
    root = tk.Tk()
313
    root.title("{} (v{})".format(GUI, __version__))
314
    root.minsize(500, 500)
315
316
    # Map the Mac 'command' key to 'control'
317
    if sys.platform == 'darwin':
318
        root.bind_class('Listbox', '<Command-Button-1>',
319
                        root.bind_class('Listbox', '<Control-Button-1>'))
320
321
    # Temporarily hide the window for other dialogs
322
    root.withdraw()
323
324
    # Start the application
325
    try:
326
        app = Application(master=root, home=args.home,
327
                          root=args.root, name=args.test)
328
        if _LAUNCH:
329
            app.mainloop()
330
    except Exception as e:  # pylint: disable=broad-except
331
        Application.show_error_from_exception(e)
332
        return False
333
334
    return True
335
336
337
if __name__ == '__main__':  # pragma: no cover (manual test)
338
    main()
339