Update to 0.7.0

This commit is contained in:
A S Lewis 2019-07-07 15:37:03 +01:00
parent 52288f05fb
commit 51c68406f1
23 changed files with 905 additions and 300 deletions

32
CHANGES
View File

@ -1,3 +1,35 @@
v0.7.0 (7 Jul 2019)
-------------------------------------------------------------------------------
- This is the first release candidate for v1.0.0
- The MS Windows installer has been redesigned again (thanks to slartie for his
generous assistance in getting it working). Some MS Windows 10 users were
still complaining that Tartube would not run; this should be fixed now
- MS Windows should find that the annoying terminal window is no longer visible
- Deleting individual videos, then adding them to the Tartube database again,
could cause problems with the statistics displayed in the Video Index (e.g.
'All Videos (0, -1)'. Fixed
- Deleting and individual video by right-clicking it removed the video from the
database, but didn't delete the video file itself. Fixed
- If an empty channel/playlist was selected, new videos did not automatically
appear in the Video Catalogue during a download operation. Fixed
- Fixed some issues with the Temporary Videos folder
- Fixed more issues with videos being dislayed in the wrong order in the Video
Catalogue
- Fixed more issues with the scrollbars not resetting themselves when switching
between channels/playlists/folders
- After right-clicking a folder, selecting Mark Videos > New didn't work. Fixed
- When marking videos as new/favourite, you can now do this to all videos
in a folder, including all child channels/playlists/folders, or you can do
it just to the videos actually inside that folder. There are several new
popup menu options for emptying a folder, or for removing all its videos
- Tartube will no longer issue a system error if you drag a folder onto itself
in the Video Index
- Fixed the remaining issues caused by the Gtk graphics libraries
- Confirmed that various Gtk issues present in Gtk3.22 are not present in
Gtk 3.24. Users with Gtk 3.22 on their system will be warned to update it.
These warnings can be disabled, if required
v0.6.0 (4 Jul 2019)
-------------------------------------------------------------------------------

View File

@ -18,11 +18,11 @@ Problems can be reported at
Downloads
---------
Latest version: **v0.6.0 (4 July 2019)**
Latest version: **v0.7.0 (7 July 2019)**
- `MS Windows (32-bit) installer <https://sourceforge.net/projects/tartube/files/v0.6.0/install-tartube-0.6.0-32bit.exe/download>`__ from Sourceforge
- `MS Windows (64-bit) installer <https://sourceforge.net/projects/tartube/files/v0.6.0/install-tartube-0.6.0-64bit.exe/download>`__ from Sourceforge
- `Source code <https://sourceforge.net/projects/tartube/files/v0.6.0/tartube_v0.6.0.tar.gz/download>`__ from Sourceforge
- `MS Windows (32-bit) installer <https://sourceforge.net/projects/tartube/files/v0.7.0/install-tartube-0.7.0-32bit.exe/download>`__ from Sourceforge
- `MS Windows (64-bit) installer <https://sourceforge.net/projects/tartube/files/v0.7.0/install-tartube-0.7.0-64bit.exe/download>`__ from Sourceforge
- `Source code <https://sourceforge.net/projects/tartube/files/v0.7.0/tartube_v0.7.0.tar.gz/download>`__ from Sourceforge
- `Source code <https://github.com/axcore/tartube>`__ and `support <https://github.com/axcore/tartube/issues>`__ from GitHub
Why should I use Tartube?
@ -94,7 +94,7 @@ Run without installing
1. Download & extract the source
2. Change directory into the **Tartube** directory
3. Run ``python3 tartube.py``
3. Run ``python3 tartube``
Getting started
---------------

5
hello_world_mswin.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
# Shell script to start the Gtk test programme on MS Windows, using the MSYS2
# environment provided by the Tartube installer
cd tartube
python3 hello_world.py

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

View File

@ -4170,54 +4170,62 @@ class SystemPrefWin(GenericPrefWin):
)
checkbutton.connect('toggled', self.on_squeeze_button_toggled)
checkbutton2 = self.add_checkbutton(grid,
'Show system warning messages in the \'Errors/Warnings\' tab',
self.app_obj.system_warning_show_flag,
True, # Can be toggled by user
0, 5, grid_width, 1,
)
checkbutton2.connect('toggled', self.on_warning_button_toggled)
# Operation preferences
self.add_label(grid,
'<u>Operation preferences</u>',
0, 5, grid_width, 1,
0, 6, grid_width, 1,
)
checkbutton2 = self.add_checkbutton(grid,
checkbutton3 = self.add_checkbutton(grid,
'Automatically update youtube-dl before every download operation',
self.app_obj.operation_auto_update_flag,
True, # Can be toggled by user
0, 6, grid_width, 1,
0, 7, grid_width, 1,
)
checkbutton2.connect('toggled', self.on_auto_update_button_toggled)
checkbutton3.connect('toggled', self.on_auto_update_button_toggled)
checkbutton3 = self.add_checkbutton(grid,
checkbutton4 = self.add_checkbutton(grid,
'Automatically save files at the end of a download/update/' \
+ 'refresh operation',
self.app_obj.operation_save_flag,
True, # Can be toggled by user
0, 7, grid_width, 1,
0, 8, grid_width, 1,
)
checkbutton3.connect('toggled', self.on_save_button_toggled)
checkbutton4.connect('toggled', self.on_save_button_toggled)
checkbutton4 = self.add_checkbutton(grid,
checkbutton5 = self.add_checkbutton(grid,
'Show a dialogue window at the end of a download/update/refresh' \
+ ' operation',
self.app_obj.operation_dialogue_flag,
True, # Can be toggled by user
0, 8, grid_width, 1,
0, 9, grid_width, 1,
)
checkbutton4.connect('toggled', self.on_dialogue_button_toggled)
checkbutton5.connect('toggled', self.on_dialogue_button_toggled)
# Module preferences
self.add_label(grid,
'<u>Module preferences</u>',
0, 9, grid_width, 1,
0, 10, grid_width, 1,
)
checkbutton5 = self.add_checkbutton(grid,
checkbutton6 = self.add_checkbutton(grid,
'Use \'moviepy\' module to get a video\'s duration, if not known'
+ ' (may be slow)',
self.app_obj.use_module_moviepy_flag,
True, # Can be toggled by user
0, 10, grid_width, 1,
0, 11, grid_width, 1,
)
checkbutton5.connect('toggled', self.on_moviepy_button_toggled)
checkbutton6.connect('toggled', self.on_moviepy_button_toggled)
if not mainapp.HAVE_MOVIEPY_FLAG:
checkbutton5.set_sensitive(False)
checkbutton6.set_sensitive(False)
def setup_backups_tab(self):
@ -5288,6 +5296,26 @@ class SystemPrefWin(GenericPrefWin):
self.app_obj.set_ytdl_write_verbose_flag(False)
def on_warning_button_toggled(self, checkbutton):
"""Called from callback in self.setup_general_tab().
Enables/disables system warnings in the 'Errors/Warnings' tab.
Args:
checkbutton (Gtk.CheckButton): The widget clicked
"""
if checkbutton.get_active() \
and not self.app_obj.system_warning_show_flag:
self.app_obj.set_system_warning_show_flag(True)
elif not checkbutton.get_active() \
and self.app_obj.system_warning_show_flag:
self.app_obj.set_system_warning_show_flag(False)
def on_worker_button_toggled(self, checkbutton):
"""Called from callback in self.setup_general_tab().

View File

@ -1487,11 +1487,12 @@ class VideoDownloader(object):
if not media_data_obj.dl_flag:
media_data_obj.set_dl_flag(True)
GObject.timeout_add(
0,
app_obj.main_win_obj.video_catalogue_update_row,
app_obj.mark_video_downloaded,
media_data_obj,
True, # Video is downloaded
True, # Video is not new
)
else:
@ -1503,11 +1504,12 @@ class VideoDownloader(object):
if not match_obj.dl_flag:
match_obj.set_dl_flag(True)
GObject.timeout_add(
0,
app_obj.main_win_obj.video_catalogue_update_row,
app_obj.mark_video_downloaded,
match_obj,
True, # Video is downloaded
True, # Video is not new
)
else:

View File

@ -465,6 +465,7 @@ SMALL_ICON_DICT = {
'error_small': 'error.png',
'warning_small': 'warning.png',
'system_error_small': 'system_error.png',
'system_warning_small': 'system_warning.png',
}
WIN_ICON_LIST = [

View File

@ -189,6 +189,9 @@ class TartubeApp(Gtk.Application):
self.main_win_height = 600
self.config_win_width = 650
self.config_win_height = 450
# Default size (in pixels) of space between various widgets
self.default_spacing_size = 5
# Flag set to True if the main toolbar should be compressed (by
# removing the labels); ideal if the toolbar's contents won't fit in
# the standard-sized window (as it almost certainly won't on MS
@ -197,8 +200,19 @@ class TartubeApp(Gtk.Application):
self.toolbar_squeeze_flag = False
else:
self.toolbar_squeeze_flag = True
# Default size (in pixels) of space between various widgets
self.default_spacing_size = 5
# Flag set to True if system warning messages should be shown (system
# error messages are always shown)
# NB The check is applied by self.system_warning(); any part of the
# code could call mainwin.MainWin.errors_list_add_system_warning()
# directly, which would bypass this flag
self.system_warning_show_flag = True
# Gtk v3.22.* produces numerous error/warning messages in the terminal
# when the Video Index and Video Catalogue are updated. Whatever the
# issues were, they appear to have been fixed by Gtk v3.24.*
# Flag set to True by self.start() if Tartube is being run before
# Gtk v3.24
self.gtk_broken_flag = False
# Tartube's data directory (platform-dependant), i.e. 'tartube-data'
# Note that, using the MSWin installer, Cygwin gives file paths with
@ -894,6 +908,16 @@ class TartubeApp(Gtk.Application):
# Import the script name (for convenience)
script_name = utils.upper_case_first(__main__.__packagename__)
# Gtk v3.22.* produces numerous error/warning messages in the terminal
# when the Video Index and Video Catalogue are updated. Whatever the
# issues were, they appear to have been fixed by Gtk v3.24.*
major = Gtk.get_major_version()
minor = Gtk.get_minor_version()
micro = Gtk.get_micro_version()
if major < 3 or (major == 3 and minor < 24):
self.gtk_broken_flag = True
# Create the main window
self.main_win_obj = mainwin.MainWin(self)
# If the debugging flag is set, move it to the top-left corner of the
@ -1117,6 +1141,17 @@ class TartubeApp(Gtk.Application):
if self.toolbar_squeeze_flag:
self.main_win_obj.redraw_main_toolbar()
# If the system's Gtk is an early, broken version, display a system
# warning
if self.gtk_broken_flag:
self.system_warning(
126,
'Gtk v' + str(major) + '.' + str(minor) + '.' + str(micro) \
+ ' is broken, which will cause problems when running ' \
+ utils.upper_case_first(__main__.__packagename__) \
+ '. Please update it to at least Gtk v3.24',
)
# If file load/save has been disabled, we can now show a dialogue
# window
if self.disable_load_save_flag:
@ -1236,9 +1271,10 @@ class TartubeApp(Gtk.Application):
Notes:
Error codes are currently assigned thus:
Error codes for this function and for self.system_warning are
currently assigned thus:
100-199: mainapp.py (in use: 101-125)
100-199: mainapp.py (in use: 101-126)
200-299: mainwin.py (in use: 201-234)
300-399: downloads.py (in use: 301-304)
400-499: config.py (in use: 401-404)
@ -1255,6 +1291,32 @@ class TartubeApp(Gtk.Application):
print('SYSTEM ERROR ' + str(error_code) + ': ' + msg)
def system_warning(self, error_code, msg):
"""Can be called by anything.
Wrapper function for mainwin.MainWin.errors_list_add_system_warning().
Args:
code (int): An error code in the range 100-999. This function and
self.system_error() share the same error codes
msg (str): A system error message to display in the main window's
Errors List.
"""
if DEBUG_FUNC_FLAG:
print('ap 992 system_warning')
if self.main_win_obj and self.system_warning_show_flag:
self.main_win_obj.errors_list_add_system_warning(error_code, msg)
else:
# Emergency fallback: display in the terminal window
print('SYSTEM WARNING ' + str(error_code) + ': ' + msg)
# (Config/database files load/save)
@ -1323,6 +1385,9 @@ class TartubeApp(Gtk.Application):
# Set IVs to their new values
if version >= 5024: # v0.5.024
self.toolbar_squeeze_flag = json_dict['toolbar_squeeze_flag']
if version >= 6006: # v0.6.006
self.system_warning_show_flag \
= json_dict['system_warning_show_flag']
self.data_dir = json_dict['data_dir']
self.downloads_dir = os.path.abspath(
@ -1427,6 +1492,7 @@ class TartubeApp(Gtk.Application):
'save_time': str(utc.strftime('%H:%M:%S')),
# Data
'toolbar_squeeze_flag': self.toolbar_squeeze_flag,
'system_warning_show_flag': self.system_warning_show_flag,
'data_dir': self.data_dir,
@ -1731,6 +1797,37 @@ class TartubeApp(Gtk.Application):
and media_data_obj.index is not None:
media_data_obj.index = int(media_data_obj.index)
if version < 6003: # v0.6.003
# This version fixes an issue in which deleting an individual video
# and then re-adding the same video, downloading it then deleting
# it a second time, messes up the parent container's count IVs
# Nothing for it but to recalculate them all, just in case
for dbid in self.media_name_dict.values():
container_obj = self.media_reg_dict[dbid]
vid_count = new_count = fav_count = dl_count = 0
for child_obj in container_obj.child_list:
if isinstance(child_obj, media.Video):
vid_count += 1
if child_obj.new_flag:
new_count += 1
if child_obj.fav_flag:
fav_count += 1
if child_obj.dl_flag:
dl_count += 1
container_obj.reset_counts(
vid_count,
new_count,
fav_count,
dl_count,
)
def save_db(self):
@ -2133,10 +2230,13 @@ class TartubeApp(Gtk.Application):
if DEBUG_FUNC_FLAG:
print('ap 1570 delete_temp_folders')
for name in self.media_name_dict:
# (Must compile a list of top-level container objects first, or Python
# will complain about the dictionary changing size)
obj_list = []
for dbid in self.media_name_dict.values():
obj_list.append(self.media_reg_dict[dbid])
dbid = self.media_name_dict[name]
media_data_obj = self.media_reg_dict[dbid]
for media_data_obj in obj_list:
if isinstance(media_data_obj, media.Folder) \
and media_data_obj.temp_flag:
@ -2645,7 +2745,6 @@ class TartubeApp(Gtk.Application):
# The downloads.DownloadItem handles a download for a video, a channel
# or a playlist
media_data_obj = download_item_obj.media_data_obj
video_obj = None
if isinstance(media_data_obj, media.Video):
@ -2661,6 +2760,7 @@ class TartubeApp(Gtk.Application):
# The downloads.DownloadItem object is handling a channel or
# playlist
# Does a media.Video object already exist?
video_obj = None
for child_obj in media_data_obj.child_list:
if isinstance(child_obj, media.Video) \
@ -2869,6 +2969,9 @@ class TartubeApp(Gtk.Application):
json_dict = self.file_manager_obj.load_json(json_path)
if 'title' in json_dict:
video_obj.set_name(json_dict['title'])
if 'upload_date' in json_dict:
# date_string in form YYYYMMDD
date_string = json_dict['upload_date']
@ -3335,8 +3438,6 @@ class TartubeApp(Gtk.Application):
self.main_win_obj.video_index_add_row(media_data_obj)
# Select the moving object, which redraws the Video Catalogue
# !!! TODO BUG: This doesn't work because the .select_iter() call
# generates an error
self.main_win_obj.video_index_select_row(media_data_obj)
@ -3364,13 +3465,17 @@ class TartubeApp(Gtk.Application):
# Do some basic checks
if source_obj is None or isinstance(source_obj, media.Video) \
or dest_obj is None or isinstance(dest_obj, media.Video) \
or source_obj == dest_obj:
or dest_obj is None or isinstance(dest_obj, media.Video):
return self.system_error(
114,
'Move container request failed sanity check',
)
elif source_obj == dest_obj:
# No need for a system error message if the user drags a folder
# onto itself; just do nothing
return
# Ignore Video Index drag-and-drop during an download/update/refresh
# operation
elif self.current_manager_obj:
@ -3486,15 +3591,14 @@ class TartubeApp(Gtk.Application):
self.main_win_obj.video_index_delete_row(source_obj)
self.main_win_obj.video_index_add_row(source_obj)
# Select the moving object, which redraws the Video Catalogue
# !!! TODO BUG: This doesn't work because the .select_iter() call
# generates an error
self.main_win_obj.video_index_select_row(source_obj)
# (Delete media data objects)
def delete_video(self, video_obj, no_update_index_flag=False):
def delete_video(self, video_obj, no_update_index_flag=False,
delete_files_flag=False):
"""Called by self.delete_temp_folders(), .delete_container(),
mainwin.MainWin.video_catalogue_popup_menu() and a callback in
@ -3510,6 +3614,11 @@ class TartubeApp(Gtk.Application):
self.delete_container(), in which case the Video Index is not
updated (because the calling function wants to do that)
delete_files_flag (True or False): True when called by
mainwin.MainWin.on_video_catalogue_delete_video, in which case
the video and its associated files are deleted from the
filesystem
"""
if DEBUG_FUNC_FLAG:
@ -3538,21 +3647,61 @@ class TartubeApp(Gtk.Application):
# Remove the video from our IVs
del self.media_reg_dict[video_obj.dbid]
# (If emptying a temporary folder on startup, the main window won't be
# visible yet)
if self.main_win_obj:
# Remove the video from the catalogue, if present
self.main_win_obj.video_catalogue_delete_row(video_obj)
# Delete files from the filesystem, if required
if delete_files_flag and video_obj.file_dir:
# Update rows in the Video Index
if not no_update_index_flag:
for container_obj in update_list:
video_path = os.path.abspath(
os.path.join(
video_obj.file_dir,
video_obj.file_name + video_obj.file_ext,
),
)
if os.path.isfile(video_path):
os.remove(video_path)
# Also need to delete the thumbnail, description and/or JSON files
# if they exist
thumb_path = utils.find_thumbnail(self, video_obj)
if thumb_path and os.path.isfile(thumb_path):
os.remove(thumb_path)
descrip_path = os.path.abspath(
os.path.join(
video_obj.file_dir,
video_obj.file_name + '.description',
),
)
if os.path.isfile(descrip_path):
os.remove(descrip_path)
json_path = os.path.abspath(
os.path.join(
video_obj.file_dir,
video_obj.file_name + '.info.json',
),
)
if os.path.isfile(json_path):
os.remove(json_path)
# Remove the video from the catalogue, if present
self.main_win_obj.video_catalogue_delete_row(video_obj)
# Update rows in the Video Index, first checking that the parent
# container object is currently drawn there (which it might not be,
# if emptying temporary folders on startup)
if not no_update_index_flag:
for container_obj in update_list:
if container_obj.name \
in self.main_win_obj.video_index_row_dict:
self.main_win_obj.video_index_update_row_text(
container_obj,
)
def delete_container(self, media_data_obj):
def delete_container(self, media_data_obj, empty_flag=False):
"""Can be called by anything.
@ -3573,6 +3722,10 @@ class TartubeApp(Gtk.Application):
media_data_obj (media.Channel, media.Playlist, media.Folder):
The container media data object
empty_flag (True or False): If True, the container media data
object is to be emptied, rather than being deleted
"""
if DEBUG_FUNC_FLAG:
@ -3593,24 +3746,42 @@ class TartubeApp(Gtk.Application):
# children
# (Even though there are no children, we can't guarantee that the
# sub-directories in Tartube's data directory are empty)
dialogue_win = mainwin.DeleteContainerDialogue(
self.main_win_obj,
media_data_obj,
)
# Exception: don't prompt for confirmation if media_data_obj is
# somewhere inside a temporary folder
confirm_flag = True
delete_file_flag = False
parent_obj = media_data_obj.parent_obj
response = dialogue_win.run()
while parent_obj is not None:
if isinstance(parent_obj, media.Folder) and parent_obj.temp_flag:
# The media data object is somewhere inside a temporary folder;
# no need to prompt for confirmation
confirm_flag = False
# Retrieve user choices from the dialogue window...
if dialogue_win.button2.get_active():
delete_file_flag = True
else:
delete_file_flag = False
parent_obj = parent_obj.parent_obj
# ...before destroying it
dialogue_win.destroy()
if confirm_flag:
if response != Gtk.ResponseType.OK:
return
# Prompt the user for confirmation
dialogue_win = mainwin.DeleteContainerDialogue(
self.main_win_obj,
media_data_obj,
empty_flag,
)
response = dialogue_win.run()
# Retrieve user choices from the dialogue window...
if dialogue_win.button2.get_active():
delete_file_flag = True
else:
delete_file_flag = False
# ...before destroying it
dialogue_win.destroy()
if response != Gtk.ResponseType.OK:
return
# Get a second confirmation, if required to delete files
if delete_file_flag:
@ -3624,17 +3795,17 @@ class TartubeApp(Gtk.Application):
# Arguments passed directly to .delete_container_continue()
{
'yes': 'delete_container_continue',
'data': media_data_obj,
'data': [media_data_obj, empty_flag],
}
)
# No second confirmation required, so we can proceed directly to the
# call to self.delete_container_complete()
else:
self.delete_container_complete(media_data_obj)
self.delete_container_complete(media_data_obj, empty_flag)
def delete_container_continue(self, media_data_obj):
def delete_container_continue(self, data_list):
"""Called by dialogue.MessageDialogue.on_clicked().
@ -3643,25 +3814,36 @@ class TartubeApp(Gtk.Application):
Args:
media_data_obj (media.Channel, media.Playlist, media.Folder):
The container media data object
data_list (list): A list of two items. The first is the container
media data object; the second is a flag set to True if the
container should be emptied, rather than being deleted
"""
if DEBUG_FUNC_FLAG:
print('ap 3034 delete_container_continue')
# Unpack the arguments
media_data_obj = data_list[0]
empty_flag = data_list[1]
# Confirmation obtained, so delete the files
container_dir = media_data_obj.get_dir(self)
if os.path.isdir(container_dir):
shutil.rmtree(container_dir)
# If emptying the container rather than deleting it, just create a
# replacement (empty) directory on the filesystem
if empty_flag:
os.makedirs(container_dir)
# Now call self.delete_container_complete() to handle the media data
# registry
self.delete_container_complete(media_data_obj)
self.delete_container_complete(media_data_obj, empty_flag)
def delete_container_complete(self, media_data_obj, recursive_flag=False):
def delete_container_complete(self, media_data_obj, empty_flag,
recursive_flag=False):
"""Called by self.delete_container(), .delete_container_continue()
and then recursively by this function.
@ -3677,6 +3859,9 @@ class TartubeApp(Gtk.Application):
media_data_obj (media.Channel, media.Playlist, media.Folder):
The container media data object
empty_flag (True or False): If True, the container media data
object is to be emptied, rather than being deleted
recursive_flag (True, False): Set to False on the initial call to
this function from some other part of the code, and True when
this function calls itself recursively
@ -3697,24 +3882,31 @@ class TartubeApp(Gtk.Application):
if isinstance(child_obj, media.Video):
self.delete_video(child_obj, True)
else:
self.delete_container_complete(child_obj, True)
self.delete_container_complete(child_obj, False, True)
# Remove the container object from its own parent object (if it has
# one)
if media_data_obj.parent_obj:
media_data_obj.parent_obj.del_child(media_data_obj)
if not empty_flag or recursive_flag:
# Remove the media data object from our IVs
del self.media_reg_dict[media_data_obj.dbid]
del self.media_name_dict[media_data_obj.name]
if media_data_obj.dbid in self.media_top_level_list:
index = self.media_top_level_list.index(media_data_obj.dbid)
del self.media_top_level_list[index]
# Remove the container object from its own parent object (if it has
# one)
if media_data_obj.parent_obj:
media_data_obj.parent_obj.del_child(media_data_obj)
# Remove the media data object from our IVs
del self.media_reg_dict[media_data_obj.dbid]
del self.media_name_dict[media_data_obj.name]
if media_data_obj.dbid in self.media_top_level_list:
index = self.media_top_level_list.index(media_data_obj.dbid)
del self.media_top_level_list[index]
# During the initial call to this function, delete the container
# object from the Video Index (which automatically resets the Video
# Catalogue)
if not recursive_flag:
# (If deleting the contents of temporary folders while loading a
# Tartube database, the Video Index may not yet have been drawn, so
# we have to check for that)
if not recursive_flag and not empty_flag \
and media_data_obj.name in self.main_win_obj.video_index_row_dict:
self.main_win_obj.video_index_delete_row(media_data_obj)
# Also redraw the private folders in the Video Index, to show the
@ -3731,6 +3923,14 @@ class TartubeApp(Gtk.Application):
self.fixed_fav_folder,
)
elif not recursive_flag and empty_flag:
# When emptying the container, the quickest way to update the Video
# Index is just to redraw it from scratch
self.main_win_obj.video_index_reset()
self.main_win_obj.video_catalogue_reset()
self.main_win_obj.video_index_populate()
# (Change media data object settings, updating all related things)
@ -3839,7 +4039,7 @@ class TartubeApp(Gtk.Application):
self.main_win_obj.video_index_update_row_text(container_obj)
def mark_video_downloaded(self, video_obj, flag):
def mark_video_downloaded(self, video_obj, dl_flag, not_new_flag=False):
"""Can be called by anything.
@ -3852,8 +4052,12 @@ class TartubeApp(Gtk.Application):
video_obj (media.Video): The media.Video object to mark.
flag (True or False): True to mark the video as downloaded, False
to mark it as not downloaded.
dl_flag (True or False): True to mark the video as downloaded,
False to mark it as not downloaded.
not_new_flag (True or False): Set to True when called by
downloads.confirm_old_video(). The video is downloaded, but not
new
"""
@ -3870,13 +4074,13 @@ class TartubeApp(Gtk.Application):
'Mark video as downloaded request failed sanity check',
)
elif not flag:
elif not dl_flag:
# Mark video as not downloaded
if not video_obj.dl_flag:
# Already marked
return
# Already marked
return
else:
@ -3892,15 +4096,16 @@ class TartubeApp(Gtk.Application):
update_list.append(self.fixed_fav_folder)
# Also mark the video as not new
self.mark_video_new(video_obj, False, True)
if not not_new_flag:
self.mark_video_new(video_obj, False, True)
else:
# Mark video as downloaded
if video_obj.dl_flag:
# Already marked
return
# Already marked
return
else:
@ -3921,7 +4126,8 @@ class TartubeApp(Gtk.Application):
update_list.append(self.fixed_fav_folder)
# Also mark the video as new
self.mark_video_new(video_obj, True, True)
if not not_new_flag:
self.mark_video_new(video_obj, True, True)
# Update rows in the Video Index
for container_obj in update_list:
@ -4095,7 +4301,8 @@ class TartubeApp(Gtk.Application):
self.main_win_obj.video_index_delete_row(folder_obj)
def mark_container_favourite(self, media_data_obj, flag):
def mark_container_favourite(self, media_data_obj, flag,
only_child_videos_flag):
"""Called by mainwin.MainWin.on_video_index_mark_favourite() and
.on_video_index_mark_not_favourite().
@ -4112,6 +4319,10 @@ class TartubeApp(Gtk.Application):
flag (True or False): True to mark as favourite, False to mark as
not favourite
only_child_videos_flag (True or False): Set to True if only child
video objects should be marked; False if the container object
and all its descendants should be marked
"""
if DEBUG_FUNC_FLAG:
@ -4154,6 +4365,14 @@ class TartubeApp(Gtk.Application):
and other_obj.fav_flag:
self.mark_video_favourite(other_obj, flag, True)
elif only_child_videos_flag:
# Check videos in this folder
for other_obj in media_data_obj.child_list:
if isinstance(other_obj, media.Video):
self.mark_video_favourite(other_obj, flag, True)
else:
# Check only video objects that are descendants of the specified
@ -4847,7 +5066,7 @@ class TartubeApp(Gtk.Application):
'ok',
)
elif not source or not utils.url_check(source):
elif not source or not utils.check_url(source):
self.dialogue_manager_obj.show_msg_dialogue(
'You must enter a valid URL',
'error',
@ -4955,20 +5174,6 @@ class TartubeApp(Gtk.Application):
# In the Video Index, select the parent media data object, which
# updates both the Video Index and the Video Catalogue
self.main_win_obj.video_index_select_row(parent_obj)
# !!! TODO BUG: That doesn't work, possibly because of the gtk_iter
# error we have been getting, so artificially update the Video
# Catalogue if the parent container is the visible one
# if self.main_win_obj.video_index_current is not None \
# and self.main_win_obj.video_index_current == parent_obj.name:
# self.main_win_obj.video_catalogue_redraw_all(parent_obj.name)
if self.main_win_obj.video_index_current is not None \
and (
self.main_win_obj.video_index_current == parent_obj.name \
or self.main_win_obj.video_index_current == 'All Videos'
):
self.main_win_obj.video_catalogue_redraw_all(
self.main_win_obj.video_index_current,
)
# If any duplicates were found, inform the user
if duplicate_list:
@ -5329,9 +5534,9 @@ class TartubeApp(Gtk.Application):
print('ap 4445 set_complex_index_flag')
if not flag:
self.complex_index = False
self.complex_index_flag = False
else:
self.complex_index = True
self.complex_index_flag = True
def set_db_backup_mode(self, value):
@ -5508,6 +5713,17 @@ class TartubeApp(Gtk.Application):
self.operation_save_flag = True
def set_system_warning_show_flag(self, flag):
if DEBUG_FUNC_FLAG:
print('ap 4563 set_system_warning_show_flag')
if not flag:
self.system_warning_show_flag = False
else:
self.system_warning_show_flag = True
def set_toolbar_squeeze_flag(self, flag):
if DEBUG_FUNC_FLAG:

View File

@ -1124,6 +1124,7 @@ class MainWin(Gtk.ApplicationWindow):
# Progress List
self.progress_list_treeview = Gtk.TreeView()
self.progress_list_frame.add(self.progress_list_treeview)
self.progress_list_treeview.set_can_focus(False)
for i, column_title in enumerate(
[
@ -1169,6 +1170,7 @@ class MainWin(Gtk.ApplicationWindow):
# Results List
self.results_list_treeview = Gtk.TreeView()
self.results_list_frame.add(self.results_list_treeview)
self.results_list_treeview.set_can_focus(False)
for i, column_title in enumerate(
[
@ -1277,6 +1279,7 @@ class MainWin(Gtk.ApplicationWindow):
self.errors_list_treeview = Gtk.TreeView()
self.errors_list_frame.add(self.errors_list_treeview)
self.errors_list_treeview.set_can_focus(False)
for i, column_title in enumerate(['', '', 'Time', 'Media', 'Message']):
@ -1754,7 +1757,8 @@ class MainWin(Gtk.ApplicationWindow):
return -1
elif obj1.upload_time < obj2.upload_time:
return 1
else:
elif obj1.receive_time is not None \
and obj2.receive_time is not None:
# In private folders, the most recently received video goes to
# the top of the list
if self.video_index_current_priv_flag:
@ -1774,6 +1778,8 @@ class MainWin(Gtk.ApplicationWindow):
return 1
else:
return 0
else:
return 0
else:
return 0
@ -1810,6 +1816,7 @@ class MainWin(Gtk.ApplicationWindow):
# Set up the widgets
self.video_index_treeview = Gtk.TreeView()
self.video_index_frame.add(self.video_index_treeview)
self.video_index_treeview.set_can_focus(False)
self.video_index_treeview.set_headers_visible(False)
# (Detect right-clicks on the treeview)
self.video_index_treeview.connect(
@ -2020,13 +2027,6 @@ class MainWin(Gtk.ApplicationWindow):
'Video index setup row request failed sanity check',
)
# Temporarily disable auto-sorting, or the call to
# Gtk.TreeView.expand_to_path() will sometimes fail
# !!! TODO BUG: Why? Who knows, it's a Gtk thing. Unfortunately that
# means rows in the Video Index might be in the wrong order, but
# that's better than a failure of .expand_to_path() )
self.video_index_no_sort_flag = True
# Add a row to the treeview
if media_data_obj.parent_obj:
@ -2051,9 +2051,6 @@ class MainWin(Gtk.ApplicationWindow):
],
)
# Expand rows to make the new media data object visible...
self.video_index_treeview.expand_to_path(parent_ref.get_path())
else:
# The media data object has no parent, so add a row to the
@ -2075,13 +2072,22 @@ class MainWin(Gtk.ApplicationWindow):
)
self.video_index_row_dict[media_data_obj.name] = tree_ref
if media_data_obj.parent_obj:
# Expand rows to make the new media data object visible...
self.video_index_treeview.expand_to_path(
self.video_index_sortmodel.convert_child_path_to_path(
parent_ref.get_path(),
),
)
# Select the row (which clears the Video Catalogue)
selection = self.video_index_treeview.get_selection()
selection.select_path(tree_ref.get_path())
# Re-enable auto-sorting, if disabled
if self.video_index_no_sort_flag:
self.video_index_no_sort_flag = False
selection.select_path(
self.video_index_sortmodel.convert_child_path_to_path(
tree_ref.get_path(),
),
)
# Make the changes visible
self.video_index_treeview.show_all()
@ -2165,19 +2171,27 @@ class MainWin(Gtk.ApplicationWindow):
# Select the row, expanding the treeview path to make it visible, if
# necessary
tree_ref = self.video_index_row_dict[media_data_obj.name]
tree_path = tree_ref.get_path()
tree_iter = self.video_index_treestore.get_iter(tree_path)
if media_data_obj.parent_obj:
self.video_index_treeview.expand_to_path(tree_path)
# Expand rows to make the new media data object visible...
parent_ref \
= self.video_index_row_dict[media_data_obj.parent_obj.name]
self.video_index_treeview.expand_to_path(
self.video_index_sortmodel.convert_child_path_to_path(
parent_ref.get_path(),
),
)
# Select the row
tree_ref = self.video_index_row_dict[media_data_obj.name]
selection = self.video_index_treeview.get_selection()
# !!! TODO BUG: This generates a Gtk error:
# Gtk-CRITICAL **: gtk_tree_model_sort_get_path: assertion
# 'priv->stamp == iter->stamp' failed
selection.select_iter(tree_iter)
self.show_all()
selection.select_path(
self.video_index_sortmodel.convert_child_path_to_path(
tree_ref.get_path(),
),
)
def video_index_update_row_icon(self, media_data_obj):
@ -2551,8 +2565,7 @@ class MainWin(Gtk.ApplicationWindow):
popup_menu.append(refresh_menu_item)
# Separator
separator_item = Gtk.SeparatorMenuItem()
popup_menu.append(separator_item)
popup_menu.append(Gtk.SeparatorMenuItem())
# Apply/remove/edit download options, disable downloads
@ -2627,8 +2640,89 @@ class MainWin(Gtk.ApplicationWindow):
enforce_check_menu_item.set_sensitive(False)
# Separator
separator_item2 = Gtk.SeparatorMenuItem()
popup_menu.append(separator_item2)
popup_menu.append(Gtk.SeparatorMenuItem())
# Contents
contents_submenu = Gtk.Menu()
if not isinstance(media_data_obj, media.Folder):
self.video_index_setup_contents_submenu(
contents_submenu,
media_data_obj,
False,
)
else:
# All contents
all_contents_submenu = Gtk.Menu()
self.video_index_setup_contents_submenu(
all_contents_submenu,
media_data_obj,
False,
)
# Separator
all_contents_submenu.append(Gtk.SeparatorMenuItem())
empty_folder_menu_item = Gtk.MenuItem.new_with_mnemonic(
'_Empty folder',
)
empty_folder_menu_item.connect(
'activate',
self.on_video_index_empty_folder,
media_data_obj,
)
all_contents_submenu.append(empty_folder_menu_item)
if not media_data_obj.child_list or media_data_obj.priv_flag:
empty_folder_menu_item.set_sensitive(False)
all_contents_menu_item = Gtk.MenuItem.new_with_mnemonic(
'_All contents',
)
all_contents_menu_item.set_submenu(all_contents_submenu)
contents_submenu.append(all_contents_menu_item)
# Just folder videos
just_videos_submenu = Gtk.Menu()
self.video_index_setup_contents_submenu(
just_videos_submenu,
media_data_obj,
True,
)
# Separator
just_videos_submenu.append(Gtk.SeparatorMenuItem())
empty_videos_menu_item = Gtk.MenuItem.new_with_mnemonic(
'_Remove videos',
)
empty_videos_menu_item.connect(
'activate',
self.on_video_index_remove_videos,
media_data_obj,
)
just_videos_submenu.append(empty_videos_menu_item)
if not media_data_obj.child_list or media_data_obj.priv_flag:
empty_videos_menu_item.set_sensitive(False)
just_videos_menu_item = Gtk.MenuItem.new_with_mnemonic(
'_Just folder videos',
)
just_videos_menu_item.set_submenu(just_videos_submenu)
contents_submenu.append(just_videos_menu_item)
contents_menu_item = Gtk.MenuItem.new_with_mnemonic(
utils.upper_case_first(media_type) + ' co_ntents',
)
contents_menu_item.set_submenu(contents_submenu)
popup_menu.append(contents_menu_item)
# Separator
popup_menu.append(Gtk.SeparatorMenuItem())
# Show properties/downloads, hide folder
show_properties_menu_item = Gtk.MenuItem.new_with_mnemonic(
@ -2683,57 +2777,7 @@ class MainWin(Gtk.ApplicationWindow):
move_top_menu_item.set_sensitive(False)
# Separator
separator_item3 = Gtk.SeparatorMenuItem()
popup_menu.append(separator_item3)
# Mark videos as new/favourite
mark_videos_submenu = Gtk.Menu()
mark_new_menu_item = Gtk.MenuItem.new_with_mnemonic('_New')
mark_new_menu_item.connect(
'activate',
self.on_video_index_mark_new,
media_data_obj,
)
mark_videos_submenu.append(mark_new_menu_item)
if media_data_obj == self.app_obj.fixed_new_folder:
mark_new_menu_item.set_sensitive(False)
mark_old_menu_item = Gtk.MenuItem.new_with_mnemonic('N_ot new')
mark_old_menu_item.connect(
'activate',
self.on_video_index_mark_not_new,
media_data_obj,
)
mark_videos_submenu.append(mark_old_menu_item)
mark_fav_menu_item = Gtk.MenuItem.new_with_mnemonic('_Favourite')
mark_fav_menu_item.connect(
'activate',
self.on_video_index_mark_favourite,
media_data_obj,
)
mark_videos_submenu.append(mark_fav_menu_item)
if media_data_obj == self.app_obj.fixed_fav_folder:
mark_fav_menu_item.set_sensitive(False)
mark_not_fav_menu_item = Gtk.MenuItem.new_with_mnemonic(
'Not f_avourite',
)
mark_not_fav_menu_item.connect(
'activate',
self.on_video_index_mark_not_favourite,
media_data_obj,
)
mark_videos_submenu.append(mark_not_fav_menu_item)
mark_videos_menu_item = Gtk.MenuItem.new_with_mnemonic('_Mark videos')
mark_videos_menu_item.set_submenu(mark_videos_submenu)
popup_menu.append(mark_videos_menu_item)
# Separator
separator_item4 = Gtk.SeparatorMenuItem()
popup_menu.append(separator_item4)
popup_menu.append(Gtk.SeparatorMenuItem())
# Delete items
delete_menu_item = Gtk.MenuItem.new_with_mnemonic(
@ -2753,6 +2797,72 @@ class MainWin(Gtk.ApplicationWindow):
popup_menu.popup(None, None, None, None, event.button, event.time)
def video_index_setup_contents_submenu(self, submenu, media_data_obj,
only_child_videos_flag=False):
"""Called by self.video_index_popup_menu().
Sets up a submenu for handling the contents of a channel, playlist
or folder.
Args:
submenu (Gtk.Menu): The submenu to set up, currently empty
media_data_obj (media.Channel, media.Playlist, media.Folder): The
channel, playlist or folder whose contents should be modified
by items in the sub-menu
only_child_videos_flag (True or False): Set to True when only a
folder's child videos (not anything in its child channels,
playlists or folders) should be modified by items in the
sub-menu; False if all child objects should be modified
"""
mark_new_menu_item = Gtk.MenuItem.new_with_mnemonic('Mark as _new')
mark_new_menu_item.connect(
'activate',
self.on_video_index_mark_new,
media_data_obj,
only_child_videos_flag,
)
submenu.append(mark_new_menu_item)
if media_data_obj == self.app_obj.fixed_new_folder:
mark_new_menu_item.set_sensitive(False)
mark_old_menu_item = Gtk.MenuItem.new_with_mnemonic('Mark as n_ot new')
mark_old_menu_item.connect(
'activate',
self.on_video_index_mark_not_new,
media_data_obj,
only_child_videos_flag,
)
submenu.append(mark_old_menu_item)
mark_fav_menu_item = Gtk.MenuItem.new_with_mnemonic('Mark _favourite')
mark_fav_menu_item.connect(
'activate',
self.on_video_index_mark_favourite,
media_data_obj,
only_child_videos_flag,
)
submenu.append(mark_fav_menu_item)
if media_data_obj == self.app_obj.fixed_fav_folder:
mark_fav_menu_item.set_sensitive(False)
mark_not_fav_menu_item = Gtk.MenuItem.new_with_mnemonic(
'Mark not f_avourite',
)
mark_not_fav_menu_item.connect(
'activate',
self.on_video_index_mark_not_favourite,
media_data_obj,
only_child_videos_flag,
)
submenu.append(mark_not_fav_menu_item)
# (Video Catalogue)
@ -2777,9 +2887,9 @@ class MainWin(Gtk.ApplicationWindow):
self.video_catalogue_dict = {}
# Set up the widgets
listbox = Gtk.ListBox()
self.catalogue_frame.add(listbox)
self.catalogue_listbox = listbox
self.catalogue_listbox = Gtk.ListBox()
self.catalogue_frame.add(self.catalogue_listbox)
self.catalogue_listbox.set_can_focus(False)
self.catalogue_listbox.set_sort_func(
self.video_catalogue_auto_sort,
@ -2791,7 +2901,8 @@ class MainWin(Gtk.ApplicationWindow):
self.catalogue_frame.show_all()
def video_catalogue_redraw_all(self, name, page_num=1):
def video_catalogue_redraw_all(self, name, page_num=1,
reset_scroll_flag=False):
"""Called from callbacks in self.on_video_index_selection_changed(),
mainapp.TartubeApp.on_button_switch_view(),
@ -2834,6 +2945,11 @@ class MainWin(Gtk.ApplicationWindow):
range 1 to self.catalogue_toolbar_last_page). If None, the
current page is drawn
reset_scroll_flag (True or False): Set to True when called by
self.on_video_index_selection_changed(). The scrollbars must
always be reset when switching between channels/playlist/
folders
"""
if DEBUG_FUNC_FLAG:
@ -2842,12 +2958,11 @@ class MainWin(Gtk.ApplicationWindow):
# If actually switching to a different channel/playlist/folder, or a
# different page on the same channel/playlist/folder, must reset the
# scrollbars later in the function
if self.video_index_current is None \
or self.video_index_current != name \
or self.catalogue_toolbar_current_page != page_num:
reset_scroll_flag = True
else:
reset_scroll_flag = False
if not reset_scroll_flag:
if self.video_index_current is None \
or self.video_index_current != name \
or self.catalogue_toolbar_current_page != page_num:
reset_scroll_flag = True
# The parent media data object is a media.Channel, media.playlist or
# media.Folder object
@ -3062,33 +3177,12 @@ class MainWin(Gtk.ApplicationWindow):
missing_obj = self.app_obj.media_reg_dict[dbid]
# Create a new catalogue item
if self.app_obj.catalogue_mode == 'simple_hide_parent' \
or self.app_obj.catalogue_mode == 'simple_show_parent':
catalogue_item_obj = SimpleCatalogueItem(
self,
missing_obj,
)
self.video_catalogue_insert_item(missing_obj)
else:
catalogue_item_obj = ComplexCatalogueItem(
self,
missing_obj,
)
else:
self.video_catalogue_dict[dbid] = catalogue_item_obj
# Add a row to the Gtk.ListBox
# Instead of using Gtk.ListBoxRow directly, use a wrapper
# class so we can quickly retrieve the video displayed on
# each row
wrapper_obj = CatalogueRow(missing_obj)
self.catalogue_listbox.add(wrapper_obj)
# Populate the row with widgets...
catalogue_item_obj.draw_widgets(wrapper_obj)
# ...and give them their initial appearance
catalogue_item_obj.update_widgets()
# Page is not full, so just create a new catalogue item
self.video_catalogue_insert_item(video_obj)
# Update widgets in the toolbar
self.video_catalogue_toolbar_update(
@ -3104,6 +3198,50 @@ class MainWin(Gtk.ApplicationWindow):
self.catalogue_listbox.show_all()
def video_catalogue_insert_item(self, video_obj):
"""Called by self.video_catalogue_update_row() (only).
Adds a new mainwin.SimpleCatalogueItem or mainwin.ComplexCatalogueItem
to the Video Catalogue.
Args:
video_obj (media.Video): The video for which a new catalogue item
should be created
"""
# Create the new catalogue item
if self.app_obj.catalogue_mode == 'simple_hide_parent' \
or self.app_obj.catalogue_mode == 'simple_show_parent':
catalogue_item_obj = SimpleCatalogueItem(
self,
video_obj,
)
else:
catalogue_item_obj = ComplexCatalogueItem(
self,
video_obj,
)
self.video_catalogue_dict[video_obj.dbid] = catalogue_item_obj
# Add a row to the Gtk.ListBox
# Instead of using Gtk.ListBoxRow directly, use a wrapper
# class so we can quickly retrieve the video displayed on
# each row
wrapper_obj = CatalogueRow(video_obj)
self.catalogue_listbox.add(wrapper_obj)
# Populate the row with widgets...
catalogue_item_obj.draw_widgets(wrapper_obj)
# ...and give them their initial appearance
catalogue_item_obj.update_widgets()
def video_catalogue_delete_row(self, video_obj):
"""Called by mainapp.TartubeApp.delete_video(),
@ -4241,7 +4379,7 @@ class MainWin(Gtk.ApplicationWindow):
utils.upper_case_first(__main__.__packagename__) + ' error',
)
row_list.append(
utils.tidy_up_long_string(str(error_code) + ': ' + msg),
utils.tidy_up_long_string('#' + str(error_code) + ': ' + msg),
)
# Create a new row in the treeview. Doing the .show_all() first
@ -4256,6 +4394,63 @@ class MainWin(Gtk.ApplicationWindow):
self.errors_list_refresh_label()
def errors_list_add_system_warning(self, error_code, msg):
"""Can be called by anything. The quickest way is to call
mainapp.TartubeApp.system_warning(), which acts as a wrapper for this
function.
Display a system warning message in the Errors List.
Args:
error_code (int): An error code in the range 100-999 (see
the .system_error() function)
msg (str): The system warning message to display
"""
if DEBUG_FUNC_FLAG:
print('mw 3631 errors_list_add_system_warning')
# Prepare the icons
pixbuf = self.pixbuf_dict['warning_small']
pixbuf2 = self.pixbuf_dict['system_warning_small']
# Prepare the new row in the treeview
row_list = []
utc = datetime.datetime.utcfromtimestamp(time.time())
time_string = str(utc.strftime('%H:%M:%S'))
row_list.append(pixbuf)
row_list.append(pixbuf2)
row_list.append(time_string)
row_list.append(
utils.upper_case_first(__main__.__packagename__) + ' warning',
)
row_list.append(
utils.tidy_up_long_string('#' + str(error_code) + ': ' + msg) \
+ '\n' + utils.tidy_up_long_string(
'To disable system warning messages, click Edit >' \
+ ' System preferences... > General, and then deselect \'' \
+ 'Show system warning messages in the \'Errors/Warnings\'' \
+ ' tab\'',
),
)
# Create a new row in the treeview. Doing the .show_all() first
# prevents a Gtk error (for unknown reasons)
self.errors_list_treeview.show_all()
self.errors_list_liststore.append(row_list)
# (Don't update the Errors/Warnings tab label if it's the visible
# tab)
if self.visible_tab_num != 2:
self.tab_warning_count += 1
self.errors_list_refresh_label()
def errors_list_refresh_label(self):
"""Called by self.errors_list_add_row(),
@ -4394,7 +4589,7 @@ class MainWin(Gtk.ApplicationWindow):
menu_item (Gtk.MenuItem): The clicked menu item
media_data_obj (media.Channel, media.Playlist or media.Channel):
media_data_obj (media.Channel, media.Playlist or media.Folder):
The clicked media data object
"""
@ -4553,6 +4748,28 @@ class MainWin(Gtk.ApplicationWindow):
)
def on_video_index_empty_folder(self, menu_item, media_data_obj):
"""Called from a callback in self.video_index_popup_menu().
Empties the folder.
Args:
menu_item (Gtk.MenuItem): The clicked menu item
media_data_obj (media.Folder): The clicked media data object
"""
if DEBUG_FUNC_FLAG:
print('mw 3945 on_video_index_empty_folder')
# The True flag tells the function to empty the container, rather than
# delete it
self.app_obj.delete_container(media_data_obj, True)
def on_video_index_enforce_check(self, menu_item, media_data_obj):
"""Called from a callback in self.video_index_popup_menu().
@ -4586,7 +4803,8 @@ class MainWin(Gtk.ApplicationWindow):
self.video_index_update_row_text(media_data_obj)
def on_video_index_mark_favourite(self, menu_item, media_data_obj):
def on_video_index_mark_favourite(self, menu_item, media_data_obj,
only_child_videos_flag):
"""Called from a callback in self.video_index_popup_menu().
@ -4600,15 +4818,24 @@ class MainWin(Gtk.ApplicationWindow):
media_data_obj (media.Channel, media.Playlist or media.Channel):
The clicked media data object
only_child_videos_flag (True or False): Set to True if only child
video objects should be marked; False if all descendants should
be marked
"""
if DEBUG_FUNC_FLAG:
print('mw 4010 on_video_index_mark_favourite')
self.app_obj.mark_container_favourite(media_data_obj, True)
self.app_obj.mark_container_favourite(
media_data_obj,
True,
only_child_videos_flag,
)
def on_video_index_mark_not_favourite(self, menu_item, media_data_obj):
def on_video_index_mark_not_favourite(self, menu_item, media_data_obj,
only_child_videos_flag):
"""Called from a callback in self.video_index_popup_menu().
@ -4622,12 +4849,20 @@ class MainWin(Gtk.ApplicationWindow):
media_data_obj (media.Channel, media.Playlist or media.Channel):
The clicked media data object
only_child_videos_flag (True or False): Set to True if only child
video objects should be marked; False if all descendants should
be marked
"""
if DEBUG_FUNC_FLAG:
print('mw 4032 on_video_index_mark_not_favourite')
self.app_obj.mark_container_favourite(media_data_obj, False)
self.app_obj.mark_container_favourite(
media_data_obj,
False,
only_child_videos_flag,
)
def on_video_index_hide_folder(self, menu_item, media_data_obj):
@ -4651,7 +4886,8 @@ class MainWin(Gtk.ApplicationWindow):
self.app_obj.mark_folder_hidden(media_data_obj, True)
def on_video_index_mark_new(self, menu_item, media_data_obj):
def on_video_index_mark_new(self, menu_item, media_data_obj,
only_child_videos_flag):
"""Called from a callback in self.video_index_popup_menu().
@ -4666,6 +4902,10 @@ class MainWin(Gtk.ApplicationWindow):
media_data_obj (media.Channel, media.Playlist or media.Channel):
The clicked media data object
only_child_videos_flag (True or False): Set to True if only child
video objects should be marked; False if all descendants should
be marked
"""
if DEBUG_FUNC_FLAG:
@ -4690,17 +4930,27 @@ class MainWin(Gtk.ApplicationWindow):
if isinstance(other_obj, media.Video) and other_obj.fav_flag:
self.app_obj.mark_video_new(other_obj, True)
elif only_child_videos_flag:
# Check only videos that are children of the specified media data
# object
for other_obj in media_data_obj.child_list:
if isinstance(other_obj, media.Video):
self.app_obj.mark_video_new(other_obj, True)
else:
# Check only videos that are descendants of the specified media
# data object
for other_obj in media_data_obj.compile_all_videos( [] ):
# (Only downloaded videos can be marked as new)
if other_obj.dl_flag:
self.app_obj.mark_video_new(other_obj, True)
def on_video_index_mark_not_new(self, menu_item, media_data_obj):
def on_video_index_mark_not_new(self, menu_item, media_data_obj,
only_child_videos_flag):
"""Called from a callback in self.video_index_popup_menu().
@ -4714,6 +4964,10 @@ class MainWin(Gtk.ApplicationWindow):
media_data_obj (media.Channel, media.Playlist or media.Channel):
The clicked media data object
only_child_videos_flag (True or False): Set to True if only child
video objects should be marked; False if all descendants should
be marked
"""
if DEBUG_FUNC_FLAG:
@ -4737,6 +4991,14 @@ class MainWin(Gtk.ApplicationWindow):
if isinstance(other_obj, media.Video) and other_obj.fav_flag:
self.app_obj.mark_video_new(other_obj, False)
elif only_child_videos_flag:
# Check only videos that are children of the specified media data
# object
for other_obj in media_data_obj.child_list:
if isinstance(other_obj, media.Video):
self.app_obj.mark_video_new(other_obj, False)
else:
# Check only videos that are descendants of the specified media
@ -4827,6 +5089,29 @@ class MainWin(Gtk.ApplicationWindow):
self.app_obj.remove_download_options(media_data_obj)
def on_video_index_remove_videos(self, menu_item, media_data_obj):
"""Called from a callback in self.video_index_popup_menu().
Empties all child videos of a folder object, but doesn't remove any
child channel, playlist or folder objects.
Args:
menu_item (Gtk.MenuItem): The clicked menu item
media_data_obj (media.Folder): The clicked media data object
"""
if DEBUG_FUNC_FLAG:
print('mw 4222 on_video_index_remove_videos')
for child_obj in media_data_obj.child_list:
if isinstance(child_obj, media.Video):
self.app_obj.delete_video(child_obj)
def on_video_index_right_click(self, treeview, event):
"""Called from callback in self.setup_videos_tab().
@ -4870,7 +5155,7 @@ class MainWin(Gtk.ApplicationWindow):
def on_video_index_selection_changed(self, selection):
"""Called from callback in self.on_video_index_selection_changed().
"""Called from callback in self.video_index_reset().
Also called from callbacks in mainapp.TartubeApp.on_menu_test,
.on_button_switch_view() and .on_menu_add_video().
@ -4893,7 +5178,7 @@ class MainWin(Gtk.ApplicationWindow):
else:
name = model[iter][1]
# Don't update the Video Catalogue during certain proecudres, such as
# Don't update the Video Catalogue during certain procedures, such as
# removing a row from the Video Index (in which case, the flag will
# be set)
if not self.ignore_video_index_select_flag:
@ -4905,7 +5190,6 @@ class MainWin(Gtk.ApplicationWindow):
else:
self.video_index_current = name
self.video_catalogue_redraw_all(name)
dbid = self.app_obj.media_name_dict[name]
media_data_obj = self.app_obj.media_reg_dict[dbid]
@ -4916,6 +5200,10 @@ class MainWin(Gtk.ApplicationWindow):
else:
self.video_index_current_priv_flag = False
# Redraw the Video Catalogue, on the first page, and reset its
# scrollbars back to the top
self.video_catalogue_redraw_all(name, 1, True)
def on_video_index_show_downloads(self, menu_item, media_data_obj):
@ -5045,7 +5333,7 @@ class MainWin(Gtk.ApplicationWindow):
if DEBUG_FUNC_FLAG:
print('mw 4439 on_video_catalogue_delete_video')
self.app_obj.delete_video(media_data_obj)
self.app_obj.delete_video(media_data_obj, False, True)
def on_video_catalogue_download(self, menu_item, media_data_obj):
@ -7300,13 +7588,16 @@ class DeleteContainerDialogue(Gtk.Dialog):
media_data_obj (media.Channel, media.Playlist or media.Folder): The
container media data object to be deleted
empty_flag (True or False): If True, the container media data object is
to be emptied, rather than being deleted
"""
# Standard class methods
def __init__(self, main_win_obj, media_data_obj):
def __init__(self, main_win_obj, media_data_obj, empty_flag):
if DEBUG_FUNC_FLAG:
print('mw 6453 __init__')
@ -7331,9 +7622,14 @@ class DeleteContainerDialogue(Gtk.Dialog):
folder_count = media_data_obj.count_descendants( [0, 0, 0, 0, 0] )
# Create the dialogue window
if not empty_flag:
title = 'Delete ' + obj_type
else:
title = 'Empty ' + obj_type
Gtk.Dialog.__init__(
self,
'Delete ' + obj_type,
title,
main_win_obj,
Gtk.DialogFlags.DESTROY_WITH_PARENT,
(
@ -7417,18 +7713,35 @@ class DeleteContainerDialogue(Gtk.Dialog):
separator = Gtk.HSeparator()
grid.attach(separator, 0, 5, 1, 1)
label6 = Gtk.Label(
'Do you want to delete the ' + obj_type + ' from ' + pkg_string \
+ '\'s data\ndirectory, deleting all of its files, or do you' \
+ ' just want to\nremove the ' + obj_type + ' from this list?',
)
if not empty_flag:
label6 = Gtk.Label(
'Do you want to delete the ' + obj_type + ' from ' \
+ pkg_string + '\'s data\ndirectory, deleting all of its' \
+ ' files, or do you just want to\nremove the ' + obj_type \
+ ' from this list?',
)
else:
label6 = Gtk.Label(
'Do you want to empty the ' + obj_type + ' in ' \
+ pkg_string + '\'s data\ndirectory, deleting all of its' \
+ ' files, or do you just want to\nempty the ' + obj_type \
+ ' in this list?',
)
grid.attach(label6, 0, 6, 1, 1)
label6.set_alignment(0, 0.5)
self.button = Gtk.RadioButton.new_with_label_from_widget(
None,
'Just remove the ' + obj_type + ' from this list',
)
if not empty_flag:
self.button = Gtk.RadioButton.new_with_label_from_widget(
None,
'Just remove the ' + obj_type + ' from this list',
)
else:
self.button = Gtk.RadioButton.new_with_label_from_widget(
None,
'Just empty the ' + obj_type + ' in this list',
)
grid.attach(self.button, 0, 7, 1, 1)
self.button2 = Gtk.RadioButton.new_from_widget(self.button)

View File

@ -315,6 +315,23 @@ class GenericContainer(GenericMedia):
# Set accessors
def reset_counts(self, vid_count, new_count, fav_count, dl_count):
"""Called by mainapp.TartubeApp.update_db().
When a database created by an earlier version of Tartube is loaded,
the calling function updates IVs as required.
This function is called if this object's video counts need to be
changed.
"""
self.vid_count = vid_count
self.new_count = new_count
self.fav_count = fav_count
self.dl_count = dl_count
def inc_dl_count(self):
self.dl_count += 1
@ -450,7 +467,8 @@ class GenericRemoteContainer(GenericContainer):
return -1
elif obj1.upload_time < obj2.upload_time:
return 1
else:
elif obj1.receive_time is not None \
and obj2.receive_time is not None:
if obj1.receive_time < obj2.receive_time:
return -1
elif obj1.receive_time > obj2.receive_time:
@ -796,8 +814,8 @@ class Video(GenericMedia):
def set_name(self, name):
"""Called by mainwin.MainWin.results_list_update_row() to set the name
of an unnamed video, replacing the default name (specified by
"""Called by mainapp.TartubeApp.update_video_when_file_found() to set
the name of an unnamed video, replacing the default name (specified by
mainapp.TartubeApp.default_video_name).
Also called by media.VideoDownloader.confirm_sim_video().
@ -1066,6 +1084,9 @@ class Channel(GenericRemoteContainer):
# Set accessors
# def reset_counts(): # Inherited from GenericContainer
# def set_dl_sim_flag(): # Inherited from GenericMedia
@ -1197,6 +1218,9 @@ class Playlist(GenericRemoteContainer):
# Set accessors
# def reset_counts(): # Inherited from GenericContainer
# def set_dl_sim_flag(): # Inherited from GenericMedia
@ -1434,7 +1458,8 @@ class Folder(GenericContainer):
return -1
elif obj1.upload_time < obj2.upload_time:
return 1
else:
elif obj1.receive_time is not None \
and obj2.receive_time is not None:
# In private folders (e.g. 'All Videos'), the most
# recently received video goes to the top of the list
if self.priv_flag:
@ -1455,6 +1480,8 @@ class Folder(GenericContainer):
return 1
else:
return 0
else:
return 0
else:
return 0
else:
@ -1494,21 +1521,7 @@ class Folder(GenericContainer):
# Set accessors
def reset_counts(self, vid_count, new_count, fav_count, dl_count):
"""Called by mainapp.TartubeApp.update_db().
When a database created by an earlier version of Tartube is loaded,
the calling function updates IVs as required.
This function is called if this object's video counts need to be
changed.
"""
self.vid_count = vid_count
self.new_count = new_count
self.fav_count = fav_count
self.dl_count = dl_count
# def reset_counts(): # Inherited from GenericContainer
# def set_dl_sim_flag(): # Inherited from GenericMedia

View File

@ -1,2 +1,2 @@
..\..\..\mingw32\bin\python3.exe hello_world.py
..\..\..\usr\bin\mintty.exe -h always /bin/env MSYSTEM=MINGW32 /bin/bash -lc /home/user/tartube/hello_world_mswin.sh
PAUSE

View File

@ -1,2 +1,2 @@
..\..\..\mingw64\bin\python3.exe hello_world.py
..\..\..\usr\bin\mintty.exe -h always /bin/env MSYSTEM=MINGW64 /bin/bash -lc /home/user/tartube/hello_world_mswin.sh
PAUSE

View File

@ -1,2 +1 @@
..\..\..\mingw32\bin\python3.exe tartube
PAUSE
..\..\..\usr\bin\mintty.exe -w hide /bin/env MSYSTEM=MINGW32 /bin/bash -lc /home/user/tartube/tartube_mswin.sh

View File

@ -1,2 +1 @@
..\..\..\mingw64\bin\python3.exe tartube
PAUSE
..\..\..\usr\bin\mintty.exe -w hide /bin/env MSYSTEM=MINGW64 /bin/bash -lc /home/user/tartube/tartube_mswin.sh

View File

@ -1,4 +1,4 @@
# Tartube v0.6.0 installer script for MS Windows
# Tartube v0.7.0 installer script for MS Windows
#
# Copyright (C) 2019 A S Lewis
#
@ -207,7 +207,7 @@
;Name and file
Name "Tartube"
OutFile "install-tartube-0.6.0-32bit.exe"
OutFile "install-tartube-0.7.0-32bit.exe"
;Default installation folder
InstallDir "$LOCALAPPDATA\Tartube"
@ -286,23 +286,19 @@ Section "Tartube" SecClient
CreateDirectory "$SMPROGRAMS\Tartube"
CreateShortCut "$SMPROGRAMS\Tartube\Tartube.lnk" \
"$INSTDIR\msys32\home\user\tartube\tartube_32bit.bat" \
"" \
"$INSTDIR\tartube_icon.ico"
"" "$INSTDIR\tartube_icon.ico" "" SW_SHOWMINIMIZED
CreateShortCut "$SMPROGRAMS\Tartube\Uninstall Tartube.lnk" \
"$INSTDIR\Uninstall.exe" \
"" \
"$INSTDIR\tartube_icon.ico"
"" "$INSTDIR\tartube_icon.ico"
# Temporary Start Menu link for bugfixing on MS Windows 10
CreateShortCut "$SMPROGRAMS\Tartube\Gtk graphics test.lnk" \
CreateShortCut "$SMPROGRAMS\Tartube\Test Gtk graphics.lnk" \
"$INSTDIR\msys32\home\user\tartube\hello_world_32bit.bat" \
"" \
"$INSTDIR\tartube_icon.ico"
"" "$INSTDIR\tartube_icon.ico" ""
# Desktop icon
CreateShortcut "$DESKTOP\Tartube.lnk" \
"$INSTDIR\msys32\home\user\tartube\tartube_32bit.bat" \
"" \
"$INSTDIR\tartube_icon.ico"
"" "$INSTDIR\tartube_icon.ico" "" SW_SHOWMINIMIZED
# Store installation folder
WriteRegStr HKLM \
@ -316,7 +312,7 @@ Section "Tartube" SecClient
"Publisher" "A S Lewis"
WriteRegStr HKLM \
"Software\Microsoft\Windows\CurrentVersion\Uninstall\Tartube" \
"DisplayVersion" "0.6.0"
"DisplayVersion" "0.7.0"
# Create uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe"

View File

@ -1,4 +1,4 @@
# Tartube v0.6.0 installer script for MS Windows
# Tartube v0.7.0 installer script for MS Windows
#
# Copyright (C) 2019 A S Lewis
#
@ -207,7 +207,7 @@
;Name and file
Name "Tartube"
OutFile "install-tartube-0.6.0-64bit.exe"
OutFile "install-tartube-0.7.0-64bit.exe"
;Default installation folder
InstallDir "$LOCALAPPDATA\Tartube"
@ -286,23 +286,19 @@ Section "Tartube" SecClient
CreateDirectory "$SMPROGRAMS\Tartube"
CreateShortCut "$SMPROGRAMS\Tartube\Tartube.lnk" \
"$INSTDIR\msys64\home\user\tartube\tartube_64bit.bat" \
"" \
"$INSTDIR\tartube_icon.ico"
"" "$INSTDIR\tartube_icon.ico" "" SW_SHOWMINIMIZED
CreateShortCut "$SMPROGRAMS\Tartube\Uninstall Tartube.lnk" \
"$INSTDIR\Uninstall.exe" \
"" \
"$INSTDIR\tartube_icon.ico"
"" "$INSTDIR\tartube_icon.ico"
# Temporary Start Menu link for bugfixing on MS Windows 10
CreateShortCut "$SMPROGRAMS\Tartube\Gtk graphics test.lnk" \
CreateShortCut "$SMPROGRAMS\Tartube\Test Gtk graphics.lnk" \
"$INSTDIR\msys64\home\user\tartube\hello_world_64bit.bat" \
"" \
"$INSTDIR\tartube_icon.ico"
"" "$INSTDIR\tartube_icon.ico"
# Desktop icon
CreateShortcut "$DESKTOP\Tartube.lnk" \
"$INSTDIR\msys64\home\user\tartube\tartube_64bit.bat" \
"" \
"$INSTDIR\tartube_icon.ico"
"" "$INSTDIR\tartube_icon.ico" "" SW_SHOWMINIMIZED
# Store installation folder
WriteRegStr HKLM \
@ -316,7 +312,7 @@ Section "Tartube" SecClient
"Publisher" "A S Lewis"
WriteRegStr HKLM \
"Software\Microsoft\Windows\CurrentVersion\Uninstall\Tartube" \
"DisplayVersion" "0.6.0"
"DisplayVersion" "0.7.0"
# Create uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 192 KiB

View File

@ -35,7 +35,7 @@ import setuptools
# Setup
setuptools.setup(
name='tartube',
version='0.6.0',
version='0.7.0',
description='GUI front-end for youtube-dl',
# long_description=long_description,
long_description="""Tartube is a GUI front-end for youtube-dl, partly based

View File

@ -34,8 +34,8 @@ from lib import mainapp
# 'Global' variables
__packagename__ = 'tartube'
__version__ = '0.6.0'
__date__ = '4 Jul 2019'
__version__ = '0.7.0'
__date__ = '7 Jul 2019'
__copyright__ = 'Copyright \xa9 2019 A S Lewis'
__license__ = """
Copyright \xc2\xa9 2019 A S Lewis.

5
tartube_mswin.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
# Shell script to start Tartube on MS Windows, using the MSYS2 environment
# provided by the Tartube installer
cd tartube
python3 tartube