mirror of
https://github.com/balzack/databag.git
synced 2025-02-12 03:29:16 +00:00
syncing channels to store
This commit is contained in:
parent
33966512f1
commit
1abe9d197b
@ -9,27 +9,33 @@ import { StoreContextProvider } from 'context/StoreContext';
|
||||
import { AppContextProvider } from 'context/AppContext';
|
||||
import { AccountContextProvider } from 'context/AccountContext';
|
||||
import { ProfileContextProvider } from 'context/ProfileContext';
|
||||
import { CardContextProvider } from 'context/CardContext';
|
||||
import { ChannelContextProvider } from 'context/ChannelContext';
|
||||
|
||||
export default function App() {
|
||||
|
||||
return (
|
||||
<StoreContextProvider>
|
||||
<AccountContextProvider>
|
||||
<ProfileContextProvider>
|
||||
<AppContextProvider>
|
||||
<NativeRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={ <Root /> } />
|
||||
<Route path="/admin" element={ <Admin /> } />
|
||||
<Route path="/login" element={ <Access mode="login" /> } />
|
||||
<Route path="/reset" element={ <Access mode="reset" /> } />
|
||||
<Route path="/create" element={ <Access mode="create" /> } />
|
||||
<Route path="/session" element={ <Session/> } />
|
||||
</Routes>
|
||||
</NativeRouter>
|
||||
</AppContextProvider>
|
||||
</ProfileContextProvider>
|
||||
</AccountContextProvider>
|
||||
<CardContextProvider>
|
||||
<ChannelContextProvider>
|
||||
<AccountContextProvider>
|
||||
<ProfileContextProvider>
|
||||
<AppContextProvider>
|
||||
<NativeRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={ <Root /> } />
|
||||
<Route path="/admin" element={ <Admin /> } />
|
||||
<Route path="/login" element={ <Access mode="login" /> } />
|
||||
<Route path="/reset" element={ <Access mode="reset" /> } />
|
||||
<Route path="/create" element={ <Access mode="create" /> } />
|
||||
<Route path="/session" element={ <Session/> } />
|
||||
</Routes>
|
||||
</NativeRouter>
|
||||
</AppContextProvider>
|
||||
</ProfileContextProvider>
|
||||
</AccountContextProvider>
|
||||
</ChannelContextProvider>
|
||||
</CardContextProvider>
|
||||
</StoreContextProvider>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function getChannelDetail(token, channelId) {
|
||||
let detail = await fetchWithTimeout(`/content/channels/${channelId}/detail?agent=${token}`, { method: 'GET' });
|
||||
export async function getChannelDetail(server, token, channelId) {
|
||||
let detail = await fetchWithTimeout(`https://${server}/content/channels/${channelId}/detail?agent=${token}`, { method: 'GET' });
|
||||
checkResponse(detail)
|
||||
return await detail.json()
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function getChannelSummary(token, channelId) {
|
||||
let summary = await fetchWithTimeout(`/content/channels/${channelId}/summary?agent=${token}`, { method: 'GET' });
|
||||
export async function getChannelSummary(server, token, channelId) {
|
||||
let summary = await fetchWithTimeout(`https://${server}/content/channels/${channelId}/summary?agent=${token}`, { method: 'GET' });
|
||||
checkResponse(summary)
|
||||
return await summary.json()
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function getChannels(token, revision) {
|
||||
export async function getChannels(server, token, revision) {
|
||||
let param = "?agent=" + token
|
||||
if (revision != null) {
|
||||
param += '&channelRevision=' + revision
|
||||
}
|
||||
let channels = await fetchWithTimeout('/content/channels' + param, { method: 'GET' });
|
||||
let channels = await fetchWithTimeout(`https://${server}/content/channels${param}`, { method: 'GET' });
|
||||
checkResponse(channels)
|
||||
let ret = await channels.json()
|
||||
return ret;
|
||||
|
14
app/mobile/src/context/CardContext.js
Normal file
14
app/mobile/src/context/CardContext.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { createContext } from 'react';
|
||||
import { useCardContext } from './useCardContext.hook';
|
||||
|
||||
export const CardContext = createContext({});
|
||||
|
||||
export function CardContextProvider({ children }) {
|
||||
const { state, actions } = useCardContext();
|
||||
return (
|
||||
<CardContext.Provider value={{ state, actions }}>
|
||||
{children}
|
||||
</CardContext.Provider>
|
||||
);
|
||||
}
|
||||
|
14
app/mobile/src/context/ChannelContext.js
Normal file
14
app/mobile/src/context/ChannelContext.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { createContext } from 'react';
|
||||
import { useChannelContext } from './useChannelContext.hook';
|
||||
|
||||
export const ChannelContext = createContext({});
|
||||
|
||||
export function ChannelContextProvider({ children }) {
|
||||
const { state, actions } = useChannelContext();
|
||||
return (
|
||||
<ChannelContext.Provider value={{ state, actions }}>
|
||||
{children}
|
||||
</ChannelContext.Provider>
|
||||
);
|
||||
}
|
||||
|
@ -34,6 +34,8 @@ export function useAccountContext() {
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
syncing.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
syncing.current = false;
|
||||
|
@ -7,6 +7,7 @@ import { getUsername } from 'api/getUsername';
|
||||
import { StoreContext } from 'context/StoreContext';
|
||||
import { AccountContext } from 'context/AccountContext';
|
||||
import { ProfileContext } from 'context/ProfileContext';
|
||||
import { ChannelContext } from 'context/ChannelContext';
|
||||
|
||||
export function useAppContext() {
|
||||
const [state, setState] = useState({
|
||||
@ -16,6 +17,7 @@ export function useAppContext() {
|
||||
const store = useContext(StoreContext);
|
||||
const account = useContext(AccountContext);
|
||||
const profile = useContext(ProfileContext);
|
||||
const channel = useContext(ChannelContext);
|
||||
|
||||
const delay = useRef(2);
|
||||
const ws = useRef(null);
|
||||
@ -41,6 +43,7 @@ export function useAppContext() {
|
||||
const setSession = async (access) => {
|
||||
await account.actions.setSession(access);
|
||||
await profile.actions.setSession(access);
|
||||
await channel.actions.setSession(access);
|
||||
updateState({ session: true });
|
||||
setWebsocket(access.server, access.appToken);
|
||||
}
|
||||
@ -48,6 +51,7 @@ export function useAppContext() {
|
||||
const clearSession = async () => {
|
||||
account.actions.clearSession();
|
||||
profile.actions.clearSession();
|
||||
channel.actions.clearSession();
|
||||
updateState({ session: false });
|
||||
clearWebsocket();
|
||||
}
|
||||
@ -84,8 +88,14 @@ export function useAppContext() {
|
||||
ws.current.onmessage = (ev) => {
|
||||
try {
|
||||
const rev = JSON.parse(ev.data);
|
||||
profile.actions.setRevision(rev.profile);
|
||||
account.actions.setRevision(rev.account);
|
||||
try {
|
||||
profile.actions.setRevision(rev.profile);
|
||||
account.actions.setRevision(rev.account);
|
||||
channel.actions.setRevision(rev.channel);
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
updateState({ disconnected: false });
|
||||
}
|
||||
catch (err) {
|
||||
|
68
app/mobile/src/context/useCardContext.hook.js
Normal file
68
app/mobile/src/context/useCardContext.hook.js
Normal file
@ -0,0 +1,68 @@
|
||||
import { useState, useRef, useContext } from 'react';
|
||||
import { StoreContext } from 'context/StoreContext';
|
||||
|
||||
export function useCardContext() {
|
||||
const [state, setState] = useState({
|
||||
});
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
const session = useRef(null);
|
||||
const curRevision = useRef(null);
|
||||
const setRevision = useRef(null);
|
||||
const syncing = useRef(false);
|
||||
|
||||
const updateState = (value) => {
|
||||
setState((s) => ({ ...s, ...value }))
|
||||
}
|
||||
|
||||
const sync = async () => {
|
||||
if (!syncing.current && setRevision.current !== curRevision.current) {
|
||||
syncing.current = true;
|
||||
|
||||
try {
|
||||
const revision = curRevision.current;
|
||||
const { server, appToken, guid } = session.current;
|
||||
|
||||
// get and store
|
||||
|
||||
updateState({ status });
|
||||
setRevision.current = revision;
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
syncing.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
syncing.current = false;
|
||||
sync();
|
||||
}
|
||||
};
|
||||
|
||||
const actions = {
|
||||
setSession: async (access) => {
|
||||
const { guid, server, appToken } = access;
|
||||
|
||||
// load
|
||||
|
||||
const revision = await store.actions.getCardRevision(guid);
|
||||
|
||||
// update
|
||||
|
||||
setRevision.current = revision;
|
||||
curRevision.current = revision;
|
||||
session.current = access;
|
||||
},
|
||||
clearSession: () => {
|
||||
session.current = {};
|
||||
updateState({ account: null });
|
||||
},
|
||||
setRevision: (rev) => {
|
||||
curRevision.current = rev;
|
||||
sync();
|
||||
},
|
||||
}
|
||||
|
||||
return { state, actions }
|
||||
}
|
||||
|
95
app/mobile/src/context/useChannelContext.hook.js
Normal file
95
app/mobile/src/context/useChannelContext.hook.js
Normal file
@ -0,0 +1,95 @@
|
||||
import { useState, useRef, useContext } from 'react';
|
||||
import { StoreContext } from 'context/StoreContext';
|
||||
import { getChannels } from 'api/getChannels';
|
||||
import { getChannelDetail } from 'api/getChannelDetail';
|
||||
import { getChannelSummary } from 'api/getChannelSummary';
|
||||
|
||||
export function useChannelContext() {
|
||||
const [state, setState] = useState({
|
||||
});
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
const session = useRef(null);
|
||||
const curRevision = useRef(null);
|
||||
const setRevision = useRef(null);
|
||||
const syncing = useRef(false);
|
||||
|
||||
const updateState = (value) => {
|
||||
setState((s) => ({ ...s, ...value }))
|
||||
}
|
||||
|
||||
const sync = async () => {
|
||||
|
||||
if (!syncing.current && setRevision.current !== curRevision.current) {
|
||||
syncing.current = true;
|
||||
|
||||
try {
|
||||
const revision = curRevision.current;
|
||||
const { server, appToken, guid } = session.current;
|
||||
|
||||
const delta = await getChannels(server, appToken, setRevision.current);
|
||||
for (let channel of delta) {
|
||||
if (channel.data) {
|
||||
if (channel.data.channelDetail && channel.data.channelSummary) {
|
||||
await store.actions.setChannelItem(guid, channel);
|
||||
}
|
||||
else {
|
||||
const { detailRevision, topicRevision, channelDetail, channelSummary } = channel.data;
|
||||
const view = await store.actions.getChannelItemView(guid, channel.id);
|
||||
if (view.detailRevision != detailRevision) {
|
||||
const detail = await getChannelDetail(server, appToken, channel.id);
|
||||
await store.actions.setChannelItemDetail(guid, channel.id, detailRevision, detail);
|
||||
}
|
||||
if (view.topicRevision != topicRevision) {
|
||||
const summary = await getChannelSummary(server, appToken, channel.id);
|
||||
await store.actions.setChannelItemSummary(guid, channel.id, topicRevision, summary);
|
||||
}
|
||||
await store.actions.setChannelItemRevision(guid, channel.revision);
|
||||
}
|
||||
}
|
||||
else {
|
||||
await store.actions.clearChannelItem(channel.id);
|
||||
}
|
||||
}
|
||||
|
||||
setRevision.current = revision;
|
||||
await store.actions.setChannelRevision(guid, revision);
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
syncing.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
syncing.current = false;
|
||||
sync();
|
||||
}
|
||||
};
|
||||
|
||||
const actions = {
|
||||
setSession: async (access) => {
|
||||
const { guid, server, appToken } = access;
|
||||
|
||||
// load
|
||||
|
||||
const revision = await store.actions.getChannelRevision(guid);
|
||||
|
||||
// update
|
||||
|
||||
setRevision.current = revision;
|
||||
curRevision.current = revision;
|
||||
session.current = access;
|
||||
},
|
||||
clearSession: () => {
|
||||
session.current = {};
|
||||
updateState({ account: null });
|
||||
},
|
||||
setRevision: (rev) => {
|
||||
curRevision.current = rev;
|
||||
sync();
|
||||
},
|
||||
}
|
||||
|
||||
return { state, actions }
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ export function useProfileContext() {
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
syncing.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
syncing.current = false;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState, useRef, useContext } from 'react';
|
||||
import SQLite from "react-native-sqlite-storage";
|
||||
|
||||
const DATABAG_DB = 'databag_v005.db';
|
||||
const DATABAG_DB = 'databag_v011.db';
|
||||
|
||||
export function useStoreContext() {
|
||||
const [state, setState] = useState({});
|
||||
@ -11,6 +11,11 @@ export function useStoreContext() {
|
||||
setState((s) => ({ ...s, ...value }))
|
||||
}
|
||||
|
||||
const initSession = async (guid) => {
|
||||
await db.current.executeSql(`CREATE TABLE IF NOT EXISTS channel_${guid} (channel_id text, revision integer, detail_revision integer, topic_revision integer, detail text, summary text, unique(channel_id))`);
|
||||
await db.current.executeSql(`CREATE TABLE IF NOT EXISTS topic_${guid} (channel_id text, topic_id text, revision integer, detail_revision integer, detail text, unique(channel_id, topic_id))`);
|
||||
}
|
||||
|
||||
const actions = {
|
||||
init: async () => {
|
||||
SQLite.DEBUG(false);
|
||||
@ -21,6 +26,7 @@ export function useStoreContext() {
|
||||
return await getAppValue(db.current, 'session');
|
||||
},
|
||||
setSession: async (access) => {
|
||||
await initSession(access.guid);
|
||||
await db.current.executeSql("UPDATE app SET value=? WHERE key='session';", [encodeObject(access)]);
|
||||
},
|
||||
clearSession: async () => {
|
||||
@ -33,17 +39,15 @@ export function useStoreContext() {
|
||||
},
|
||||
setProfile: async (guid, profile) => {
|
||||
const dataId = `${guid}_profile`;
|
||||
await db.current.executeSql("INSERT OR IGNORE INTO app (key, value) values (?, null);", [dataId]);
|
||||
await db.current.executeSql("UPDATE app SET value=? WHERE key=?;", [encodeObject(profile), dataId]);
|
||||
await db.current.executeSql("INSERT OR REPLACE INTO app (key, value) values (?, ?);", [dataId, encodeObject(profile)]);
|
||||
},
|
||||
getProfileRevision: async (guid) => {
|
||||
const dataId = `${guid}_profileRevision`;
|
||||
return await getAppValue(db.current, dataId, 0);
|
||||
return await getAppValue(db.current, dataId, null);
|
||||
},
|
||||
setProfileRevision: async (guid, revision) => {
|
||||
const dataId = `${guid}_profileRevision`;
|
||||
await db.current.executeSql("INSERT OR IGNORE INTO app (key, value) values (?, 0);", [dataId]);
|
||||
await db.current.executeSql("UPDATE app SET value=? WHERE key=?;", [encodeObject(revision), dataId]);
|
||||
await db.current.executeSql("INSERT OR REPLACE INTO app (key, value) values (?, ?);", [dataId, encodeObject(revision)]);
|
||||
},
|
||||
|
||||
getAccountStatus: async (guid) => {
|
||||
@ -52,18 +56,73 @@ export function useStoreContext() {
|
||||
},
|
||||
setAccountStatus: async (guid, status) => {
|
||||
const dataId = `${guid}_status`;
|
||||
await db.current.executeSql("INSERT OR IGNORE INTO app (key, value) values (?, null);", [dataId]);
|
||||
await db.current.executeSql("UPDATE app SET value=? WHERE key=?;", [encodeObject(status), dataId]);
|
||||
await db.current.executeSql("INSERT OR REPLACE INTO app (key, value) values (?, ?);", [dataId, encodeObject(status)]);
|
||||
},
|
||||
getAccountRevision: async (guid) => {
|
||||
const dataId = `${guid}_accountRevision`;
|
||||
return await getAppValue(db.current, dataId, 0);
|
||||
return await getAppValue(db.current, dataId, null);
|
||||
},
|
||||
setAccountRevision: async (guid, revision) => {
|
||||
const dataId = `${guid}_accountRevision`;
|
||||
await db.current.executeSql("INSERT OR IGNORE INTO app (key, value) values (?, 0);", [dataId]);
|
||||
await db.current.executeSql("UPDATE app SET value=? WHERE key=?;", [encodeObject(revision), dataId]);
|
||||
await db.current.executeSql("INSERT OR REPLACE INTO app (key, value) values (?, ?);", [dataId, encodeObject(revision)]);
|
||||
},
|
||||
|
||||
getCardRevision: async (guid) => {
|
||||
const dataId = `${guid}_cardRevision`;
|
||||
return await getAppValue(db.current, dataId, null);
|
||||
},
|
||||
setCardRevision: async (guid, revision) => {
|
||||
const dataId = `${guid}_cardRevision`;
|
||||
await db.current.executeSql("INSERT OR REPLACE INTO app (key, value) values (?, ?);", [dataId, encodeObject(revision)]);
|
||||
},
|
||||
|
||||
getChannelRevision: async (guid) => {
|
||||
const dataId = `${guid}_channelRevision`;
|
||||
return await getAppValue(db.current, dataId, null);
|
||||
},
|
||||
setChannelRevision: async (guid, revision) => {
|
||||
const dataId = `${guid}_channelRevision`;
|
||||
await db.current.executeSql("INSERT OR REPLACE INTO app (key, value) values (?, ?);", [dataId, encodeObject(revision)]);
|
||||
},
|
||||
setChannelItem: async (guid, channel) => {
|
||||
const { id, revision, data } = channel;
|
||||
await db.current.executeSql(`INSERT OR REPLACE INTO channel_${guid} (channel_id, revision, detail_revision, topic_revision, detail, summary) values (?, ?, ?, ?, ?, ?);`, [id, revision, data.detailRevision, data.topicRevision, encodeObject(data.channelDetail), encodeObject(data.channelSummary)]);
|
||||
},
|
||||
clearChannelItem: async (guid, channelId) => {
|
||||
await db.current.executeSql(`DELETE FROM channel_${guid} WHERE channel_id=?`, [channelId]);
|
||||
},
|
||||
setChannelItemRevision: async (guid, channelId, revision) => {
|
||||
await db.current.executeSql(`UPDATE channel_${guid} set revision=? where channel_id=?`, [revision, channelId]);
|
||||
},
|
||||
setChannelItemDetail: async (guid, channelId, revision, detail) => {
|
||||
await db.current.executeSql(`UPDATE channel_${guid} set detail_revision=?, detail=? where channel_id=?`, [revision, encodeObject(detail), channelId]);
|
||||
},
|
||||
setChannelItemSummary: async (guid, channelId, revision, summary) => {
|
||||
await db.current.executeSql(`UPDATE channel_${guid} set topic_revision=?, summary=? where channel_id=?`, [revision, encodeObject(summary), channelId]);
|
||||
},
|
||||
getChannelItemView: async (guid, channelId) => {
|
||||
console.log("HERE", channelId);
|
||||
const values = await getAppValues(db.current, `SELECT revision, detail_revision, topic_revision FROM channel_${guid} WHERE channel_id=?`, [channelId]);
|
||||
if (!values.length) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
revision: values[0].revision,
|
||||
detailRevision: values[0].detail_revision,
|
||||
topicRevision: values[0].topic_revision,
|
||||
};
|
||||
},
|
||||
getChannelItems: async (guid) => {
|
||||
const values = await getAppValues(db.current, `SELECT channel_id, revision, detail_revision, topic_revision, detail, summary FROM channel_${guid}`, []);
|
||||
return values.map(channel => ({
|
||||
channelId: channel.channel_id,
|
||||
revision: channel.revision,
|
||||
detailRevision: channel.detail_revision,
|
||||
topicRevision: channel.topic_revision,
|
||||
detail: decodeObject(channel.detail),
|
||||
summary: decodeObject(channel.summary),
|
||||
}));
|
||||
},
|
||||
}
|
||||
return { state, actions }
|
||||
}
|
||||
@ -105,4 +164,17 @@ async function getAppValue(sql: SQLite.SQLiteDatabase, id: string, unset) {
|
||||
return unset;
|
||||
}
|
||||
|
||||
async function getAppValues(sql: SQLite.SQLiteDatabase, query: string, params) {
|
||||
const res = await sql.executeSql(query, params);
|
||||
if (!hasResult(res)) {
|
||||
return [];
|
||||
}
|
||||
const values = [];
|
||||
for (let i = 0; i < res[0].rows.length; i++) {
|
||||
values.push(res[0].rows.item(i));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user