Compare commits
8 Commits
139ff51dcd
...
4c7b310f82
Author | SHA1 | Date | |
---|---|---|---|
4c7b310f82 | |||
1250037cfd | |||
cdc936d2d5 | |||
4f370440d9 | |||
feaa8e315e | |||
14f7253ece | |||
9d3d27faf3 | |||
dd235e8f25 |
2
cista-front/.gitignore
vendored
2
cista-front/.gitignore
vendored
@ -9,6 +9,7 @@ lerna-debug.log*
|
||||
|
||||
# No locking
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
@ -16,6 +17,7 @@ dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
components.d.ts
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
24
cista-front/components.d.ts
vendored
24
cista-front/components.d.ts
vendored
@ -1,24 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
BreadCrumb: typeof import('./src/components/BreadCrumb.vue')['default']
|
||||
FileExplorer: typeof import('./src/components/FileExplorer.vue')['default']
|
||||
FileRenameInput: typeof import('./src/components/FileRenameInput.vue')['default']
|
||||
FileViewer: typeof import('./src/components/FileViewer.vue')['default']
|
||||
HeaderMain: typeof import('./src/components/HeaderMain.vue')['default']
|
||||
HeaderSelected: typeof import('./src/components/HeaderSelected.vue')['default']
|
||||
LoginModal: typeof import('./src/components/LoginModal.vue')['default']
|
||||
ModalDialog: typeof import('./src/components/ModalDialog.vue')['default']
|
||||
NotificationLoading: typeof import('./src/components/NotificationLoading.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SvgButton: typeof import('./src/components/SvgButton.vue')['default']
|
||||
UploadButton: typeof import('./src/components/UploadButton.vue')['default']
|
||||
}
|
||||
}
|
@ -52,7 +52,11 @@ const globalShortcutHandler = (event: KeyboardEvent) => {
|
||||
const c = documentStore.fileExplorer.isCursor()
|
||||
const keyup = event.type === 'keyup'
|
||||
if (event.repeat) {
|
||||
if (event.key === 'ArrowUp' || event.key === 'ArrowDown' || (c && event.code === 'Space')) {
|
||||
if (
|
||||
event.key === 'ArrowUp' ||
|
||||
event.key === 'ArrowDown' ||
|
||||
(c && event.code === 'Space')
|
||||
) {
|
||||
event.preventDefault()
|
||||
}
|
||||
return
|
||||
@ -70,7 +74,11 @@ const globalShortcutHandler = (event: KeyboardEvent) => {
|
||||
documentStore.fileExplorer.toggleSelectAll()
|
||||
}
|
||||
// Keys 1-3 to sort columns
|
||||
else if (c && keyup && (event.key === '1' || event.key === '2' || event.key === '3')) {
|
||||
else if (
|
||||
c &&
|
||||
keyup &&
|
||||
(event.key === '1' || event.key === '2' || event.key === '3')
|
||||
) {
|
||||
documentStore.fileExplorer.toggleSortColumn(+event.key)
|
||||
}
|
||||
// Rename
|
||||
@ -85,17 +93,22 @@ const globalShortcutHandler = (event: KeyboardEvent) => {
|
||||
event.preventDefault()
|
||||
if (!vert) {
|
||||
if (timer) {
|
||||
clearTimeout(timer) // Good for either timeout or interval
|
||||
clearTimeout(timer) // Good for either timeout or interval
|
||||
timer = null
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!timer) {
|
||||
// Initial move, then t0 delay until repeats at tr intervals
|
||||
documentStore.fileExplorer.cursorMove(vert)
|
||||
const t0 = 200, tr = 30
|
||||
const select = event.shiftKey
|
||||
documentStore.fileExplorer.cursorMove(vert, select)
|
||||
const t0 = 200,
|
||||
tr = 30
|
||||
timer = setTimeout(
|
||||
() => timer = setInterval(() => { documentStore.fileExplorer.cursorMove(vert) }, tr),
|
||||
() =>
|
||||
(timer = setInterval(() => {
|
||||
documentStore.fileExplorer.cursorMove(vert, select)
|
||||
}, tr)),
|
||||
t0 - tr
|
||||
)
|
||||
}
|
||||
|
@ -46,7 +46,9 @@
|
||||
--header-background: none;
|
||||
--header-color: black;
|
||||
}
|
||||
nav, .menu, .rename-button {
|
||||
nav,
|
||||
.menu,
|
||||
.rename-button {
|
||||
display: none;
|
||||
}
|
||||
.breadcrumb > a {
|
||||
@ -57,8 +59,12 @@
|
||||
clip-path: none !important;
|
||||
max-width: none !important;
|
||||
}
|
||||
.breadcrumb > a::after { content: '/'; }
|
||||
.breadcrumb svg { fill: black !important; }
|
||||
.breadcrumb > a::after {
|
||||
content: '/';
|
||||
}
|
||||
.breadcrumb svg {
|
||||
fill: black !important;
|
||||
}
|
||||
main {
|
||||
height: auto !important;
|
||||
padding-bottom: 0 !important;
|
||||
@ -72,22 +78,27 @@
|
||||
min-width: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.selection input { display: none }
|
||||
.selection input:checked { display: inherit; }
|
||||
.selection input {
|
||||
display: none;
|
||||
}
|
||||
.selection input:checked {
|
||||
display: inherit;
|
||||
}
|
||||
tbody .selection input:checked {
|
||||
opacity: 1 !important;
|
||||
transform: scale(0.5);
|
||||
top: 0.1rem !important;
|
||||
left: -0.3rem !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Hide scrollbar for all browsers */
|
||||
main::-webkit-scrollbar { display: none; }
|
||||
main::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
main {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
body {
|
||||
background-color: var(--primary-background);
|
||||
@ -96,7 +107,8 @@ body {
|
||||
color: var(--primary-color);
|
||||
margin: 0;
|
||||
}
|
||||
tbody .size, tbody .modified {
|
||||
tbody .size,
|
||||
tbody .modified {
|
||||
font-family: 'Roboto Mono';
|
||||
}
|
||||
header {
|
||||
@ -121,6 +133,9 @@ button {
|
||||
min-width: 1rem;
|
||||
min-height: 1rem;
|
||||
}
|
||||
input {
|
||||
margin: 0;
|
||||
}
|
||||
:focus {
|
||||
outline: none;
|
||||
}
|
||||
@ -133,6 +148,7 @@ a:hover {
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
border: 0;
|
||||
gap: 0;
|
||||
}
|
||||
@ -142,7 +158,7 @@ table {
|
||||
flex-direction: column;
|
||||
}
|
||||
main {
|
||||
height: calc(100svh - 9rem); /* fill almost the rest of the screen after header */
|
||||
padding-bottom: 3rem; /* convenience space on the bottom */
|
||||
height: calc(100svh - 9rem); /* fill almost the rest of the screen after header */
|
||||
padding-bottom: 3rem; /* convenience space on the bottom */
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
@ -8,10 +8,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
//import home from '@/assets/svg/home.svg'
|
||||
import { defineProps, defineAsyncComponent } from 'vue'
|
||||
|
||||
const home = defineAsyncComponent(() => import(`@/assets/svg/home.svg`))
|
||||
import home from '@/assets/svg/home.svg'
|
||||
|
||||
const props = defineProps<{
|
||||
path: Array<string>
|
||||
|
@ -48,7 +48,11 @@
|
||||
"
|
||||
/>
|
||||
</td>
|
||||
<td class="modified right">{{ editing.modified }}</td>
|
||||
<td class="modified right">
|
||||
<time :datetime="new Date(editing.mtime).toISOString().replace('.000', '')">{{
|
||||
editing.modified
|
||||
}}</time>
|
||||
</td>
|
||||
<td class="size right">{{ editing.sizedisp }}</td>
|
||||
<td class="menu"></td>
|
||||
</tr>
|
||||
@ -92,17 +96,30 @@
|
||||
:href="url_for(doc)"
|
||||
tabindex="-1"
|
||||
@contextmenu.stop
|
||||
@click.stop
|
||||
@focus.stop="cursor = doc"
|
||||
>{{ doc.name }}</a
|
||||
>
|
||||
<button v-if="cursor == doc" class="rename-button" @click="() => (editing = doc)">🖊️</button>
|
||||
<button
|
||||
v-if="cursor == doc"
|
||||
class="rename-button"
|
||||
@click="() => (editing = doc)"
|
||||
>
|
||||
🖊️
|
||||
</button>
|
||||
</template>
|
||||
</td>
|
||||
<td class="modified right">{{ doc.modified }}</td>
|
||||
<td class="modified right">
|
||||
<time
|
||||
:datetime="new Date(1000 * doc.mtime).toISOString().replace('.000', '')"
|
||||
>{{ doc.modified }}</time
|
||||
>
|
||||
</td>
|
||||
<td class="size right">{{ doc.sizedisp }}</td>
|
||||
<td class="menu">
|
||||
<button tabindex="-1" @click.stop="cursor = doc; contextMenu($event, doc)">
|
||||
<button
|
||||
tabindex="-1"
|
||||
@click.stop="contextMenu($event, doc)"
|
||||
>
|
||||
⋮
|
||||
</button>
|
||||
</td>
|
||||
@ -190,7 +207,6 @@ defineExpose({
|
||||
editing.value = cursor.value
|
||||
},
|
||||
cursorSelect() {
|
||||
console.log('select', documentStore.selected)
|
||||
const doc = cursor.value
|
||||
if (!doc) return
|
||||
if (documentStore.selected.has(doc.key)) {
|
||||
@ -200,19 +216,31 @@ defineExpose({
|
||||
}
|
||||
this.cursorMove(1)
|
||||
},
|
||||
cursorMove(d: number) {
|
||||
cursorMove(d: number, select = false) {
|
||||
// Move cursor up or down (keyboard navigation)
|
||||
const documents = sorted(props.documents as FolderDocument[])
|
||||
if (documents.length === 0) {
|
||||
cursor.value = null
|
||||
return
|
||||
}
|
||||
const N = documents.length
|
||||
const mod = (a: number, b: number) => ((a % b) + b) % b
|
||||
const index = cursor.value !== null ? documents.indexOf(cursor.value) : -1
|
||||
cursor.value = documents[mod(index + d, documents.length + 1)] ?? null
|
||||
const tr = document.getElementById(
|
||||
`file-${cursor.value.key}`
|
||||
) as HTMLTableRowElement | null
|
||||
const increment = (i: number, d: number) => mod(i + d, N + 1)
|
||||
const index =
|
||||
cursor.value !== null ? documents.indexOf(cursor.value) : documents.length
|
||||
const moveto = increment(index, d)
|
||||
cursor.value = documents[moveto] ?? null
|
||||
const tr = cursor.value ? document.getElementById(`file-${cursor.value.key}`) : null
|
||||
if (select) {
|
||||
// Go forwards, possibly wrapping over the end; the last entry is not toggled
|
||||
let [begin, end] = d > 0 ? [index, moveto] : [moveto, index]
|
||||
for (let p = begin; p !== end; p = increment(p, 1)) {
|
||||
if (p === N) continue
|
||||
const key = documents[p].key
|
||||
if (documentStore.selected.has(key)) documentStore.selected.delete(key)
|
||||
else documentStore.selected.add(key)
|
||||
}
|
||||
}
|
||||
// @ts-ignore
|
||||
scrolltr = tr
|
||||
if (!scrolltimer) {
|
||||
@ -307,6 +335,7 @@ watchEffect(() => {
|
||||
if (editing.value) cursor.value = editing.value
|
||||
})
|
||||
const contextMenu = (ev: Event, doc: Document) => {
|
||||
cursor.value = doc
|
||||
console.log('Context menu', ev, doc)
|
||||
}
|
||||
</script>
|
||||
@ -336,9 +365,12 @@ table tbody input[type='checkbox'] {
|
||||
}
|
||||
table .selection {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
text-align: center;
|
||||
text-overflow: clip;
|
||||
}
|
||||
table .modified {
|
||||
width: 10rem;
|
||||
width: 8rem;
|
||||
}
|
||||
table .size {
|
||||
width: 4rem;
|
||||
@ -363,12 +395,21 @@ table td {
|
||||
position: relative;
|
||||
}
|
||||
.name .rename-button {
|
||||
padding-left: 1rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
animation: appear calc(5 * var(--transition-time)) linear;
|
||||
}
|
||||
@keyframes appear { from { opacity: 0 } 80% { opacity: 0 } to { opacity: 1 } }
|
||||
@keyframes appear {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
80% {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
thead tr {
|
||||
background: linear-gradient(to bottom, #eee, #fff 30%, #ddd);
|
||||
color: #000;
|
||||
@ -379,9 +420,6 @@ tbody tr.cursor {
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
.selection {
|
||||
width: 2em;
|
||||
}
|
||||
.sortcolumn:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -405,18 +443,19 @@ tbody tr.cursor {
|
||||
.name a {
|
||||
text-decoration: none;
|
||||
}
|
||||
tr {
|
||||
height: 2.5rem;
|
||||
}
|
||||
tbody .selection input {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
left: 0;
|
||||
left: 0.5rem;
|
||||
top: 0;
|
||||
}
|
||||
.selection {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
}
|
||||
.selection input:checked {
|
||||
opacity: .7;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.file .selection::before {
|
||||
content: '📄 ';
|
||||
|
@ -3,6 +3,7 @@
|
||||
ref="input"
|
||||
id="FileRenameInput"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
v-model="name"
|
||||
@blur="exit"
|
||||
@keyup.esc="exit"
|
||||
@ -34,7 +35,11 @@ const props = defineProps<{
|
||||
|
||||
const apply = () => {
|
||||
props.exit()
|
||||
if (props.doc.key !== 'new' && (name.value === props.doc.name || name.value.length === 0)) return
|
||||
if (
|
||||
props.doc.key !== 'new' &&
|
||||
(name.value === props.doc.name || name.value.length === 0)
|
||||
)
|
||||
return
|
||||
props.rename(props.doc, name.value)
|
||||
}
|
||||
</script>
|
||||
@ -44,9 +49,9 @@ input#FileRenameInput {
|
||||
color: var(--primary-color);
|
||||
background: var(--primary-background);
|
||||
border: 0;
|
||||
border-radius: .3rem;
|
||||
padding: .4rem;
|
||||
margin: -.4rem;
|
||||
border-radius: 0.3rem;
|
||||
padding: 0.4rem;
|
||||
margin: -0.4rem;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
font: inherit;
|
||||
|
@ -46,7 +46,7 @@ defineExpose({
|
||||
/>
|
||||
</template>
|
||||
<SvgButton ref="searchButton" name="find" @click="toggleSearchInput" />
|
||||
<SvgButton name="cog" @click="console.log('TODO open settings')" />
|
||||
<SvgButton name="cog" @click="console.log('settings menu')" />
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
@ -8,12 +8,12 @@ import router from './router'
|
||||
|
||||
import piniaPluginPersistedState from 'pinia-plugin-persistedstate'
|
||||
|
||||
|
||||
const app = createApp(App)
|
||||
app.config.errorHandler = err => {
|
||||
/* handle error */
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
const pinia = createPinia()
|
||||
pinia.use(piniaPluginPersistedState)
|
||||
app.use(pinia)
|
||||
|
@ -30,13 +30,13 @@ export type errorEvent = {
|
||||
// Raw types the backend /api/watch sends us
|
||||
|
||||
export type FileEntry = {
|
||||
id: FUID
|
||||
key: FUID
|
||||
size: number
|
||||
mtime: number
|
||||
}
|
||||
|
||||
export type DirEntry = {
|
||||
id: FUID
|
||||
key: FUID
|
||||
size: number
|
||||
mtime: number
|
||||
dir: DirList
|
||||
@ -47,7 +47,7 @@ export type DirList = Record<string, FileEntry | DirEntry>
|
||||
export type UpdateEntry = {
|
||||
name: string
|
||||
deleted?: boolean
|
||||
id?: FUID
|
||||
key?: FUID
|
||||
size?: number
|
||||
mtime?: number
|
||||
dir?: DirList
|
||||
@ -99,7 +99,7 @@ export class DocumentHandler {
|
||||
this.handleUpdateMessage(msg)
|
||||
break
|
||||
case !!msg.space:
|
||||
console.log("Watch space", msg.space)
|
||||
console.log('Watch space', msg.space)
|
||||
break
|
||||
case !!msg.error:
|
||||
this.handleError(msg)
|
||||
@ -109,14 +109,14 @@ export class DocumentHandler {
|
||||
}
|
||||
|
||||
private handleRootMessage({ root }: { root: DirEntry }) {
|
||||
console.log("Watch root", root)
|
||||
console.log('Watch root', root)
|
||||
if (this.store && this.store.root) {
|
||||
this.store.user.isLoggedIn = true
|
||||
this.store.root = root
|
||||
}
|
||||
}
|
||||
private handleUpdateMessage(updateData: { update: UpdateEntry[] }) {
|
||||
console.log("Watch update", updateData.update)
|
||||
console.log('Watch update', updateData.update)
|
||||
let node: DirEntry = this.store.root
|
||||
for (const elem of updateData.update) {
|
||||
if (elem.deleted) {
|
||||
@ -127,7 +127,7 @@ export class DocumentHandler {
|
||||
// @ts-ignore
|
||||
node = node.dir[elem.name] ||= {}
|
||||
}
|
||||
if (elem.id !== undefined) node.id = elem.id
|
||||
if (elem.key !== undefined) node.key = elem.key
|
||||
if (elem.size !== undefined) node.size = elem.size
|
||||
if (elem.mtime !== undefined) node.mtime = elem.mtime
|
||||
if (elem.dir !== undefined) node.dir = elem.dir
|
||||
|
@ -60,10 +60,10 @@ export const useDocumentStore = defineStore({
|
||||
// Transform data
|
||||
const dataMapped = []
|
||||
for (const [name, attr] of Object.entries(matched)) {
|
||||
const { id, size, mtime } = attr
|
||||
const { key, size, mtime } = attr
|
||||
const element: Document = {
|
||||
name,
|
||||
key: id,
|
||||
key,
|
||||
size,
|
||||
sizedisp: formatSize(size),
|
||||
mtime,
|
||||
@ -182,21 +182,22 @@ export const useDocumentStore = defineStore({
|
||||
if (!('dir' in data)) return
|
||||
for (const [name, attr] of Object.entries(data.dir)) {
|
||||
const fullname = path ? `${path}/${name}` : name
|
||||
const key = attr.key
|
||||
// Is this the file we are looking for? Ignore if nested within another selection.
|
||||
let r = relpath
|
||||
if (selected.has(attr.id) && !relpath) {
|
||||
ret.selected.add(attr.id)
|
||||
if (selected.has(key) && !relpath) {
|
||||
ret.selected.add(key)
|
||||
ret.rootdir[name] = attr
|
||||
r = name
|
||||
} else if (relpath) {
|
||||
r = `${relpath}/${name}`
|
||||
}
|
||||
if (r) {
|
||||
ret.entries[attr.id] = attr
|
||||
ret.fullpath[attr.id] = fullname
|
||||
ret.relpath[attr.id] = r
|
||||
ret.ids.push(attr.id)
|
||||
if (!('dir' in attr)) ret.url[attr.id] = `/files/${fullname}`
|
||||
ret.entries[key] = attr
|
||||
ret.fullpath[key] = fullname
|
||||
ret.relpath[key] = r
|
||||
ret.ids.push(key)
|
||||
if (!('dir' in attr)) ret.url[key] = `/files/${fullname}`
|
||||
}
|
||||
traverseDir(attr, fullname, r)
|
||||
}
|
||||
|
@ -27,28 +27,31 @@ export function formatUnixDate(t: number) {
|
||||
return 'now'
|
||||
}
|
||||
if (Math.abs(diff) <= 60000) {
|
||||
return formatter.format(Math.round(diff / 1000), 'second')
|
||||
return formatter.format(Math.round(diff / 1000), 'second').replace(' ago', '').replaceAll(' ', '\u202F')
|
||||
}
|
||||
|
||||
if (Math.abs(diff) <= 3600000) {
|
||||
return formatter.format(Math.round(diff / 60000), 'minute')
|
||||
return formatter.format(Math.round(diff / 60000), 'minute').replace('utes', '').replace('ute', '').replaceAll(' ', '\u202F')
|
||||
}
|
||||
|
||||
if (Math.abs(diff) <= 86400000) {
|
||||
return formatter.format(Math.round(diff / 3600000), 'hour')
|
||||
return formatter.format(Math.round(diff / 3600000), 'hour').replaceAll(' ', '\u202F')
|
||||
}
|
||||
|
||||
if (Math.abs(diff) <= 604800000) {
|
||||
return formatter.format(Math.round(diff / 86400000), 'day')
|
||||
return formatter.format(Math.round(diff / 86400000), 'day').replaceAll(' ', '\u202F')
|
||||
}
|
||||
|
||||
const d = date.toLocaleDateString("us", {
|
||||
let d = date.toLocaleDateString('en-ie', {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
}).replace("Sept", "Sep")
|
||||
return (d.length === 16 ? d : d.replace(', ', ',\u202F\u2007')).replaceAll(' ', '\u202F')
|
||||
if (d.length === 14) d = d.replace(' ', ' \u2007') // dom < 10 alignment (add figure space)
|
||||
d = d.replaceAll(' ', '\u202F').replace('\u202F', '\u00A0') // nobr spaces, thin w/ date but not weekday
|
||||
d = d.slice(0, -4) + d.slice(-2) // Two digit year is enough
|
||||
return d
|
||||
}
|
||||
|
||||
export function getFileExtension(filename: string) {
|
||||
|
@ -110,13 +110,13 @@ class ErrorMsg(msgspec.Struct):
|
||||
|
||||
|
||||
class FileEntry(msgspec.Struct):
|
||||
id: str
|
||||
key: str
|
||||
size: int
|
||||
mtime: int
|
||||
|
||||
|
||||
class DirEntry(msgspec.Struct):
|
||||
id: str
|
||||
key: str
|
||||
size: int
|
||||
mtime: int
|
||||
dir: DirList
|
||||
@ -146,7 +146,7 @@ class UpdateEntry(msgspec.Struct, omit_defaults=True):
|
||||
|
||||
name: str = ""
|
||||
deleted: bool = False
|
||||
id: str | None = None
|
||||
key: str | None = None
|
||||
size: int | None = None
|
||||
mtime: int | None = None
|
||||
dir: DirList | None = None
|
||||
|
@ -90,7 +90,9 @@ def format_tree():
|
||||
return msgspec.json.encode(
|
||||
{
|
||||
"update": [
|
||||
UpdateEntry(id=root.id, size=root.size, mtime=root.mtime, dir=root.dir),
|
||||
UpdateEntry(
|
||||
key=root.key, size=root.size, mtime=root.mtime, dir=root.dir
|
||||
),
|
||||
],
|
||||
},
|
||||
).decode()
|
||||
@ -99,10 +101,11 @@ def format_tree():
|
||||
def walk(path: Path) -> DirEntry | FileEntry | None:
|
||||
try:
|
||||
s = path.stat()
|
||||
id_ = fuid(s)
|
||||
key = fuid(s)
|
||||
assert key, repr(key)
|
||||
mtime = int(s.st_mtime)
|
||||
if path.is_file():
|
||||
return FileEntry(id_, s.st_size, mtime)
|
||||
return FileEntry(key, s.st_size, mtime)
|
||||
|
||||
tree = {
|
||||
p.name: v
|
||||
@ -115,7 +118,7 @@ def walk(path: Path) -> DirEntry | FileEntry | None:
|
||||
mtime = max(mtime, *(v.mtime for v in tree.values()))
|
||||
else:
|
||||
size = 0
|
||||
return DirEntry(id_, size, mtime, tree)
|
||||
return DirEntry(key, size, mtime, tree)
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
except OSError as e:
|
||||
|
Loading…
x
Reference in New Issue
Block a user