adding comments
This commit is contained in:
159
flit.py
159
flit.py
@@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
@@ -9,10 +8,30 @@ import string
|
|||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
"""FLIT: Fluttering folders for simple file sharing.
|
||||||
|
|
||||||
|
The script maintains a set of folders, named so that they are hard to guess,
|
||||||
|
and contents are deleted after expiration date.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
CONFIG (str): Configuration file name stored in each shared folder.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
CONFIG = ".flit.json"
|
CONFIG = ".flit.json"
|
||||||
|
|
||||||
|
|
||||||
def parse_opts():
|
def parse_opts():
|
||||||
|
"""Options parser
|
||||||
|
|
||||||
|
Args:
|
||||||
|
None
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Namespace: Options from the command line
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
class _HelpAction(argparse._HelpAction):
|
class _HelpAction(argparse._HelpAction):
|
||||||
def __call__(self, parser, namespace, values, option_string=None):
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
@@ -94,10 +113,30 @@ def parse_opts():
|
|||||||
|
|
||||||
|
|
||||||
def random_char():
|
def random_char():
|
||||||
|
"""Random character generator
|
||||||
|
|
||||||
|
Args:
|
||||||
|
None
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: A single uppercase letter or number
|
||||||
|
|
||||||
|
"""
|
||||||
return random.choice(string.ascii_uppercase + string.digits)
|
return random.choice(string.ascii_uppercase + string.digits)
|
||||||
|
|
||||||
|
|
||||||
def random_name():
|
def random_name():
|
||||||
|
"""Random unique name for a new share. Share names follow the syntax [NUM-ABC-DEF],
|
||||||
|
Where NUM is a 3 digit growing number (to help navigating new folders), but the
|
||||||
|
rest are random, to ensure secrecy.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
None
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: New folder name
|
||||||
|
|
||||||
|
"""
|
||||||
while True:
|
while True:
|
||||||
existing_names = [c["name"] for c in get_folders()]
|
existing_names = [c["name"] for c in get_folders()]
|
||||||
index = 0
|
index = 0
|
||||||
@@ -118,6 +157,17 @@ def random_name():
|
|||||||
|
|
||||||
|
|
||||||
def create_new(p, days, description):
|
def create_new(p, days, description):
|
||||||
|
"""Creates a new folder, and stores configuration
|
||||||
|
|
||||||
|
Args:
|
||||||
|
p (str): Share name
|
||||||
|
days (int): Days to store the folder
|
||||||
|
description (str): Description of the fileshare
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
|
||||||
|
"""
|
||||||
os.mkdir(p)
|
os.mkdir(p)
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
del_delta = timedelta(days=days)
|
del_delta = timedelta(days=days)
|
||||||
@@ -130,6 +180,17 @@ def create_new(p, days, description):
|
|||||||
|
|
||||||
|
|
||||||
def copy_files(cwd, new_name, files):
|
def copy_files(cwd, new_name, files):
|
||||||
|
"""Copy files to a new share
|
||||||
|
|
||||||
|
Args:
|
||||||
|
c (str): Working directory when the script was started
|
||||||
|
new_name (str): Name of the new share
|
||||||
|
files (list): List of paths to copy
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
|
||||||
|
"""
|
||||||
target = os.path.abspath(new_name)
|
target = os.path.abspath(new_name)
|
||||||
for f in opts.files:
|
for f in opts.files:
|
||||||
print(f"Copying: {f}")
|
print(f"Copying: {f}")
|
||||||
@@ -150,9 +211,17 @@ def copy_files(cwd, new_name, files):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_stats(p):
|
def get_stats(p):
|
||||||
# TODO: named tuple
|
"""Get share properties
|
||||||
|
|
||||||
|
Args:
|
||||||
|
p (str): Name of the share
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: Share properties
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
config = read_config(p)
|
config = read_config(p)
|
||||||
|
|
||||||
del_time = datetime.fromisoformat(config["delete_time"])
|
del_time = datetime.fromisoformat(config["delete_time"])
|
||||||
@@ -171,6 +240,16 @@ def get_stats(p):
|
|||||||
|
|
||||||
|
|
||||||
def get_sub_files(p):
|
def get_sub_files(p):
|
||||||
|
"""Get 1st level files in a share
|
||||||
|
|
||||||
|
Args:
|
||||||
|
p (str): Name of the share
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List: Folders and Files in the share
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
file_list = sorted([x for x in os.listdir(p) if not x.startswith(".")])
|
file_list = sorted([x for x in os.listdir(p) if not x.startswith(".")])
|
||||||
dir_list = [x + "/" for x in file_list if os.path.isdir(os.path.join(p, x))]
|
dir_list = [x + "/" for x in file_list if os.path.isdir(os.path.join(p, x))]
|
||||||
file_list = [x for x in file_list if os.path.isfile(os.path.join(p, x))]
|
file_list = [x for x in file_list if os.path.isfile(os.path.join(p, x))]
|
||||||
@@ -178,15 +257,38 @@ def get_sub_files(p):
|
|||||||
|
|
||||||
|
|
||||||
def get_folders():
|
def get_folders():
|
||||||
|
"""Get share properties for all the shares
|
||||||
|
|
||||||
|
Args:
|
||||||
|
None:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List: List that contain `get_stats(p)` for every share
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
dir_list = [p for p in os.listdir(".") if os.path.exists(os.path.join(p, CONFIG))]
|
dir_list = [p for p in os.listdir(".") if os.path.exists(os.path.join(p, CONFIG))]
|
||||||
configs = [get_stats(p) for p in dir_list]
|
configs = [get_stats(p) for p in dir_list]
|
||||||
configs.sort(key=lambda c: c["created"], reverse=True)
|
configs.sort(key=lambda c: c["created"], reverse=True)
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
|
|
||||||
def list_folders(root_folder, verbose=False, filter_name=None):
|
def list_folders(root_url, verbose=False, filter_name=None):
|
||||||
|
"""Show folder list
|
||||||
|
|
||||||
|
Args:
|
||||||
|
root_url (str): URL to root of the share system
|
||||||
|
verbose (bool): If set true, displays folder contents
|
||||||
|
filter_name (str): If set, display only one folder matching the name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
folders = get_folders()
|
folders = get_folders()
|
||||||
print("Folder Created ToDelete InDays")
|
header_format = "{:" + str(12 + len(root_url)) + "} {} {} {} {}"
|
||||||
|
print(header_format.format("URL","Created","ToDelete","InDays","Description"))
|
||||||
for c in folders:
|
for c in folders:
|
||||||
if filter_name is not None:
|
if filter_name is not None:
|
||||||
if filter_name != c["name"]:
|
if filter_name != c["name"]:
|
||||||
@@ -194,7 +296,7 @@ def list_folders(root_folder, verbose=False, filter_name=None):
|
|||||||
due = "*" if c["due"] else " "
|
due = "*" if c["due"] else " "
|
||||||
print(
|
print(
|
||||||
"{}/{}/ {} {} {: 4d}d{} {}".format(
|
"{}/{}/ {} {} {: 4d}d{} {}".format(
|
||||||
root_folder,
|
root_url,
|
||||||
c["name"],
|
c["name"],
|
||||||
c["created"].isoformat()[0:10],
|
c["created"].isoformat()[0:10],
|
||||||
c["delete_time"].isoformat()[0:10],
|
c["delete_time"].isoformat()[0:10],
|
||||||
@@ -206,10 +308,19 @@ def list_folders(root_folder, verbose=False, filter_name=None):
|
|||||||
if verbose:
|
if verbose:
|
||||||
sub_files = get_sub_files(c["name"])
|
sub_files = get_sub_files(c["name"])
|
||||||
for sp in sub_files:
|
for sp in sub_files:
|
||||||
print(" {}/{}/{}".format(root_folder, c["name"], quote(sp, "/")))
|
print(" {}/{}/{}".format(root_url, c["name"], quote(sp, "/")))
|
||||||
|
|
||||||
|
|
||||||
def del_due_folders():
|
def del_due_folders():
|
||||||
|
"""Delete folders where due date has passed
|
||||||
|
|
||||||
|
Args:
|
||||||
|
None
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
|
||||||
|
"""
|
||||||
folders = get_folders()
|
folders = get_folders()
|
||||||
for c in folders:
|
for c in folders:
|
||||||
if c["due"]:
|
if c["due"]:
|
||||||
@@ -218,11 +329,37 @@ def del_due_folders():
|
|||||||
|
|
||||||
|
|
||||||
def read_config(p):
|
def read_config(p):
|
||||||
|
"""Read the share configuration file
|
||||||
|
|
||||||
|
Args:
|
||||||
|
p (str): Share name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: Share config
|
||||||
|
|
||||||
|
Example:
|
||||||
|
{
|
||||||
|
"created": "2020-12-28T23:04:07.275200",
|
||||||
|
"delete_time": "2020-12-29T23:04:07.275200",
|
||||||
|
"description": "Test share"
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
with open(os.path.join(p, CONFIG), "rt") as fp:
|
with open(os.path.join(p, CONFIG), "rt") as fp:
|
||||||
return json.load(fp)
|
return json.load(fp)
|
||||||
|
|
||||||
|
|
||||||
def write_config(p, config):
|
def write_config(p, config):
|
||||||
|
"""Write the share configuration file
|
||||||
|
|
||||||
|
Args:
|
||||||
|
p (str): Share name
|
||||||
|
config (Dict): Share configuration
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
|
||||||
with open(os.path.join(p, CONFIG), "wt") as fp:
|
with open(os.path.join(p, CONFIG), "wt") as fp:
|
||||||
return json.dump(config, fp, indent=2, sort_keys=True)
|
return json.dump(config, fp, indent=2, sort_keys=True)
|
||||||
|
|
||||||
@@ -232,6 +369,8 @@ if __name__ == "__main__":
|
|||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
os.chdir(os.path.dirname(__file__))
|
os.chdir(os.path.dirname(__file__))
|
||||||
if opts.command == "add":
|
if opts.command == "add":
|
||||||
|
""" Add a new share """
|
||||||
|
|
||||||
new_name = random_name()
|
new_name = random_name()
|
||||||
create_new(new_name, opts.days, opts.description)
|
create_new(new_name, opts.days, opts.description)
|
||||||
print(os.path.abspath(new_name))
|
print(os.path.abspath(new_name))
|
||||||
@@ -240,7 +379,11 @@ if __name__ == "__main__":
|
|||||||
list_folders(opts.root, verbose=True, filter_name=new_name)
|
list_folders(opts.root, verbose=True, filter_name=new_name)
|
||||||
|
|
||||||
if opts.command == "list" or opts.command is None:
|
if opts.command == "list" or opts.command is None:
|
||||||
|
""" List shares """
|
||||||
|
|
||||||
list_folders(opts.root, verbose=opts.verbose)
|
list_folders(opts.root, verbose=opts.verbose)
|
||||||
|
|
||||||
if opts.command == "del":
|
if opts.command == "del":
|
||||||
|
""" Delete due shares """
|
||||||
|
|
||||||
del_due_folders()
|
del_due_folders()
|
||||||
|
|||||||
Reference in New Issue
Block a user