Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy files to journal #469

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
52 changes: 48 additions & 4 deletions rednotebook/gui/insert_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ def on_insert_pic(self, sel_text):
if not filesystem.IS_MAC:
picture_chooser.add_filter(file_filter)

# Add box for copying the picture to the journal folder
copy_checkbutton = Gtk.CheckButton()
copy_checkbutton.set_label('Copy to journal folder')
copy_checkbutton.set_active(True) # Set copy as default behaviour

# Add box for inserting image width.
box = Gtk.HBox()
box.set_spacing(2)
Expand All @@ -207,8 +212,14 @@ def on_insert_pic(self, sel_text):
box.pack_start(label, False, False, 0)
box.pack_start(width_entry, False, False, 0)
box.pack_start(Gtk.Label(_('pixels')), True, True, 0)
box.show_all()
picture_chooser.set_extra_widget(box)

# Add widgets to the window
extra_widgets_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
extra_widgets_box.set_spacing(4)
extra_widgets_box.pack_start(copy_checkbutton, False, False, 0)
extra_widgets_box.pack_start(box, False, False, 0)
extra_widgets_box.show_all()
picture_chooser.set_extra_widget(extra_widgets_box)

response = picture_chooser.run()
picture_chooser.hide()
Expand All @@ -233,14 +244,26 @@ def on_insert_pic(self, sel_text):
if sel_text:
sel_text += ' '

# Check if the image is to be copied
copy = copy_checkbutton.get_active()

# iterate through all selected images
lines = []
for filename in picture_chooser.get_filenames():
# If required, copy the file and get relative filename
if copy:
filename = self.main_window.journal.add_file(filename)
if filename is None:
# TODO: manage error
return

base, ext = os.path.splitext(filename)

# If not copied to the journal dir, get file absolute path
# On windows firefox accepts absolute filenames only
# with the file:// prefix
base = urls.get_local_url(base)
if not copy:
base = urls.get_local_url(base)

lines.append('[{}""{}""{}{}]'.format(sel_text, base, ext, width_text))

Expand All @@ -252,16 +275,37 @@ def on_insert_file(self, sel_text):
file_chooser = self.main_window.builder.get_object('file_chooser')
file_chooser.set_current_folder(dirs.last_file_dir)

# Add box for copying the file to the journal folder
copy_checkbutton = Gtk.CheckButton()
copy_checkbutton.set_label('Copy to journal folder')
copy_checkbutton.set_active(False) # Set default behaviour
copy_checkbutton.show_all()
file_chooser.set_extra_widget(copy_checkbutton)

response = file_chooser.run()
file_chooser.hide()

if response == Gtk.ResponseType.OK:
folder = file_chooser.get_current_folder()
# Folder is None if the file was chosen from the "recently used" section.

# Check if the file is to be copied
copy = copy_checkbutton.get_active()

if folder:
dirs.last_file_dir = folder
filename = file_chooser.get_filename()
filename = urls.get_local_url(filename)

# If required, copy the file and get the relative path
if copy:
filename = self.main_window.journal.add_file(filename)
if filename is None:
# TODO: manage error
return
# Else, get the sanitized absolute path
else:
filename = urls.get_local_url(filename)

sel_text = self.main_window.day_text_field.get_selected_text()
_, tail = os.path.split(filename)
# It is always safer to add the "file://" protocol and the ""s
Expand Down
9 changes: 9 additions & 0 deletions rednotebook/gui/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,17 @@ def select_journal(self, action, title, message):
return

if action == 'saveas':
# TODO: read directory name from self.dirs
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's best to add a property Filesystem.media_dir(self): return os.path.join(self.data_dir, 'media').

old_media_dir = os.path.join(self.journal.dirs.data_dir, 'media')

self.journal.dirs.data_dir = new_dir
self.journal.save_to_disk(saveas=True)

# TODO: read from self.dirs
new_media_dir = os.path.join(self.journal.dirs.data_dir, 'media')

filesystem.copytree(old_media_dir, new_media_dir)

self.journal.open_journal(new_dir)

def on_new_journal_button_activate(self, widget):
Expand Down
60 changes: 60 additions & 0 deletions rednotebook/journal.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,16 @@ def open_journal(self, data_dir):

self.months = storage.load_all_months_from_disk(data_dir)

# Create the media folder if it does not exist
# TODO: read media folder path from dirs
media_dir = os.path.join(self.dirs.data_dir, 'media')
try:
filesystem.make_directory(media_dir)
except OSError as err:
logging.error(
'Creating journal media directory failed: {}'.format(err))
return

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's only create the media dir when we need it, i.e., right before storing a file.

# Nothing to save before first day change
self.load_day(self.actual_date)

Expand Down Expand Up @@ -556,6 +566,56 @@ def add_instruction_content(self):

self.change_date(current_date)

def add_file(self, src):
'''
Copy file to journal directory.

The file is copied to the directory:
<journal_path>/media/<year>_<month>

If a different file with the same name is found in the destination
directory, the copy filename is modified to avoid overwritting.
See util.filesystem.safecopy() function for details.

The destination path of the copied relative to the journal
directory file is returned.
If there is a problem while copying the file, a None vale is
returned.
'''
# TODO: read media folder path from dirs
media_dir = os.path.join(self.dirs.data_dir, 'media')
try:
filesystem.make_directory(media_dir)
except OSError as err:
logging.error(
'Creating journal media directory failed: {}'.format(err))
return

# Create month directory
monthmedia_dirname = storage.format_year_and_month(
self.date.year, self.date.month)
monthmedia_dir = os.path.join(media_dir, monthmedia_dirname)
try:
filesystem.make_directory(monthmedia_dir)
except OSError as err:
logging.error('Creating month media directory failed: {}'.format(err))
return

# Copy file to previous dir
dst = os.path.join(
monthmedia_dir, os.path.basename(src))
try:
dst = filesystem.safecopy(src, dst)
except OSError as err:
logging.error(
'Copying file to journal directory failed: {}'.format(err))
return

dst_rel = filesystem.get_relative_path(
self.dirs.data_dir, dst)

return dst_rel


def main():
start_time = time.time()
Expand Down
3 changes: 2 additions & 1 deletion rednotebook/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ def get_journal_files(data_dir):
assert month in range(1, 12 + 1)
path = os.path.join(data_dir, file)
yield (path, year, month)
else:
# TODO: read media directory name from dirs
elif file != 'media':
logging.debug('%s is not a valid month filename' % file)


Expand Down
101 changes: 101 additions & 0 deletions rednotebook/util/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
import platform
import subprocess
import sys
import shutil
import filecmp
import stat

ENCODING = sys.getfilesystemencoding() or locale.getlocale()[1] or 'UTF-8'
LANGUAGE = locale.getdefaultlocale()[0]
Expand Down Expand Up @@ -172,6 +175,104 @@ def make_file_with_dir(file, content):
make_file(file, content)


def _safecopy_filename_generator(filename, marker='-'):
'''Returns a generator that yields filenames with a counter.

This counter is placed before the file extension, and incremented with
every iteration.

For example:

f1 = increment_filename('myimage.jpeg')
f1.next() # myimage.jpeg
f1.next() # myimage-2.jpeg
f1.next() # myimage-3.jpeg

Adapted from: http://alexwlchan.net/2015/06/safer-file-copying/

'''
# First we split the filename into the base and the extension
base, fileext = os.path.splitext(filename)

# The counter is just an integer, so we can increment it indefinitely.
value = 0
while True:
if value == 0:
value += 1
yield filename
value += 1
yield '{}{}{}{}'.format(base, marker, value, fileext)


def safecopy(src, dst, change_permissions=True):
'''Copy a file from path src to path dst without overwriting.

If a file already exists at dst, it will not be overwritten. Instead:

* If it is the same as the source file, nothing will be done.
* If it is different to the source file, a new unused name for the
copy will be picked.

Returns the path to the copied file.

Adapted from: http://alexwlchan.net/2015/06/safer-file-copying/

'''
if not os.path.exists(src):
raise ValueError('Source file does not exist: {}'.format(src))

# Start the filename generator
dst_gen = _safecopy_filename_generator(dst)

# Iterate over different destination filenames until it works
copied = False
while not copied:
# Generate next destination filename
dst = next(dst_gen)

# Check if there is a file at the destination location
if os.path.exists(dst):

# If the namesake is the same as the source file, then we
# don't need to do anything else.
if filecmp.cmp(src, dst):
copied = True

else:
# If there is no file at the destination, attempt to write
# to it.
shutil.copy(src, dst)
copied = True

logging.debug(
'Copied file from {} to {}.'.format(src, dst))

# Make file only readable and writable by user
if change_permissions:
try:
os.chmod(dst, stat.S_IRUSR | stat.S_IWUSR)
except OSError:
pass

return dst


def copytree(src, dst, change_permissions=True):
'''Copy directory and make it only writable and readable by user.'''
shutil.copytree(src, dst)

if change_permissions:
try:
# Make files and folders only writable and readable by user
for root, dirs, filenames in os.walk(dst):
os.chmod(root, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
for filename in filenames:
os.chmod(os.path.join(root, filename),
stat.S_IRUSR | stat.S_IWUSR)
except OSError:
pass


def get_relative_path(from_dir, to_dir):
'''
Try getting the relative path from from_dir to to_dir
Expand Down