+ {{ form.email.label(class="form-control-label") }}
+
+ {% if form.email.errors %}
+ {{ form.email(class="form-control form-control-sm is-invalid", autocomplete="off") }}
+
+ {% for error in form.email.errors %}
+ {{ error }}
+ {% endfor %}
+ {% else %}
+ {{ form.email(class="form-control form-control-sm", autocomplete="off") }}
+ {% endif %}
+
+
+{% endblock body_content_additional %}
+
+{% block body_footer_additional %}
+{% endblock body_footer_additional %}
+
+
+{% block body_scripts_additional %}
+{% endblock body_scripts_additional %}
diff --git a/src/core/utils/Logger.py b/src/core/utils/Logger.py
new file mode 100644
index 0000000..fed0b0e
--- /dev/null
+++ b/src/core/utils/Logger.py
@@ -0,0 +1,59 @@
+# Python imports
+import os, logging
+
+# Application imports
+
+
+class Logger:
+ def __init__(self, name = "NO_LOGGER_NAME_PASSED_ON_INIT"):
+ self.logger = self.create_logger(name)
+
+ def get_logger(self):
+ return self.logger
+
+ def create_logger(self, loggerName, createFile = True):
+ """
+ Create a new logging object and return it.
+ :note:
+ NOSET # Don't know the actual log level of this... (defaulting or literally none?)
+ Log Levels (From least to most)
+ Type Value
+ CRITICAL 50
+ ERROR 40
+ WARNING 30
+ INFO 20
+ DEBUG 10
+ :param loggerName: Sets the name of the logger object. (Used in log lines)
+ :param createFile: Whether we create a log file or just pump to terminal
+
+ :return: the logging object we created
+ """
+
+ globalLogLvl = logging.DEBUG # Keep this at highest so that handlers can filter to their desired levels
+ chLogLevel = logging.CRITICAL # Prety musch the only one we change ever
+ fhLogLevel = logging.DEBUG
+ log = logging.getLogger(loggerName)
+ log.setLevel(globalLogLvl)
+
+ # Set our log output styles
+ fFormatter = logging.Formatter('[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s', '%m-%d %H:%M:%S')
+ cFormatter = logging.Formatter('%(pathname)s:%(lineno)d] %(levelname)s - %(message)s')
+
+ ch = logging.StreamHandler()
+ ch.setLevel(level=chLogLevel)
+ ch.setFormatter(cFormatter)
+ log.addHandler(ch)
+
+ if createFile:
+ folder = "logs"
+ file = folder + "/flask-application.log"
+
+ if not os.path.exists(folder):
+ os.mkdir(folder)
+
+ fh = logging.FileHandler(file)
+ fh.setLevel(level=fhLogLevel)
+ fh.setFormatter(fFormatter)
+ log.addHandler(fh)
+
+ return log
diff --git a/src/core/utils/MessageHandler.py b/src/core/utils/MessageHandler.py
new file mode 100644
index 0000000..f6538f2
--- /dev/null
+++ b/src/core/utils/MessageHandler.py
@@ -0,0 +1,14 @@
+# Gtk imports
+
+# Python imports
+
+# Application imports
+
+
+class MessageHandler:
+ def __init__(self):
+ print("MessageHandler initialized...")
+
+
+ def createMessageJSON(self, type, text):
+ return '{"message": { "type": "' + type + '", "text": "' + text + '" } }'
diff --git a/src/core/utils/__init__.py b/src/core/utils/__init__.py
new file mode 100644
index 0000000..9e9378f
--- /dev/null
+++ b/src/core/utils/__init__.py
@@ -0,0 +1,2 @@
+from .Logger import Logger
+from .MessageHandler import MessageHandler
diff --git a/src/core/utils/shellfm/__init__.py b/src/core/utils/shellfm/__init__.py
new file mode 100644
index 0000000..0c8b591
--- /dev/null
+++ b/src/core/utils/shellfm/__init__.py
@@ -0,0 +1 @@
+from .windows import WindowController
diff --git a/src/core/utils/shellfm/windows/Window.py b/src/core/utils/shellfm/windows/Window.py
new file mode 100644
index 0000000..5284e08
--- /dev/null
+++ b/src/core/utils/shellfm/windows/Window.py
@@ -0,0 +1,22 @@
+from .view import View
+
+
+class Window:
+ def __init__(self):
+ self.name = ""
+ self.nickname = ""
+ self.id = 0
+ self.views = []
+
+ def create_view(self):
+ view = View()
+ self.views.append(view)
+
+ def pop_view(self):
+ self.views.pop()
+
+ def delete_view(self, index):
+ del self.views[index]
+
+ def get_view(self, index):
+ return self.views[index]
diff --git a/src/core/utils/shellfm/windows/WindowController.py b/src/core/utils/shellfm/windows/WindowController.py
new file mode 100644
index 0000000..30cc30d
--- /dev/null
+++ b/src/core/utils/shellfm/windows/WindowController.py
@@ -0,0 +1,67 @@
+from . import Window
+
+
+class WindowController:
+ def __init__(self):
+ self.windows = []
+ self.add_window()
+
+ def get_window(self, win_id):
+ for window in self.windows:
+ if window.id == win_id:
+ return window
+
+ raise("No Window by ID {} found!".format(win_id))
+
+ def get_windows(self):
+ return self.windows
+
+ def add_window(self):
+ window = Window()
+ window.id = len(self.windows) + 1
+ window.name = "window_" + str(window.id)
+ window.create_view()
+ self.windows.append(window)
+
+ def add_view_for_window(self, win_id):
+ for window in self.windows:
+ if window.id == win_id:
+ window.create_view()
+ break
+
+ def pop_window(self):
+ self.windows.pop()
+
+ def delete_window_by_id(self, win_id):
+ i = 0
+ for window in self.windows:
+ if window.id == win_id:
+ self.window.remove(win_id)
+ break
+ i += 1
+
+ def set_window_nickname(self, win_id = None, nickname = ""):
+ for window in self.windows:
+ if window.id == win_id:
+ window.nickname = nickname
+
+ def list_windows(self):
+ for window in self.windows:
+ print("\n[ Window ]")
+ print("ID: " + str(window.id))
+ print("Name: " + window.name)
+ print("Nickname: " + window.nickname)
+ print("View Count: " + str( len(window.views) ))
+
+
+ def list_views_from_window(self, win_id):
+ for window in self.windows:
+ if window.id == win_id:
+ for view in window.views:
+ print(view.files)
+ break
+
+ def return_views_from_window(self, win_id):
+ for window in self.windows:
+ if window.id == win_id:
+ return window.views
diff --git a/src/core/utils/shellfm/windows/__init__.py b/src/core/utils/shellfm/windows/__init__.py
new file mode 100644
index 0000000..cd9f6ce
--- /dev/null
+++ b/src/core/utils/shellfm/windows/__init__.py
@@ -0,0 +1,2 @@
+from .Window import Window
+from .WindowController import WindowController
diff --git a/src/core/utils/shellfm/windows/view/Path.py b/src/core/utils/shellfm/windows/view/Path.py
new file mode 100644
index 0000000..5d46fbd
--- /dev/null
+++ b/src/core/utils/shellfm/windows/view/Path.py
@@ -0,0 +1,46 @@
+# Python imports
+import os
+
+# Lib imports
+
+# Application imports
+
+
+class Path:
+ def get_path(self):
+ return "/" + "/".join(self.path)
+
+ def get_path_list(self):
+ return self.path
+
+ def push_to_path(self, dir):
+ self.path.append(dir)
+ self.load_directory()
+
+ def pop_from_path(self):
+ self.path.pop()
+
+ if not self.go_past_home:
+ if self.get_home() not in self.get_path():
+ self.set_to_home()
+
+ self.load_directory()
+
+
+ def set_path(self, path):
+ self.path = list( filter(None, path.replace("\\", "/").split('/')) )
+ self.load_directory()
+
+ def set_path_with_sub_path(self, sub_path):
+ path = os.path.join(self.get_home(), sub_path)
+ self.path = list( filter(None, path.replace("\\", "/").split('/')) )
+ self.load_directory()
+
+ def set_to_home(self):
+ home = os.path.expanduser("~") + self.subpath
+ path = list( filter(None, home.replace("\\", "/").split('/')) )
+ self.path = path
+ self.load_directory()
+
+ def get_home(self):
+ return os.path.expanduser("~") + self.subpath
diff --git a/src/core/utils/shellfm/windows/view/View.py b/src/core/utils/shellfm/windows/view/View.py
new file mode 100644
index 0000000..2a37a5f
--- /dev/null
+++ b/src/core/utils/shellfm/windows/view/View.py
@@ -0,0 +1,169 @@
+# Python imports
+import hashlib
+import os
+from os import listdir
+from os.path import isdir, isfile, join
+
+
+# Lib imports
+
+
+# Application imports
+from .utils import Settings, Launcher
+from . import Path
+
+class View(Settings, Launcher, Path):
+ def __init__(self):
+ self.files = []
+ self.dirs = []
+ self.vids = []
+ self.images = []
+ self.desktop = []
+ self.ungrouped = []
+ self.error_message = None
+
+ self.set_to_home()
+
+ def load_directory(self):
+ path = self.get_path()
+ self.dirs = []
+ self.vids = []
+ self.images = []
+ self.desktop = []
+ self.ungrouped = []
+ self.files = []
+
+ if not isdir(path):
+ self._set_error_message("Path can not be accessed.")
+ return ""
+
+ for f in listdir(path):
+ file = join(path, f)
+ if self.HIDE_HIDDEN_FILES:
+ if f.startswith('.'):
+ continue
+
+ if isfile(file):
+ lowerName = file.lower()
+ if lowerName.endswith(self.fvideos):
+ self.vids.append(f)
+ elif lowerName.endswith(self.fimages):
+ self.images.append(f)
+ elif lowerName.endswith((".desktop",)):
+ self.desktop.append(f)
+ else:
+ self.ungrouped.append(f)
+ else:
+ self.dirs.append(f)
+
+ self.dirs.sort()
+ self.vids.sort()
+ self.images.sort()
+ self.desktop.sort()
+ self.ungrouped.sort()
+
+ self.files = self.dirs + self.vids + self.images + self.desktop + self.ungrouped
+
+ def hashText(self, text):
+ return hashlib.sha256(str.encode(text)).hexdigest()[:18]
+
+ def hashSet(self, arry):
+ data = []
+ for arr in arry:
+ data.append([arr, self.hashText(arr)])
+ return data
+
+ def get_path_part_from_hash(self, hash):
+ files = self.get_files()
+ file = None
+
+ for f in files:
+ if hash == f[1]:
+ file = f[0]
+ break
+
+ return file
+
+ def get_files_formatted(self):
+ files = self.hashSet(self.files),
+ dirs = self.hashSet(self.dirs),
+ videos = self.get_videos(),
+ images = self.hashSet(self.images),
+ desktops = self.hashSet(self.desktop),
+ ungrouped = self.hashSet(self.ungrouped)
+
+ return {
+ 'path_head': self.get_path(),
+ 'list': {
+ 'files': files,
+ 'dirs': dirs,
+ 'videos': videos,
+ 'images': images,
+ 'desktops': desktops,
+ 'ungrouped': ungrouped
+ }
+ }
+
+ def is_folder_locked(self, hash):
+ if self.lock_folder:
+ path_parts = self.get_path().split('/')
+ file = self.get_path_part_from_hash(hash)
+
+ # Insure chilren folders are locked too.
+ lockedFolderInPath = False
+ for folder in self.locked_folders:
+ if folder in path_parts:
+ lockedFolderInPath = True
+ break
+
+ return (file in self.locked_folders or lockedFolderInPath)
+ else:
+ return False
+
+
+ def _set_error_message(self, text):
+ self.error_message = text
+
+ def unset_error_message(self):
+ self.error_message = None
+
+ def get_error_message(self):
+ return self.error_message
+
+ def get_current_directory(self):
+ return self.get_path()
+
+ def get_current_sub_path(self):
+ path = self.get_path()
+ home = self.get_home() + "/"
+ return path.replace(home, "")
+
+ def get_dot_dots(self):
+ return self.hashSet(['.', '..'])
+
+ def get_files(self):
+ return self.hashSet(self.files)
+
+ def get_dirs(self):
+ return self.hashSet(self.dirs)
+
+ def get_videos(self):
+ videos_set = self.hashSet(self.vids)
+ current_directory = self.get_current_directory()
+ for video in videos_set:
+ hashImgPth = join(self.ABS_THUMBS_PTH, video[1]) + ".jpg"
+ if not os.path.exists(hashImgPth) :
+ fullPath = join(current_directory, video[0])
+ self.logger.debug(f"Hash Path: {hashImgPth}\nFile Path: {fullPath}")
+ self.generateVideoThumbnail(fullPath, hashImgPth)
+
+ return videos_set
+
+ def get_images(self):
+ return self.hashSet(self.images)
+
+ def get_desktops(self):
+ return self.hashSet(self.desktop)
+
+ def get_ungrouped(self):
+ return self.hashSet(self.ungrouped)
diff --git a/src/core/utils/shellfm/windows/view/__init__.py b/src/core/utils/shellfm/windows/view/__init__.py
new file mode 100644
index 0000000..da63bd2
--- /dev/null
+++ b/src/core/utils/shellfm/windows/view/__init__.py
@@ -0,0 +1,4 @@
+from .utils import *
+
+from .Path import Path
+from .View import View
diff --git a/src/core/utils/shellfm/windows/view/utils/Launcher.py b/src/core/utils/shellfm/windows/view/utils/Launcher.py
new file mode 100644
index 0000000..6e3dcac
--- /dev/null
+++ b/src/core/utils/shellfm/windows/view/utils/Launcher.py
@@ -0,0 +1,102 @@
+# System import
+import os, subprocess, threading
+
+
+# Lib imports
+
+
+# Apoplication imports
+
+
+class Launcher:
+ def openFilelocally(self, file):
+ lowerName = file.lower()
+ command = []
+
+ if lowerName.endswith(self.fvideos):
+ command = [self.media_app]
+
+ if "mplayer" in self.media_app:
+ command += self.mplayer_options
+
+ command += [file]
+ elif lowerName.endswith(self.fimages):
+ command = [self.image_app, file]
+ elif lowerName.endswith(self.fmusic):
+ command = [self.music_app, file]
+ elif lowerName.endswith(self.foffice):
+ command = [self.office_app, file]
+ elif lowerName.endswith(self.ftext):
+ command = [self.text_app, file]
+ elif lowerName.endswith(self.fpdf):
+ command = [self.pdf_app, file]
+ else:
+ command = [self.file_manager_app, file]
+
+ self.logger.debug(command)
+ DEVNULL = open(os.devnull, 'w')
+ subprocess.Popen(command, start_new_session=True, stdout=DEVNULL, stderr=DEVNULL, close_fds=True)
+
+
+ def remuxVideo(self, hash, file):
+ remux_vid_pth = self.REMUX_FOLDER + "/" + hash + ".mp4"
+ self.logger.debug(remux_vid_pth)
+
+ if not os.path.isfile(remux_vid_pth):
+ self.check_remux_space()
+
+ command = ["ffmpeg", "-i", file, "-hide_banner", "-movflags", "+faststart"]
+ if file.endswith("mkv"):
+ command += ["-codec", "copy", "-strict", "-2"]
+ if file.endswith("avi"):
+ command += ["-c:v", "libx264", "-crf", "21", "-c:a", "aac", "-b:a", "192k", "-ac", "2"]
+ if file.endswith("wmv"):
+ command += ["-c:v", "libx264", "-crf", "23", "-c:a", "aac", "-strict", "-2", "-q:a", "100"]
+ if file.endswith("f4v") or file.endswith("flv"):
+ command += ["-vcodec", "copy"]
+
+ command += [remux_vid_pth]
+ try:
+ proc = subprocess.Popen(command)
+ proc.wait()
+ except Exception as e:
+ self.logger.debug(message)
+ self.logger.debug(e)
+ return False
+
+ return True
+
+
+ def generateVideoThumbnail(self, fullPath, hashImgPth):
+ try:
+ proc = subprocess.Popen([self.FFMPG_THUMBNLR, "-t", "65%", "-s", "300", "-c", "jpg", "-i", fullPath, "-o", hashImgPth])
+ proc.wait()
+ except Exception as e:
+ self.logger.debug(repr(e))
+
+
+ def check_remux_space(self):
+ limit = self.remux_folder_max_disk_usage
+ try:
+ limit = int(limit)
+ except Exception as e:
+ self.logger.debug(e)
+ return
+
+ usage = self.getRemuxFolderUsage(self.REMUX_FOLDER)
+ if usage > limit:
+ files = os.listdir(self.REMUX_FOLDER)
+ for file in files:
+ fp = os.path.join(self.REMUX_FOLDER, file)
+ os.unlink(fp)
+
+
+ def getRemuxFolderUsage(self, start_path = "."):
+ total_size = 0
+ for dirpath, dirnames, filenames in os.walk(start_path):
+ for f in filenames:
+ fp = os.path.join(dirpath, f)
+ if not os.path.islink(fp): # Skip if it is symbolic link
+ total_size += os.path.getsize(fp)
+
+ return total_size
diff --git a/src/core/utils/shellfm/windows/view/utils/Settings.py b/src/core/utils/shellfm/windows/view/utils/Settings.py
new file mode 100644
index 0000000..a6e3ea8
--- /dev/null
+++ b/src/core/utils/shellfm/windows/view/utils/Settings.py
@@ -0,0 +1,93 @@
+# System import
+import json
+import os
+from os import path
+
+# Lib imports
+
+
+# Apoplication imports
+
+
+
+class Settings:
+ logger = None
+
+ USER_HOME = path.expanduser('~')
+ CONFIG_PATH = USER_HOME + "/.config/webfm"
+ CONFIG_FILE = CONFIG_PATH + "/settings.json"
+ HIDE_HIDDEN_FILES = True
+
+ GTK_ORIENTATION = 1 # HORIZONTAL (0) VERTICAL (1)
+ DEFAULT_ICONS = CONFIG_PATH + "/icons"
+ DEFAULT_ICON = DEFAULT_ICONS + "/text.png"
+ FFMPG_THUMBNLR = CONFIG_PATH + "/ffmpegthumbnailer" # Thumbnail generator binary
+ REMUX_FOLDER = USER_HOME + "/.remuxs" # Remuxed files folder
+
+ STEAM_BASE_URL = "https://steamcdn-a.akamaihd.net/steam/apps/"
+ ICON_DIRS = ["/usr/share/pixmaps", "/usr/share/icons", USER_HOME + "/.icons" ,]
+ BASE_THUMBS_PTH = USER_HOME + "/.thumbnails" # Used for thumbnail generation
+ ABS_THUMBS_PTH = BASE_THUMBS_PTH + "/normal" # Used for thumbnail generation
+ STEAM_ICONS_PTH = BASE_THUMBS_PTH + "/steam_icons"
+ CONTAINER_ICON_WH = [128, 128]
+ VIDEO_ICON_WH = [128, 64]
+ SYS_ICON_WH = [56, 56]
+
+ # CONTAINER_ICON_WH = [128, 128]
+ # VIDEO_ICON_WH = [96, 48]
+ # SYS_ICON_WH = [96, 96]
+
+ subpath = ""
+ go_past_home = None
+ lock_folder = None
+ locked_folders = None
+ mplayer_options = None
+ music_app = None
+ media_app = None
+ image_app = None
+ office_app = None
+ pdf_app = None
+ text_app = None
+ file_manager_app = None
+ remux_folder_max_disk_usage = None
+
+ if path.isfile(CONFIG_FILE):
+ with open(CONFIG_FILE) as infile:
+ settings = json.load(infile)["settings"]
+
+ subpath = settings["base_of_home"]
+ HIDE_HIDDEN_FILES = True if settings["hide_hidden_files"] == "true" else False
+ go_past_home = True if settings["go_past_home"] == "true" else False
+ lock_folder = True if settings["lock_folder"] == "true" else False
+ locked_folders = settings["locked_folders"].split("::::")
+ mplayer_options = settings["mplayer_options"].split()
+ music_app = settings["music_app"]
+ media_app = settings["media_app"]
+ image_app = settings["image_app"]
+ office_app = settings["office_app"]
+ pdf_app = settings["pdf_app"]
+ text_app = settings["text_app"]
+ file_manager_app = settings["file_manager_app"]
+ remux_folder_max_disk_usage = settings["remux_folder_max_disk_usage"]
+
+ # Filters
+ fvideos = ('.mkv', '.avi', '.flv', '.mov', '.m4v', '.mpg', '.wmv', '.mpeg', '.mp4', '.webm')
+ foffice = ('.doc', '.docx', '.xls', '.xlsx', '.xlt', '.xltx', '.xlm', '.ppt', 'pptx', '.pps', '.ppsx', '.odt', '.rtf')
+ fimages = ('.png', '.jpg', '.jpeg', '.gif', '.ico', '.tga')
+ ftext = ('.txt', '.text', '.sh', '.cfg', '.conf')
+ fmusic = ('.psf', '.mp3', '.ogg', '.flac', '.m4a')
+ fpdf = ('.pdf')
+
+
+ # Dire structure check
+ if path.isdir(REMUX_FOLDER) == False:
+ os.mkdir(REMUX_FOLDER)
+
+ if path.isdir(BASE_THUMBS_PTH) == False:
+ os.mkdir(BASE_THUMBS_PTH)
+
+ if path.isdir(ABS_THUMBS_PTH) == False:
+ os.mkdir(ABS_THUMBS_PTH)
+
+ if path.isdir(STEAM_ICONS_PTH) == False:
+ os.mkdir(STEAM_ICONS_PTH)
diff --git a/src/core/utils/shellfm/windows/view/utils/__init__.py b/src/core/utils/shellfm/windows/view/utils/__init__.py
new file mode 100644
index 0000000..3c05646
--- /dev/null
+++ b/src/core/utils/shellfm/windows/view/utils/__init__.py
@@ -0,0 +1,2 @@
+from .Settings import Settings
+from .Launcher import Launcher
diff --git a/src/linux-start.sh b/src/linux-start.sh
new file mode 100755
index 0000000..724101e
--- /dev/null
+++ b/src/linux-start.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+# set -o xtrace ## To debug scripts
+# set -o errexit ## To exit on error
+# set -o errunset ## To exit if a variable is referenced but not set
+
+
+function main() {
+ SCRIPTPATH="$( cd "$(dirname "")" >/dev/null 2>&1 ; pwd -P )"
+ echo "Working Dir: " $(pwd)
+ source "../venv/bin/activate"
+
+ LOG_LEVEL=warn
+ WORKER_COUNT=1
+ ADDR=127.0.0.1
+ PORT=6969
+ TIMEOUT=120
+
+ # Note can replace 127.0.0.1 with 0.0.0.0 to make it 'network/internet' accessable...
+ # Note: NEED -k eventlet for this to work! I spent too many hours on this...
+ #
: IE :
+ gunicorn wsgi:app -p app.pid -b $ADDR:$PORT \
+ -k eventlet \
+ -w $WORKER_COUNT \
+ --timeout $TIMEOUT \
+ --log-level $LOG_LEVEL
+}
+main $@;
diff --git a/src/socket_run.sh b/src/socket_run.sh
new file mode 100755
index 0000000..baaaa3b
--- /dev/null
+++ b/src/socket_run.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+# set -o xtrace ## To debug scripts
+# set -o errexit ## To exit on error
+# set -o errunset ## To exit if a variable is referenced but not set
+
+
+function main() {
+ SCRIPTPATH="$( cd "$(dirname "")" >/dev/null 2>&1 ; pwd -P )"
+ echo "Working Dir: " $(pwd)
+ source "../venv/bin/activate"
+
+ LOG_LEVEL=error
+ WORKER_COUNT=1
+ TIMEOUT=120
+
+ # : IE :
+ gunicorn wsgi:app -p app.pid -b unix:/tmp/app.sock \
+ -k eventlet \
+ -w $WORKER_COUNT \
+ --timeout $TIMEOUT \
+ --log-level $LOG_LEVEL
+}
+main $@;
diff --git a/src/user_config/webfm/ffmpegthumbnailer b/src/user_config/webfm/ffmpegthumbnailer
new file mode 100755
index 0000000..0b6e6e2
Binary files /dev/null and b/src/user_config/webfm/ffmpegthumbnailer differ
diff --git a/src/user_config/webfm/icons/archive.png b/src/user_config/webfm/icons/archive.png
new file mode 100644
index 0000000..7943e4e
Binary files /dev/null and b/src/user_config/webfm/icons/archive.png differ
diff --git a/src/user_config/webfm/icons/audio.png b/src/user_config/webfm/icons/audio.png
new file mode 100644
index 0000000..c010134
Binary files /dev/null and b/src/user_config/webfm/icons/audio.png differ
diff --git a/src/user_config/webfm/icons/bin.png b/src/user_config/webfm/icons/bin.png
new file mode 100644
index 0000000..d6954e3
Binary files /dev/null and b/src/user_config/webfm/icons/bin.png differ
diff --git a/src/user_config/webfm/icons/dir.png b/src/user_config/webfm/icons/dir.png
new file mode 100644
index 0000000..a9b5e9f
Binary files /dev/null and b/src/user_config/webfm/icons/dir.png differ
diff --git a/src/user_config/webfm/icons/doc.png b/src/user_config/webfm/icons/doc.png
new file mode 100644
index 0000000..f838826
Binary files /dev/null and b/src/user_config/webfm/icons/doc.png differ
diff --git a/src/user_config/webfm/icons/pdf.png b/src/user_config/webfm/icons/pdf.png
new file mode 100644
index 0000000..9f40122
Binary files /dev/null and b/src/user_config/webfm/icons/pdf.png differ
diff --git a/src/user_config/webfm/icons/presentation.png b/src/user_config/webfm/icons/presentation.png
new file mode 100644
index 0000000..3a339af
Binary files /dev/null and b/src/user_config/webfm/icons/presentation.png differ
diff --git a/src/user_config/webfm/icons/spreadsheet.png b/src/user_config/webfm/icons/spreadsheet.png
new file mode 100644
index 0000000..710efa6
Binary files /dev/null and b/src/user_config/webfm/icons/spreadsheet.png differ
diff --git a/src/user_config/webfm/icons/text.png b/src/user_config/webfm/icons/text.png
new file mode 100644
index 0000000..2546fcd
Binary files /dev/null and b/src/user_config/webfm/icons/text.png differ
diff --git a/src/user_config/webfm/icons/trash.png b/src/user_config/webfm/icons/trash.png
new file mode 100644
index 0000000..c6514b9
Binary files /dev/null and b/src/user_config/webfm/icons/trash.png differ
diff --git a/src/user_config/webfm/icons/video.png b/src/user_config/webfm/icons/video.png
new file mode 100644
index 0000000..55afa98
Binary files /dev/null and b/src/user_config/webfm/icons/video.png differ
diff --git a/src/user_config/webfm/icons/web.png b/src/user_config/webfm/icons/web.png
new file mode 100644
index 0000000..17017ce
Binary files /dev/null and b/src/user_config/webfm/icons/web.png differ
diff --git a/src/user_config/webfm/settings.json b/src/user_config/webfm/settings.json
new file mode 100644
index 0000000..2ba7a28
--- /dev/null
+++ b/src/user_config/webfm/settings.json
@@ -0,0 +1,18 @@
+{
+ "settings": {
+ "base_of_home": "/LazyShare",
+ "hide_hidden_files": "true",
+ "go_past_home": "true",
+ "lock_folder": "true",
+ "locked_folders": "Synced Backup::::venv::::flasks::::Cryptomator",
+ "mplayer_options": "-quiet -really-quiet -xy 1600 -geometry 50%:50%",
+ "music_app": "/opt/deadbeef/bin/deadbeef",
+ "media_app": "mpv",
+ "image_app": "mirage",
+ "office_app": "libreoffice",
+ "pdf_app": "evince",
+ "text_app": "leafpad",
+ "file_manager_app": "spacefm",
+ "remux_folder_max_disk_usage": "8589934592"
+ }
+}
diff --git a/src/windows-start.sh b/src/windows-start.sh
new file mode 100644
index 0000000..c38a23e
--- /dev/null
+++ b/src/windows-start.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# set -o xtrace ## To debug scripts
+# set -o errexit ## To exit on error
+# set -o errunset ## To exit if a variable is referenced but not set
+
+
+function main() {
+ source "../venv/Scripts/activate"
+ # Note: Can replace 127.0.0.1 with 0.0.0.0 to make it 'network/internet' accessable...
+ # Note 2: Keycloak uses 8080. Change it or keep this as is.
+ waitress-serve --listen=127.0.0.1:6969 wsgi:app # : IE :
+}
+main $@;
diff --git a/src/wsgi.py b/src/wsgi.py
new file mode 100644
index 0000000..1bec6c6
--- /dev/null
+++ b/src/wsgi.py
@@ -0,0 +1,14 @@
+import eventlet
+eventlet.monkey_patch()
+#
+# import eventlet.debug
+# from engineio.payload import Payload
+#
+# # Some fixers for Websockets
+# eventlet.debug.hub_prevent_multiple_readers(False)
+# Payload.max_decode_packets = 120 # Fix too many small packets causing error
+
+from core import app
+
+if __name__ == '__main__':
+ app.run()
diff --git a/windows-requirements.txt b/windows-requirements.txt
new file mode 100644
index 0000000..c55e489
--- /dev/null
+++ b/windows-requirements.txt
@@ -0,0 +1,25 @@
+click==7.1.2
+dnspython==2.1.0
+eventlet==0.30.1
+email-validator==1.1.2
+Flask==1.1.2
+Flask-Login==0.5.0
+flask-oidc==1.4.0
+Flask-Bcrypt==0.7.1
+Flask-SQLAlchemy==2.4.4
+Flask-WTF==0.14.3
+greenlet==1.0.0
+waitress==1.4.3
+httplib2==0.19.0
+itsdangerous==1.1.0
+Jinja2==2.11.3
+MarkupSafe==1.1.1
+oauth2client==4.1.3
+pyasn1==0.4.8
+pyasn1-modules==0.2.8
+pyparsing==2.4.7
+rsa==4.7
+six==1.15.0
+SQLAlchemy==1.3.23
+Werkzeug==1.0.1
+WTForms==2.3.3
\ No newline at end of file