Skip to content

Commit

Permalink
feat(filemanager): 文件管理器添加硬链接自动同步/硬链接定位
Browse files Browse the repository at this point in the history
1. 在“同步目录”的配置中增加“自动定位”的开关,每套目录同步允许配置独立的打开或关闭自动定位硬链接;
2. 当同步目录关闭自动定位时,在文件区仍可对单个文件或所有文件手工执行定位硬链接;
3. 目录框输入或切换的目录为硬链接同步目录时,当前目录中的文件也支持自动定位或手工定位硬链接;
4. 硬链接同步目录的配置中,未配置目标目录的来源目录,在目录树中其目标目录现在显示为“未配置”,且点击不会跳转。
  • Loading branch information
waylonwang committed Sep 17, 2023
1 parent b4577ea commit 299bfaa
Show file tree
Hide file tree
Showing 17 changed files with 801 additions and 132 deletions.
1 change: 1 addition & 0 deletions app/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class CONFIGSYNCPATHS(Base):
RENAME = Column(Integer)
ENABLED = Column(Integer)
NOTE = Column(Text)
LOCATING = Column(Integer)


class CONFIGUSERS(Base):
Expand Down
13 changes: 10 additions & 3 deletions app/helper/db_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2315,7 +2315,7 @@ def is_custom_word_group_existed(self, tmdbid=None, gtype=None):
return False

@DbPersist(_db)
def insert_config_sync_path(self, source, dest, unknown, mode, compatibility, rename, enabled, note=None):
def insert_config_sync_path(self, source, dest, unknown, mode, compatibility, rename, enabled, locating, note=None):
"""
增加目录同步
"""
Expand All @@ -2327,6 +2327,7 @@ def insert_config_sync_path(self, source, dest, unknown, mode, compatibility, re
COMPATIBILITY=int(compatibility),
RENAME=int(rename),
ENABLED=int(enabled),
LOCATING=int(locating),
NOTE=note
))

Expand All @@ -2348,7 +2349,7 @@ def get_config_sync_paths(self, sid=None):
return self._db.query(CONFIGSYNCPATHS).order_by(CONFIGSYNCPATHS.SOURCE).all()

@DbPersist(_db)
def check_config_sync_paths(self, sid=None, compatibility=None, rename=None, enabled=None):
def check_config_sync_paths(self, sid=None, compatibility=None, rename=None, enabled=None, locating=None):
"""
设置目录同步状态
"""
Expand All @@ -2370,7 +2371,13 @@ def check_config_sync_paths(self, sid=None, compatibility=None, rename=None, ena
"COMPATIBILITY": int(compatibility)
}
)

elif sid and locating is not None:
self._db.query(CONFIGSYNCPATHS).filter(CONFIGSYNCPATHS.ID == int(sid)).update(
{
"LOCATING": int(locating)
}
)

@DbPersist(_db)
def delete_download_setting(self, sid):
"""
Expand Down
17 changes: 11 additions & 6 deletions app/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ def init_config(self):
rename = True if sync_conf.RENAME else False
# 兼容模式
compatibility = True if sync_conf.COMPATIBILITY else False
# 自动定位
locating = True if sync_conf.LOCATING else False
# 转移方式
syncmode = sync_conf.MODE
syncmode_enum = ModuleConf.RMT_MODES.get(syncmode)
Expand Down Expand Up @@ -107,7 +109,8 @@ def init_config(self):
'syncmod_name': syncmode_enum.value,
"compatibility": compatibility,
'rename': rename,
'enabled': enabled
'enabled': enabled,
'locating': locating
}
if monpath and os.path.exists(monpath):
if enabled:
Expand All @@ -132,14 +135,14 @@ def get_sync_path_conf(self, sid=None):
return self._sync_path_confs.get(str(sid)) or {}
return self._sync_path_confs

def get_hardlinks_sync_dirs(self):
def get_filehardlinks_sync_dirs(self):
"""
获取所有硬链接的同步目录设置
"""
sync_dirs = []
for src, conf in self.get_sync_path_conf().items():
if conf["syncmod"].upper() == 'LINK':
sync_dirs.append([conf["from"], conf["to"]])
sync_dirs.append([conf["from"], conf["to"], conf["locating"]])
return sync_dirs

def check_source(self, source=None, sid=None):
Expand Down Expand Up @@ -429,7 +432,7 @@ def delete_sync_path(self, sid):
self.init_config()
return ret

def insert_sync_path(self, source, dest, unknown, mode, compatibility, rename, enabled, note=None):
def insert_sync_path(self, source, dest, unknown, mode, compatibility, rename, enabled, locating, note=None):
"""
添加同步目录配置
"""
Expand All @@ -440,19 +443,21 @@ def insert_sync_path(self, source, dest, unknown, mode, compatibility, rename, e
compatibility=compatibility,
rename=rename,
enabled=enabled,
locating=locating,
note=note)
self.init_config()
return ret

def check_sync_paths(self, sid=None, compatibility=None, rename=None, enabled=None):
def check_sync_paths(self, sid=None, compatibility=None, rename=None, enabled=None, locating=None):
"""
检查配置的同步目录
"""
ret = self.dbhelper.check_config_sync_paths(
sid=sid,
compatibility=compatibility,
rename=rename,
enabled=enabled
enabled=enabled,
locating=locating
)
self.init_config()
return ret
28 changes: 28 additions & 0 deletions scripts/versions/ff1b04a637f8_1_3_0.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""1.3.0
Revision ID: ff1b04a637f8
Revises: 7c14267ffbe4
Create Date: 2023-09-17 09:35:42.773359
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'ff1b04a637f8'
down_revision = '7c14267ffbe4'
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('CONFIG_SYNC_PATHS', sa.Column('LOCATING', sa.Integer(), nullable=True))
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('CONFIG_SYNC_PATHS', 'LOCATING')
# ### end Alembic commands ###
115 changes: 114 additions & 1 deletion web/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ def __init__(self):
"get_downloading": self.get_downloading,
"test_site": self.__test_site,
"get_sub_path": self.__get_sub_path,
"get_filehardlinks": self.__get_filehardlinks,
"get_dirhardlink": self.__get_dirhardlink,
"rename_file": self.__rename_file,
"delete_files": self.__delete_files,
"download_subtitle": self.__download_subtitle,
Expand Down Expand Up @@ -1280,6 +1282,7 @@ def __add_or_edit_sync_path(data):
compatibility = data.get("compatibility")
rename = data.get("rename")
enabled = data.get("enabled")
locating = data.get("locating")

_sync = Sync()

Expand Down Expand Up @@ -1317,7 +1320,8 @@ def __add_or_edit_sync_path(data):
mode=mode,
compatibility=compatibility,
rename=rename,
enabled=enabled)
enabled=enabled,
locating=locating)
return {"code": 0, "msg": ""}

@staticmethod
Expand Down Expand Up @@ -1363,6 +1367,9 @@ def __check_sync_path(data):
_sync.check_source(sid=sid)
_sync.check_sync_paths(sid=sid, enabled=1 if checked else 0)
return {"code": 0}
elif flag == "locating":
_sync.check_sync_paths(sid=sid, locating=1 if checked else 0)
return {"code": 0}
else:
return {"code": 1}

Expand Down Expand Up @@ -4076,6 +4083,25 @@ def __get_sub_path(data):
for f in os.listdir("C:/")]
else:
dirs = [os.path.join("/", f) for f in os.listdir("/")]
elif d == "[SYNC-FOLDERS]":
sync_dirs = []
for id, conf in Sync().get_sync_path_conf().items():
sync_dirs.append(conf["from"])
sync_dirs.append(conf["to"])
dirs = list(set(sync_dirs))
elif d == "[DOWNLOAD-FOLDERS]":
dirs = [path.rstrip('/') for path in Downloader().get_download_visit_dirs()]
elif d == "[MEDIA-FOLDERS]":
media_dirs = []
movie_path = Config().get_config('media').get('movie_path')
tv_path = Config().get_config('media').get('tv_path')
anime_path = Config().get_config('media').get('anime_path')
unknown_path = Config().get_config('media').get('unknown_path')
if movie_path is not None: media_dirs.extend([path.rstrip('/') for path in movie_path])
if tv_path is not None: media_dirs.extend([path.rstrip('/') for path in tv_path])
if anime_path is not None: media_dirs.extend([path.rstrip('/') for path in anime_path])
if unknown_path is not None: media_dirs.extend([path.rstrip('/') for path in unknown_path])
dirs = list(set(media_dirs))
else:
d = os.path.normpath(unquote(d))
if not os.path.isdir(d):
Expand Down Expand Up @@ -4124,6 +4150,93 @@ def __get_sub_path(data):
"data": r
}

@staticmethod
def __get_filehardlinks(data):
"""
获取文件硬链接
"""
def parse_hardlinks(hardlinks):
paths = []
for link in hardlinks:
paths.append([SystemUtils.shorten_path(link["file"], 'left', 2), link["file"], link["filepath"]])
return paths
r = {}
try:
file = data.get("filepath")
direction = ""
hardlinks = []
# 获取所有硬链接的同步目录设置
sync_dirs = Sync().get_filehardlinks_sync_dirs()
# 按设置遍历检查文件是否在同步目录内,只查找第一个匹配项,多余的忽略
for dir in sync_dirs:
if dir[0] and file.startswith(f"{dir[0]}/"):
direction = '→'
hardlinks = parse_hardlinks(SystemUtils().find_hardlinks(file=file, fdir=dir[1]))
break
elif dir[1] and file.startswith(f"{dir[1]}/"):
direction = '←'
hardlinks = parse_hardlinks(SystemUtils().find_hardlinks(file=file, fdir=dir[0]))
break
r={
"filepath": file, # 文件路径
"direction": direction, # 同步方向
"hardlinks": hardlinks # 同步链接,内容分别为缩略路径、文件路径、目录路径
}
except Exception as e:
ExceptionUtils.exception_traceback(e)
return {
"code": -1,
"message": '加载路径失败: %s' % str(e)
}
return {
"code": 0,
"count": len(r),
"data": r
}

@staticmethod
def __get_dirhardlink(data):
"""
获取同步目录硬链接
"""
r = {}
try:
path = data.get("dirpath")
direction = ""
hardlink = []
locating = False
# 获取所有硬链接的同步目录设置
sync_dirs = Sync().get_filehardlinks_sync_dirs()
# 按设置遍历检查目录是否是同步目录或在同步目录内
for dir in sync_dirs:
if dir[0] and (dir[0] == path or path.startswith(f"{dir[0]}/")):
direction = '→'
hardlink = dir[0].replace(dir[0], dir[1])
locating = dir[2]
break
elif dir[1] and (dir[1] == path or path.startswith(f"{dir[1]}/")):
direction = '←'
hardlink = dir[1].replace(dir[1], dir[0])
locating = dir[2]
break
r={
"dirpath": path, # 同步目录路径
"direction": direction, # 同步方向
"hardlink": hardlink, # 同步链接,内容为配置中对应的目录或子目录
"locating": locating # 自动定位
}
except Exception as e:
ExceptionUtils.exception_traceback(e)
return {
"code": -1,
"message": '加载路径失败: %s' % str(e)
}
return {
"code": 0,
"count": len(r),
"data": r
}

@staticmethod
def __rename_file(data):
"""
Expand Down
24 changes: 24 additions & 0 deletions web/apiv1.py
Original file line number Diff line number Diff line change
Expand Up @@ -2131,6 +2131,30 @@ def get():
return WebAction().api_action(cmd='sch', data={"item": "sync"})


@sync.route('/file/hardlinks')
class SystemFileHardlinks(ClientResource):
parser = reqparse.RequestParser()
parser.add_argument('filepath', type=str, help='路径', location='form', required=True)
@system.doc(parser=parser)
def post(self):
"""
查询文件的硬链接
"""
return WebAction().api_action(cmd='get_filehardlinks', data=self.parser.parse_args())


@sync.route('/directory/hardlink')
class SystemDirectoryHardlink(ClientResource):
parser = reqparse.RequestParser()
parser.add_argument('dirpath', type=str, help='路径', location='form', required=True)
@system.doc(parser=parser)
def post(self):
"""
查询目录的硬链接
"""
return WebAction().api_action(cmd='get_dirhardlink', data=self.parser.parse_args())


@message.route('/client/update')
class MessageClientUpdate(ClientResource):
parser = reqparse.RequestParser()
Expand Down
Loading

0 comments on commit 299bfaa

Please sign in to comment.