diff --git a/app/mobile/App.js b/app/mobile/App.js
index b81fad99..f662cc97 100644
--- a/app/mobile/App.js
+++ b/app/mobile/App.js
@@ -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 (
-
-
-
-
-
- } />
- } />
- } />
- } />
- } />
- } />
-
-
-
-
-
+
+
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+
+
+
);
}
diff --git a/app/mobile/src/api/getChannelDetail.js b/app/mobile/src/api/getChannelDetail.js
index cd5b6f04..3b873a86 100644
--- a/app/mobile/src/api/getChannelDetail.js
+++ b/app/mobile/src/api/getChannelDetail.js
@@ -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()
}
diff --git a/app/mobile/src/api/getChannelSummary.js b/app/mobile/src/api/getChannelSummary.js
index ca9ea8cf..062528bc 100644
--- a/app/mobile/src/api/getChannelSummary.js
+++ b/app/mobile/src/api/getChannelSummary.js
@@ -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()
}
diff --git a/app/mobile/src/api/getChannels.js b/app/mobile/src/api/getChannels.js
index 85af261c..f7cb3a53 100644
--- a/app/mobile/src/api/getChannels.js
+++ b/app/mobile/src/api/getChannels.js
@@ -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;
diff --git a/app/mobile/src/context/CardContext.js b/app/mobile/src/context/CardContext.js
new file mode 100644
index 00000000..1bc8fb18
--- /dev/null
+++ b/app/mobile/src/context/CardContext.js
@@ -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 (
+
+ {children}
+
+ );
+}
+
diff --git a/app/mobile/src/context/ChannelContext.js b/app/mobile/src/context/ChannelContext.js
new file mode 100644
index 00000000..103e537c
--- /dev/null
+++ b/app/mobile/src/context/ChannelContext.js
@@ -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 (
+
+ {children}
+
+ );
+}
+
diff --git a/app/mobile/src/context/useAccountContext.hook.js b/app/mobile/src/context/useAccountContext.hook.js
index 5e2c7031..26f1568a 100644
--- a/app/mobile/src/context/useAccountContext.hook.js
+++ b/app/mobile/src/context/useAccountContext.hook.js
@@ -34,6 +34,8 @@ export function useAccountContext() {
}
catch(err) {
console.log(err);
+ syncing.current = false;
+ return;
}
syncing.current = false;
diff --git a/app/mobile/src/context/useAppContext.hook.js b/app/mobile/src/context/useAppContext.hook.js
index 60b9c4d9..f4aba29e 100644
--- a/app/mobile/src/context/useAppContext.hook.js
+++ b/app/mobile/src/context/useAppContext.hook.js
@@ -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) {
diff --git a/app/mobile/src/context/useCardContext.hook.js b/app/mobile/src/context/useCardContext.hook.js
new file mode 100644
index 00000000..f039a77a
--- /dev/null
+++ b/app/mobile/src/context/useCardContext.hook.js
@@ -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 }
+}
+
diff --git a/app/mobile/src/context/useChannelContext.hook.js b/app/mobile/src/context/useChannelContext.hook.js
new file mode 100644
index 00000000..cd3a784f
--- /dev/null
+++ b/app/mobile/src/context/useChannelContext.hook.js
@@ -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 }
+}
+
diff --git a/app/mobile/src/context/useProfileContext.hook.js b/app/mobile/src/context/useProfileContext.hook.js
index b3f92c42..3fcd4764 100644
--- a/app/mobile/src/context/useProfileContext.hook.js
+++ b/app/mobile/src/context/useProfileContext.hook.js
@@ -36,6 +36,8 @@ export function useProfileContext() {
}
catch(err) {
console.log(err);
+ syncing.current = false;
+ return;
}
syncing.current = false;
diff --git a/app/mobile/src/context/useStoreContext.hook.js b/app/mobile/src/context/useStoreContext.hook.js
index 89b68342..e5ec4ee6 100644
--- a/app/mobile/src/context/useStoreContext.hook.js
+++ b/app/mobile/src/context/useStoreContext.hook.js
@@ -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;
+}
+
+