Compare commits
No commits in common. "main" and "v30" have entirely different histories.
7 changed files with 32 additions and 196 deletions
18
main.go
18
main.go
|
|
@ -27,7 +27,7 @@ import (
|
||||||
|
|
||||||
const VERSION = "v30"
|
const VERSION = "v30"
|
||||||
const CONTEXT_USER = 1
|
const CONTEXT_USER = 1
|
||||||
const SYNC_PAGINATION = 1
|
const SYNC_PAGINATION = 200
|
||||||
|
|
||||||
var (
|
var (
|
||||||
FlagGenerate bool
|
FlagGenerate bool
|
||||||
|
|
@ -36,7 +36,6 @@ var (
|
||||||
FlagConfig string
|
FlagConfig string
|
||||||
FlagCreateUser string
|
FlagCreateUser string
|
||||||
FlagChangePassword string
|
FlagChangePassword string
|
||||||
FlagVersion bool
|
|
||||||
Webengine HTMLTemplate.Engine
|
Webengine HTMLTemplate.Engine
|
||||||
config Config
|
config Config
|
||||||
Log *slog.Logger
|
Log *slog.Logger
|
||||||
|
|
@ -67,7 +66,6 @@ func init() { // {{{
|
||||||
flag.BoolVar(&FlagLoremIpsum, "lorem-ipsum", false, "Replace all G- nodes with lorem ipsum paragraphs")
|
flag.BoolVar(&FlagLoremIpsum, "lorem-ipsum", false, "Replace all G- nodes with lorem ipsum paragraphs")
|
||||||
flag.StringVar(&FlagCreateUser, "create-user", "", "Username for creating a new user")
|
flag.StringVar(&FlagCreateUser, "create-user", "", "Username for creating a new user")
|
||||||
flag.StringVar(&FlagChangePassword, "change-password", "", "Change the password for the given username")
|
flag.StringVar(&FlagChangePassword, "change-password", "", "Change the password for the given username")
|
||||||
flag.BoolVar(&FlagVersion, "version", false, "Print version and exit")
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
RxpBearerToken = regexp.MustCompile("(?i)^\\s*Bearer\\s+(.*?)\\s*$")
|
RxpBearerToken = regexp.MustCompile("(?i)^\\s*Bearer\\s+(.*?)\\s*$")
|
||||||
|
|
@ -78,14 +76,7 @@ func initLog() { // {{{
|
||||||
Log = slog.New(slog.NewJSONHandler(os.Stdout, &opts))
|
Log = slog.New(slog.NewJSONHandler(os.Stdout, &opts))
|
||||||
} // }}}
|
} // }}}
|
||||||
func main() { // {{{
|
func main() { // {{{
|
||||||
if FlagVersion {
|
|
||||||
fmt.Println(VERSION)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
initLog()
|
initLog()
|
||||||
Log.Info("application", "version", VERSION)
|
|
||||||
|
|
||||||
err := readConfig(FlagConfig)
|
err := readConfig(FlagConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Log.Error("config", "error", err)
|
Log.Error("config", "error", err)
|
||||||
|
|
@ -287,9 +278,6 @@ func pageSync(w http.ResponseWriter, r *http.Request) { // {{{
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
func actionSyncFromServer(w http.ResponseWriter, r *http.Request) { // {{{
|
func actionSyncFromServer(w http.ResponseWriter, r *http.Request) { // {{{
|
||||||
// XXX - DELETE ME!
|
|
||||||
time.Sleep(time.Millisecond * 200)
|
|
||||||
|
|
||||||
// The purpose of the Client UUID is to avoid
|
// The purpose of the Client UUID is to avoid
|
||||||
// sending nodes back once again to a client that
|
// sending nodes back once again to a client that
|
||||||
// just created or modified it.
|
// just created or modified it.
|
||||||
|
|
@ -554,10 +542,6 @@ func actionFile(w http.ResponseWriter, r *http.Request) { // {{{
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Add("X-File-Name", md.Name)
|
|
||||||
w.Header().Add("X-File-Modified", fmt.Sprintf("%d", md.Modified.UnixMilli()))
|
|
||||||
w.Header().Add("Content-Type", md.Type)
|
|
||||||
|
|
||||||
http.ServeContent(w, r, md.Name, md.Modified, f)
|
http.ServeContent(w, r, md.Name, md.Modified, f)
|
||||||
}// }}}
|
}// }}}
|
||||||
func actionFileMetadata(w http.ResponseWriter, r *http.Request) {// {{{
|
func actionFileMetadata(w http.ResponseWriter, r *http.Request) {// {{{
|
||||||
|
|
|
||||||
1
node.go
1
node.go
|
|
@ -129,7 +129,6 @@ func NodesCount(userID int, synced uint64, clientUUID string) (count int, err er
|
||||||
FROM
|
FROM
|
||||||
public.node
|
public.node
|
||||||
WHERE
|
WHERE
|
||||||
NOT special AND
|
|
||||||
user_id = $1 AND
|
user_id = $1 AND
|
||||||
client != $3 AND
|
client != $3 AND
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -178,34 +178,6 @@ button {
|
||||||
border-right: 1px solid var(--line-color);
|
border-right: 1px solid var(--line-color);
|
||||||
|
|
||||||
n2-sidebar {
|
n2-sidebar {
|
||||||
display: grid;
|
|
||||||
grid-template-rows: min-content min-content min-content 1fr;
|
|
||||||
|
|
||||||
.el-sync-status {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: min-content 1fr;
|
|
||||||
grid-gap: 0px 8px;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px 16px 8px 16px;
|
|
||||||
border-bottom: 1px solid var(--line-color);
|
|
||||||
|
|
||||||
.el-sync-icon {
|
|
||||||
grid-column: 1;
|
|
||||||
grid-row: 1 / 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-sync-label {
|
|
||||||
grid-column: 2;
|
|
||||||
grid-row: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
progress {
|
|
||||||
grid-column: 2;
|
|
||||||
grid-row: 2;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-treenodes {
|
.el-treenodes {
|
||||||
margin: 24px 32px 32px 32px;
|
margin: 24px 32px 32px 32px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -566,32 +566,21 @@ export class NodeStore {
|
||||||
})
|
})
|
||||||
}//}}}
|
}//}}}
|
||||||
|
|
||||||
async storeFile(file, reader, progressCallback) {// {{{
|
async storeFile(file) {// {{{
|
||||||
const metadata = new FileMetadata(file)
|
const metadata = new FileMetadata(file)
|
||||||
let processedBytes = 0
|
|
||||||
let stream
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Do potentially larger blocks.
|
// Do potentially larger blocks.
|
||||||
const BLOCKSIZE = 1048576
|
const BLOCKSIZE = 1048576
|
||||||
|
const stream = file.stream()
|
||||||
|
const reader = stream.getReader({ mode: 'byob' })
|
||||||
let buffer = new Uint8Array(BLOCKSIZE)
|
let buffer = new Uint8Array(BLOCKSIZE)
|
||||||
|
|
||||||
// Files from server got through fetch() will bring its own
|
|
||||||
// reader without going through a stream.
|
|
||||||
if (!reader) {
|
|
||||||
stream = file.stream()
|
|
||||||
reader = stream.getReader({ mode: 'byob' })
|
|
||||||
}
|
|
||||||
|
|
||||||
let seq = 0
|
let seq = 0
|
||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await reader.read(buffer)
|
const { done, value } = await reader.read(buffer)
|
||||||
if (done) break
|
if (done) break
|
||||||
|
|
||||||
processedBytes += value.length
|
|
||||||
if (progressCallback)
|
|
||||||
progressCallback(processedBytes)
|
|
||||||
|
|
||||||
// Block is added as a file segment.
|
// Block is added as a file segment.
|
||||||
await this.fileSegments.add({
|
await this.fileSegments.add({
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -906,6 +895,7 @@ class FileNodesLinkStore extends SimpleNodeStore {
|
||||||
|
|
||||||
class FileMetadata {
|
class FileMetadata {
|
||||||
constructor(data) {// {{{
|
constructor(data) {// {{{
|
||||||
|
console.log('md', data)
|
||||||
this.UUID = data.UUID || uuidv7()
|
this.UUID = data.UUID || uuidv7()
|
||||||
this.uploaded = data.uploaded || 'only_on_device'
|
this.uploaded = data.uploaded || 'only_on_device'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,16 +20,9 @@ export class N2PageStorage extends CustomHTMLElement {
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin-top: 24px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<h1>Local storage</h1>
|
<h1>Local storage</h1>
|
||||||
|
|
||||||
<h2>Nodes</h2>
|
|
||||||
<div class="table">
|
<div class="table">
|
||||||
<div>Local nodes</div>
|
<div>Local nodes</div>
|
||||||
<div data-el="count-nodes"></div>
|
<div data-el="count-nodes"></div>
|
||||||
|
|
@ -41,14 +34,11 @@ export class N2PageStorage extends CustomHTMLElement {
|
||||||
<div data-el="count-history-nodes"></div>
|
<div data-el="count-history-nodes"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>Files</h2>
|
<h1>Files</h1>
|
||||||
<div class="table">
|
<div class="table">
|
||||||
<div>Files on server</div>
|
<div>Files on server</div>
|
||||||
<div data-el="count-files-on-server"></div>
|
<div data-el="count-files-on-server"></div>
|
||||||
|
|
||||||
<div>Files on device</div>
|
|
||||||
<div data-el="count-files-on-device"></div>
|
|
||||||
|
|
||||||
<div>Files to send</div>
|
<div>Files to send</div>
|
||||||
<div data-el="count-queued-files"></div>
|
<div data-el="count-queued-files"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -78,8 +68,6 @@ export class N2PageStorage extends CustomHTMLElement {
|
||||||
this.elCountQueuedNodes.innerText = await globalThis.nodeStore.sendQueue.count()
|
this.elCountQueuedNodes.innerText = await globalThis.nodeStore.sendQueue.count()
|
||||||
this.elCountHistoryNodes.innerText = await globalThis.nodeStore.nodesHistory.count()
|
this.elCountHistoryNodes.innerText = await globalThis.nodeStore.nodesHistory.count()
|
||||||
this.elCountQueuedFiles.innerText = await globalThis.nodeStore.filesMetadata.countToUpload()
|
this.elCountQueuedFiles.innerText = await globalThis.nodeStore.filesMetadata.countToUpload()
|
||||||
|
|
||||||
this.elCountFilesOnDevice.innerText = await globalThis.nodeStore.filesMetadata.count()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
customElements.define('n2-pagestorage', N2PageStorage)
|
customElements.define('n2-pagestorage', N2PageStorage)
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ export class N2Sidebar extends CustomHTMLElement {
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
gap: 8px 16px;
|
gap: 8px 16px;
|
||||||
padding-left: 16px;
|
padding-left: 40px;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
border-bottom: 1px solid var(--line-color);
|
border-bottom: 1px solid var(--line-color);
|
||||||
|
|
||||||
|
|
@ -112,11 +112,6 @@ export class N2Sidebar extends CustomHTMLElement {
|
||||||
<img data-el="search" class='search colorize' src="/images/${_VERSION}/icon_search.svg" style="height: 22px" />
|
<img data-el="search" class='search colorize' src="/images/${_VERSION}/icon_search.svg" style="height: 22px" />
|
||||||
<img data-el="settings" class='settings colorize' src="/images/${_VERSION}/icon_settings.svg" />
|
<img data-el="settings" class='settings colorize' src="/images/${_VERSION}/icon_settings.svg" />
|
||||||
</div>
|
</div>
|
||||||
<div data-el="sync-status">
|
|
||||||
<img data-el="sync-icon" class="colorize">
|
|
||||||
<div data-el="sync-label"></div>
|
|
||||||
<progress data-el="sync-progress" min="0" max="100" value="0"></progress>
|
|
||||||
</div>
|
|
||||||
<div data-el="treenodes"></div>
|
<div data-el="treenodes"></div>
|
||||||
`
|
`
|
||||||
}// }}}
|
}// }}}
|
||||||
|
|
@ -131,8 +126,6 @@ export class N2Sidebar extends CustomHTMLElement {
|
||||||
this.expandedNodes = {} // keyed on UUID
|
this.expandedNodes = {} // keyed on UUID
|
||||||
this.selectedNode = null
|
this.selectedNode = null
|
||||||
this.rendered = false
|
this.rendered = false
|
||||||
this.nodesDownloadCount = 0
|
|
||||||
this.nodesUploadCount = 0
|
|
||||||
|
|
||||||
new TreeExpansionHandler()
|
new TreeExpansionHandler()
|
||||||
|
|
||||||
|
|
@ -158,46 +151,6 @@ export class N2Sidebar extends CustomHTMLElement {
|
||||||
treenode.render(true)
|
treenode.render(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
// SYNC_DOWNLOAD_COUNT and SYNC_UPLOAD_COUNT will be sent first before progress messages comes for either.
|
|
||||||
_mbus.subscribe('SYNC_DOWNLOAD_COUNT', event => this.nodesDownloadCount = event.detail.data.count)
|
|
||||||
_mbus.subscribe('SYNC_UPLOAD_COUNT', event => this.nodesUploadCount = event.detail.data.count)
|
|
||||||
_mbus.subscribe('SYNC_DOWNLOADED', event => {
|
|
||||||
this.elSyncIcon.classList.add('colorize')
|
|
||||||
this.elSyncIcon.setAttribute('src', `/images/${_VERSION}/icon_node_download.svg`)
|
|
||||||
this.elSyncProgress.setAttribute('max', this.nodesDownloadCount)
|
|
||||||
this.elSyncProgress.value = event.detail.data.handled
|
|
||||||
})
|
|
||||||
_mbus.subscribe('SYNC_UPLOADED', event => {
|
|
||||||
this.elSyncIcon.classList.add('colorize')
|
|
||||||
this.elSyncIcon.setAttribute('src', `/images/${_VERSION}/icon_node_upload.svg`)
|
|
||||||
this.elSyncProgress.setAttribute('max', this.nodesUploadCount)
|
|
||||||
this.elSyncProgress.value = event.detail.data.count
|
|
||||||
})
|
|
||||||
_mbus.subscribe('SYNC_DONE', () => {
|
|
||||||
this.elSyncIcon.classList.remove('colorize')
|
|
||||||
this.elSyncIcon.setAttribute('src', `/images/${_VERSION}/icon_sync_done.svg`)
|
|
||||||
})
|
|
||||||
|
|
||||||
_mbus.subscribe('FILE_SYNC', event => {
|
|
||||||
const { direction, name } = event.detail.data
|
|
||||||
this.elSyncIcon.setAttribute('src', `/images/${_VERSION}/icon_file_${direction}.svg`)
|
|
||||||
this.elSyncLabel.innerText = name
|
|
||||||
})
|
|
||||||
|
|
||||||
_mbus.subscribe('FILE_DOWNLOAD_PROGRESS', event => {
|
|
||||||
const { uuid, size, downloaded, percent, done } = event.detail.data
|
|
||||||
console.log(percent)
|
|
||||||
|
|
||||||
if (percent) {
|
|
||||||
this.elSyncProgress.value = percent
|
|
||||||
}
|
|
||||||
|
|
||||||
if (done) {
|
|
||||||
this.elSyncLabel.innerText = ''
|
|
||||||
this.elSyncProgress.value = 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/* XXX - set color */
|
/* XXX - set color */
|
||||||
let color = new Color(0x80, 0x00, 0x33)
|
let color = new Color(0x80, 0x00, 0x33)
|
||||||
let solver = new Solver(color)
|
let solver = new Solver(color)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ export class Sync {
|
||||||
|
|
||||||
async run() {//{{{
|
async run() {//{{{
|
||||||
try {
|
try {
|
||||||
|
let duration = 0 // in ms
|
||||||
|
|
||||||
// The latest sync node value is used to retrieve the changes
|
// The latest sync node value is used to retrieve the changes
|
||||||
// from the backend.
|
// from the backend.
|
||||||
const state = await nodeStore.getAppState('latest_sync_node')
|
const state = await nodeStore.getAppState('latest_sync_node')
|
||||||
|
|
@ -22,12 +24,11 @@ export class Sync {
|
||||||
_mbus.dispatch('SYNC_DOWNLOAD_COUNT', { count: nodeCountDownload })
|
_mbus.dispatch('SYNC_DOWNLOAD_COUNT', { count: nodeCountDownload })
|
||||||
_mbus.dispatch('SYNC_UPLOAD_COUNT', { count: nodeCountUpload })
|
_mbus.dispatch('SYNC_UPLOAD_COUNT', { count: nodeCountUpload })
|
||||||
|
|
||||||
const durationNodes = await this.nodesFromServer(oldMax)
|
await this.nodesFromServer(oldMax)
|
||||||
|
.then(durationNodes => {
|
||||||
|
duration = durationNodes // in ms
|
||||||
console.log(`Total time: ${Math.round(1000 * durationNodes) / 1000}s`)
|
console.log(`Total time: ${Math.round(1000 * durationNodes) / 1000}s`)
|
||||||
|
})
|
||||||
// Files are uploaded first since nodes can have references to them
|
|
||||||
// and the server requires linking a reference to the node and the file table.
|
|
||||||
await this.filesToServer()
|
|
||||||
|
|
||||||
// Uploads of modified nodes to server.
|
// Uploads of modified nodes to server.
|
||||||
await this.nodesToServer()
|
await this.nodesToServer()
|
||||||
|
|
@ -79,8 +80,7 @@ export class Sync {
|
||||||
await this.handleNode(backendNode, objstore)
|
await this.handleNode(backendNode, objstore)
|
||||||
|
|
||||||
handled++
|
handled++
|
||||||
// XXX - should this be back for performance or not?
|
if (handled % 100 === 0)
|
||||||
// if (handled % 100 === 0)
|
|
||||||
_mbus.dispatch('SYNC_DOWNLOADED', { handled })
|
_mbus.dispatch('SYNC_DOWNLOADED', { handled })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,6 +195,7 @@ export class Sync {
|
||||||
alert(e.message)
|
alert(e.message)
|
||||||
}
|
}
|
||||||
}// }}}
|
}// }}}
|
||||||
|
|
||||||
async filesFromServer() {// {{{
|
async filesFromServer() {// {{{
|
||||||
let offset = 0
|
let offset = 0
|
||||||
let more = true
|
let more = true
|
||||||
|
|
@ -203,64 +204,13 @@ export class Sync {
|
||||||
while (more) {
|
while (more) {
|
||||||
const res = await API.query('GET', `/file/server_uuids/${offset}`)
|
const res = await API.query('GET', `/file/server_uuids/${offset}`)
|
||||||
more = res.MoreRowsExist
|
more = res.MoreRowsExist
|
||||||
offset++
|
console.log(res)
|
||||||
|
|
||||||
for (const uuid of res.UUIDs) {
|
|
||||||
const md = await globalThis.nodeStore.filesMetadata.get(uuid)
|
|
||||||
if (!md) {
|
|
||||||
await this.storeFileFromServer(uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
alert(e.message)
|
alert(e.message)
|
||||||
}
|
}
|
||||||
}// }}}
|
}// }}}
|
||||||
async storeFileFromServer(uuid) {// {{{
|
|
||||||
const headers = {}
|
|
||||||
const token = localStorage.getItem('token')
|
|
||||||
if (token) {
|
|
||||||
headers.Authorization = `Bearer ${token}`
|
|
||||||
}
|
|
||||||
const res = await fetch(`/file/uuid/${uuid}`, { headers })
|
|
||||||
|
|
||||||
const size = parseInt(res.headers.get('content-length'))
|
|
||||||
const fileData = {
|
|
||||||
UUID: uuid,
|
|
||||||
uploaded: 'on_server',
|
|
||||||
name: res.headers.get('x-file-name'),
|
|
||||||
size: res.headers.get('content-length'),
|
|
||||||
type: res.headers.get('content-type'),
|
|
||||||
modified: res.headers.get('x-file-modified'),
|
|
||||||
}
|
|
||||||
|
|
||||||
_mbus.dispatch('FILE_SYNC', {
|
|
||||||
direction: 'download',
|
|
||||||
name: fileData.name,
|
|
||||||
})
|
|
||||||
|
|
||||||
let prevPercent = 0
|
|
||||||
await globalThis.nodeStore.storeFile(fileData, res.body.getReader({ mode: 'byob' }), (downloaded) => {
|
|
||||||
|
|
||||||
const percent = Math.round((downloaded / size) * 100)
|
|
||||||
if (prevPercent != percent) {
|
|
||||||
_mbus.dispatch('FILE_DOWNLOAD_PROGRESS', {
|
|
||||||
uuid,
|
|
||||||
downloaded,
|
|
||||||
size,
|
|
||||||
percent,
|
|
||||||
done: false,
|
|
||||||
})
|
|
||||||
prevPercent = percent
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
_mbus.dispatch('FILE_DOWNLOAD_PROGRESS', {
|
|
||||||
uuid,
|
|
||||||
done: true,
|
|
||||||
})
|
|
||||||
}// }}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class N2SyncProgress extends CustomHTMLElement {
|
export class N2SyncProgress extends CustomHTMLElement {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue