Storing data

This commit is contained in:
KoalaSat 2022-11-10 20:56:34 +01:00
parent d63bf299ff
commit edd30bb0cd
No known key found for this signature in database
GPG Key ID: 2F7F61C6146AB157
11 changed files with 170 additions and 212 deletions

View File

@ -1,5 +1,6 @@
package com.nostros.classes;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
@ -20,7 +21,7 @@ public class Event {
public Event(JSONObject data) throws JSONException {
created_at = data.getInt("created_at");
content = data.getString("content");
content = data.optString("content");
id = data.getString("id");
kind = data.getString("kind");
pubkey = data.getString("pubkey");
@ -28,23 +29,32 @@ public class Event {
tags = data.getJSONArray("tags");
}
public void save(SQLiteDatabase database) {
Log.d("EVENT", kind);
public void save(SQLiteDatabase database, String userPubKey) {
if (isValid()) {
if (kind.equals("1") || kind.equals("2")) {
try {
if (kind.equals("0")) {
saveUserMeta(database);
} else if (kind.equals("1") || kind.equals("2")) {
saveNote(database);
} else if (kind.equals("0")) {
saveUser(database);
} else if (kind.equals("3")) {
if (pubkey.equals(userPubKey)) {
savePets(database);
} else {
saveFollower(database, userPubKey);
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
protected boolean isValid() {
return !id.isEmpty() && !sig.isEmpty() && (kind.equals("1") || kind.equals("2"));
return !id.isEmpty() && !sig.isEmpty();
}
protected String getMainEventId() {
JSONArray eTags = getETags();
JSONArray eTags = filterTags("e");
String mainEventId = null;
try {
for (int i = 0; i < eTags.length(); ++i) {
@ -64,7 +74,7 @@ public class Event {
}
protected String getReplyEventId() {
JSONArray eTags = getETags();
JSONArray eTags = filterTags("e");
String mainEventId = null;
try {
for (int i = 0; i < eTags.length(); ++i) {
@ -83,14 +93,34 @@ public class Event {
return mainEventId;
}
protected JSONArray getETags() {
protected String saveFollower(String pubKey) {
JSONArray eTags = filterTags("p");
String mainEventId = null;
try {
for (int i = 0; i < eTags.length(); ++i) {
JSONArray tag = eTags.getJSONArray(i);
if (tag.getString(3).equals("reply")) {
mainEventId = tag.getString(1);
}
}
if (mainEventId == null && eTags.length() > 0) {
mainEventId = eTags.getJSONArray(eTags.length() - 1).getString(1);
}
} catch (JSONException ignored) {
}
return mainEventId;
}
protected JSONArray filterTags(String kind) {
JSONArray filtered = new JSONArray();
try {
for (int i = 0; i < tags.length(); ++i) {
JSONArray tag = tags.getJSONArray(i);
String tagKind = tag.getString(0);
if (tagKind.equals("e")) {
if (tagKind.equals(kind)) {
filtered.put(tag);
}
}
@ -102,9 +132,6 @@ public class Event {
}
protected void saveNote(SQLiteDatabase database) {
String query = "SELECT nostros_notes.id FROM nostros_notes WHERE id = " + this.id + ";";
Cursor cursor = database.rawQuery(query, null);
if (cursor.getInt(0) == 0) {
ContentValues values = new ContentValues();
values.put("id", id);
values.put("content", content.replace("'", "''"));
@ -115,26 +142,71 @@ public class Event {
values.put("tags", tags.toString());
values.put("main_event_id", getMainEventId());
values.put("reply_event_id", getReplyEventId());
database.insert("nostros_notes", null, values);
}
database.replace("nostros_notes", null, values);
}
protected void saveUser(SQLiteDatabase database) {
String queryCheck = "SELECT contact, follower FROM nostros_users WHERE id = " + this.pubkey + ";";
Cursor cursor = database.rawQuery(queryCheck, null);
try {
protected void saveUserMeta(SQLiteDatabase database) throws JSONException {
String[] tableColumns = new String[] {
"contact",
"follower"
};
String whereClause = "id = ?";
String[] whereArgs = new String[] {
this.pubkey
};
@SuppressLint("Recycle") Cursor cursor = database.query("nostros_users", tableColumns, whereClause, whereArgs, null, null, null);
JSONObject userContent = new JSONObject(content);
ContentValues values = new ContentValues();
values.put("id", pubkey);
values.put("name", userContent.getString("name").replace("'", "''"));
values.put("picture", userContent.getString("picture").replace("'", "''"));
values.put("about", userContent.getString("about").replace("'", "''"));
values.put("main_relay", userContent.getString("main_relay").replace("'", "''"));
values.put("name", userContent.optString("name"));
values.put("picture", userContent.optString("picture"));
values.put("about", userContent.optString("about"));
values.put("main_relay", userContent.optString("main_relay"));
values.put("contact", cursor.getInt(0));
values.put("follower", cursor.getInt(1));
database.replace("nostros_users", null, values);
} catch (JSONException ignored) {
}
protected void savePets(SQLiteDatabase database) throws JSONException {
for (int i = 0; i < tags.length(); ++i) {
JSONArray tag = tags.getJSONArray(i);
String petId = tag.getString(1);
String query = "SELECT * FROM nostros_users WHERE id = ?";
Cursor cursor = database.rawQuery(query, new String[] {petId});
if (cursor.getCount() == 0) {
ContentValues values = new ContentValues();
values.put("id", petId);
values.put("name", tag.getString(3));
values.put("contact", true);
database.insert("nostros_users", null, values);
}
}
}
protected void saveFollower(SQLiteDatabase database, String userPubKey) throws JSONException {
JSONArray pTags = filterTags("p");
for (int i = 0; i < pTags.length(); ++i) {
JSONArray tag = pTags.getJSONArray(i);
String query = "SELECT * FROM nostros_users WHERE id = ?";
Cursor cursor = database.rawQuery(query, new String[] {pubkey});
ContentValues values = new ContentValues();
values.put("id", pubkey);
if (tag.getString(1).equals(userPubKey)) {
if (cursor.getCount() == 0) {
values.put("follower", true);
database.insert("nostros_users", null, values);
} else {
String whereClause = "id = ?";
String[] whereArgs = new String[] {
this.pubkey
};
values.put("follower", true);
database.update("nostros_users", values, whereClause, whereArgs);
}
}
}
}
}

View File

@ -26,8 +26,8 @@ public class DatabaseModule extends ReactContextBaseJavaModule {
}
@ReactMethod
public void saveEvent(JSONObject data) throws JSONException {
public void saveEvent(JSONObject data, String userPubKey) throws JSONException {
Event event = new Event(data);
event.save(database);
event.save(database, userPubKey);
}
}

View File

@ -22,6 +22,7 @@ import java.util.Map;
public class WebsocketModule extends ReactContextBaseJavaModule {
private WebSocket webSocket;
private DatabaseModule database;
private String userPubKey;
public WebsocketModule(ReactApplicationContext reactContext) {
super(reactContext);
@ -36,7 +37,6 @@ public class WebsocketModule extends ReactContextBaseJavaModule {
@ReactMethod
public void send(String message) {
webSocket.sendText(message);
}
@ReactMethod
@ -52,10 +52,10 @@ public class WebsocketModule extends ReactContextBaseJavaModule {
@Override
public void onTextMessage(WebSocket websocket, String message) throws Exception {
Log.d("Websocket", message);
JSONArray jsonArray = new JSONArray(message);
if (jsonArray.get(0).toString().equals("EVENT")) {
Log.d("JSON Event", jsonArray.get(0).toString());
database.saveEvent(jsonArray.getJSONObject(2));
database.saveEvent(jsonArray.getJSONObject(2), userPubKey);
}
}
});
@ -77,4 +77,9 @@ public class WebsocketModule extends ReactContextBaseJavaModule {
Log.d("WebSocket", "Failed to establish a WebSocket connection.");
}
}
@ReactMethod
public void setUserPubKey(String userPubKey) {
this.userPubKey = userPubKey;
}
}

View File

@ -3,25 +3,16 @@ import React, { useCallback, useContext, useEffect, useState } from 'react'
import { RefreshControl, ScrollView, StyleSheet, TouchableOpacity } from 'react-native'
import { AppContext } from '../../Contexts/AppContext'
import Icon from 'react-native-vector-icons/FontAwesome5'
import { Event, EventKind } from '../../lib/nostr/Events'
import { EventKind } from '../../lib/nostr/Events'
import { useTranslation } from 'react-i18next'
import {
getUser,
getUsers,
insertUserPets,
updateUserContact,
updateUserFollower,
User,
} from '../../Functions/DatabaseFunctions/Users'
import { getUsers, updateUserContact, User } from '../../Functions/DatabaseFunctions/Users'
import UserCard from '../UserCard'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import Relay from '../../lib/nostr/Relay'
import { populatePets } from '../../Functions/RelayFunctions/Users'
export const ContactsPage: React.FC = () => {
const { database } = useContext(AppContext)
const { relayPool, publicKey, lastEventId, setLastEventId, privateKey } =
useContext(RelayPoolContext)
const { relayPool, publicKey, privateKey } = useContext(RelayPoolContext)
const theme = useTheme()
const [users, setUsers] = useState<User[]>()
const [refreshing, setRefreshing] = useState(true)
@ -31,10 +22,6 @@ export const ContactsPage: React.FC = () => {
const { t } = useTranslation('common')
useEffect(() => {
loadUsers()
}, [lastEventId])
useEffect(() => {
setUsers([])
loadUsers()
@ -43,44 +30,26 @@ export const ContactsPage: React.FC = () => {
const loadUsers: () => void = () => {
if (database && publicKey) {
let filters: object = { followers: true }
if (selectedTab === 0) {
getUsers(database, { contacts: true }).then((results) => {
if (results) setUsers(results)
setRefreshing(false)
})
} else {
getUsers(database, { followers: true }).then((results) => {
if (results) setUsers(results)
setRefreshing(false)
filters = { contacts: true }
}
getUsers(database, filters).then((results) => {
if (results) {
setUsers(results)
relayPool?.subscribe('main-channel', {
kinds: [EventKind.meta],
authors: results.map((user) => user.id),
})
}
setRefreshing(false)
})
}
}
const subscribeContacts: () => void = async () => {
relayPool?.unsubscribeAll()
relayPool?.on('event', 'contacts', async (relay: Relay, _subId?: string, event?: Event) => {
console.log('CONTACTS PAGE EVENT =======>', relay.url, event)
if (database && event?.id && publicKey && event.kind === EventKind.petNames) {
if (event.pubkey === publicKey) {
insertUserPets(event, database).finally(() => setLastEventId(event?.id ?? ''))
relayPool?.removeOn('event', 'contacts')
} else {
const isFollower = event.tags.some((tag) => tag[1] === publicKey)
await updateUserFollower(event.pubkey, database, isFollower)
setLastEventId(event?.id ?? '')
if (isFollower) {
const user = await getUser(event.pubkey, database)
if (!user?.name && user?.id) {
relayPool?.subscribe('main-channel', {
kinds: [EventKind.meta],
authors: [user.id],
})
}
}
}
}
})
if (publicKey) {
if (selectedTab === 0) {
relayPool?.subscribe('main-channel', {

View File

@ -41,7 +41,8 @@ export const HomePage: React.FC = () => {
}
const subscribeNotes: (users: User[], past?: boolean) => void = (users, past) => {
if (!database || !publicKey) return
if (!database || !publicKey || users.length === 0) return
const limit = past ? pageSize : initialPageSize
getNotes(database, { contacts: true, includeIds: [publicKey], limit }).then((results) => {
const message: RelayFilters = {
@ -50,13 +51,11 @@ export const HomePage: React.FC = () => {
limit: initialPageSize,
}
// TODO
// if (past) {
// message.until = results[results.length - 1]?.created_at
// } else if (results.length >= pageSize) {
// message.since = results[0]?.created_at
// }
if (past) {
message.until = results[results.length - 1]?.created_at
} else if (results.length >= pageSize) {
message.since = results[0]?.created_at
}
relayPool?.subscribe('main-channel', message)
})
}
@ -73,8 +72,10 @@ export const HomePage: React.FC = () => {
useEffect(() => {
relayPool?.unsubscribeAll()
if (relayPool && publicKey) {
calculateInitialNotes().then(() => loadNotes())
}, [publicKey])
}
}, [publicKey, relayPool])
useEffect(() => {
loadNotes()

View File

@ -3,18 +3,15 @@ import { Button, Input, Layout, useTheme } from '@ui-kitten/components'
import { Clipboard, StyleSheet } from 'react-native'
import { RelayPoolContext } from '../../../Contexts/RelayPoolContext'
import { useTranslation } from 'react-i18next'
import { tagToUser } from '../../../Functions/RelayFunctions/Users'
import Relay from '../../../lib/nostr/Relay'
import { Event, EventKind } from '../../../lib/nostr/Events'
import { EventKind } from '../../../lib/nostr/Events'
import { AppContext } from '../../../Contexts/AppContext'
import { insertUserPets } from '../../../Functions/DatabaseFunctions/Users'
import SInfo from 'react-native-sensitive-info'
import Icon from 'react-native-vector-icons/FontAwesome5'
import { generateRandomKey, getPublickey } from '../../../lib/nostr/Bip'
import { showMessage } from 'react-native-flash-message'
export const Logger: React.FC = () => {
const { database, goToPage, loadingDb } = useContext(AppContext)
const { goToPage, loadingDb } = useContext(AppContext)
const { privateKey, publicKey, relayPool, loadingRelayPool, setPrivateKey, setPublicKey } =
useContext(RelayPoolContext)
const { t } = useTranslation('common')
@ -22,10 +19,7 @@ export const Logger: React.FC = () => {
const [loading, setLoading] = useState<boolean>(false)
const [status, setStatus] = useState<number>(0)
const [isPrivate, setIsPrivate] = useState<boolean>(true)
const [authors, setAuthors] = useState<string[]>([])
const [inputValue, setInputValue] = useState<string>('')
const [loadedUsers, setLoadedUsers] = useState<number>()
const [lastEventId, setLastEventId] = useState<string>('')
const onPress: () => void = () => {
if (inputValue && inputValue !== '') {
@ -46,71 +40,14 @@ export const Logger: React.FC = () => {
useEffect(() => {
if (!loadingRelayPool && !loadingDb && publicKey) {
setStatus(1)
initEvents()
relayPool?.subscribe('main-channel', {
kinds: [EventKind.petNames],
kinds: [EventKind.petNames, EventKind.meta],
authors: [publicKey],
})
setTimeout(() => goToPage('home', true), 3000)
}
}, [loadingRelayPool, publicKey, loadingDb])
const initEvents: () => void = () => {
relayPool?.on('event', 'landing', (_relay: Relay, _subId?: string, event?: Event) => {
console.log('LANDING EVENT =======>', event)
if (event && database) {
setLastEventId(event.id ?? '')
if (event.kind === EventKind.petNames) {
loadPets(event)
} else if (event.kind === EventKind.meta) {
setLoadedUsers((prev) => (prev ? prev + 1 : 1))
// insertUserMeta(event, database) FIXME
if (loadedUsers && loadedUsers >= authors.length && status < 3) setStatus(3)
}
}
})
}
const loadPets: (event: Event) => void = (event) => {
if (database) {
if (event.tags.length > 0) {
setStatus(2)
insertUserPets(event, database).then(() => {
requestUserData(event)
})
} else {
setStatus(3)
}
}
}
const requestUserData: (event: Event) => void = (event) => {
if (publicKey) {
const authors: string[] = [publicKey, ...event.tags.map((tag) => tagToUser(tag).id)]
setAuthors(authors)
relayPool?.subscribe('main-channel', {
kinds: [EventKind.meta],
authors,
})
}
}
useEffect(() => {
if (status > 2) {
relayPool?.removeOn('event', 'landing')
goToPage('home', true)
}
}, [status])
useEffect(() => {
if (status > 1) {
const timer = setTimeout(() => setStatus(4), 8000)
return () => {
clearTimeout(timer)
}
}
}, [lastEventId])
const randomKeyGenerator: () => JSX.Element = () => {
if (!isPrivate) return <></>
@ -141,8 +78,7 @@ export const Logger: React.FC = () => {
const statusName: { [status: number]: string } = {
0: t('landing.connect'),
1: t('landing.connecting'),
2: t('landing.loadingContacts'),
3: t('landing.ready'),
2: t('landing.ready'),
}
const styles = StyleSheet.create({
inputsContainer: {
@ -172,13 +108,15 @@ export const Logger: React.FC = () => {
<Icon name={isPrivate ? 'lock' : 'eye'} size={16} color={theme['text-basic-color']} solid />
)
const label: string = isPrivate ? t('landing.privateKey') : t('landing.publicKey')
return !privateKey || !publicKey || status !== 0 ? (
<>
<Layout style={styles.inputsContainer}>
<Layout style={styles.input}>
<Input
size='medium'
label={isPrivate ? t('landing.privateKey') : t('landing.publicKey')}
label={label}
onChangeText={setInputValue}
value={inputValue}
disabled={loading}

View File

@ -9,6 +9,7 @@ import SInfo from 'react-native-sensitive-info'
import { getPublickey } from '../lib/nostr/Bip'
import { defaultRelays } from '../Constants/RelayConstants'
import WebsocketModule from '../lib/nostr/Native/WebsocketModule'
import moment from 'moment'
export interface RelayPoolContextProps {
loadingRelayPool: boolean
@ -79,9 +80,15 @@ export const RelayPoolContextProvider = ({
}
}
const setClock: () => void = () => {
setLastEventId(moment().unix().toString())
setTimeout(setClock, 500)
}
useEffect(() => {
WebsocketModule.connectWebsocket((message) => {
console.log('WEBSOCKET', message)
setClock()
})
}, [])
@ -94,6 +101,7 @@ export const RelayPoolContextProvider = ({
useEffect(() => {
if (publicKey && publicKey !== '') {
WebsocketModule.setUserPubKey(publicKey)
if (!loadingRelayPool && page !== 'landing') {
goToPage('home', true)
} else {

View File

@ -1,7 +1,5 @@
import { getItems } from '..'
import { QuickSQLiteConnection, QueryResult } from 'react-native-quick-sqlite'
import { Event, EventKind, verifySignature } from '../../../lib/nostr/Events'
import { tagToUser } from '../../RelayFunctions/Users'
export interface User {
id: string
@ -38,23 +36,6 @@ export const updateUserContact: (
return db.execute(userQuery, [contact ? 1 : 0, userId])
}
export const insertUserPets: (
event: Event,
db: QuickSQLiteConnection,
) => Promise<User[] | null> = async (event, db) => {
const valid = await verifySignature(event)
if (valid && event.kind === EventKind.petNames) {
const users: User[] = event.tags.map((tag) => tagToUser(tag))
users.map(async (user) => {
return await updateUserContact(user.id, db, true)
})
return users
} else {
return null
}
}
export const getUser: (pubkey: string, db: QuickSQLiteConnection) => Promise<User | null> = async (
pubkey,
db,

View File

@ -10,12 +10,6 @@ export const usersToTags: (users: User[]) => string[][] = (users) => {
})
}
export const tagsToUsers: (tags: string[][]) => User[] = (tags) => {
return tags.map((tag): User => {
return tagToUser(tag)
})
}
export const tagToUser: (tag: string[]) => User = (tag) => {
return {
id: tag[1],

View File

@ -4,6 +4,7 @@ const { WebsocketModule } = NativeModules
interface WebsocketInterface {
connectWebsocket: (callback: (message: string) => void) => void
send: (message: string) => void
setUserPubKey: (pubKey: string) => void
}
export default WebsocketModule as WebsocketInterface

View File

@ -78,18 +78,8 @@ class Relay {
private readonly send: (message: object) => void = async (message) => {
const tosend = JSON.stringify(message)
if (this.socket.readyState !== WebSocket.OPEN) {
setTimeout(() => {
this.send(message)
}, 500)
} else {
try {
console.log('SEND =====>', tosend)
WebsocketModule.send(tosend)
} catch (e) {
console.log('Failed ot send Event', e)
}
}
}
public url: string
@ -133,9 +123,8 @@ class Relay {
public readonly unsubscribeAll: () => void = async () => {
Object.keys(this.subscriptions).forEach((subId: string) => {
this.send(['CLOSE', subId])
this.unsubscribe(subId)
})
this.subscriptions = {}
}
}