aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--README.md32
-rwxr-xr-xs2s-inotify.sh.example43
-rwxr-xr-xs2s.py50
-rw-r--r--s2slib.py145
5 files changed, 275 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ae9d040
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+__pycache__
+s2s-inotify.sh
+old
+log*
+.nfs*
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..08d7a7d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,32 @@
+# README for shortcut2symlink project
+## Overview
+A Windows client is used to maintain a website/ subdirectory of files that will be processed using the [gallery](https://gitlab.com/bgstack15/gallery) project. To facilitate the process of preparing files for this website/ subdirectory, particularly over a wireless network connection, users have been instructed to just make Windows-style shortcuts (using CTRL+SHIFT dragging) in the website subdirectory in whatever layout desired.
+
+This project includes components that will scan the designated directory for .lnk files anywhere underneath, and interpret their targets, and generate symlinks in place of the .lnk files.
+
+The point of using symlinks in this case is to reduce the disk usage and number of duplicate files and directories. To SMB users on the Windows client, the symlink files will look like copies, but that is acceptable because the main focus is the gallery generation for the web server.
+
+## shortcut2symlink upstream
+[s2slib](https://gitlab.com/bgstack15/s2slib)
+
+## Alternatives
+This appears to be original work with no alternatives. I have not found other places on the Internet where a user wants to interpret a .lnk file and substitute with a Linux-style symbolic link.
+
+## Reason for existence
+Reduce disk space by removing duplicate files, while still having a whole directory structure readable by the gallery solution for generating static site pages.
+
+## How to use
+On a Devuan Ceres system with nfs access to the filesystem, run s2s-inotify.sh after configuring its watchfile settings and commands.
+
+ /mnt/public/Support/Programs/shortcuts/s2s-inotify.sh /mnt/public/Images/username1-photos/website 2>&1 | tee -a /mnt/public/Support/Programs/shortcuts/log1
+
+## Dependencies
+Distro | Packages
+--------------- | -----------------------------
+Devuan Ceres | python3-liblnk, inotify-tools
+
+## References
+[.lnk format reference](https://github.com/libyal/liblnk/blob/main/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc)
+
+## Differences from upstream
+N/A
diff --git a/s2s-inotify.sh.example b/s2s-inotify.sh.example
new file mode 100755
index 0000000..a1fd4e3
--- /dev/null
+++ b/s2s-inotify.sh.example
@@ -0,0 +1,43 @@
+#/usr/bin/sh
+# Startdate: 2021-02-12 10:00
+# Dependencies:
+# centos8-req: inotify-tools
+# Rewritten to handle across nfs because the reference implementation runs this monitor on a nfs client and not the server
+
+# suggested INDIR: /mnt/public/Images/username1-photos/website
+test -z "${INDIR}" && export INDIR="${1}"
+test -z "${INDIR}" && { echo "Please provide directory name as INDIR or parameter 1. Aborted." 1>&2 ; exit 1 ; }
+
+# CONFIGURE THESE
+WATCHFILE="${INDIR}"/updatenow.txt
+STOPFILE="${INDIR}"/stop.txt
+COMMAND1="ssh server1 \"sudo find ${INDIR} ! -type l \( ! -user user1 -o ! -group usergroup1 \) -exec chown user1.usergroup1 {} \; ; sudo find ${INDIR} ! -type l \( ! -perm /g+r -o ! -perm /g+w \) -exec chmod g+rwX {} \; ;\""
+COMMAND2="/mnt/public/Support/Programs/shortcuts/s2s.py --indir ${INDIR} --apply --debug 8 --delete"
+LOOPTIMER=5
+
+# the looptimer is so that the program will pause every once in a while to check for the stop file
+
+clean_s2s_inotify() {
+ rm -f "${STOPFILE}" 1>/dev/null 2>&1
+ :
+}
+
+trap '__ec=$? ; printf " caused s2s-inotify to stop.\n" ; clean_s2s_inotify ; trap "" 2 ; exit ${__ec} ;' 2 # catch CTRL+C
+
+rm -f "${STOPFILE}"
+while test ! -f "${STOPFILE}" ; do
+ oldstat="${stat}"
+ count="$( inotifywait -e modify -t "${LOOPTIMER}" "${WATCHFILE}" 1>/dev/null 2>&1 ; echo $? )"
+ stat="$( stat -c "%Y" "${WATCHFILE}" )"
+ #echo "count=${count}"
+ #if test ${count} -gt 0 2>/dev/null ;
+ if test "${count}" = "0" || test "${oldstat}" != "${stat}" 2>/dev/null ;
+ then
+ eval ${COMMAND1}
+ eval ${COMMAND2}
+ fi
+done
+trap '' 2
+
+echo "Stopped because of stop.txt"
+clean_s2s_inotify
diff --git a/s2s.py b/s2s.py
new file mode 100755
index 0000000..5a528ca
--- /dev/null
+++ b/s2s.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+# File: s2s.py
+# Locations:
+# /mnt/public/Support/Programs/shortcuts/
+# https://gitlab.com/bgstack15/s2s
+# Author: bgstack15@gmail.com
+# SPDX-License-Identifier: GPL-3.0
+# Startdate: 2021-02-11 20:00
+# Title: Shortcut To Symlink cli
+# Project: shortcut to symlink (s2s)
+# Purpose: Convert .lnk files to POSIX symlinks within a single filesystem
+# History:
+# Usage: ./s2s.py --help
+# Reference:
+# generate.py from /mnt/public/Support/Programs/gallery
+# Improve:
+# Documentation:
+# Dependencies:
+
+from argparse import ArgumentParser
+from s2slib import *
+s2s_version = "2021-02-12a"
+
+parser = ArgumentParser(description="Convert .lnk shortcuts to symlinks on a SMB share")
+parser.add_argument("-d","--debug",nargs='?', default=0, type=int, choices=range(0,11), help="Set debug level")
+parser.add_argument("-v","--version", action="version", version="%(prog)s " + s2s_version)
+g_dryrun = parser.add_mutually_exclusive_group()
+g_dryrun.add_argument("-n","--dryrun", action="store_true", help="Make no changes (default)")
+g_dryrun.add_argument("-a","--apply", action="store_true", help="Actually make changes")
+g_delete = parser.add_mutually_exclusive_group()
+g_delete.add_argument("-D","--delete", action="store_true",default=False, help="Delete .lnk if symlink is successful")
+g_delete.add_argument("-N","--nodelete","--no-delete", action="store_true", default=True, help="Do not delete .lnk")
+parser.add_argument("-i","--indir",required=True)
+
+# pull useful values out of the argparse entry
+args = parser.parse_args()
+debuglevel=0
+if args.debug is None:
+ debuglevel = 10 # if -d without a number, then 10
+elif args.debug:
+ debuglevel = args.debug
+indir = args.indir
+dryrun = args.dryrun or not args.apply # the mutually exclusive group handles this OK
+delete = args.delete or not args.nodelete # the mutually exclusive group handles this OK
+eprint(args)
+eprint(f"dryrun {dryrun}")
+eprint(f"delete {delete}")
+eprint(f"debuglevel {debuglevel}")
+
+replace_all_symlinks(indir, delete = delete, debuglevel = debuglevel, dryrun = dryrun)
diff --git a/s2slib.py b/s2slib.py
new file mode 100644
index 0000000..409157e
--- /dev/null
+++ b/s2slib.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env python3
+# File: s2slib.py
+# Locations:
+# /mnt/public/Support/Programs/shortcuts/
+# https://gitlab.com/bgstack15/s2s
+# Author: bgstack15@gmail.com
+# SPDX-License-Identifier: GPL-3.0
+# Startdate: 2021-02-11 20:00
+# Title: Shortcut To Symlink library
+# Purpose: Convert .lnk files to POSIX symlinks within a single filesystem
+# History:
+# Usage:
+# see s2s.py
+# Reference:
+# https://stackoverflow.com/questions/25146960/python-convert-back-slashes-to-forward-slashes/25147093#25147093
+# https://github.com/libyal/liblnk/blob/main/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc#file_attribute_flags
+# Improve:
+# Handle absolute paths when relative paths are absent?
+# Add a force=True option to overwrite existing symlink that points to something else
+# Dependencies:
+# devuan-req: python3-liblnk
+import pylnk # python3-liblnk
+import os, re, posixpath
+from sys import stderr
+
+debuglevel = 8
+
+# Functions
+def eprint(*args, **kwargs):
+ print(*args, file=stderr, **kwargs)
+
+# to benefit from caching outside of the function, maybe
+lnkregex = re.compile(".*\.lnk$")
+
+def replace_one_symlink(
+ lnkfile
+ , delete = False
+ , debuglevel = debuglevel
+ , dryrun = False
+ ):
+
+ thislnk = pylnk.file()
+ basename = os.path.basename(lnkfile)
+ #workdir = os.path.dirname(lnkfile) # not used
+
+ # Exit if this is not a .lnk file
+ if not re.match(lnkregex,basename):
+ eprint(f"Cannot operate on {lnkfile}")
+ return -1
+
+ thislnk.open(lnkfile)
+
+ # Get relative path
+ # we are really expecting a relative path for every single one, because we are dealing with Windows shortcuts to files all on the same network share.
+ # Although I suppose a symlink could work if you could somehow map the expected SMB path to a POSIX filesystem
+ try:
+ relative_path = thislnk.relative_path
+ except:
+ eprint(f"No relative path for {lnkfile}, and abs path not yet implemented.")
+ return -1
+ # and make it a Unix-style path
+ relative_path = posixpath.join(*thislnk.relative_path.split('\\'))
+
+ # Not used for anything, but here if we want it.
+ linktype = "file"
+ if thislnk.file_attribute_flags & 0x10:
+ linktype = "dir"
+
+ # Get preferred name, and strip any " - Shortcut"
+ pn = os.path.splitext(lnkfile)[:-1][0] # remove the .lnk part, but preserve any other dots
+ symlink_path = re.sub(' - Shortcut$', "",pn)
+
+ # take action
+ if debuglevel >= 5:
+ eprint(f"Want symlink \"{symlink_path}\" ({linktype})--> \"{relative_path}\"")
+
+ ready_to_delete = False
+
+ if os.path.exists(symlink_path):
+ current_source = ""
+ try:
+ current_source = os.path.realpath(symlink_path)
+ except:
+ eprint(f"Unable to determine current_source for {symlink_path} which already exists.")
+ normpath = os.path.normpath(os.path.join(os.path.dirname(symlink_path),relative_path))
+ #eprint(f"Please ensure {current_source} matches {relative_path} which is {normpath}")
+ if normpath != current_source:
+ # The existing symlink points to something else. Not sure how to proceed.
+ # probably need to add a --force option to overwrite
+ eprint(f"Warning: Existing symlink {symlink_path} points to {current_source} and not {normpath} as requested")
+ else:
+ # it exists and already points to what we want it to point to.
+ if debuglevel >= 3:
+ eprint(f"symlink {symlink_path} already points to {current_source} which is {normpath}")
+ ready_to_delete = True
+ else:
+ # symlink does not exist, so make it!
+ if debuglevel >= 1:
+ eprint(f"ln -s {relative_path} {symlink_path}")
+ if not dryrun:
+ try:
+ os.symlink(relative_path,symlink_path)
+ ready_to_delete = True
+ except Exception as e:
+ eprint(e)
+
+ if delete:
+ if ready_to_delete:
+ if debuglevel >= 2:
+ eprint(f"rm {lnkfile}")
+ if not dryrun:
+ os.remove(lnkfile)
+ return 0
+
+def list_all_child_lnk_files(indir):
+ mylist = []
+ for root, dirs, files in os.walk(indir):
+ for name in files:
+ if ".lnk" in name:
+ mylist.append(os.path.join(root,name))
+ #eprint(os.path.join(root,name))
+ return mylist
+
+def replace_all_symlinks(
+ indir
+ , delete = False
+ , debuglevel = debuglevel
+ , dryrun = False
+ ):
+ result = 0
+
+ # get list of all lnk files
+ mylist = list_all_child_lnk_files(indir)
+ for lnkfile in mylist:
+ replace_one_symlink(
+ lnkfile
+ , delete = delete
+ , debuglevel = debuglevel
+ , dryrun = dryrun
+ )
+
+ return result
+
+if __name__ == "__main__":
+ print("running the main loop")
bgstack15