@@ -9,8 +9,11 @@ import cls from "../../services/cls.js";
99import log from "../../services/log.js" ;
1010import protectedSessionService from "../../services/protected_session.js" ;
1111import blobService from "../../services/blob.js" ;
12+ import blobStorageService from "../../services/blob-storage.js" ;
13+ import type { Blob } from "../../services/blob-interface.js" ;
1214import type { default as Becca , ConstructorData } from "../becca-interface.js" ;
1315import becca from "../becca.js" ;
16+ import type { BlobContentLocation , BlobRow } from "@triliumnext/commons" ;
1417
1518interface ContentOpts {
1619 forceSave ?: boolean ;
@@ -195,6 +198,11 @@ abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
195198 return ;
196199 }
197200
201+ if ( blobStorageService . hasExternalContentColumns ( ) ) {
202+ const row = sql . getRow < { contentLocation : string } > ( "SELECT contentLocation FROM blobs WHERE blobId = ?" , [ oldBlobId ] ) ;
203+ blobStorageService . deleteExternal ( row ) ;
204+ }
205+
198206 sql . execute ( "DELETE FROM blobs WHERE blobId = ?" , [ oldBlobId ] ) ;
199207 // blobs are not marked as erased in entity_changes, they are just purged completely
200208 // this is because technically every keystroke can create a new blob, and there would be just too many
@@ -225,14 +233,39 @@ abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
225233 return newBlobId ;
226234 }
227235
228- const pojo = {
236+ // Check if we should store this blob externally
237+ const shouldStoreExternally = blobStorageService . shouldStoreExternally ( content ) ;
238+ let contentLocation : BlobContentLocation = "internal" ;
239+ if ( shouldStoreExternally ) {
240+ try {
241+ contentLocation = blobStorageService . saveExternal ( newBlobId , content ) ;
242+ } catch ( error ) {
243+ log . error ( `Failed to store blob ${ newBlobId } externally, falling back to internal storage: ${ error } ` ) ;
244+ contentLocation = "internal" ;
245+ }
246+ }
247+
248+ const contentLength = blobService . getContentLength ( content ) ;
249+
250+ const pojo : BlobRow = {
229251 blobId : newBlobId ,
230- content : content ,
252+ content : contentLocation === 'internal' ? content : null ,
253+ contentLocation,
254+ contentLength,
231255 dateModified : dateUtils . localNowDateTime ( ) ,
232256 utcDateModified : dateUtils . utcNowDateTime ( )
233257 } ;
234258
235- sql . upsert ( "blobs" , "blobId" , pojo ) ;
259+ // external content columns might not be present when applying older migrations
260+ const pojoToSave = blobStorageService . hasExternalContentColumns ( )
261+ ? pojo
262+ : {
263+ blobId : pojo . blobId ,
264+ content,
265+ dateModified : pojo . dateModified ,
266+ utcDateModified : pojo . utcDateModified
267+ } ;
268+ sql . upsert ( "blobs" , "blobId" , pojoToSave ) ;
236269
237270 // we can't reuse blobId as an entity_changes hash, because this one has to be calculatable without having
238271 // access to the decrypted content
@@ -259,14 +292,20 @@ abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
259292 }
260293
261294 protected _getContent ( ) : string | Buffer {
262- const row = sql . getRow < { content : string | Buffer } > ( /*sql*/ `SELECT content FROM blobs WHERE blobId = ?` , [ this . blobId ] ) ;
295+ const query = blobStorageService . hasExternalContentColumns ( )
296+ ? /*sql*/ `SELECT content, contentLocation FROM blobs WHERE blobId = ?`
297+ : /*sql*/ `SELECT content, 'internal' as contentLocation FROM blobs WHERE blobId = ?` ;
298+
299+ const row = sql . getRow < { content : string | Buffer , contentLocation : string } > ( query , [ this . blobId ] ) ;
263300
264301 if ( ! row ) {
265302 const constructorData = this . constructor as unknown as ConstructorData < T > ;
266303 throw new Error ( `Cannot find content for ${ constructorData . primaryKeyName } '${ ( this as any ) [ constructorData . primaryKeyName ] } ', blobId '${ this . blobId } '` ) ;
267304 }
268305
269- return blobService . processContent ( row . content , this . isProtected || false , this . hasStringContent ( ) ) ;
306+ const content = blobStorageService . getContent ( row ) ;
307+
308+ return blobService . processContent ( content , this . isProtected || false , this . hasStringContent ( ) ) ;
270309 }
271310
272311 /**
0 commit comments