Compare commits

...

2 Commits

Author SHA1 Message Date
Matteo Cypriani bf7e090fbf Add option --fs-fix=<auto|force|no>
Currently, the filesystem fixes are applied only for FAT, in which these
special characters (:, ", etc.) are actually forbidden. NTFS, however, will
allow you to create file names containing these characters, but Windows will
still be unable to deal with the files (even to rename them!).

--fs-fix=force will be useful when copying to a NTFS filesystem that will be
read on Windows.

--fs-fix=no is the same as --no-fs-fix.

--fs-fix=auto is the same as the current default behaviour (i.e. fix names only
for FAT).
2018-04-15 12:32:39 +02:00
Matteo Cypriani 20baa5ecd9 Strip trailing whitespaces 2018-04-15 11:49:29 +02:00
4 changed files with 44 additions and 31 deletions

View File

@ -1,7 +1,7 @@
gcp 0.1.3 (20/06/11): gcp 0.1.3 (20/06/11):
- fixed exit status - fixed exit status
- updated manpage with exit status - updated manpage with exit status
- gcp DIR1 DIR2 syntax fixed - gcp DIR1 DIR2 syntax fixed
- tests - tests
gcp 0.1.2 (16/06/11): gcp 0.1.2 (16/06/11):
- removed bad fd close - removed bad fd close
@ -10,12 +10,12 @@ gcp 0.1.2 (16/06/11):
- unaccessible source dir crash fix - unaccessible source dir crash fix
- os.stat precision fix - os.stat precision fix
- manpage - manpage
- install script - install script
gcp 0.1.1 (30/09/10): gcp 0.1.1 (30/09/10):
- double entry check in journal - double entry check in journal
- unicode source_path send via dbus (second instance of gcp) fixed - unicode source_path send via dbus (second instance of gcp) fixed
- errors are now shown after copy - errors are now shown after copy
- fixed bad closure when a file already exists - fixed bad closure when a file already exists
- added "gcp SOURCE_FILE DEST_FILE" syntax management - added "gcp SOURCE_FILE DEST_FILE" syntax management
gcp 0.1 (28/09/10): gcp 0.1 (28/09/10):
**INITIAL PUBLIC RELEASE** **INITIAL PUBLIC RELEASE**

2
README
View File

@ -35,7 +35,7 @@ gcp is a file copier, loosely inspired from cp, but with high level functionalit
gcp is at an early stage of development, and really experimental: use at your own risks ! gcp is at an early stage of development, and really experimental: use at your own risks !
** How to use it ? ** ** How to use it ? **
Pretty much like cp (see gcp --help). Pretty much like cp (see gcp --help).
Please note that the behaviour is not exactly the same as cp, even if gcp want to be option-compatible. Mainly, the destination filenames can be changed (by default, can be deactivated). Please note that the behaviour is not exactly the same as cp, even if gcp want to be option-compatible. Mainly, the destination filenames can be changed (by default, can be deactivated).
gcp doesn't implement yet all the options from cp, but it's planed. gcp doesn't implement yet all the options from cp, but it's planed.

59
gcp
View File

@ -69,10 +69,11 @@ This software is an advanced file copier
Get the latest version at http://wiki.goffi.org/wiki/Gcp Get the latest version at http://wiki.goffi.org/wiki/Gcp
""") """)
const_DBUS_INTERFACE = "org.goffi.gcp" const_DBUS_INTERFACE = "org.goffi.gcp"
const_DBUS_PATH = "/org/goffi/gcp" const_DBUS_PATH = "/org/goffi/gcp"
const_BUFF_SIZE = 4096 const_BUFF_SIZE = 4096
const_PRESERVE = set(['mode','ownership','timestamps']) const_PRESERVE = set(['mode','ownership','timestamps'])
const_FS_FIX = set(['auto','force','no'])
const_FILES_DIR = "~/.gcp" const_FILES_DIR = "~/.gcp"
const_JOURNAL_PATH = const_FILES_DIR + "/journal" const_JOURNAL_PATH = const_FILES_DIR + "/journal"
const_SAVED_LIST = const_FILES_DIR + "/saved_list" const_SAVED_LIST = const_FILES_DIR + "/saved_list"
@ -86,7 +87,7 @@ class DbusObject(dbus.service.Object):
dbus.service.Object.__init__(self, bus, path) dbus.service.Object.__init__(self, bus, path)
debug(_("Init DbusObject...")) debug(_("Init DbusObject..."))
self.cb={} self.cb={}
@dbus.service.method(const_DBUS_INTERFACE, @dbus.service.method(const_DBUS_INTERFACE,
in_signature='', out_signature='s') in_signature='', out_signature='s')
def getVersion(self): def getVersion(self):
@ -294,7 +295,7 @@ class GCP():
dest_path = os.path.normpath(os.path.join(source_dir, args.pop())) dest_path = os.path.normpath(os.path.join(source_dir, args.pop()))
except OSError,e: except OSError,e:
error (_("Invalid dest_path: %s"),e) error (_("Invalid dest_path: %s"),e)
for path in args: for path in args:
abspath = os.path.normpath(os.path.join(os.path.expanduser(source_dir), path)) abspath = os.path.normpath(os.path.join(os.path.expanduser(source_dir), path))
if not os.path.exists(abspath): if not os.path.exists(abspath):
@ -312,7 +313,7 @@ class GCP():
def __copyNextFile(self): def __copyNextFile(self):
"""Take the last file in the list, and launch the copy using glib io_watch event """Take the last file in the list, and launch the copy using glib io_watch event
@return: True a file was added, False else""" @return: True a file was added, False else"""
if self.copy_list: if self.copy_list:
source_file, dest_path, options = self.copy_list.pop() source_file, dest_path, options = self.copy_list.pop()
self.journal.startFile(source_file) self.journal.startFile(source_file)
@ -341,7 +342,7 @@ class GCP():
self.journal.closeFile() self.journal.closeFile()
source_fd.close() source_fd.close()
return True return True
gobject.io_add_watch(source_fd,gobject.IO_IN,self._copyFile, gobject.io_add_watch(source_fd,gobject.IO_IN,self._copyFile,
(dest_fd, options), priority=gobject.PRIORITY_DEFAULT) (dest_fd, options), priority=gobject.PRIORITY_DEFAULT)
if not self.progress: if not self.progress:
@ -362,7 +363,7 @@ class GCP():
self.journal.closeFile() self.journal.closeFile()
source_fd.close() source_fd.close()
dest_fd.close() dest_fd.close()
def _copyFile(self, source_fd, condition, data): def _copyFile(self, source_fd, condition, data):
@ -411,7 +412,7 @@ class GCP():
@return: fixed filename""" @return: fixed filename"""
fixed_filename = filename fixed_filename = filename
if self.getFsType(filename) == 'vfat' and options.fs_fix: if options.fs_fix == 'force' or (options.fs_fix == 'auto' and self.getFsType(filename) == 'vfat'):
fixed_filename = filename.replace('\\','_')\ fixed_filename = filename.replace('\\','_')\
.replace(':',';')\ .replace(':',';')\
.replace('*','+')\ .replace('*','+')\
@ -520,7 +521,7 @@ class GCP():
if options.sources_save or options.sources_replace: if options.sources_save or options.sources_replace:
if saved_files.has_key(options.sources_save) and not options.sources_replace: if saved_files.has_key(options.sources_save) and not options.sources_replace:
error(_("There is already a saved sources with this name, skipping --sources-save")) error(_("There is already a saved sources with this name, skipping --sources-save"))
else: else:
if len(args)>1: if len(args)>1:
saved_files[options.sources_save] = map(os.path.abspath,args[:-1]) saved_files[options.sources_save] = map(os.path.abspath,args[:-1])
@ -552,38 +553,41 @@ class GCP():
if isinstance(full_args[idx], unicode): if isinstance(full_args[idx], unicode):
#We don't want unicode as some filenames can be invalid unicode #We don't want unicode as some filenames can be invalid unicode
full_args[idx] = full_args[idx].encode('utf-8') full_args[idx] = full_args[idx].encode('utf-8')
parser = OptionParser(usage=_usage,version=ABOUT) parser = OptionParser(usage=_usage,version=ABOUT)
parser.add_option("-r", "--recursive", action="store_true", default=False, parser.add_option("-r", "--recursive", action="store_true", default=False,
help=_("copy directories recursively")) help=_("copy directories recursively"))
parser.add_option("-f", "--force", action="store_true", default=False, parser.add_option("-f", "--force", action="store_true", default=False,
help=_("force overwriting of existing files")) help=_("force overwriting of existing files"))
parser.add_option("--preserve", action="store", default='mode,ownership,timestamps', parser.add_option("--preserve", action="store", default='mode,ownership,timestamps',
help=_("preserve the specified attributes")) help=_("preserve the specified attributes"))
parser.add_option("-L", "--dereference", action="store_true", default=False, parser.add_option("-L", "--dereference", action="store_true", default=False,
help=_("always follow symbolic links in sources")) help=_("always follow symbolic links in sources"))
parser.add_option("-P", "--no-dereference", action="store_false", dest='dereference', parser.add_option("-P", "--no-dereference", action="store_false", dest='dereference',
help=_("never follow symbolic links in sources")) help=_("never follow symbolic links in sources"))
#parser.add_option("--no-unicode-fix", action="store_false", dest='unicode_fix', default=True, #parser.add_option("--no-unicode-fix", action="store_false", dest='unicode_fix', default=True,
# help=_("don't fix name encoding errors")) #TODO # help=_("don't fix name encoding errors")) #TODO
parser.add_option("--no-fs-fix", action="store_false", dest='fs_fix', default=True, parser.add_option("--fs-fix", action="store", dest='fs_fix', default='auto',
help=_("don't fix filesystem name incompatibily")) help=_("fix filesystem name incompatibily; can be 'auto' (default), 'force' or 'no'"))
parser.add_option("--no-fs-fix", action="store_true", dest='no_fs_fix', default=False,
help=_("same as --fs-fix=no (overrides --fs-fix)"))
parser.add_option("--no-progress", action="store_false", dest="progress", default=True, parser.add_option("--no-progress", action="store_false", dest="progress", default=True,
help=_("deactivate progress bar")) help=_("deactivate progress bar"))
parser.add_option("-v", "--verbose", action="store_true", default=False, parser.add_option("-v", "--verbose", action="store_true", default=False,
help=_("Show what is currently done")) help=_("Show what is currently done"))
group_saving = OptionGroup(parser, "sources saving") group_saving = OptionGroup(parser, "sources saving")
group_saving.add_option("--sources-save", action="store", group_saving.add_option("--sources-save", action="store",
help=_("Save source arguments")) help=_("Save source arguments"))
@ -592,19 +596,19 @@ class GCP():
group_saving.add_option("--sources-load", action="store", group_saving.add_option("--sources-load", action="store",
help=_("Load source arguments")) help=_("Load source arguments"))
group_saving.add_option("--sources-del", action="store", group_saving.add_option("--sources-del", action="store",
help=_("delete saved sources")) help=_("delete saved sources"))
group_saving.add_option("--sources-list", action="store_true", default=False, group_saving.add_option("--sources-list", action="store_true", default=False,
help=_("List names of saved sources")) help=_("List names of saved sources"))
group_saving.add_option("--sources-full-list", action="store_true", default=False, group_saving.add_option("--sources-full-list", action="store_true", default=False,
help=_("List names of saved sources and files in it")) help=_("List names of saved sources and files in it"))
parser.add_option_group(group_saving) parser.add_option_group(group_saving)
(options, args) = parser.parse_args(full_args) (options, args) = parser.parse_args(full_args)
options.directdir = False #True only in the special case: we are copying a dir and it doesn't exists options.directdir = False #True only in the special case: we are copying a dir and it doesn't exists
#options check #options check
@ -617,9 +621,18 @@ class GCP():
if options.verbose: if options.verbose:
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
if options.no_fs_fix:
options.fs_fix = 'no'
else:
if not options.fs_fix in const_FS_FIX:
error (_("Invalid --fs-fix value\nvalid values are:"))
for value in const_FS_FIX:
error('- %s' % value)
exit(1)
preserve = set(options.preserve.split(',')) preserve = set(options.preserve.split(','))
if not preserve.issubset(const_PRESERVE): if not preserve.issubset(const_PRESERVE):
error (_("Invalide --preserve value\nvalid values are:")) error (_("Invalid --preserve value\nvalid values are:"))
for value in const_PRESERVE: for value in const_PRESERVE:
error('- %s' % value) error('- %s' % value)
exit(1) exit(1)
@ -642,7 +655,7 @@ class GCP():
options.dest_file = None options.dest_file = None
else: else:
options.dest_file = None options.dest_file = None
#if there is an other instance of gcp, we send options to it #if there is an other instance of gcp, we send options to it
if not self._main_instance: if not self._main_instance:
info (_("There is already one instance of %s running, pluging to it") % NAME_SHORT) info (_("There is already one instance of %s running, pluging to it") % NAME_SHORT)

View File

@ -59,8 +59,8 @@ def dirCheck(dir_path):
if isdir(full_path): if isdir(full_path):
recursive_sum(full_path, result) recursive_sum(full_path, result)
else: else:
result[full_path] = sha1sum(full_path) result[full_path] = sha1sum(full_path)
result = {} result = {}
_ori_dir = getcwd() _ori_dir = getcwd()
chdir(dir_path) chdir(dir_path)
@ -94,7 +94,7 @@ def makeRandomFile(path, size=S10K, buf_size=4096):
def makeTestDir(path): def makeTestDir(path):
"""Helper method to easily create a test dir """Helper method to easily create a test dir
@param path: where the dir must be created""" @param path: where the dir must be created"""
for i in range(2): for i in range(2):
subdir = join(path,'subdir_%d' % i) subdir = join(path,'subdir_%d' % i)
makedirs(subdir) makedirs(subdir)
@ -198,7 +198,7 @@ class TestCopyCases(unittest.TestCase):
self.assertEqual(ret,0) self.assertEqual(ret,0)
check_2 = dirCheck('dest_dir') check_2 = dirCheck('dest_dir')
self.assertEqual(check_1, check_2) self.assertEqual(check_1, check_2)
def test_mixt_copy_existing_dest_nonrecursive(self): def test_mixt_copy_existing_dest_nonrecursive(self):
"""Check that a mixt copy (files + dir) to an existing dest without the recursive option work as expected""" """Check that a mixt copy (files + dir) to an existing dest without the recursive option work as expected"""
for i in range(2): for i in range(2):