BloodyInside commited on
Commit
4af0f6c
·
1 Parent(s): cffd4ca

NEW update 1

Browse files
backend/invoke_worker/__pycache__/chapter_queue.cpython-312.pyc CHANGED
Binary files a/backend/invoke_worker/__pycache__/chapter_queue.cpython-312.pyc and b/backend/invoke_worker/__pycache__/chapter_queue.cpython-312.pyc differ
 
backend/invoke_worker/chapter_queue.py CHANGED
@@ -233,10 +233,10 @@ class UpdateSocketQueue(Thread):
233
  MAX_QUEUE = SocketRequestChapterQueueCache.objects.count()
234
 
235
  if (MAX_QUEUE):
236
- query_result = list(set(SocketRequestChapterQueueCache.objects.order_by("-datetime").values_list('socket_id', flat=True).distinct()))
237
  for socket_id in query_result:
238
  object = {}
239
- query_result_2 = SocketRequestChapterQueueCache.objects.filter(socket_id=socket_id).order_by("-datetime").values("source","comic_id","chapter_idx")
240
 
241
  for item in query_result_2:
242
  source = item.get("source")
 
233
  MAX_QUEUE = SocketRequestChapterQueueCache.objects.count()
234
 
235
  if (MAX_QUEUE):
236
+ query_result = list(set(SocketRequestChapterQueueCache.objects.order_by("datetime").values_list('socket_id', flat=True).distinct()))
237
  for socket_id in query_result:
238
  object = {}
239
+ query_result_2 = SocketRequestChapterQueueCache.objects.filter(socket_id=socket_id).order_by("datetime").values("source","comic_id","chapter_idx")
240
 
241
  for item in query_result_2:
242
  source = item.get("source")
frontend/app/index.tsx CHANGED
@@ -9,7 +9,7 @@ const Index = () => {
9
  const pathname = usePathname()
10
 
11
  if (pathname === "/" || pathname === "") return (
12
- <Redirect href="/read/colamanga/manga-nf048578?idx=165" />
13
  )
14
 
15
  }
 
9
  const pathname = usePathname()
10
 
11
  if (pathname === "/" || pathname === "") return (
12
+ <Redirect href="/read/colamanga/manga-wp55334?idx=985" />
13
  )
14
 
15
  }
frontend/app/read/[source]/[comic_id]/index.tsx CHANGED
@@ -42,18 +42,13 @@ const Index = ({}:any) => {
42
  const {apiBaseContext, setApiBaseContext}:any = useContext(CONTEXT)
43
 
44
  const [isError,setIsError]:any = useState({state:false,text:""})
45
- const [firstLoading, setFirstLoading]:any = useState(true)
46
  const [chapterInfo, setChapterInfo]:any = useState({})
47
  const [showOptions, setShowOptions]:any = useState({type:"general",state:false})
48
- const [imageKeys, setImageKeys]:any = useState([])
49
- const [isLoading, setIsLoading]:any = useState("")
50
- const [addChapter, setAddChapter]:any = useState(false)
51
  const [zoom, setZoom]:any = useState(0)
52
 
53
- const is_adding:any = useRef({state:false,interval:null})
54
- const temp_loading:any = useRef(false)
55
- const temp_image_keys:any = useRef({})
56
- const images:any = useRef({})
57
  const CHAPTER_IDX = useRef(Number(useLocalSearchParams().idx as string));
58
 
59
 
@@ -67,7 +62,7 @@ const Index = ({}:any) => {
67
  setIsError({state:true,text:"Invalid source, comic id or chapter idx!"})
68
  return
69
  }
70
- temp_loading.current = true
71
  const stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,CHAPTER_IDX.current, {exclude_fields:["data"]})
72
  if (stored_chapter?.data_state !== "completed"){
73
  setIsError({state:true,text:"Chapter is not download yet!"})
@@ -82,88 +77,51 @@ const Index = ({}:any) => {
82
  const stored_chapter_zoom = await Storage.get("CHAPTER_ZOOM") || 0
83
  setZoom(stored_chapter_zoom)
84
 
85
-
86
-
87
- const chapter_current = await get_chapter(SOURCE,COMIC_ID,CHAPTER_IDX.current)
88
- if (chapter_current){
89
- images.current = {...images.current,...chapter_current?.files}
90
- setImageKeys(chapter_current?.file_keys)
91
- setFirstLoading(false)
92
- }
93
 
94
- const chapter_next = await get_chapter(SOURCE,COMIC_ID,CHAPTER_IDX.current+1)
95
- if (chapter_next){
96
- temp_image_keys.current[`${CHAPTER_IDX.current+1}`] = chapter_next?.file_keys
97
- images.current = {...images.current,...chapter_next?.files}
98
- }
99
-
100
- temp_loading.current = false
101
  })()},[])
102
 
103
 
104
  useFocusEffect(useCallback(() => {
105
  return () => {
106
- setImageKeys([])
107
- images.current = {}
108
- clearInterval(is_adding.current.interval)
109
  }
110
  },[]))
111
 
112
- useEffect(()=>{
113
- if (!addChapter) return
114
- if (is_adding.current.state) return
115
- is_adding.current.state = true
116
- setIsLoading(true)
117
- clearInterval(is_adding.current.interval)
118
- is_adding.current.interval = setInterval(()=>{
119
- if (!temp_loading.current) {
120
- clearInterval(is_adding.current.interval);
121
- temp_loading.current = true;
122
- (async ()=>{
123
- const new_chapter_idx = CHAPTER_IDX.current+1
124
- if (imageKeys.slice(-1)[0]?.type !== "no-chapter-banner"){
125
- const next_stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,new_chapter_idx, {exclude_fields:["data"]})
126
- if (next_stored_chapter?.data_state === "completed"){
127
- if (temp_image_keys.current.hasOwnProperty(new_chapter_idx)){
128
- delete temp_image_keys.current[new_chapter_idx-2]
129
- const pre_image_keys = imageKeys.filter((data:any) => {
130
- if (data.type === "image"){
131
- const idx = Number(data.value.split("-")[0])
132
- if (idx < new_chapter_idx-2) return false
133
- else return true
134
- }else if (data.type === "chapter-info-banner"){
135
- if (data.idx < new_chapter_idx-2) return false
136
- }else return true
137
- })
138
-
139
- setImageKeys([...pre_image_keys,{type:"chapter-info-banner", idx:CHAPTER_IDX.current, value:{last:chapterInfo.title, next:next_stored_chapter?.title}},...temp_image_keys.current[new_chapter_idx]])
140
- setIsLoading(false)
141
- setAddChapter(false)
142
- is_adding.current.state = false
143
 
144
- for (const item of Object.keys(images.current)){
145
-
146
- if (Number(item.split("-")[0]) < new_chapter_idx-2) delete images.current[item]
147
- }
148
- const chapter_next = await get_chapter(SOURCE,COMIC_ID,new_chapter_idx + 1)
149
- if (chapter_next){
150
- temp_image_keys.current[`${new_chapter_idx + 1}`] = chapter_next?.file_keys
151
- images.current = {...images.current,...chapter_next?.files}
152
- }else{
153
- temp_image_keys.current[`${new_chapter_idx + 1}`] = [{type:"no-chapter-banner"}]
154
- }
155
- }else setImageKeys([...imageKeys,{type:"no-chapter-banner"}])
156
- }else setImageKeys([...imageKeys,{type:"no-chapter-banner"}])
157
- }
158
- setAddChapter(false)
159
- setIsLoading(false)
160
- temp_loading.current = false
161
- is_adding.current.state = false
162
- })()
163
  }
164
- },100)
165
- },[imageKeys, addChapter])
 
 
166
 
 
 
 
 
 
167
 
168
  return (<>
169
  {isError.state
@@ -246,7 +204,7 @@ const Index = ({}:any) => {
246
  >{isError.text}</Text>
247
  </View>
248
  </View>
249
- : <>{!firstLoading
250
  ? <>
251
  <View
252
 
@@ -257,47 +215,16 @@ const Index = ({}:any) => {
257
  zIndex:0
258
  }}
259
  >
260
- <FlashList
261
- data={imageKeys}
262
- renderItem={({item,index}:any) => {
263
- if (item.type === "image"){
264
- item.image_data = images.current[item.value].data
265
- item.layout = images.current[item.value].layout
266
- }
267
- return <ChapterImage key={index} item={item} zoom={zoom} showOptions={showOptions} setShowOptions={setShowOptions}/>
268
- }}
269
- estimatedItemSize={StaticDimensions.height}
270
- extraData={{zoom:zoom,showOptions:showOptions,setShowOptions:setShowOptions}}
271
  onEndReachedThreshold={0.5}
272
- onEndReached={()=>{
273
- setAddChapter(true)
274
- }}
275
- onViewableItemsChanged={async ({ viewableItems, changed }) => {
276
- const expect_chapter_idx = [CHAPTER_IDX.current + 1, CHAPTER_IDX.current - 1]
277
- const current_count = viewableItems.filter((data:any) => data.item.idx === CHAPTER_IDX.current).length
278
- const existed_count = viewableItems.filter((data:any) => expect_chapter_idx.includes(data.item.idx)).length
279
-
280
- if (current_count || existed_count){
281
- const choose_idx = current_count > existed_count ? CHAPTER_IDX.current : viewableItems.find((data:any) => expect_chapter_idx.includes(data.item.idx))?.item.idx
282
- if (choose_idx === CHAPTER_IDX.current) return
283
- const stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,choose_idx, {exclude_fields:["data"]})
284
- setChapterInfo({
285
- chapter_id: stored_chapter?.id,
286
- chapter_idx: stored_chapter?.id,
287
- title: stored_chapter?.title,
288
- })
289
- const stored_comic = await ComicStorage.getByID(SOURCE, COMIC_ID)
290
- if (stored_comic.history.idx && choose_idx > stored_comic.history.idx) {
291
- await ComicStorage.updateHistory(SOURCE, COMIC_ID, {idx:stored_chapter?.idx,id:stored_chapter?.id,title:stored_chapter?.title})
292
- }
293
- router.setParams({idx:choose_idx})
294
- CHAPTER_IDX.current = choose_idx
295
- }
296
-
297
-
298
- }}
299
  />
300
- {isLoading && (
301
  <View
302
  style={{
303
  display:"flex",
 
42
  const {apiBaseContext, setApiBaseContext}:any = useContext(CONTEXT)
43
 
44
  const [isError,setIsError]:any = useState({state:false,text:""})
45
+ const [isLoading, setIsLoading]:any = useState(true)
46
  const [chapterInfo, setChapterInfo]:any = useState({})
47
  const [showOptions, setShowOptions]:any = useState({type:"general",state:false})
48
+ const [DATA, SET_DATA]:any = useState([])
49
+ const [isAdding, setIsAdding]:any = useState(false)
 
50
  const [zoom, setZoom]:any = useState(0)
51
 
 
 
 
 
52
  const CHAPTER_IDX = useRef(Number(useLocalSearchParams().idx as string));
53
 
54
 
 
62
  setIsError({state:true,text:"Invalid source, comic id or chapter idx!"})
63
  return
64
  }
65
+
66
  const stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,CHAPTER_IDX.current, {exclude_fields:["data"]})
67
  if (stored_chapter?.data_state !== "completed"){
68
  setIsError({state:true,text:"Chapter is not download yet!"})
 
77
  const stored_chapter_zoom = await Storage.get("CHAPTER_ZOOM") || 0
78
  setZoom(stored_chapter_zoom)
79
 
80
+ const chapter_current_data = await get_chapter(SOURCE,COMIC_ID,CHAPTER_IDX.current)
 
 
 
 
 
 
 
81
 
82
+ SET_DATA(chapter_current_data)
83
+ setIsLoading(false)
 
 
 
 
 
84
  })()},[])
85
 
86
 
87
  useFocusEffect(useCallback(() => {
88
  return () => {
89
+ SET_DATA([])
 
 
90
  }
91
  },[]))
92
 
93
+ const renderItem = useCallback(({item,index}:any) => {
94
+ return <ChapterImage key={index} item={item} zoom={zoom} showOptions={showOptions} setShowOptions={setShowOptions}/>
95
+ },[zoom,showOptions,setShowOptions])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
+ const onViewableItemsChanged = useCallback(async ({viewableItems, changed}:any) => {
98
+ const expect_chapter_idx = [CHAPTER_IDX.current + 1, CHAPTER_IDX.current - 1]
99
+ const current_count = viewableItems.filter((data:any) => data.item.chapter_idx === CHAPTER_IDX.current).length
100
+ const existed_count = viewableItems.filter((data:any) => expect_chapter_idx.includes(data.item.chapter_idx)).length
101
+
102
+ if (current_count || existed_count){
103
+ const choose_idx = current_count > existed_count ? CHAPTER_IDX.current : viewableItems.find((data:any) => expect_chapter_idx.includes(data.item.chapter_idx))?.item.idx
104
+ if (choose_idx === CHAPTER_IDX.current) return
105
+ const stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,choose_idx, {exclude_fields:["data"]})
106
+ setChapterInfo({
107
+ chapter_id: stored_chapter?.id,
108
+ chapter_idx: stored_chapter?.id,
109
+ title: stored_chapter?.title,
110
+ })
111
+ const stored_comic = await ComicStorage.getByID(SOURCE, COMIC_ID)
112
+ if (stored_comic.history.idx && choose_idx > stored_comic.history.idx) {
113
+ await ComicStorage.updateHistory(SOURCE, COMIC_ID, {idx:stored_chapter?.idx,id:stored_chapter?.id,title:stored_chapter?.title})
 
 
114
  }
115
+ router.setParams({idx:choose_idx})
116
+ CHAPTER_IDX.current = choose_idx
117
+ }
118
+ },[])
119
 
120
+ const onEndReached = useCallback(async () => {
121
+ const chapter_current_data = await get_chapter(SOURCE,COMIC_ID,CHAPTER_IDX.current+1)
122
+
123
+ SET_DATA([...DATA,...chapter_current_data])
124
+ },[DATA])
125
 
126
  return (<>
127
  {isError.state
 
204
  >{isError.text}</Text>
205
  </View>
206
  </View>
207
+ : <>{!isLoading
208
  ? <>
209
  <View
210
 
 
215
  zIndex:0
216
  }}
217
  >
218
+ <FlatList
219
+ data={DATA}
220
+ renderItem={renderItem}
 
 
 
 
 
 
 
 
221
  onEndReachedThreshold={0.5}
222
+ windowSize={21}
223
+ ItemSeparatorComponent={undefined}
224
+ onEndReached={onEndReached}
225
+ onViewableItemsChanged={onViewableItemsChanged}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  />
227
+ {isAdding && (
228
  <View
229
  style={{
230
  display:"flex",
frontend/app/read/components/chapter_image.tsx CHANGED
@@ -15,6 +15,7 @@ import NetInfo from "@react-native-community/netinfo";
15
  import JSZip from 'jszip';
16
 
17
  import ChapterStorage from '@/constants/module/storages/chapter_storage';
 
18
  import Image from '@/components/Image';
19
  import {CONTEXT} from '@/constants/module/context';
20
  import {blobToBase64, base64ToBlob} from "@/constants/module/file_manager";
@@ -30,111 +31,171 @@ const ChapterImage = ({item, zoom, showOptions,setShowOptions}:any)=>{
30
  const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
31
  const {showCloudflareTurnstileContext, setShowCloudflareTurnstileContext}:any = useContext(CONTEXT)
32
 
 
 
33
 
34
- return (
35
- <Pressable
36
- onPress={()=>{setShowOptions({type:"general",state:!showOptions.state})}}
37
- style={{
38
- display:"flex",
39
- width:"100%",
40
- height:"auto",
41
- borderWidth:0,
42
- alignItems:"center",
43
- }}
44
- >
45
- {item.type === "image" && (
46
 
47
- <Image source={{type:"base64",data:item.image_data}}
48
- contentFit="contain"
49
- style={{
50
- width:Dimensions.width > 720
51
- ? 0.8 * Dimensions.width * (1 - zoom / 100)
52
- : `${100 - zoom}%`,
53
- aspectRatio: item.layout.width / item.layout.height,
54
- }}
55
- onLoadEnd={()=>{
56
- // console.log("CLEANED")
57
- // delete images.current[image_key]
58
- }}
59
- />
60
- )}
61
- {item.type === "chapter-info-banner" && (
62
- <View
63
- style={{
64
- display:"flex",
65
- flexDirection:"column",
66
- alignItems:"center",
67
- gap:12,
68
- width:Dimensions.width > 720
69
- ? 0.8 * Dimensions.width * (1 - zoom / 100)
70
- : `${100 - zoom}%`,
71
- height:"auto",
72
- backgroundColor:"black",
73
- padding:16,
74
- }}
75
- >
76
- <Text selectable={false}
77
- numberOfLines={1}
78
- style={{
79
- color:Theme[themeTypeContext].text_color,
80
- fontSize:(0.03 * ((Dimensions.width+Dimensions.height)/2)) * (1 - zoom/100),
81
- fontFamily:"roboto-bold",
82
- }}
83
- >
84
- End: {item.value.last}
85
- </Text>
86
 
87
- <Text selectable={false}
88
- numberOfLines={1}
89
- style={{
90
- color:Theme[themeTypeContext].text_color,
91
- fontSize:(0.03 * ((Dimensions.width+Dimensions.height)/2)) * (1 - zoom/100),
92
- fontFamily:"roboto-bold",
93
- }}
94
- >
95
- Next: {item.value.next}
96
- </Text>
97
 
98
- </View>
99
- )}
100
- {item.type === "no-chapter-banner" && (
101
- <View
102
- style={{
103
- display:"flex",
104
- flexDirection:"column",
105
- alignItems:"center",
106
- gap:12,
107
- width:Dimensions.width > 720
108
- ? 0.8 * Dimensions.width * (1 - zoom / 100)
109
- : `${100 - zoom}%`,
110
- height:"auto",
111
- backgroundColor:"black",
112
- padding:16,
113
- }}
114
- >
115
- <Text selectable={false}
116
- numberOfLines={1}
117
- style={{
118
- color:Theme[themeTypeContext].text_color,
119
- fontSize:(0.03 * ((Dimensions.width+Dimensions.height)/2)) * (1 - zoom/100),
120
- fontFamily:"roboto-bold",
121
- }}
122
- >
123
- No more available chapters on local.
124
- </Text>
125
- <Text selectable={false}
126
- numberOfLines={1}
127
- style={{
128
- color:Theme[themeTypeContext].text_color,
129
- fontSize:(0.03 * ((Dimensions.width+Dimensions.height)/2)) * (1 - zoom/100),
130
- fontFamily:"roboto-bold",
131
- }}
132
- >
133
- You can go back and download more.
134
- </Text>
135
- </View>
136
- )}
137
- </Pressable>)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  }
139
 
140
  export default ChapterImage
 
15
  import JSZip from 'jszip';
16
 
17
  import ChapterStorage from '@/constants/module/storages/chapter_storage';
18
+ import ChapterDataStorage from '@/constants/module/storages/chapter_data_storage';
19
  import Image from '@/components/Image';
20
  import {CONTEXT} from '@/constants/module/context';
21
  import {blobToBase64, base64ToBlob} from "@/constants/module/file_manager";
 
31
  const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
32
  const {showCloudflareTurnstileContext, setShowCloudflareTurnstileContext}:any = useContext(CONTEXT)
33
 
34
+ const [isReady, setIsReady] = useState(false);
35
+ const [isError, setIsError] = useState({state:false,text:""});
36
 
37
+ const image = useRef<any>(null);
38
+ const image_layout = useRef<any>(null);
 
 
 
 
 
 
 
 
 
 
39
 
40
+ useEffect(()=>{(async () => {
41
+ if (item.type === "page"){
42
+ const store_chapter_image_data = await ChapterDataStorage.get(item.id)
43
+ if (store_chapter_image_data){
44
+ image.current = {type:"blob",data:store_chapter_image_data.data}
45
+ image_layout.current = store_chapter_image_data.layout
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
+ setIsReady(true)
48
+ }else{
49
+ setIsError({state:true,text:"Image not found!"})
50
+ }
51
+ }else setIsReady(true)
52
+
53
+ })()},[])
 
 
 
54
 
55
+
56
+ return ( <Pressable
57
+ onPress={()=>{setShowOptions({type:"general",state:!showOptions.state})}}
58
+ style={{
59
+ display:"flex",
60
+ width:"100%",
61
+ height:"auto",
62
+ borderWidth:0,
63
+ alignItems:"center",
64
+ }}
65
+ >
66
+ <>{isReady
67
+ ? (<>
68
+ {item.type === "page" && (
69
+ <Image source={image.current}
70
+ contentFit="contain"
71
+ style={{
72
+ width:Dimensions.width > 720
73
+ ? 0.8 * Dimensions.width * (1 - zoom / 100)
74
+ : `${100 - zoom}%`,
75
+ aspectRatio: image_layout.current.width / image_layout.current.height,
76
+ }}
77
+ onLoadEnd={()=>{
78
+ }}
79
+ />
80
+ )}
81
+ {item.type === "chapter-info-banner" && (
82
+ <View
83
+ style={{
84
+ display:"flex",
85
+ flexDirection:"column",
86
+ alignItems:"center",
87
+ gap:12,
88
+ width:Dimensions.width > 720
89
+ ? 0.8 * Dimensions.width * (1 - zoom / 100)
90
+ : `${100 - zoom}%`,
91
+ height:"auto",
92
+ backgroundColor:"black",
93
+ padding:16,
94
+ }}
95
+ >
96
+ <Text selectable={false}
97
+ numberOfLines={1}
98
+ style={{
99
+ color:Theme[themeTypeContext].text_color,
100
+ fontSize:(0.03 * ((Dimensions.width+Dimensions.height)/2)) * (1 - zoom/100),
101
+ fontFamily:"roboto-bold",
102
+ }}
103
+ >
104
+ End: {item.value.last}
105
+ </Text>
106
+
107
+ <Text selectable={false}
108
+ numberOfLines={1}
109
+ style={{
110
+ color:Theme[themeTypeContext].text_color,
111
+ fontSize:(0.03 * ((Dimensions.width+Dimensions.height)/2)) * (1 - zoom/100),
112
+ fontFamily:"roboto-bold",
113
+ }}
114
+ >
115
+ Next: {item.value.next}
116
+ </Text>
117
+
118
+ </View>
119
+ )}
120
+ {item.type === "no-chapter-banner" && (
121
+ <View
122
+ style={{
123
+ display:"flex",
124
+ flexDirection:"column",
125
+ alignItems:"center",
126
+ gap:12,
127
+ width:Dimensions.width > 720
128
+ ? 0.8 * Dimensions.width * (1 - zoom / 100)
129
+ : `${100 - zoom}%`,
130
+ height:"auto",
131
+ backgroundColor:"black",
132
+ padding:16,
133
+ }}
134
+ >
135
+ <Text selectable={false}
136
+ numberOfLines={1}
137
+ style={{
138
+ color:Theme[themeTypeContext].text_color,
139
+ fontSize:(0.03 * ((Dimensions.width+Dimensions.height)/2)) * (1 - zoom/100),
140
+ fontFamily:"roboto-bold",
141
+ }}
142
+ >
143
+ No more available chapters on local.
144
+ </Text>
145
+ <Text selectable={false}
146
+ numberOfLines={1}
147
+ style={{
148
+ color:Theme[themeTypeContext].text_color,
149
+ fontSize:(0.03 * ((Dimensions.width+Dimensions.height)/2)) * (1 - zoom/100),
150
+ fontFamily:"roboto-bold",
151
+ }}
152
+ >
153
+ You can go back and download more.
154
+ </Text>
155
+ </View>
156
+ )}
157
+ </>)
158
+ : (
159
+ <View
160
+ style={{
161
+ display:"flex",
162
+ justifyContent:"center",
163
+ alignItems:"center",
164
+ backgroundColor:Theme[themeTypeContext].background_color,
165
+ width:Dimensions.width > 720
166
+ ? 0.8 * Dimensions.width * (1 - zoom / 100)
167
+ : `${100 - zoom}%`,
168
+ height: Dimensions.height * 0.75
169
+ }}
170
+ >
171
+ {isError.state
172
+ ? (
173
+ <View
174
+ style={{
175
+ display:'flex',
176
+ flexDirection:"column",
177
+ justifyContent:"center",
178
+ alignItems:"center",
179
+ width:"100%",
180
+ height:"100%",
181
+ gap:12,
182
+ }}
183
+ >
184
+
185
+ <Icon source={"alert-circle"} size={25} color={"red"}/>
186
+ <Text style={{color:"white",fontSize:12,fontFamily:"roboto-bold"}}>{isError.text}</Text>
187
+ </View>
188
+ )
189
+ : (<ActivityIndicator animating={true}/>)
190
+ }
191
+
192
+
193
+ </View>
194
+ )
195
+ }</>
196
+
197
+ </Pressable>
198
+ )
199
  }
200
 
201
  export default ChapterImage
frontend/app/read/modules/get_chapter.tsx CHANGED
@@ -9,53 +9,25 @@ export const get_chapter = async (
9
  COMIC_ID:string | string[],
10
  CHAPTER_IDX:number,
11
  ) => {
12
- // return
13
  console.log(SOURCE,COMIC_ID,CHAPTER_IDX)
14
 
15
- const stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,CHAPTER_IDX)
16
- if (stored_chapter?.data_state === "completed"){
17
- const zip = new JSZip();
18
- const file_keys:Array<any> = []
19
- const files: { [key: string]: any } = {};
20
-
21
- if (stored_chapter?.data.type === "blob"){
22
- const zipContent = await zip.loadAsync(stored_chapter?.data.value);
23
- for (const fileName in zipContent.files) {
24
- if (zipContent.files[fileName].dir) {
25
- continue; // Skip directories
26
- }
27
- const fileData = await zipContent.files[fileName].async('base64');
28
- file_keys.push({type:"image", idx:CHAPTER_IDX, id:Number(fileName.split(".")[0]), value: `${CHAPTER_IDX}-${fileName}`})
29
- files[`${CHAPTER_IDX}-${fileName}`] = {
30
- layout: await getImageLayout("data:image/png;base64," + fileData),
31
- data: "data:image/png;base64," + fileData
32
- };
33
-
34
- }
35
-
36
- }else if (stored_chapter?.data.type === "file_path"){
37
-
38
- const base64String = await FileSystem.readAsStringAsync(stored_chapter?.data.value, {
39
- encoding: FileSystem.EncodingType.Base64,
40
- });
41
-
42
- const zipContent = await zip.loadAsync(base64String, {base64: true});
43
 
44
- for (const fileName in zipContent.files) {
45
- if (zipContent.files[fileName].dir) {
46
- continue; // Skip directories
47
- }
48
- const fileData = await zipContent.files[fileName].async('base64');
49
- file_keys.push({type:"image", idx:CHAPTER_IDX, id:Number(fileName.split(".")[0]), value: `${CHAPTER_IDX}-${fileName}`})
50
- files[`${CHAPTER_IDX}-${fileName}`] = {
51
- layout: await getImageLayout("data:image/png;base64," + fileData),
52
- data: "data:image/png;base64," + fileData
53
- };
54
- }
55
  }
56
-
57
- file_keys.sort((a, b) => Number(a.id - b.id))
58
- console.log("HUH?",file_keys)
59
- return {file_keys: file_keys,files:files}
 
 
 
 
60
  }
61
  }
 
9
  COMIC_ID:string | string[],
10
  CHAPTER_IDX:number,
11
  ) => {
12
+
13
  console.log(SOURCE,COMIC_ID,CHAPTER_IDX)
14
 
15
+ const DATA = []
16
+
17
+ const current_stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,CHAPTER_IDX)
18
+ const next_stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,CHAPTER_IDX+1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
+ if (current_stored_chapter?.data_state === "completed" && current_stored_chapter?.max_page) {
21
+ for (let i = 1; i < current_stored_chapter.max_page; i++) {
22
+ DATA.push({type:"page",id:`${SOURCE}-${COMIC_ID}-${CHAPTER_IDX}-${i}`, chapter_idx: CHAPTER_IDX})
 
 
 
 
 
 
 
 
23
  }
24
+ if (next_stored_chapter) {
25
+ DATA.push({type:"chapter-info-banner", value:{last:current_stored_chapter.title, next:next_stored_chapter.title}})
26
+ }else{
27
+ DATA.push({type:"no-chapter-banner"})
28
+ }
29
+ return DATA
30
+ }else{
31
+ return []
32
  }
33
  }
frontend/app/view/[source]/[comic_id].tsx CHANGED
@@ -190,7 +190,7 @@ const Index = ({}:any) => {
190
  for (const [key, value] of Object.entries(stored_comic.info)) {
191
  DATA[key] = value
192
  }
193
- DATA["chapters"] = await ChapterStorage.getAll(`${SOURCE}-${ID}`,{exclude_field:["data","item"]})
194
 
195
  SET_CONTENT(DATA)
196
  setIsLoading(false)
@@ -308,7 +308,7 @@ const Index = ({}:any) => {
308
 
309
  onPress={()=>{
310
  if (router.canGoBack()) router.back()
311
- else router.navigate("/explore")
312
  }}
313
  >
314
  <Icon source={"arrow-left-thin"} size={((Dimensions.width+Dimensions.height)/2)*0.045} color={Theme[themeTypeContext].icon_color}/>
@@ -362,7 +362,7 @@ const Index = ({}:any) => {
362
  }}
363
 
364
  onPress={(async ()=>{
365
- await Clipboard.setStringAsync(`${apiBaseContext}/view/${SOURCE}/${ID}/`)
366
  Toast.show({
367
  type: 'info',
368
  text1: '📋 Copied to your clipboard.',
 
190
  for (const [key, value] of Object.entries(stored_comic.info)) {
191
  DATA[key] = value
192
  }
193
+ DATA["chapters"] = await ChapterStorage.getAll(`${SOURCE}-${ID}`,{exclude_field:["item"]})
194
 
195
  SET_CONTENT(DATA)
196
  setIsLoading(false)
 
308
 
309
  onPress={()=>{
310
  if (router.canGoBack()) router.back()
311
+ else router.replace("/explore")
312
  }}
313
  >
314
  <Icon source={"arrow-left-thin"} size={((Dimensions.width+Dimensions.height)/2)*0.045} color={Theme[themeTypeContext].icon_color}/>
 
362
  }}
363
 
364
  onPress={(async ()=>{
365
+ await Clipboard.setStringAsync(`https://comicmtl.netlify.app/view/${SOURCE}/${ID}/`)
366
  Toast.show({
367
  type: 'info',
368
  text1: '📋 Copied to your clipboard.',
frontend/app/view/componenets/chapter.tsx CHANGED
@@ -64,13 +64,13 @@ const ChapterComponent = ({
64
  set_is_net_connected(net_info.isConnected)
65
 
66
  setStyles(__styles(themeTypeContext,Dimensions))
67
- const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id, {exclude_fields:["data"]})
68
  if (stored_chapter?.data_state === "completed") set_is_saved(true)
69
  else set_is_saved(false)
70
  })()}, [])
71
 
72
  useEffect(()=>{(async () => {
73
- const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id, {exclude_fields:["data"]})
74
  if (stored_chapter?.data_state === "completed") set_is_saved(true)
75
  else set_is_saved(false)
76
  })()},[page,sort])
@@ -130,7 +130,7 @@ const ChapterComponent = ({
130
  padding:5,
131
  }}
132
  onPress={async () => {
133
- const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id, {exclude_fields:["data"]})
134
  if (stored_chapter?.data_state === "completed") {
135
  const stored_comic = await ComicStorage.getByID(SOURCE,ID)
136
  if (!stored_comic.history.idx || chapter.idx > stored_comic.history.idx) await ComicStorage.updateHistory(SOURCE,ID,{idx:chapter.idx, id:chapter.id, title:chapter.title})
@@ -205,8 +205,8 @@ const ChapterComponent = ({
205
  <>{chapterRequested[chapter.id]?.state === "ready"
206
  && <>{chapterToDownload[chapter.id]?.state === "downloading"
207
  ? <CircularProgress
208
- value={downloadProgress[chapter.id]?.current}
209
- maxValue={downloadProgress[chapter.id]?.total}
210
  radius={((Dimensions.width+Dimensions.height)/2)*0.0225}
211
  inActiveStrokeColor={Theme[themeTypeContext].border_color}
212
 
@@ -220,6 +220,8 @@ const ChapterComponent = ({
220
  textAlign:"center",
221
  }}
222
  onAnimationComplete={async ()=>{
 
 
223
  const stored_chapter_requested = (await ComicStorage.getByID(SOURCE,ID)).chapter_requested
224
  const new_chapter_requested = stored_chapter_requested.filter((item:any) => item.chapter_id !== chapter.id);
225
  await ComicStorage.updateChapterQueue(SOURCE,ID,new_chapter_requested)
@@ -237,6 +239,7 @@ const ChapterComponent = ({
237
 
238
  set_is_saved(true)
239
  isDownloading.current = false
 
240
  }}
241
  />
242
  : <ActivityIndicator animating={true} color={"green"} />
 
64
  set_is_net_connected(net_info.isConnected)
65
 
66
  setStyles(__styles(themeTypeContext,Dimensions))
67
+ const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id)
68
  if (stored_chapter?.data_state === "completed") set_is_saved(true)
69
  else set_is_saved(false)
70
  })()}, [])
71
 
72
  useEffect(()=>{(async () => {
73
+ const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id)
74
  if (stored_chapter?.data_state === "completed") set_is_saved(true)
75
  else set_is_saved(false)
76
  })()},[page,sort])
 
130
  padding:5,
131
  }}
132
  onPress={async () => {
133
+ const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id)
134
  if (stored_chapter?.data_state === "completed") {
135
  const stored_comic = await ComicStorage.getByID(SOURCE,ID)
136
  if (!stored_comic.history.idx || chapter.idx > stored_comic.history.idx) await ComicStorage.updateHistory(SOURCE,ID,{idx:chapter.idx, id:chapter.id, title:chapter.title})
 
205
  <>{chapterRequested[chapter.id]?.state === "ready"
206
  && <>{chapterToDownload[chapter.id]?.state === "downloading"
207
  ? <CircularProgress
208
+ value={downloadProgress[chapter.id].progress}
209
+ maxValue={downloadProgress[chapter.id].total}
210
  radius={((Dimensions.width+Dimensions.height)/2)*0.0225}
211
  inActiveStrokeColor={Theme[themeTypeContext].border_color}
212
 
 
220
  textAlign:"center",
221
  }}
222
  onAnimationComplete={async ()=>{
223
+ if (downloadProgress[chapter.id].progress !== downloadProgress[chapter.id].total) return
224
+
225
  const stored_chapter_requested = (await ComicStorage.getByID(SOURCE,ID)).chapter_requested
226
  const new_chapter_requested = stored_chapter_requested.filter((item:any) => item.chapter_id !== chapter.id);
227
  await ComicStorage.updateChapterQueue(SOURCE,ID,new_chapter_requested)
 
239
 
240
  set_is_saved(true)
241
  isDownloading.current = false
242
+ console.log("DONE!",downloadProgress[chapter.id])
243
  }}
244
  />
245
  : <ActivityIndicator animating={true} color={"green"} />
frontend/app/view/modules/content.tsx CHANGED
@@ -3,12 +3,15 @@ import JSZip from 'jszip';
3
  import * as FileSystem from 'expo-file-system';
4
  import { Platform } from 'react-native';
5
 
 
6
  import translator from '@/constants/module/translator';
7
  import Storage from '@/constants/module/storages/storage';
8
  import ComicStorage from '@/constants/module/storages/comic_storage';
9
  import ImageCacheStorage from '@/constants/module/storages/image_cache_storage';
10
  import ChapterStorage from '@/constants/module/storages/chapter_storage';
 
11
  import {blobToBase64} from '@/constants/module/file_manager';
 
12
 
13
 
14
 
@@ -180,9 +183,7 @@ export const download_chapter = async (
180
  const [chapter_id, request_info]:any = Object.entries(chapterToDownload)[0];
181
 
182
  var progress_lenth:number = 0
183
- var total_length:number = 100
184
- setDownloadProgress({...downloadProgress, [chapter_id]:{progress:progress_lenth, total:total_length}})
185
- setChapterToDownload({...chapterToDownload,[chapter_id]:{...chapterToDownload[chapter_id],state:"downloading"}})
186
 
187
  await axios({
188
  method: 'post',
@@ -201,9 +202,16 @@ export const download_chapter = async (
201
  onDownloadProgress: (progressEvent) => {
202
  const _total_length = progressEvent.total
203
  if (total_length !== undefined && progressEvent.loaded !== progress_lenth) {
204
- total_length = _total_length as number + 5
205
- progress_lenth = progressEvent.loaded;
206
- setDownloadProgress({...downloadProgress, [chapter_id]:{progress:progress_lenth, total:total_length}})
 
 
 
 
 
 
 
207
  }
208
  },
209
  timeout: 600000,
@@ -211,23 +219,34 @@ export const download_chapter = async (
211
  }).then(async (response) => {
212
  const DATA = response.data
213
  if (Platform.OS === "web"){
214
- await ChapterStorage.update(`${source}-${comic_id}`,chapter_id,{type:"blob", value:DATA}, "completed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  }else{
216
- const chapter_dir = FileSystem.documentDirectory + "ComicMTL/" + `${source}/` + `${comic_id}/` + `chapters/`;
217
- const dirInfo = await FileSystem.getInfoAsync(chapter_dir);
218
- if (!dirInfo.exists) await FileSystem.makeDirectoryAsync(chapter_dir, { intermediates: true });
219
- await FileSystem.writeAsStringAsync(chapter_dir + `${request_info.chapter_idx}.zip`, (await blobToBase64(DATA)).split(',')[1], {
220
- encoding: FileSystem.EncodingType.Base64,
221
- });
222
 
223
- await ChapterStorage.update(`${source}-${comic_id}`,chapter_id,{type:"file_path", value:chapter_dir + `${request_info.chapter_idx}.zip`}, "completed")
224
  }
225
 
226
-
227
-
228
-
229
-
230
- setDownloadProgress({...downloadProgress, [chapter_id]:{progress:progress_lenth+5, total:total_length}})
231
 
232
 
233
 
 
3
  import * as FileSystem from 'expo-file-system';
4
  import { Platform } from 'react-native';
5
 
6
+
7
  import translator from '@/constants/module/translator';
8
  import Storage from '@/constants/module/storages/storage';
9
  import ComicStorage from '@/constants/module/storages/comic_storage';
10
  import ImageCacheStorage from '@/constants/module/storages/image_cache_storage';
11
  import ChapterStorage from '@/constants/module/storages/chapter_storage';
12
+ import ChapterDataStorage from '@/constants/module/storages/chapter_data_storage';
13
  import {blobToBase64} from '@/constants/module/file_manager';
14
+ import { getImageLayout } from '@/constants/module/file_manager';
15
 
16
 
17
 
 
183
  const [chapter_id, request_info]:any = Object.entries(chapterToDownload)[0];
184
 
185
  var progress_lenth:number = 0
186
+ var total_length:number = 0
 
 
187
 
188
  await axios({
189
  method: 'post',
 
202
  onDownloadProgress: (progressEvent) => {
203
  const _total_length = progressEvent.total
204
  if (total_length !== undefined && progressEvent.loaded !== progress_lenth) {
205
+ total_length = (_total_length as number) + (_total_length as number)*0.25
206
+
207
+ if (progress_lenth === 0) {
208
+ setDownloadProgress({...downloadProgress, [chapter_id]:{progress:progress_lenth, total:total_length}})
209
+ setChapterToDownload({...chapterToDownload,[chapter_id]:{...chapterToDownload[chapter_id],state:"downloading"}})
210
+ progress_lenth = progressEvent.loaded;
211
+ }else{
212
+ progress_lenth = progressEvent.loaded;
213
+ setDownloadProgress({...downloadProgress, [chapter_id]:{progress:progress_lenth, total:total_length}})
214
+ }
215
  }
216
  },
217
  timeout: 600000,
 
219
  }).then(async (response) => {
220
  const DATA = response.data
221
  if (Platform.OS === "web"){
222
+
223
+ const zip = new JSZip();
224
+ const zipContent = await zip.loadAsync(DATA);
225
+ let page = 0;
226
+ for (const fileName of Object.keys(zipContent.files).sort((a,b) => parseInt(a, 10) - parseInt(b, 10))) {
227
+ if (zipContent.files[fileName].dir) {
228
+ continue; // Skip directories
229
+ }
230
+ page += 1
231
+ const fileData = await zipContent.files[fileName].async('blob');
232
+ const layout = await getImageLayout(await blobToBase64(fileData, "image/png"));
233
+ await ChapterDataStorage.store(`${source}-${comic_id}-${request_info.chapter_idx}-${page}`,comic_id,request_info.chapter_idx, fileData, layout)
234
+
235
+ }
236
+
237
+ await ChapterStorage.update(`${source}-${comic_id}`,chapter_id, "completed", page)
238
  }else{
239
+ // const chapter_dir = FileSystem.documentDirectory + "ComicMTL/" + `${source}/` + `${comic_id}/` + `chapters/`;
240
+ // const dirInfo = await FileSystem.getInfoAsync(chapter_dir);
241
+ // if (!dirInfo.exists) await FileSystem.makeDirectoryAsync(chapter_dir, { intermediates: true });
242
+ // await FileSystem.writeAsStringAsync(chapter_dir + `${request_info.chapter_idx}.zip`, (await blobToBase64(DATA)).split(',')[1], {
243
+ // encoding: FileSystem.EncodingType.Base64,
244
+ // });
245
 
246
+ // await ChapterStorage.update(`${source}-${comic_id}`,chapter_id,{type:"file_path", value:chapter_dir + `${request_info.chapter_idx}.zip`}, "completed")
247
  }
248
 
249
+ setDownloadProgress({...downloadProgress, [chapter_id]:{progress:total_length, total:total_length}})
 
 
 
 
250
 
251
 
252
 
frontend/components/Image.tsx CHANGED
@@ -21,7 +21,7 @@ const Image = ({source, style, onError, contentFit, transition, onLoad, onLoadEn
21
  (async ()=>{
22
  if(source.hasOwnProperty("type")){
23
  if (source.type === "blob"){
24
- imageData.current = {uri:await blobToBase64(source.data)}
25
  setImageLoaded(true)
26
  }else if (source.type === "base64"){
27
  imageData.current = {uri:source.data}
 
21
  (async ()=>{
22
  if(source.hasOwnProperty("type")){
23
  if (source.type === "blob"){
24
+ imageData.current = {uri:await blobToBase64(source.data,"image/png")}
25
  setImageLoaded(true)
26
  }else if (source.type === "base64"){
27
  imageData.current = {uri:source.data}
frontend/constants/module/file_manager.tsx CHANGED
@@ -1,12 +1,18 @@
1
  import { Image as RNImage } from 'react-native';
2
 
3
- export const blobToBase64 = (blob: Blob): Promise<string> => {
4
- return new Promise((resolve, reject) => {
5
  const reader = new FileReader();
6
- reader.onloadend = () => resolve(reader.result as string);
 
 
 
 
 
 
7
  reader.onerror = reject;
8
  reader.readAsDataURL(blob);
9
- });
10
  };
11
 
12
  export const base64ToBlob = (base64: string, mimetype: string = 'image/png'): Blob => {
 
1
  import { Image as RNImage } from 'react-native';
2
 
3
+ export const blobToBase64 = (blob: Blob, mimetype: string = ""): Promise<string> => {
4
+ return new Promise((resolve, reject) => {
5
  const reader = new FileReader();
6
+ reader.onloadend = () => {
7
+ const base64String = reader.result as string;
8
+ if (mimetype){
9
+ const base64WithMimeType = `data:${mimetype};base64,${base64String.split(',')[1]}`;
10
+ resolve(base64WithMimeType);
11
+ }else resolve(base64String);
12
+ };
13
  reader.onerror = reject;
14
  reader.readAsDataURL(blob);
15
+ });
16
  };
17
 
18
  export const base64ToBlob = (base64: string, mimetype: string = 'image/png'): Blob => {
frontend/constants/module/storages/chapter_data_storage.tsx ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Platform } from "react-native";
2
+ import * as SQLite from 'expo-sqlite';
3
+
4
+ const DATABASE_NAME = 'ChapterDataStorageDB'
5
+
6
+ class Chapter_Data_Storage_Web {
7
+ private static dbPromise: Promise<IDBDatabase>;
8
+
9
+ private static getDB(): Promise<IDBDatabase> {
10
+ if (!this.dbPromise) {
11
+ this.dbPromise = new Promise((resolve, reject) => {
12
+ const request = indexedDB.open(DATABASE_NAME, 1);
13
+
14
+ request.onupgradeneeded = (event) => {
15
+ const db = (event.target as IDBOpenDBRequest).result;
16
+ const store = db.createObjectStore('dataStore', { keyPath: 'id' });
17
+ store.createIndex('comic_id', 'comic_id', { unique: false });
18
+ store.createIndex('chapter_idx', 'chapter_idx', { unique: false });
19
+ };
20
+
21
+ request.onsuccess = () => {
22
+ resolve(request.result);
23
+ };
24
+
25
+ request.onerror = () => {
26
+ reject(request.error);
27
+ };
28
+ });
29
+ }
30
+ return this.dbPromise;
31
+ }
32
+
33
+ static async store(id: string, comic_id:string, chapter_idx: number, data:any, layout:any ): Promise<void> {
34
+ const db = await this.getDB();
35
+ return new Promise((resolve, reject) => {
36
+ const transaction = db.transaction('dataStore', 'readwrite');
37
+ const store = transaction.objectStore('dataStore');
38
+ const request = store.put({ id, comic_id, chapter_idx, data, layout });
39
+
40
+ request.onsuccess = () => {
41
+ resolve();
42
+ };
43
+
44
+ request.onerror = () => {
45
+ reject(request.error);
46
+ };
47
+ });
48
+ }
49
+
50
+ static async get(id: string): Promise<{id: string, comic_id: string, chapter_idx: number, data: any } | null> {
51
+ const db = await this.getDB();
52
+ return new Promise((resolve, reject) => {
53
+ const transaction = db.transaction('dataStore', 'readonly');
54
+ const store = transaction.objectStore('dataStore');
55
+ const request = store.get(id); // Query by id
56
+
57
+ request.onsuccess = () => {
58
+ const data = request.result;
59
+ if (data) {
60
+ resolve(data);
61
+ } else {
62
+ resolve(null);
63
+ }
64
+ };
65
+
66
+ request.onerror = () => {
67
+ console.error('Error retrieving item:', request.error);
68
+ reject(request.error);
69
+ };
70
+ });
71
+ }
72
+ static async removeByID(id: string): Promise<void> {
73
+ const db = await this.getDB();
74
+ return new Promise((resolve, reject) => {
75
+ const transaction = db.transaction('dataStore', 'readwrite');
76
+ const store = transaction.objectStore('dataStore');
77
+ const request = store.delete(id); // Delete the item
78
+ request.onsuccess = () => {
79
+ resolve();
80
+ };
81
+ request.onerror = () => {
82
+ reject(request.error);
83
+ };
84
+ request.onerror = () => {
85
+ reject(request.error);
86
+ };
87
+ });
88
+ }
89
+
90
+ static async removeByComicID(comic_id: string): Promise<void> {
91
+ const db = await this.getDB();
92
+ return new Promise((resolve, reject) => {
93
+ const transaction = db.transaction('dataStore', 'readwrite');
94
+ const store = transaction.objectStore('dataStore');
95
+ const index = store.index('comic_id');
96
+ const request = index.openCursor(comic_id);
97
+
98
+ request.onsuccess = (event) => {
99
+ const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
100
+ if (cursor) {
101
+ cursor.delete();
102
+ cursor.continue();
103
+ } else {
104
+ resolve();
105
+ }
106
+ };
107
+
108
+ request.onerror = () => {
109
+ reject(request.error);
110
+ };
111
+ });
112
+ }
113
+ }
114
+
115
+
116
+ class Chapter_Data_Storage_Native {
117
+ private DATABASE: any;
118
+
119
+ constructor() {
120
+ this.DATABASE = new Promise(async (resolve, reject) => {
121
+ resolve(await SQLite.openDatabaseAsync(DATABASE_NAME));
122
+ });
123
+
124
+ }
125
+
126
+ public async store(source: string, id: string, tag: string, info: any) {
127
+ try {
128
+ const db = await this.DATABASE;
129
+ await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
130
+ await db.runAsync(
131
+ 'INSERT OR REPLACE INTO ComicStorage (source, id, tag, info, chapter_requested, history) VALUES (?, ?, ?, ?, ?, ?);', source, id, tag, JSON.stringify(info), JSON.stringify([]), JSON.stringify({})
132
+ );
133
+ } catch (error) {
134
+ console.log(error);
135
+ }
136
+ }
137
+
138
+ public async getByID(source: string, id: string) {
139
+ try {
140
+ const db = await this.DATABASE;
141
+ // await db.runAsync("DROP TABLE IF EXISTS ComicStorage;");
142
+ await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
143
+ const DATA: any = await db.getFirstAsync(
144
+ 'SELECT * FROM ComicStorage WHERE source = ? AND id = ?;', source, id
145
+ );
146
+ if (DATA) return { id: DATA.id, source: DATA.source, tag: DATA.tag, info: JSON.parse(DATA.info), chapter_requested: JSON.parse(DATA.chapter_requested), history: JSON.parse(DATA.history) };
147
+ else return DATA;
148
+ } catch (error) {
149
+ console.log("[Comic Storage - getByID] error:", error);
150
+ }
151
+ }
152
+
153
+ public async getByTag(tag: string) {
154
+ try {
155
+ const db = await this.DATABASE;
156
+ await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
157
+ const DATA: any = await db.allAsync(
158
+ 'SELECT * FROM ComicStorage WHERE tag = ?;', tag
159
+ );
160
+
161
+ return DATA.map((row: any) => ({ id: row.id, source: row.source, tag: row.tag, info: JSON.parse(row.info), chapter_requested: JSON.parse(row.chapter_requested), history: JSON.parse(row.history) }));
162
+ } catch (error) {
163
+ console.log(error);
164
+ }
165
+ }
166
+
167
+ public async updateChapterQueue(source: string, id: string, chapter_requested: any) {
168
+ try {
169
+ const db = await this.DATABASE;
170
+ await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
171
+ await db.runAsync(
172
+ 'UPDATE ComicStorage SET chapter_requested = ? WHERE source = ? AND id = ?;', JSON.stringify(chapter_requested), source, id
173
+ );
174
+ } catch (error) {
175
+ console.log(error);
176
+ }
177
+ }
178
+
179
+ public async updateInfo(source: string, id: string, info: any) {
180
+ try {
181
+ const db = await this.DATABASE;
182
+ await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
183
+ await db.runAsync(
184
+ 'UPDATE ComicStorage SET info = ? WHERE source = ? AND id = ?;', JSON.stringify(info), source, id
185
+ );
186
+ } catch (error) {
187
+ console.log(error);
188
+ }
189
+ }
190
+
191
+ public async updateHistory(source:string, id: string, history: any): Promise<void> {
192
+ try {
193
+ const db = await this.DATABASE;
194
+ await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
195
+ await db.runAsync(
196
+ 'UPDATE ComicStorage SET history = ? WHERE source = ? AND id = ?;', JSON.stringify(history), source, id
197
+ );
198
+ } catch (error) {
199
+ console.log(error);
200
+ }
201
+ }
202
+
203
+ public async replaceTag(source: string, id: string, new_tag: string) {
204
+ try {
205
+ const db = await this.DATABASE;
206
+ await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
207
+ await db.runAsync(
208
+ 'UPDATE ComicStorage SET tag = ? WHERE source = ? AND id = ?;', new_tag, source, id
209
+ );
210
+ } catch (error) {
211
+ console.log(error);
212
+ }
213
+ }
214
+
215
+ public async removeByID(source: string, id: string) {
216
+ try {
217
+ const db = await this.DATABASE;
218
+ await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
219
+ await db.runAsync(
220
+ 'DELETE FROM ComicStorage WHERE source = ? AND id = ?;', source, id
221
+ );
222
+ } catch (error) {
223
+ console.log(error);
224
+ }
225
+ }
226
+
227
+ public async removeByTag(tag: string) {
228
+ try {
229
+ const db = await this.DATABASE;
230
+ await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
231
+ await db.runAsync(
232
+ 'DELETE FROM ComicStorage WHERE tag = ?;', tag
233
+ );
234
+ } catch (error) {
235
+ console.log(error);
236
+ }
237
+ }
238
+ }
239
+
240
+
241
+
242
+ var ChapterDataStorage:any
243
+ if (Platform.OS === "web") {
244
+ ChapterDataStorage = Chapter_Data_Storage_Web;
245
+ }else{
246
+ ChapterDataStorage = new Chapter_Data_Storage_Native();
247
+ }
248
+
249
+ export default ChapterDataStorage;
250
+
frontend/constants/module/storages/chapter_storage.tsx CHANGED
@@ -8,7 +8,7 @@ import { ensure_safe_table_name } from "../ensure_safe_table_name";
8
  const DATABASE_NAME = 'ChapterDB';
9
 
10
  class Chapter_Storage_Web {
11
- private static DATABASE_VERSION: number = 1;
12
 
13
  private static async openDB(): Promise<IDBDatabase> {
14
  return new Promise((resolve, reject) => {
@@ -137,13 +137,13 @@ class Chapter_Storage_Web {
137
  }
138
 
139
 
140
- public static async add(item: string, idx: number, id: string, title: string, data: any): Promise<void> {
141
  const db = await this.openDB();
142
  return new Promise((resolve, reject) => {
143
  const transaction = db.transaction('dataStore', 'readwrite');
144
  const store = transaction.objectStore('dataStore');
145
 
146
- const request = store.add({ id, item, idx, title, data, data_state:"empty" });
147
 
148
  request.onsuccess = () => {
149
  resolve();
@@ -154,7 +154,7 @@ class Chapter_Storage_Web {
154
  };
155
  });
156
  }
157
- public static async update(item: string, id: string, newData: any, data_state: string): Promise<void> {
158
  const db = await this.openDB();
159
  return new Promise((resolve, reject) => {
160
  const transaction = db.transaction('dataStore', 'readwrite');
@@ -166,8 +166,8 @@ class Chapter_Storage_Web {
166
  const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
167
  if (cursor) {
168
  if (cursor.value.id === id) {
169
- cursor.value.data = newData;
170
  cursor.value.data_state = data_state;
 
171
  const updateRequest = cursor.update(cursor.value);
172
 
173
  updateRequest.onsuccess = () => {
 
8
  const DATABASE_NAME = 'ChapterDB';
9
 
10
  class Chapter_Storage_Web {
11
+ private static DATABASE_VERSION: number = 2;
12
 
13
  private static async openDB(): Promise<IDBDatabase> {
14
  return new Promise((resolve, reject) => {
 
137
  }
138
 
139
 
140
+ public static async add(item: string, idx: number, id: string, title: string): Promise<void> {
141
  const db = await this.openDB();
142
  return new Promise((resolve, reject) => {
143
  const transaction = db.transaction('dataStore', 'readwrite');
144
  const store = transaction.objectStore('dataStore');
145
 
146
+ const request = store.add({ id, item, idx, title, data_state:"", max_page:0 });
147
 
148
  request.onsuccess = () => {
149
  resolve();
 
154
  };
155
  });
156
  }
157
+ public static async update(item: string, id: string, data_state: string, max_page:number): Promise<void> {
158
  const db = await this.openDB();
159
  return new Promise((resolve, reject) => {
160
  const transaction = db.transaction('dataStore', 'readwrite');
 
166
  const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
167
  if (cursor) {
168
  if (cursor.value.id === id) {
 
169
  cursor.value.data_state = data_state;
170
+ cursor.value.max_page = max_page;
171
  const updateRequest = cursor.update(cursor.value);
172
 
173
  updateRequest.onsuccess = () => {
frontend/constants/module/storages/comic_storage.tsx CHANGED
@@ -9,7 +9,7 @@ class Comic_Storage_Web {
9
  private static getDB(): Promise<IDBDatabase> {
10
  if (!this.dbPromise) {
11
  this.dbPromise = new Promise((resolve, reject) => {
12
- const request = indexedDB.open(DATABASE_NAME, 1);
13
 
14
  request.onupgradeneeded = (event) => {
15
  const db = (event.target as IDBOpenDBRequest).result;
 
9
  private static getDB(): Promise<IDBDatabase> {
10
  if (!this.dbPromise) {
11
  this.dbPromise = new Promise((resolve, reject) => {
12
+ const request = indexedDB.open(DATABASE_NAME, 2);
13
 
14
  request.onupgradeneeded = (event) => {
15
  const db = (event.target as IDBOpenDBRequest).result;