Skip to content

Migration from vsivsi:file-collection #899

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
StorytellerCZ opened this issue Mar 27, 2025 · 59 comments
Open

Migration from vsivsi:file-collection #899

StorytellerCZ opened this issue Mar 27, 2025 · 59 comments

Comments

@StorytellerCZ
Copy link

StorytellerCZ commented Mar 27, 2025

Hello everyone!

I'm taking on an old code base and I'm in the process of upgrading it to Meteor 3 (started at Meteor 1.4, currently at 2.16). Part of that project is vsivsi:file-collection which uses GridFS to store the files. I was wondering if anyone has migrated already to ostrio:files or if there is some guide that I could use.

Here is a sample setup from the app:

FsFiles = new FileCollection('cfs_gridfs.fs', {
  resumable: false,          // Disable resumable.js upload support
  resumableIndexName: undefined,    // Not used when resumable is false
  chunkSize: 2*1024*1024 - 1024,    // Use 2MB chunks for gridFS and resumable
  baseURL: '/gridfs/fs',     // Default base URL for all HTTP methods
  http: [{
    method: 'get',  // Enable a GET endpoint
    path: '/:_id/:filename',  // this will be at route "/gridfs/myFiles/:md5"
    lookup: function (params, query) {  // uses express style url params
      return { _id: params._id };       // a query mapping url to myFiles
    },
    handler: function (req, res, next) {
      if (req.headers && req.headers.origin) {
        res.setHeader('Access-Control-Allow-Origin', 'http://meteor.local'); // For Cordova
        res.setHeader('Access-Control-Allow-Credentials', true);
      }
      next();
    }
  }]    // HTTP method definitions
 }
);

With the upload on server looking like this:

    var Future = require('fibers/future');
    var future = new Future();
    var fileID = FsFiles.insert({
      filename: file.name,
      contentType: file.type,
      metadata: {
        modified: file.lastModified,
        uploader: uid,
        parent_collection: collection,
        parent_id: objectID
      }
    });
    var newFile = FsFiles.findOne(fileID);
    var writeableStream =  FsFiles.upsertStream(newFile);
    writeableStream.write(new Buffer(fileData));
    writeableStream.end();
    writeableStream.on('finish', () => {
      future['return'](true);
    });
    var finished = future.wait();

And also using resumable.js on the client, which I guess will have to be replaced as well:

file_readers.forEach(function (file, index) {
        var reader = new FileReader();
        var fileInfo = _.pick(file, ['name', 'type', 'lastModified']);
        reader.onload = function(fileLoadEvent) {
          var buffer = new Uint8Array(reader.result) // convert to binary
          Meteor.call('add_file_attached_action', objectID, collection, fileInfo, buffer, function (error, response) {
            if (error) {
              alertify.error(error.reason);
            }
            else {
              alertify.success('Uploaded ' + file.name);
              alertify.success(response.message);
              fileIDs.push(response.fileID);
              file_readers[index] = null;
              
              if (file_readers.every(function(value){return !value;})) {
                alertify.success('Finished uploading all files.');
                $('#client_uploads').fileinput('clear');
                file_readers = [];
                if (typeof callback === 'function') {
                  callback(fileIDs);
                }
                if (redirect) {
                  Router.go(redirect);
                }
              }
            }
          });
        };
        reader.readAsArrayBuffer(file);
      });

I will check out the examples here and see how far I can get on my own. I suspect that I will have to set things up from scratch and then potentially migrate the DB.

I will try to keep this thread updated as well for future reference. Please chime in if you have any idea.

@dr-dimitru
Copy link
Member

@StorytellerCZ

I assume you can still keep the files in GridFS following this tutorial lmk if something is out of date

To migrate existing files (you only need to migrate its metadata):

  1. Loop over files from existing cfs_gridfs.fs
  2. Use FilesCollection#_dataToSchema() to generate compatible record with FilesCollection
  3. Insert meta-data directly to FilesCollection#collection
  const newFilesCollection = new FilesCollection();
  const filesFromGridFs = FsFiles.find();

  await filesFromGridFs.forEachAsync(async (file) => {
    const migratedFile = newFilesCollection._dataToSchema({
      name: file.filename,
      path: ''
      meta: {
        ...file.metadata,
        gridFsFileId: file._id.toHexString(),
      },
      type: file.contentType,
      size: file.length,
      // userId: file.metadata.owner, // <- assign if you have used this field
      extension: newFilesCollection._getExt(file.filename),
      _storagePath: 'gridFs',
    });
    
    await newFilesCollection.collection.insertAsync(migratedFile);
  });

@dpatte
Copy link

dpatte commented Mar 30, 2025

I'm in the same boat...

I have been away from the game for two years, but I decided to start to migrate my several small apps to Meteor 3.

For several apps I have images and files stored in GridFS (on my own server) placed there using VSIVSI:File-Collection, a package that has not been updated in 6-7 years, and doesn't work on mongo 6 (due to changes in mongo 6).

Does your Meteor-Files package work in meteor 2.16 (the last meteor 2 version), and does it also work in Meteor 3 (no fibres)?

I am thinking of writing a custom app using VSIVSI on the old server to extract all the images and files to the fs, then I could possibly write a custom app to upload them using a new files collection package. I am also confused whether ostrio:files and 'Meteor Files' and Meteor File Collection are just different names for the same package of yours.

Do you think this is feasible?

I want to eventually store all the files and images on my own server, not a commercial one. Also, should the final resting place for the images and files be in the fs, or in gridfs?

What do you recommend?

Thanks

David (in Canada)

@dr-dimitru
Copy link
Member

dr-dimitru commented Mar 30, 2025

Hello @dpatte

Does your Meteor-Files package work in meteor 2.16 (the last meteor 2 version), and does it also work in Meteor 3 (no fibres)?

Yes, for v2.x use latest v2.x package release
For v3 use latest v3 pre-release (release candidate)

I am thinking of writing a custom app using VSIVSI on the old server to extract all the images and files to the fs, then I could possibly write a custom app to upload them using a new files collection package.

You should be able to migrate to this lib and use it from on, see my example in the message above

I am also confused whether ostrio:files and 'Meteor Files' and Meteor File Collection are just different names for the same package of yours.

Yes, same package, where:

  • meteor-files is repo name
  • ostrio:files package name
  • FilesCollection class name

I want to eventually store all the files and images on my own server, not a commercial one. Also, should the final resting place for the images and files be in the fs, or in gridfs?

Depends from the average file-size and size of the storage you lan to use. From DevOps perspective GridFS is easier to migrate and manage

Also see latest PR updating GridFS docs, I hope these details helps, lmk

@dpatte
Copy link

dpatte commented Mar 30, 2025

Thanks. I will let you know how it goes.

@dpatte
Copy link

dpatte commented Apr 1, 2025

I ran the conversion code above, with some minor tweaks. My original collection which had .chunks, .files,, and ,locks collections seem unchanged, and I now have two new collections called 'MeteorUploadFiles' (with data), and '__pre_MeteorUploadFiles' (empty).

Should MeteorUploadFiles now replace my old collections files?

@dr-dimitru
Copy link
Member

@dpatte try passing name of your collection into new FilesCollection();

Like

new FilesCollection({collectionName: "your.Original.CollectionName");

@dpatte
Copy link

dpatte commented Apr 2, 2025

The app has 2 such gridfs collectioins. The first collection conversion worked first shot :) and produces a single collection which is readable.

But the second collection does not convert. I get this error:
A collection called 'MeteorUploadFiles' already exists.
But when i check no collection by that name exists in the db.
I do see pre_MeteorUploadFiles or pre_my.collection,name collections though.
i then tried various combinations of removing the pre_ collections, and converting again to no avail.

But if i remove all the pre_ collections plus the first converted collecction, it converts the first collection again, but fails again on the second collection.

@dr-dimitru
Copy link
Member

@dpatte It's because default name of collection is MeteorUploadFiles, registering two collections without passing collectionName option will mean you attempt to create two collections without passing the same name. Anyways to be able to read from migrated collections collectionName option has to match your currently existing collections

@dpatte
Copy link

dpatte commented Apr 2, 2025

but I did provide different collection names to each migration. and the first collection worked, and used my first collection name. but when i tried the second collection with its own name (not the same as the first collection name) it did not work.

@dpatte
Copy link

dpatte commented Apr 2, 2025

Maybe my situation is an edge case, since it has no actual records in the 2nd collection.

@dr-dimitru
Copy link
Member

@dpatte here are my suggestions:

  1. Share code so we can review
  2. Enable debug mode to see what's happening inside, post its logs somewhere and link here
  3. What's 2nd collection?

@dr-dimitru
Copy link
Member

@StorytellerCZ did migration worked for you? Please share your scripts if any

@dpatte
Copy link

dpatte commented Apr 3, 2025

My app actually uses 2 vsivsi FileCollections. 'imagepage', and 'filepage'. They have their own gridfs buckets and vsivsi metadata collections (mycollectionname.chunks, mycollectionname.locks and mycollectionname.files).

what I was doing was convert to 'imagepage' first, which worked and left 3 new collections
pre_MeteorUploadFiles (empty)
pre_imagepage (empty(
imagepage (has records)

the app then works perfectly after the first conversion to FilesCollection.

But then converting 'filepage' collection constantly gives the error MeteorUploadFiles already exists, even though a collection with that name does not exist at the point it crashes.

I think that the issue shows up due to an async race condition in the process, because I actually wrote the conversions directly within my app during startup. It reports the error at startup when i hit the new FilesCollection('imagepage'), which i had previously converted, and worked..

Since I have about 8 apps that I have to migrate, and to avoid async issues, I have decided to write a dedicated app to convert a single collection and use it individually on each collection in each app. I'll let you know how it goes, and perhaps we can put it on github if its useful for others.

(BTW can i remove the pre_ collections after the conversions are done? they seem empty).

@dr-dimitru
Copy link
Member

dr-dimitru commented Apr 4, 2025

@dpatte The problem in new FilesCollection('imagepage') where it has to be new FilesCollection({collectionName: 'imagepage'})

Migration goal here is to simply update files metadata from vsivsi format to FilesCollection format

Note: pre_* collection only meant for unfinished upload files, and should exist if you're planning g to use upload from Client

@dpatte
Copy link

dpatte commented Apr 4, 2025

status: i now have a small meteor app that will run a vsi->ostrio collection conversion for a single collection. for my apps i run it twice for my two collections, and the conversions now seem to work, though i still have a few cursor tweaks to do, but its moving forward.

Thanks for your help.

@dr-dimitru
Copy link
Member

@dpatte I'm glad it worked, feel free to share a piece of code used for migration, I'll add it to docs

@dpatte
Copy link

dpatte commented Apr 7, 2025

well, it looks like I was not quite as bright as I had thought I was.

First a question: For your conversion, Is the collection name provided for the input FileCollection and output FilesCollection supposed be the same?

This is what I have assumed, but I have been unable to access any images since the conversion, so it may not have worked out. From my own console messages I know it runs through the complete collection, but I see no change to any of the collection s before and after the conversion - except for the added pre_ files (I convert my two FileCollections, one at a time).

In my main app, that uses the new FilesCollection, it can then see the new FilesCollection object, but find() returns 'undefined'.

I have attached the conversion code I use, which sits alone in the server portion of a basic meteor app as a wrapper.

import { Meteor } from 'meteor/meteor';

Meteor.startup(() => {

  // OLD VSI CODE

  let collection = 'filepage'; // original collection name
  let idx = 'fpgIdx';

  oldFiles = FileCollection(collection, {
    resumable: true,     // Enable built-in resumable.js chunked upload support
    resumableIndexName: idx,
    http: [{             // Define HTTP route
      method: 'get',     // Enable a GET endpoint
      path: '/:filename',     // this will be at route "/gridfs/filepage/:filename"
      lookup: function (params, query) {      // uses express style url params
        return { filename: params.filename }; // a query mapping url to myFiles
      }
    }]
  });

  oldFiles.allow({ // T/F
    insert(userId,file) { return true; },
    remove(userId,file) { return true; },
    read(userId,file) { return true; },
    write(userId,file) { return true; },
  });

   // OSTRIO CONVERSION

  const newFilesCollection = new FilesCollection({collectionName:collection, debug:true});
  const filesFromGridFs = oldFiles.find();

  let count = 0;
  filesFromGridFs.forEach(async (file) => {

    console.log(++count);
    console.log('old',file);

    const migratedFile = newFilesCollection._dataToSchema({
      name: file.filename,
      path: '',
      meta: {
        ...file.metadata,
        gridFsFileId: file._id.toHexString(),
      },
      type: file.contentType,
      size: file.length,
      // userId: file.metadata.owner, // <- assign if you have used this field
      extension: newFilesCollection._getExt(file.filename),
      _storagePath: 'gridFs',
    });

    console.log('new',migratedFile);

    await newFilesCollection.collection.insert(migratedFile);
  });

})

@dr-dimitru
Copy link
Member

@dpatte In my example in the first post FsFiles is instance of vsivsi collection, where name of the actual collection is different as it's the GridFS collection:

const filesFromGridFs = FsFiles.find();

The purpose of this migration:

  1. Keep file in the GridFS collection as it is -- unchanged
  2. Create new FilesCollection with metadata linking to files in the GridFS collection -- inserting data exported from existing vsivsi collection into new FilesCollection

Lmk if it sounds helpful

@dpatte
Copy link

dpatte commented Apr 7, 2025

yes, in the code I posted, filesFromGridFs all the files I read from the old vsivsi collection (oldFile).

but does your code then write them out into the stored db? I see nothing new written into the stored db except the _pre... collections.

@dr-dimitru
Copy link
Member

@dpatte please try to use different name for new FilesCollection here:

const newFilesCollection = new FilesCollection({collectionName:collection, debug:true});

but does your code then write them out into the stored db? I see nothing new written into the stored db except the _pre... collections.

I computed this in my head based on input from the OP by @StorytellerCZ, where I assumed that vsivsi used *.fs postfix for its collections like: cfs_gridfs.fs, so it's try and error process. That's why I was also asking for the final code you used.

Again, to summarize:

  1. Existing GridFS collection and chunks collection should not get changed in the process of migration
  2. New FilesCollection should get created with meta-data linking existing GridFS files and chunks to the new FilesCollection

@dpatte
Copy link

dpatte commented Apr 7, 2025

thanks for the help

ok. First, i will try using a different name for the new FilesCollection

To summarize:

I am assuming the following (case A)

  1. my code shown above, when changed, will fully modify the actual stored db
  2. i can then proceed to modify my app to read and write to the fully updated stored database using only the FilesCollection apis

But, is instead the following?

(B)

  1. my code shown above, when changed, will add the ability to read the old vsivsi database using FilesCollection apis

  2. (is this extra step required?) I then will have to read, using the new process, all the old vsivsi records in the old collection, and write them out to a new ostrio physical collection.

  3. i can then proceed to modify my app to read and write to the fully updated stored ostrio database using normal FilesCollection apis

@dr-dimitru
Copy link
Member

@dpatte in B scenario:

  1. No modifications needed in existing DB records
  2. No need to move actual files/chunks — it will remain where it is in the existing GridFS
  3. After meta-data migrated to new FilesCollection you can use FilesCollection API as normal

So we need to migrate meta-data only

@dpatte
Copy link

dpatte commented Apr 7, 2025

I ran the 2 conversions and the only things I can visibly see changed in the DB are the two 2 pre_ collections added.

I then ran my main app using the new collection names with debug: true
the server console shows

[FilesCollection][find({},undefined)]

in the client console, using my own code

console.log(myNewCollection)

shows a FilesCollection Object which looks correct (except the chunksize it shows is 524288 ?)

and

console.log(myNewCollection.find())

shows undefined

@dr-dimitru
Copy link
Member

@dpatte

  1. Are those logs from server?
  2. can you check data via MongoDB CLI mongosh or meteor mongo? What do you get running myNewCollection.find() or myNewCollection.countDocuments()?

@dpatte
Copy link

dpatte commented Apr 7, 2025

  1. In my previous message, i indicate which logs come from the server, and which logs come from the client
  2. I use Studio3T to view the db, and i checked using meteor mongo and it shows the same thing

@dpatte
Copy link

dpatte commented Apr 7, 2025

console.log(myNewCollection.find()) on the client shows undefined

@dpatte
Copy link

dpatte commented Apr 7, 2025

BTW, I do I publish myNewCollection serverside, and i subscribe myNewCollection.cursor clientside

@dr-dimitru
Copy link
Member

@dpatte I checked your migration code and you call insert instead of insertAsync try to fix that. And add additional logging to ensure migratedFile object is written

@dr-dimitru
Copy link
Member

@dpatte we need to find out why it isn't inserted, so try to log .insert result. And/or provide server logs in debug mode

@dpatte
Copy link

dpatte commented Apr 8, 2025

So, what I have now done is update my main app, and my conversion app to meteor 2.9.1. These versions now have the ...Async functions built in.
I then tried to reproduce your initial suggested migration code to use the await s and ....Async functions, but it does not accept the first await, at the top of the loop, so I removed it.

Now when I run the conversions (the one with no records first) and leave the 2nd one (with hundreds of images) running, I now see PageFiles and PageImages collections with an index record in the DB.
But when I then stop the conversion app, the new PageFiles and PageImages collections get removed from the DB automatically.

They are being written out it seems, but are removed when I close the conversion app.

@dpatte
Copy link

dpatte commented Apr 8, 2025

i will attach the converssion app tomorrrow. And it seems that 'ForEachAsync' is not actually async, in Meteor version 2.9.1

@dr-dimitru
Copy link
Member

@dpatte I don't see where you use forEachAsync in your example. Please post latest migration code you use. And versions of meteor and files package. As OP was regarding meteor@3 and latest release candidate of files package

@dpatte
Copy link

dpatte commented Apr 8, 2025

Like the OP, I, too, am trying to migrate (eventually) to Meteor 3. I was still on Meteor 2.7.3 yesterday but I am now at 2.9.1 which added support for most Meteor Async functions.

vsivsi:FileCollecction is not supported on Mongo 6 and beyond, which is why I am required to migrate the DB BEFORE upgrading to Meteor 2.15 (beause Meteor 2.15 uses Mongo 7 internally).

The ostrio:files package being used now is 2.3.3

This is the current migration code

import { Meteor } from 'meteor/meteor';

Meteor.startup(() => {

  // SETUP

  let collection = 'imagepage'; // original collection name
  let idx = 'fpgIdx';
  let newCollection = 'PageImages'; // new ostrio collection name

  // OLD VSI CODE

  oldFiles = FileCollection(collection, {
    resumable: true,     // Enable built-in resumable.js chunked upload support
    resumableIndexName: idx,
    http: [{             // Define HTTP route
      method: 'get',     // Enable a GET endpoint
      path: '/:filename',     // this will be at route "/gridfs/filepage/:filename"
      lookup: function (params, query) {      // uses express style url params
        return { filename: params.filename }; // a query mapping url to myFiles
      }
    }]
  });

  oldFiles.allow({ // T/F
    insert(userId,file) { return true; },
    remove(userId,file) { return true; },
    read(userId,file) { return true; },
    write(userId,file) { return true; },
  });

   // OSTRIO CONVERSION

  const newFilesCollection = new FilesCollection({collectionName:newCollection, debug:true});
  const filesFromGridFs = oldFiles.find();

  let count = 0;
  /*await*/ filesFromGridFs.forEachAsync(async (file) => {

    console.log(++count);
    console.log('old',file);

    const migratedFile = newFilesCollection._dataToSchema({
      name: file.filename,
      path: '',
      meta: {
        ...file.metadata,
        gridFsFileId: file._id.toHexString(),
      },
      type: file.contentType,
      size: file.length,
      // userId: file.metadata.owner, // <- assign if you have used this field
      extension: newFilesCollection._getExt(file.filename),
      _storagePath: 'gridFs',
    });

    console.log('new',migratedFile);

    await newFilesCollection.collection.insertAsync(migratedFile);
  });

})

@dr-dimitru
Copy link
Member

@dpatte

  1. What error do you get with await .forEachAsync?
  2. What's the result of .insertAsync? Add log to its result
  3. Log migratedFile object, perhaps something is off in its results

But when I then stop the conversion app, the new PageFiles and PageImages collections get removed from the DB automatically.

This is really weird, collections cannot simply disappear from MongoDB unless explicitly removed. Try to avoid using IDEs, check collections via CLI

@dpatte
Copy link

dpatte commented Apr 8, 2025

  • with the await, it reports that it is an illegal use of the keyword await. I checked and in 2.9.1 it seems that it did not return a promise.

Here is what the server returns when converting filepage to PageFiles (the source colection has no records)

=> Started MongoDB.                           
[FilesCollection.storagePath] Set to: assets/app/uploads/PageFiles
=> Started your app.

=> App running at: http://localhost:3000/

Here is what the server returns when converting imagepage to PageImages (the source collection has several hundred records, so this is just the tail of the output, showing the last record processed)

I20250408-08:19:26.679(-4)? 366
I20250408-08:19:26.679(-4)? old {
I20250408-08:19:26.679(-4)?   _id: ObjectID { _str: 'e430d70a61d381e1a9687f5f' },
I20250408-08:19:26.679(-4)?   filename: 'Edge1-2023.jpg',
I20250408-08:19:26.679(-4)?   contentType: 'image/jpeg',
I20250408-08:19:26.679(-4)?   length: 87600,
I20250408-08:19:26.679(-4)?   chunkSize: 2096128,
I20250408-08:19:26.679(-4)?   uploadDate: 2025-04-03T01:01:28.023Z,
I20250408-08:19:26.679(-4)?   aliases: [],
I20250408-08:19:26.679(-4)?   metadata: {},
I20250408-08:19:26.680(-4)?   md5: 'cb7ee51ca03d81a07a3619364e3a8269'
I20250408-08:19:26.680(-4)? }
I20250408-08:19:26.680(-4)? new {
I20250408-08:19:26.680(-4)?   name: 'Edge1-2023.jpg',
I20250408-08:19:26.680(-4)?   extension: { ext: 'jpg', extension: 'jpg', extensionWithDot: '.jpg' },
I20250408-08:19:26.680(-4)?   ext: { ext: 'jpg', extension: 'jpg', extensionWithDot: '.jpg' },
I20250408-08:19:26.680(-4)?   extensionWithDot: '.[object Object]',
I20250408-08:19:26.680(-4)?   path: '',
I20250408-08:19:26.680(-4)?   meta: { gridFsFileId: 'e430d70a61d381e1a9687f5f' },
I20250408-08:19:26.680(-4)?   type: 'image/jpeg',
I20250408-08:19:26.680(-4)?   mime: 'image/jpeg',
I20250408-08:19:26.680(-4)?   'mime-type': 'image/jpeg',
I20250408-08:19:26.680(-4)?   size: 87600,
I20250408-08:19:26.680(-4)?   userId: null,
I20250408-08:19:26.680(-4)?   versions: {
I20250408-08:19:26.680(-4)?     original: { path: '', size: 87600, type: 'image/jpeg', extension: [Object] }
I20250408-08:19:26.681(-4)?   },
I20250408-08:19:26.681(-4)?   _downloadRoute: '/cdn/storage',
I20250408-08:19:26.681(-4)?   _collectionName: 'PageImages',
I20250408-08:19:26.681(-4)?   isVideo: false,
I20250408-08:19:26.681(-4)?   isAudio: false,
I20250408-08:19:26.681(-4)?   isImage: true,
I20250408-08:19:26.681(-4)?   isText: false,
I20250408-08:19:26.681(-4)?   isJSON: false,
I20250408-08:19:26.681(-4)?   isPDF: false,
I20250408-08:19:26.681(-4)?   _storagePath: 'gridFs'
I20250408-08:19:26.681(-4)? }
=> Started your app.

=> App running at: http://localhost:3000/

The DB (temporarily) adds the following collections

FilePage
  Indexes
    _id
ImagePage
  Indexes
    _id

Nothing else is changed or added to the DB, and when i stop conversion app, these two collections disappear!

@dr-dimitru
Copy link
Member

@dpatte

  1. I assume you run await outside sync function. Adding await in front of regular function has no side effects.
  2. I don't see result of .insert method in your logs
  3. In new file object -- Extensions looks off, replace
{
  extension: newFilesCollection._getExt(file.filename)
}

To spread operator

{
  ...newFilesCollection._getExt(file.filename)
}
  1. I re-checked you code sample, -- Use different collectionName for the new FilesCollection

  2. Disappearing Collections are still mystery for me. What's output of show collections command? @StorytellerCZ Do you have any clues about this one?

@dpatte
Copy link

dpatte commented Apr 8, 2025

  1. the code i use is what i posted above - the await is commented out. Uncommenting it gives a compiler error.
  2. the records for each image DO show up in the PageImage collection, when it is there
  3. i will change it, and will try again. Should that section become
    size: file.length,
    // userId: file.metadata.owner, // <- assign if you have used this field
    extension: ...newFilesCollection._getExt(file.filename))
    _storagePath: 'gridFs',
  4. the code above does use a new collection name
  5. me too :)

the collections within the gridfs have the old collection names. How do you determine the paths to the correct gridfs collections if I have changed my collection names as you suggested?

@dpatte
Copy link

dpatte commented Apr 8, 2025

Two key points:

  1. since I have changed the collection name (to PageImages from imagepage), but the gridfs has the old collection name (imagepage), shouldn't the path: in the conversion somehow indicate to use 'imagepage'?

  2. I am doing this in dev mode, if that makes a difference.

@dr-dimitru
Copy link
Member

@dpatte

  1. T0 be able to use await in this context change top function to async, like Meteor.startup(async () => {})

  2. Then how and where it disappears?

  3. No, just spread operator:

{
  size: file.length,
  _storagePath: 'gridFs',
  ...newFilesCollection._getExt(file.filename)),
}
  1. Good, but again disappearing collection is very confusing in this case

  2. Still, whats output of show collections command in MongoDB CLI?

the collections within the gridfs have the old collection names. How do you determine the paths to the correct gridfs collections if I have changed my collection names as you suggested?

As I mentioned above — existing GridFS should remain unchanged. We only connect existing files and chunks via creating meta-data in new FliesCollection. The connection with GridFS for upload and download are created in onAfterUpload, interceptDownload, and onAfterRemove hooks as you can find in this tutorial — https://github.com/veliovgroup/Meteor-Files/blob/master/docs/gridfs-bucket-integration.md#use-gridfs-with-gridfsbucket-as-a-storage this is for meteor@2, GridFS tutorial for meteor@3 will be released with next package release, see PR and changes for meteor@3

@dpatte
Copy link

dpatte commented Apr 8, 2025

i'll make the changes and get back to you

@dpatte
Copy link

dpatte commented Apr 8, 2025

Made the changes and the effect is the same.
I run the two conversions, and it adds the collections
then i run my main app and inspect the db.

The only collections listed (using Studio3T or meteor mongo - i checked both)
_pre_PageFiles
_pre_ImageFiles
filepage.chunks
filepage.files
filepage.locks
imagepage.chunks
imagepage.files
imagepage.chunks
menu
meteor_accounts....
page
roles
users

The new collections are gone.

Maybe something funky is happening on the client side of the conversion app. I'll check.

@dpatte
Copy link

dpatte commented Apr 8, 2025

the client side of the conversion app is trivial, but doesnt display anything.

Here is the latest converter

import { Meteor } from 'meteor/meteor';

await Meteor.startup(async () => {

  // SETUP

  let collection = 'filepage'; // original collection name
  let idx = 'fpgIdx';
  let newCollection = 'PageFiles'; // new ostrio collection name

  // OLD VSI CODE

  oldFiles = FileCollection(collection, {
    resumable: true,     // Enable built-in resumable.js chunked upload support
    resumableIndexName: idx,
    http: [{             // Define HTTP route
      method: 'get',     // Enable a GET endpoint
      path: '/:filename',     // this will be at route "/gridfs/filepage/:filename"
      lookup: function (params, query) {      // uses express style url params
        return { filename: params.filename }; // a query mapping url to myFiles
      }
    }]
  });

  oldFiles.allow({ // T/F
    insert(userId,file) { return true; },
    remove(userId,file) { return true; },
    read(userId,file) { return true; },
    write(userId,file) { return true; },
  });

   // OSTRIO CONVERSION

  const newFilesCollection = new FilesCollection({collectionName:newCollection, debug:true});
  const filesFromGridFs = oldFiles.find();

  let count = 0;
  /*await*/ filesFromGridFs.forEachAsync(async (file) => {

    console.log(++count);
    console.log('old',file);

    const migratedFile = newFilesCollection._dataToSchema({
      name: file.filename,
      path: '',
      meta: {
        ...file.metadata,
        gridFsFileId: file._id.toHexString(),
      },
      type: file.contentType,
      size: file.length,
      // userId: file.metadata.owner, // <- assign if you have used this field
      ...newFilesCollection._getExt(file.filename),
      _storagePath: 'gridFs',
    });

    console.log('new',migratedFile);

    await newFilesCollection.collection.insertAsync(migratedFile);
  });

})
  1. I'm still not clear if what you want for extension is correct
  2. I'll add a log message after the startup to see if it ever gets there

@dpatte
Copy link

dpatte commented Apr 8, 2025

oops - let me try again

@dr-dimitru
Copy link
Member

@dpatte

the client side of the conversion app is trivial, but doesnt display anything.

Note: Migration should get execute on the server only, wrap it into Meteor.isServer condition

I'm still not clear if what you want for extension is correct

Looks correct now

I'll add a log message after the startup to see if it ever gets there

👍

@dpatte
Copy link

dpatte commented Apr 8, 2025

yes migration is only on the server

still no luck. Same result. I'm stumped. I also tried without the initial await, no luck.

Maybe something is wrong in my main app that wipes out the DB, or maybe forEachAsync has a bug that doesnt work if there are no elements in the array - who knows.

Leaving it for now.

@dr-dimitru
Copy link
Member

@dpatte As part for test-try-error process I'd recommend to create collection and insert 1-5 records manually via mongoDB CLI. To check if it's MongoDB or app causing this error. Then launch app and see if collections remain or removed again

Keep us updated

@dpatte
Copy link

dpatte commented Apr 8, 2025

I have rolled back my code to Meteor 2.7.3, which works.

If you have a working version of Ostrio:Files from before Meteor 2.8 then I'll try with that.

Why?

Because 2.8 is when they started adding ...Async functions and changing mongo drivers.

Since it still has fibres, I may succeed by rewriting the conversion as fibres-only functions. I can then move forward to get the main app up to 2.16.

@dr-dimitru
Copy link
Member

@dpatte v2.3.0 and above to v2.3.3 all should be good, we haven't incorporated new *async methods until current RC

@dpatte
Copy link

dpatte commented Apr 9, 2025

I think I have the lost-collections problem solved!

Its basic meteor that I knew way back at meteor 1.0, but had long forgotten.....

In dev mode, its db is stored with the project code. When I start the conversion code,the db is then visible in the 'meteor' folder of mongo. When I close the conversion app, the modified db stays with the app code, but is no longer available in mongo.

So this means that when I then restarted my main app after the conversion, its own db had not been altered - because the new collections were never in its own DB.

@dpatte
Copy link

dpatte commented Apr 9, 2025

Some Good Progress!!

I did a conversion on 2.7.3 (I love fibres :) ), and my app can now see my new image collection.

using console.log:

PageImages now has data!

PageImages.findOne({_id: 'some_valid_id"}) has data!

BUT:

PageImages.findOne({_id: 'some_valid_id"}).link() returns a download path:
http://192.168.1.218:3000/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg
which gives me 'File Not Found' when used

I have tried to add a download handler, but not sure if I should use createBucket,
when I try createBucket, i get a router error.

@dr-dimitru
Copy link
Member

@dpatte Link looks correct. Returned "file not found" may indicate that interceptDownload hook wasn't set or wasn't set correctly

Please share code of FilesCollection setup with hooks

@dpatte
Copy link

dpatte commented Apr 10, 2025

Here it is.

This version runs with no errors until I try to display an image: 'file not found'

It is in my /lib folder

// BUCKET ACCESS

import { MongoInternals } from 'meteor/mongo';

const createBucket = (bucketName) => {
  const options = bucketName ? {bucketName} : (void 0);
  return new MongoInternals.NpmModules.mongodb.module.GridFSBucket(MongoInternals.defaultRemoteCollectionDriver().mongo.db, options);
}


const createInterceptDownload = bucket =>
  function interceptDownload (http, file, versionName) {
    const { gridFsFileId } = file.versions[ versionName ].meta || {};
    if (gridFsFileId) {
      // opens the download stream using a given gfs id
      // see: http://mongodb.github.io/node-mongodb-native/3.6/api/GridFSBucket.html#openDownloadStream
      const gfsId = createObjectId({ gridFsFileId });
      const readStream = bucket.openDownloadStream(gfsId);

      readStream.on('data', (data) => {
        http.response.write(data);
      });

      readStream.on('end', () => {
        http.response.end('end');
      });

      readStream.on('error', () => {
        // not found probably
        // eslint-disable-next-line no-param-reassign
        http.response.statusCode = 404;
        http.response.end('not found');
      });

      http.response.setHeader('Cache-Control', this.cacheControl);

      const dispositionName = "filename=\"" + (encodeURIComponent(file.name)) + "\"; filename=*UTF-8\"" + (encodeURIComponent(file.name)) + "\"; ";
      const dispositionEncoding = 'charset=utf-8';
      http.response.setHeader('Content-Disposition', dispositionType + dispositionName + dispositionEncoding);
    }
    return Boolean(gridFsFileId); // Serve file from either GridFS or FS if it wasn't uploaded yet
  }


// called using PageImages = gridAccess('imagepage','ipgIdx','PageImages');

gridAccess = function(vsiName,vsiIdx,ostrioName) {

  let ostrioCollection = new FilesCollection({
    collectionName: ostrioName,
    debug: true,
    interceptDownload: createInterceptDownload(vsiName),               // page not found
//    interceptDownload: createInterceptDownload(createBucket(vsiName)), // crashes in router
  });

  return ostrioCollection;

}

@dr-dimitru
Copy link
Member

@dpatte

  1. Any debug logs? Any console errors?
  2. Does interceptDownload got called? What are file and versionName values?
  3. What are values of gfsId and readStream?

@dpatte
Copy link

dpatte commented Apr 10, 2025

In my code, createBucket and createInterceptDownload are now only done on the server side., and the app comes up with the following server log (I have added more log messages)

SERVER LOG WITH LOG MESSAGES

dpatte@218-DEVELOPMENT:~/meteor/sisi$ ROOT_URL="http://192.168.1.218:3000" meteor
[[[[[ ~/meteor/sisi ]]]]]                     

=> Started proxy.                             
Browserslist: caniuse-lite is outdated. Please run:
  npx browserslist@latest --update-db
  Why you should do it regularly: https://github.com/browserslist/browserslist#browsers-data-updating
=> Meteor 3.1.2 is available. Check the changelog
https://docs.meteor.com/changelog.html and update this project with 'meteor
update'.
=> Started MongoDB.                           
I20250410-10:42:30.164(-4)? ostrioName:  PageFiles 
I20250410-10:42:30.174(-4)? vsiName:  filepage
I20250410-10:42:30.174(-4)? [FilesCollection.storagePath] Set to: assets/app/uploads/PageFiles
I20250410-10:42:30.178(-4)? ostrioName:  PageImages 
I20250410-10:42:30.178(-4)? vsiName:  imagepage
I20250410-10:42:30.178(-4)? [FilesCollection.storagePath] Set to: assets/app/uploads/PageImages
I20250410-10:42:30.267(-4)? Server Starting - default main - SiSiStyling
I20250410-10:42:30.279(-4)? Server Startup - default - SiSiStyling
I20250410-10:42:30.279(-4)? {"node":"14.19.3","v8":"8.4.371.23-node.87","uv":"1.42.0","zlib":"1.2.11","brotli":"1.0.9","ares":"1.18.1","modules":"83","nghttp2":"1.42.0","napi":"8","llhttp":"2.1.4","openssl":"1.1.1o","cldr":"40.0","icu":"70.1","tz":"2021a3","unicode":"14.0"}
I20250410-10:42:30.299(-4)? dpatte is admin
I20250410-10:42:30.312(-4)? Server Startup - noCart - SiSiStyling
I20250410-10:42:30.312(-4)? Server Startup - core - SiSiStyling
=> Started your app.

// AT THIS POINT THE SERVER IS UP AND I START THE CLIENT

=> App running at: http://192.168.1.218:3000
I20250410-10:42:52.952(-4)? publish codeSet noCart
I20250410-10:42:52.953(-4)? as not admin
I20250410-10:42:52.953(-4)? [FilesCollection] [find({}, undefined)]
I20250410-10:42:52.953(-4)? [FilesCollection] [find({}, undefined)]
I20250410-10:42:53.317(-4)? [FilesCollection] [download(/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg, original)]
I20250410-10:42:53.326(-4)? http:  {
I20250410-10:42:53.336(-4)?   request: IncomingMessage {
I20250410-10:42:53.336(-4)?     _readableState: ReadableState {
I20250410-10:42:53.336(-4)?       objectMode: false,
I20250410-10:42:53.337(-4)?       highWaterMark: 16384,
I20250410-10:42:53.337(-4)?       buffer: BufferList { head: null, tail: null, length: 0 },
I20250410-10:42:53.337(-4)?       length: 0,
I20250410-10:42:53.338(-4)?       pipes: [],
I20250410-10:42:53.338(-4)?       flowing: null,
I20250410-10:42:53.338(-4)?       ended: true,
I20250410-10:42:53.338(-4)?       endEmitted: false,
I20250410-10:42:53.339(-4)?       reading: false,
I20250410-10:42:53.339(-4)?       sync: true,
I20250410-10:42:53.339(-4)?       needReadable: false,
I20250410-10:42:53.339(-4)?       emittedReadable: false,
I20250410-10:42:53.340(-4)?       readableListening: false,
I20250410-10:42:53.340(-4)?       resumeScheduled: false,
I20250410-10:42:53.340(-4)?       errorEmitted: false,
I20250410-10:42:53.340(-4)?       emitClose: true,
I20250410-10:42:53.341(-4)?       autoDestroy: false,
I20250410-10:42:53.341(-4)?       destroyed: false,
I20250410-10:42:53.341(-4)?       errored: null,
I20250410-10:42:53.341(-4)?       closed: false,
I20250410-10:42:53.342(-4)?       closeEmitted: false,
I20250410-10:42:53.342(-4)?       defaultEncoding: 'utf8',
I20250410-10:42:53.346(-4)?       awaitDrainWriters: null,
I20250410-10:42:53.346(-4)?       multiAwaitDrain: false,
I20250410-10:42:53.347(-4)?       readingMore: true,
I20250410-10:42:53.347(-4)?       dataEmitted: false,
I20250410-10:42:53.347(-4)?       decoder: null,
I20250410-10:42:53.347(-4)?       encoding: null,
I20250410-10:42:53.348(-4)?       [Symbol(kPaused)]: null
I20250410-10:42:53.348(-4)?     },
I20250410-10:42:53.348(-4)?     _events: [Object: null prototype] { end: [Function: clearRequestTimeout] },
I20250410-10:42:53.348(-4)?     _eventsCount: 1,
I20250410-10:42:53.348(-4)?     _maxListeners: undefined,
I20250410-10:42:53.349(-4)?     socket: Socket {
I20250410-10:42:53.349(-4)?       connecting: false,
I20250410-10:42:53.349(-4)?       _hadError: false,
I20250410-10:42:53.349(-4)?       _parent: null,
I20250410-10:42:53.350(-4)?       _host: null,
I20250410-10:42:53.350(-4)?       _readableState: [ReadableState],
I20250410-10:42:53.350(-4)?       _events: [Object: null prototype],
I20250410-10:42:53.350(-4)?       _eventsCount: 8,
I20250410-10:42:53.351(-4)?       _maxListeners: undefined,
I20250410-10:42:53.351(-4)?       _writableState: [WritableState],
I20250410-10:42:53.351(-4)?       allowHalfOpen: true,
I20250410-10:42:53.351(-4)?       _sockname: null,
I20250410-10:42:53.352(-4)?       _pendingData: null,
I20250410-10:42:53.352(-4)?       _pendingEncoding: '',
I20250410-10:42:53.352(-4)?       server: [Server],
I20250410-10:42:53.352(-4)?       _server: [Server],
I20250410-10:42:53.353(-4)?       timeout: 120000,
I20250410-10:42:53.353(-4)?       parser: [HTTPParser],
I20250410-10:42:53.353(-4)?       on: [Function: socketListenerWrap],
I20250410-10:42:53.353(-4)?       addListener: [Function: socketListenerWrap],
I20250410-10:42:53.353(-4)?       prependListener: [Function: socketListenerWrap],
I20250410-10:42:53.353(-4)?       _paused: false,
I20250410-10:42:53.354(-4)?       _httpMessage: [ServerResponse],
I20250410-10:42:53.354(-4)?       [Symbol(async_id_symbol)]: 2299,
I20250410-10:42:53.354(-4)?       [Symbol(kHandle)]: [TCP],
I20250410-10:42:53.354(-4)?       [Symbol(kSetNoDelay)]: false,
I20250410-10:42:53.355(-4)?       [Symbol(lastWriteQueueSize)]: 0,
I20250410-10:42:53.355(-4)?       [Symbol(timeout)]: Timeout {
I20250410-10:42:53.355(-4)?         _idleTimeout: 120000,
I20250410-10:42:53.355(-4)?         _idlePrev: [TimersList],
I20250410-10:42:53.356(-4)?         _idleNext: [TimersList],
I20250410-10:42:53.356(-4)?         _idleStart: 24311,
I20250410-10:42:53.356(-4)?         _onTimeout: [Function: bound ],
I20250410-10:42:53.356(-4)?         _timerArgs: undefined,
I20250410-10:42:53.357(-4)?         _repeat: null,
I20250410-10:42:53.357(-4)?         _destroyed: false,
I20250410-10:42:53.357(-4)?         [Symbol(refed)]: false,
I20250410-10:42:53.357(-4)?         [Symbol(kHasPrimitive)]: false,
I20250410-10:42:53.358(-4)?         [Symbol(asyncId)]: 2304,
I20250410-10:42:53.358(-4)?         [Symbol(triggerId)]: 2301
I20250410-10:42:53.358(-4)?       },
I20250410-10:42:53.358(-4)?       [Symbol(kBuffer)]: null,
I20250410-10:42:53.359(-4)?       [Symbol(kBufferCb)]: null,
I20250410-10:42:53.359(-4)?       [Symbol(kBufferGen)]: null,
I20250410-10:42:53.359(-4)?       [Symbol(kCapture)]: false,
I20250410-10:42:53.359(-4)?       [Symbol(kBytesRead)]: 0,
I20250410-10:42:53.359(-4)?       [Symbol(kBytesWritten)]: 0,
I20250410-10:42:53.360(-4)?       [Symbol(RequestTimeout)]: undefined
I20250410-10:42:53.360(-4)?     },
I20250410-10:42:53.360(-4)?     httpVersionMajor: 1,
I20250410-10:42:53.360(-4)?     httpVersionMinor: 1,
I20250410-10:42:53.361(-4)?     httpVersion: '1.1',
I20250410-10:42:53.361(-4)?     complete: true,
I20250410-10:42:53.361(-4)?     headers: {
I20250410-10:42:53.361(-4)?       'x-forwarded-host': '192.168.1.218:3000',
I20250410-10:42:53.361(-4)?       'x-forwarded-proto': 'http',
I20250410-10:42:53.362(-4)?       'x-forwarded-port': '3000',
I20250410-10:42:53.362(-4)?       'x-forwarded-for': '192.168.1.218',
I20250410-10:42:53.362(-4)?       priority: 'u=5, i',
I20250410-10:42:53.362(-4)?       referer: 'http://localhost:3000/',
I20250410-10:42:53.362(-4)?       connection: 'keep-alive',
I20250410-10:42:53.363(-4)?       'accept-encoding': 'gzip, deflate',
I20250410-10:42:53.363(-4)?       'accept-language': 'en-CA,en-US;q=0.7,en;q=0.3',
I20250410-10:42:53.363(-4)?       accept: 'image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5',
I20250410-10:42:53.363(-4)?       'user-agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0',
I20250410-10:42:53.363(-4)?       host: '192.168.1.218:3000'
I20250410-10:42:53.364(-4)?     },
I20250410-10:42:53.364(-4)?     rawHeaders: [
I20250410-10:42:53.364(-4)?       'x-forwarded-host',
I20250410-10:42:53.364(-4)?       '192.168.1.218:3000',
I20250410-10:42:53.364(-4)?       'x-forwarded-proto',
I20250410-10:42:53.365(-4)?       'http',
I20250410-10:42:53.365(-4)?       'x-forwarded-port',
I20250410-10:42:53.365(-4)?       '3000',
I20250410-10:42:53.365(-4)?       'x-forwarded-for',
I20250410-10:42:53.366(-4)?       '192.168.1.218',
I20250410-10:42:53.366(-4)?       'priority',
I20250410-10:42:53.366(-4)?       'u=5, i',
I20250410-10:42:53.366(-4)?       'referer',
I20250410-10:42:53.366(-4)?       'http://localhost:3000/',
I20250410-10:42:53.367(-4)?       'connection',
I20250410-10:42:53.367(-4)?       'keep-alive',
I20250410-10:42:53.377(-4)?       'accept-encoding',
I20250410-10:42:53.377(-4)?       'gzip, deflate',
I20250410-10:42:53.377(-4)?       'accept-language',
I20250410-10:42:53.378(-4)?       'en-CA,en-US;q=0.7,en;q=0.3',
I20250410-10:42:53.378(-4)?       'accept',
I20250410-10:42:53.378(-4)?       'image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5',
I20250410-10:42:53.378(-4)?       'user-agent',
I20250410-10:42:53.378(-4)?       'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0',
I20250410-10:42:53.379(-4)?       'host',
I20250410-10:42:53.379(-4)?       '192.168.1.218:3000'
I20250410-10:42:53.379(-4)?     ],
I20250410-10:42:53.379(-4)?     trailers: {},
I20250410-10:42:53.380(-4)?     rawTrailers: [],
I20250410-10:42:53.380(-4)?     aborted: false,
I20250410-10:42:53.380(-4)?     upgrade: false,
I20250410-10:42:53.380(-4)?     url: '/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg',
I20250410-10:42:53.380(-4)?     method: 'GET',
I20250410-10:42:53.387(-4)?     statusCode: null,
I20250410-10:42:53.387(-4)?     statusMessage: null,
I20250410-10:42:53.388(-4)?     client: Socket {
I20250410-10:42:53.388(-4)?       connecting: false,
I20250410-10:42:53.388(-4)?       _hadError: false,
I20250410-10:42:53.389(-4)?       _parent: null,
I20250410-10:42:53.389(-4)?       _host: null,
I20250410-10:42:53.389(-4)?       _readableState: [ReadableState],
I20250410-10:42:53.389(-4)?       _events: [Object: null prototype],
I20250410-10:42:53.390(-4)?       _eventsCount: 8,
I20250410-10:42:53.390(-4)?       _maxListeners: undefined,
I20250410-10:42:53.390(-4)?       _writableState: [WritableState],
I20250410-10:42:53.390(-4)?       allowHalfOpen: true,
I20250410-10:42:53.390(-4)?       _sockname: null,
I20250410-10:42:53.391(-4)?       _pendingData: null,
I20250410-10:42:53.391(-4)?       _pendingEncoding: '',
I20250410-10:42:53.391(-4)?       server: [Server],
I20250410-10:42:53.391(-4)?       _server: [Server],
I20250410-10:42:53.392(-4)?       timeout: 120000,
I20250410-10:42:53.392(-4)?       parser: [HTTPParser],
I20250410-10:42:53.392(-4)?       on: [Function: socketListenerWrap],
I20250410-10:42:53.392(-4)?       addListener: [Function: socketListenerWrap],
I20250410-10:42:53.392(-4)?       prependListener: [Function: socketListenerWrap],
I20250410-10:42:53.393(-4)?       _paused: false,
I20250410-10:42:53.393(-4)?       _httpMessage: [ServerResponse],
I20250410-10:42:53.393(-4)?       [Symbol(async_id_symbol)]: 2299,
I20250410-10:42:53.393(-4)?       [Symbol(kHandle)]: [TCP],
I20250410-10:42:53.393(-4)?       [Symbol(kSetNoDelay)]: false,
I20250410-10:42:53.394(-4)?       [Symbol(lastWriteQueueSize)]: 0,
I20250410-10:42:53.394(-4)?       [Symbol(timeout)]: Timeout {
I20250410-10:42:53.394(-4)?         _idleTimeout: 120000,
I20250410-10:42:53.395(-4)?         _idlePrev: [TimersList],
I20250410-10:42:53.395(-4)?         _idleNext: [TimersList],
I20250410-10:42:53.395(-4)?         _idleStart: 24311,
I20250410-10:42:53.395(-4)?         _onTimeout: [Function: bound ],
I20250410-10:42:53.395(-4)?         _timerArgs: undefined,
I20250410-10:42:53.396(-4)?         _repeat: null,
I20250410-10:42:53.396(-4)?         _destroyed: false,
I20250410-10:42:53.396(-4)?         [Symbol(refed)]: false,
I20250410-10:42:53.396(-4)?         [Symbol(kHasPrimitive)]: false,
I20250410-10:42:53.396(-4)?         [Symbol(asyncId)]: 2304,
I20250410-10:42:53.397(-4)?         [Symbol(triggerId)]: 2301
I20250410-10:42:53.397(-4)?       },
I20250410-10:42:53.397(-4)?       [Symbol(kBuffer)]: null,
I20250410-10:42:53.397(-4)?       [Symbol(kBufferCb)]: null,
I20250410-10:42:53.397(-4)?       [Symbol(kBufferGen)]: null,
I20250410-10:42:53.398(-4)?       [Symbol(kCapture)]: false,
I20250410-10:42:53.398(-4)?       [Symbol(kBytesRead)]: 0,
I20250410-10:42:53.398(-4)?       [Symbol(kBytesWritten)]: 0,
I20250410-10:42:53.398(-4)?       [Symbol(RequestTimeout)]: undefined
I20250410-10:42:53.399(-4)?     },
I20250410-10:42:53.399(-4)?     _consuming: false,
I20250410-10:42:53.399(-4)?     _dumped: false,
I20250410-10:42:53.399(-4)?     originalUrl: '/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg',
I20250410-10:42:53.399(-4)?     _parsedUrl: Url {
I20250410-10:42:53.399(-4)?       protocol: null,
I20250410-10:42:53.400(-4)?       slashes: null,
I20250410-10:42:53.400(-4)?       auth: null,
I20250410-10:42:53.400(-4)?       host: null,
I20250410-10:42:53.400(-4)?       port: null,
I20250410-10:42:53.400(-4)?       hostname: null,
I20250410-10:42:53.401(-4)?       hash: null,
I20250410-10:42:53.401(-4)?       search: null,
I20250410-10:42:53.401(-4)?       query: null,
I20250410-10:42:53.401(-4)?       pathname: '/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg',
I20250410-10:42:53.402(-4)?       path: '/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg',
I20250410-10:42:53.402(-4)?       href: '/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg',
I20250410-10:42:53.403(-4)?       _raw: '/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg'
I20250410-10:42:53.403(-4)?     },
I20250410-10:42:53.403(-4)?     secret: undefined,
I20250410-10:42:53.403(-4)?     cookies: [Object: null prototype] {},
I20250410-10:42:53.404(-4)?     signedCookies: [Object: null prototype] {},
I20250410-10:42:53.404(-4)?     query: {},
I20250410-10:42:53.404(-4)?     Cookies: __cookies {
I20250410-10:42:53.404(-4)?       __pendingCookies: [],
I20250410-10:42:53.405(-4)?       TTL: false,
I20250410-10:42:53.405(-4)?       response: [ServerResponse],
I20250410-10:42:53.405(-4)?       runOnServer: true,
I20250410-10:42:53.405(-4)?       allowQueryStringCookies: false,
I20250410-10:42:53.405(-4)?       allowedCordovaOrigins: false,
I20250410-10:42:53.406(-4)?       originRE: /^https?:\/\/(http:\/\/192.168.1.218:3000|http:\/\/192.168.1.218:3000\/)$/,
I20250410-10:42:53.406(-4)?       cookies: {}
I20250410-10:42:53.406(-4)?     },
I20250410-10:42:53.409(-4)?     [Symbol(kCapture)]: false,
I20250410-10:42:53.409(-4)?     [Symbol(RequestTimeout)]: undefined
I20250410-10:42:53.410(-4)?   },
I20250410-10:42:53.410(-4)?   response: <ref *1> ServerResponse {
I20250410-10:42:53.410(-4)?     _events: [Object: null prototype] { finish: [Array] },
I20250410-10:42:53.411(-4)?     _eventsCount: 1,
I20250410-10:42:53.411(-4)?     _maxListeners: undefined,
I20250410-10:42:53.411(-4)?     outputData: [],
I20250410-10:42:53.411(-4)?     outputSize: 0,
I20250410-10:42:53.411(-4)?     writable: true,
I20250410-10:42:53.412(-4)?     destroyed: false,
I20250410-10:42:53.412(-4)?     _last: false,
I20250410-10:42:53.412(-4)?     chunkedEncoding: false,
I20250410-10:42:53.412(-4)?     shouldKeepAlive: true,
I20250410-10:42:53.412(-4)?     _defaultKeepAlive: true,
I20250410-10:42:53.413(-4)?     useChunkedEncodingByDefault: true,
I20250410-10:42:53.413(-4)?     sendDate: true,
I20250410-10:42:53.413(-4)?     _removedConnection: false,
I20250410-10:42:53.413(-4)?     _removedContLen: false,
I20250410-10:42:53.414(-4)?     _removedTE: false,
I20250410-10:42:53.414(-4)?     _contentLength: null,
I20250410-10:42:53.414(-4)?     _hasBody: true,
I20250410-10:42:53.414(-4)?     _trailer: '',
I20250410-10:42:53.415(-4)?     finished: false,
I20250410-10:42:53.415(-4)?     _headerSent: false,
I20250410-10:42:53.416(-4)?     socket: Socket {
I20250410-10:42:53.416(-4)?       connecting: false,
I20250410-10:42:53.416(-4)?       _hadError: false,
I20250410-10:42:53.416(-4)?       _parent: null,
I20250410-10:42:53.417(-4)?       _host: null,
I20250410-10:42:53.417(-4)?       _readableState: [ReadableState],
I20250410-10:42:53.418(-4)?       _events: [Object: null prototype],
I20250410-10:42:53.418(-4)?       _eventsCount: 8,
I20250410-10:42:53.418(-4)?       _maxListeners: undefined,
I20250410-10:42:53.418(-4)?       _writableState: [WritableState],
I20250410-10:42:53.418(-4)?       allowHalfOpen: true,
I20250410-10:42:53.419(-4)?       _sockname: null,
I20250410-10:42:53.419(-4)?       _pendingData: null,
I20250410-10:42:53.419(-4)?       _pendingEncoding: '',
I20250410-10:42:53.419(-4)?       server: [Server],
I20250410-10:42:53.420(-4)?       _server: [Server],
I20250410-10:42:53.420(-4)?       timeout: 120000,
I20250410-10:42:53.420(-4)?       parser: [HTTPParser],
I20250410-10:42:53.420(-4)?       on: [Function: socketListenerWrap],
I20250410-10:42:53.421(-4)?       addListener: [Function: socketListenerWrap],
I20250410-10:42:53.421(-4)?       prependListener: [Function: socketListenerWrap],
I20250410-10:42:53.421(-4)?       _paused: false,
I20250410-10:42:53.421(-4)?       _httpMessage: [Circular *1],
I20250410-10:42:53.422(-4)?       [Symbol(async_id_symbol)]: 2299,
I20250410-10:42:53.422(-4)?       [Symbol(kHandle)]: [TCP],
I20250410-10:42:53.422(-4)?       [Symbol(kSetNoDelay)]: false,
I20250410-10:42:53.422(-4)?       [Symbol(lastWriteQueueSize)]: 0,
I20250410-10:42:53.423(-4)?       [Symbol(timeout)]: Timeout {
I20250410-10:42:53.423(-4)?         _idleTimeout: 120000,
I20250410-10:42:53.423(-4)?         _idlePrev: [TimersList],
I20250410-10:42:53.423(-4)?         _idleNext: [TimersList],
I20250410-10:42:53.423(-4)?         _idleStart: 24311,
I20250410-10:42:53.424(-4)?         _onTimeout: [Function: bound ],
I20250410-10:42:53.424(-4)?         _timerArgs: undefined,
I20250410-10:42:53.424(-4)?         _repeat: null,
I20250410-10:42:53.424(-4)?         _destroyed: false,
I20250410-10:42:53.424(-4)?         [Symbol(refed)]: false,
I20250410-10:42:53.424(-4)?         [Symbol(kHasPrimitive)]: false,
I20250410-10:42:53.425(-4)?         [Symbol(asyncId)]: 2304,
I20250410-10:42:53.425(-4)?         [Symbol(triggerId)]: 2301
I20250410-10:42:53.425(-4)?       },
I20250410-10:42:53.425(-4)?       [Symbol(kBuffer)]: null,
I20250410-10:42:53.426(-4)?       [Symbol(kBufferCb)]: null,
I20250410-10:42:53.426(-4)?       [Symbol(kBufferGen)]: null,
I20250410-10:42:53.426(-4)?       [Symbol(kCapture)]: false,
I20250410-10:42:53.426(-4)?       [Symbol(kBytesRead)]: 0,
I20250410-10:42:53.426(-4)?       [Symbol(kBytesWritten)]: 0,
I20250410-10:42:53.427(-4)?       [Symbol(RequestTimeout)]: undefined
I20250410-10:42:53.427(-4)?     },
I20250410-10:42:53.427(-4)?     _header: null,
I20250410-10:42:53.427(-4)?     _keepAliveTimeout: 5000,
I20250410-10:42:53.428(-4)?     _onPendingData: [Function: bound updateOutgoingData],
I20250410-10:42:53.428(-4)?     _sent100: false,
I20250410-10:42:53.428(-4)?     _expect_continue: false,
I20250410-10:42:53.428(-4)?     flush: [Function: flush],
I20250410-10:42:53.428(-4)?     write: [Function: write],
I20250410-10:42:53.428(-4)?     end: [Function: end],
I20250410-10:42:53.429(-4)?     on: [Function: on],
I20250410-10:42:53.429(-4)?     writeHead: [Function: writeHead],
I20250410-10:42:53.429(-4)?     [Symbol(kCapture)]: false,
I20250410-10:42:53.429(-4)?     [Symbol(kNeedDrain)]: false,
I20250410-10:42:53.430(-4)?     [Symbol(corked)]: 0,
I20250410-10:42:53.430(-4)?     [Symbol(kOutHeaders)]: null
I20250410-10:42:53.431(-4)?   },
I20250410-10:42:53.431(-4)?   params: {
I20250410-10:42:53.431(-4)?     _id: '4EZkvSztExmJ4qFLE',
I20250410-10:42:53.431(-4)?     query: {},
I20250410-10:42:53.432(-4)?     name: '4EZkvSztExmJ4qFLE.jpg',
I20250410-10:42:53.432(-4)?     version: 'original'
I20250410-10:42:53.432(-4)?   }
I20250410-10:42:53.432(-4)? } 
I20250410-10:42:53.433(-4)? file:  {
I20250410-10:42:53.433(-4)?   _id: '4EZkvSztExmJ4qFLE',
I20250410-10:42:53.433(-4)?   name: 'Polyviewer-App.jpg',
I20250410-10:42:53.433(-4)?   extension: 'jpg',
I20250410-10:42:53.433(-4)?   ext: 'jpg',
I20250410-10:42:53.434(-4)?   extensionWithDot: '.jpg',
I20250410-10:42:53.435(-4)?   path: '',
I20250410-10:42:53.436(-4)?   meta: { gridFsFileId: '7a56171f2cca6be8b9ab34b6' },
I20250410-10:42:53.436(-4)?   type: 'image/jpeg',
I20250410-10:42:53.436(-4)?   mime: 'image/jpeg',
I20250410-10:42:53.436(-4)?   'mime-type': 'image/jpeg',
I20250410-10:42:53.436(-4)?   size: 102015,
I20250410-10:42:53.436(-4)?   userId: null,
I20250410-10:42:53.437(-4)?   versions: {
I20250410-10:42:53.437(-4)?     original: {
I20250410-10:42:53.437(-4)?       path: '',
I20250410-10:42:53.437(-4)?       size: 102015,
I20250410-10:42:53.437(-4)?       type: 'image/jpeg',
I20250410-10:42:53.438(-4)?       extension: 'jpg',
I20250410-10:42:53.438(-4)?       _id: '4EZkvSztExmJ4qFLE'
I20250410-10:42:53.465(-4)?     }
I20250410-10:42:53.466(-4)?   },
I20250410-10:42:53.466(-4)?   _downloadRoute: '/cdn/storage',
I20250410-10:42:53.467(-4)?   _collectionName: 'PageImages',
I20250410-10:42:53.467(-4)?   isVideo: false,
I20250410-10:42:53.467(-4)?   isAudio: false,
I20250410-10:42:53.468(-4)?   isImage: true,
I20250410-10:42:53.468(-4)?   isText: false,
I20250410-10:42:53.468(-4)?   isJSON: false,
I20250410-10:42:53.468(-4)?   isPDF: false,
I20250410-10:42:53.469(-4)?   _storagePath: 'gridFs'
I20250410-10:42:53.469(-4)? } 
I20250410-10:42:53.469(-4)? versionName:  original
I20250410-10:42:53.469(-4)? gridFsFileId: undefined
I20250410-10:42:53.470(-4)? [FilesCollection] [download(/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg)] [_404] File not found
I20250410-10:45:15.847(-4)? publish codeSet noCart
I20250410-10:45:15.848(-4)? as not admin
I20250410-10:45:15.848(-4)? [FilesCollection] [find({}, undefined)]
I20250410-10:45:15.849(-4)? [FilesCollection] [find({}, undefined)]
I20250410-10:45:16.529(-4)? publish codeSet noCart
I20250410-10:45:16.529(-4)? as not admin
I20250410-10:45:16.530(-4)? [FilesCollection] [find({}, undefined)]
I20250410-10:45:16.530(-4)? [FilesCollection] [find({}, undefined)]
I20250410-10:45:16.756(-4)? [FilesCollection] [download(/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg, original)]
I20250410-10:45:16.759(-4)? http:  {
I20250410-10:45:16.765(-4)?   request: IncomingMessage {
I20250410-10:45:16.765(-4)?     _readableState: ReadableState {
I20250410-10:45:16.765(-4)?       objectMode: false,
I20250410-10:45:16.765(-4)?       highWaterMark: 16384,
I20250410-10:45:16.765(-4)?       buffer: BufferList { head: null, tail: null, length: 0 },
I20250410-10:45:16.765(-4)?       length: 0,
I20250410-10:45:16.765(-4)?       pipes: [],
I20250410-10:45:16.766(-4)?       flowing: null,
I20250410-10:45:16.766(-4)?       ended: true,
I20250410-10:45:16.766(-4)?       endEmitted: false,
I20250410-10:45:16.766(-4)?       reading: false,
I20250410-10:45:16.766(-4)?       sync: true,
I20250410-10:45:16.766(-4)?       needReadable: false,
I20250410-10:45:16.766(-4)?       emittedReadable: false,
I20250410-10:45:16.766(-4)?       readableListening: false,
I20250410-10:45:16.767(-4)?       resumeScheduled: false,
I20250410-10:45:16.767(-4)?       errorEmitted: false,
I20250410-10:45:16.767(-4)?       emitClose: true,
I20250410-10:45:16.767(-4)?       autoDestroy: false,
I20250410-10:45:16.767(-4)?       destroyed: false,
I20250410-10:45:16.767(-4)?       errored: null,
I20250410-10:45:16.767(-4)?       closed: false,
I20250410-10:45:16.767(-4)?       closeEmitted: false,
I20250410-10:45:16.768(-4)?       defaultEncoding: 'utf8',
I20250410-10:45:16.768(-4)?       awaitDrainWriters: null,
I20250410-10:45:16.768(-4)?       multiAwaitDrain: false,
I20250410-10:45:16.768(-4)?       readingMore: true,
I20250410-10:45:16.768(-4)?       dataEmitted: false,
I20250410-10:45:16.769(-4)?       decoder: null,
I20250410-10:45:16.769(-4)?       encoding: null,
I20250410-10:45:16.769(-4)?       [Symbol(kPaused)]: null
I20250410-10:45:16.769(-4)?     },
I20250410-10:45:16.769(-4)?     _events: [Object: null prototype] { end: [Function: clearRequestTimeout] },
I20250410-10:45:16.770(-4)?     _eventsCount: 1,
I20250410-10:45:16.770(-4)?     _maxListeners: undefined,
I20250410-10:45:16.770(-4)?     socket: Socket {
I20250410-10:45:16.770(-4)?       connecting: false,
I20250410-10:45:16.770(-4)?       _hadError: false,
I20250410-10:45:16.771(-4)?       _parent: null,
I20250410-10:45:16.771(-4)?       _host: null,
I20250410-10:45:16.771(-4)?       _readableState: [ReadableState],
I20250410-10:45:16.771(-4)?       _events: [Object: null prototype],
I20250410-10:45:16.771(-4)?       _eventsCount: 8,
I20250410-10:45:16.772(-4)?       _maxListeners: undefined,
I20250410-10:45:16.772(-4)?       _writableState: [WritableState],
I20250410-10:45:16.772(-4)?       allowHalfOpen: true,
I20250410-10:45:16.772(-4)?       _sockname: null,
I20250410-10:45:16.772(-4)?       _pendingData: null,
I20250410-10:45:16.773(-4)?       _pendingEncoding: '',
I20250410-10:45:16.773(-4)?       server: [Server],
I20250410-10:45:16.773(-4)?       _server: [Server],
I20250410-10:45:16.773(-4)?       timeout: 120000,
I20250410-10:45:16.774(-4)?       parser: [HTTPParser],
I20250410-10:45:16.774(-4)?       on: [Function: socketListenerWrap],
I20250410-10:45:16.774(-4)?       addListener: [Function: socketListenerWrap],
I20250410-10:45:16.774(-4)?       prependListener: [Function: socketListenerWrap],
I20250410-10:45:16.774(-4)?       _paused: false,
I20250410-10:45:16.775(-4)?       _httpMessage: [ServerResponse],
I20250410-10:45:16.776(-4)?       [Symbol(async_id_symbol)]: 5829,
I20250410-10:45:16.776(-4)?       [Symbol(kHandle)]: [TCP],
I20250410-10:45:16.776(-4)?       [Symbol(kSetNoDelay)]: false,
I20250410-10:45:16.776(-4)?       [Symbol(lastWriteQueueSize)]: 0,
I20250410-10:45:16.777(-4)?       [Symbol(timeout)]: Timeout {
I20250410-10:45:16.777(-4)?         _idleTimeout: 120000,
I20250410-10:45:16.777(-4)?         _idlePrev: [TimersList],
I20250410-10:45:16.777(-4)?         _idleNext: [TimersList],
I20250410-10:45:16.777(-4)?         _idleStart: 167756,
I20250410-10:45:16.778(-4)?         _onTimeout: [Function: bound ],
I20250410-10:45:16.778(-4)?         _timerArgs: undefined,
I20250410-10:45:16.778(-4)?         _repeat: null,
I20250410-10:45:16.778(-4)?         _destroyed: false,
I20250410-10:45:16.779(-4)?         [Symbol(refed)]: false,
I20250410-10:45:16.779(-4)?         [Symbol(kHasPrimitive)]: false,
I20250410-10:45:16.779(-4)?         [Symbol(asyncId)]: 5834,
I20250410-10:45:16.779(-4)?         [Symbol(triggerId)]: 5831
I20250410-10:45:16.780(-4)?       },
I20250410-10:45:16.780(-4)?       [Symbol(kBuffer)]: null,
I20250410-10:45:16.780(-4)?       [Symbol(kBufferCb)]: null,
I20250410-10:45:16.780(-4)?       [Symbol(kBufferGen)]: null,
I20250410-10:45:16.780(-4)?       [Symbol(kCapture)]: false,
I20250410-10:45:16.781(-4)?       [Symbol(kBytesRead)]: 0,
I20250410-10:45:16.781(-4)?       [Symbol(kBytesWritten)]: 0,
I20250410-10:45:16.781(-4)?       [Symbol(RequestTimeout)]: undefined
I20250410-10:45:16.781(-4)?     },
I20250410-10:45:16.782(-4)?     httpVersionMajor: 1,
I20250410-10:45:16.782(-4)?     httpVersionMinor: 1,
I20250410-10:45:16.782(-4)?     httpVersion: '1.1',
I20250410-10:45:16.783(-4)?     complete: true,
I20250410-10:45:16.783(-4)?     headers: {
I20250410-10:45:16.783(-4)?       'x-forwarded-host': '192.168.1.218:3000',
I20250410-10:45:16.784(-4)?       'x-forwarded-proto': 'http',
I20250410-10:45:16.784(-4)?       'x-forwarded-port': '3000',
I20250410-10:45:16.784(-4)?       'x-forwarded-for': '192.168.1.218',
I20250410-10:45:16.784(-4)?       priority: 'u=4, i',
I20250410-10:45:16.784(-4)?       referer: 'http://localhost:3000/',
I20250410-10:45:16.785(-4)?       connection: 'keep-alive',
I20250410-10:45:16.785(-4)?       'accept-encoding': 'gzip, deflate',
I20250410-10:45:16.785(-4)?       'accept-language': 'en-CA,en-US;q=0.7,en;q=0.3',
I20250410-10:45:16.785(-4)?       accept: 'image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5',
I20250410-10:45:16.785(-4)?       'user-agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0',
I20250410-10:45:16.786(-4)?       host: '192.168.1.218:3000'
I20250410-10:45:16.786(-4)?     },
I20250410-10:45:16.786(-4)?     rawHeaders: [
I20250410-10:45:16.786(-4)?       'x-forwarded-host',
I20250410-10:45:16.786(-4)?       '192.168.1.218:3000',
I20250410-10:45:16.787(-4)?       'x-forwarded-proto',
I20250410-10:45:16.787(-4)?       'http',
I20250410-10:45:16.787(-4)?       'x-forwarded-port',
I20250410-10:45:16.787(-4)?       '3000',
I20250410-10:45:16.787(-4)?       'x-forwarded-for',
I20250410-10:45:16.788(-4)?       '192.168.1.218',
I20250410-10:45:16.788(-4)?       'priority',
I20250410-10:45:16.788(-4)?       'u=4, i',
I20250410-10:45:16.788(-4)?       'referer',
I20250410-10:45:16.788(-4)?       'http://localhost:3000/',
I20250410-10:45:16.788(-4)?       'connection',
I20250410-10:45:16.789(-4)?       'keep-alive',
I20250410-10:45:16.789(-4)?       'accept-encoding',
I20250410-10:45:16.789(-4)?       'gzip, deflate',
I20250410-10:45:16.789(-4)?       'accept-language',
I20250410-10:45:16.790(-4)?       'en-CA,en-US;q=0.7,en;q=0.3',
I20250410-10:45:16.790(-4)?       'accept',
I20250410-10:45:16.790(-4)?       'image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5',
I20250410-10:45:16.790(-4)?       'user-agent',
I20250410-10:45:16.790(-4)?       'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0',
I20250410-10:45:16.790(-4)?       'host',
I20250410-10:45:16.791(-4)?       '192.168.1.218:3000'
I20250410-10:45:16.791(-4)?     ],
I20250410-10:45:16.791(-4)?     trailers: {},
I20250410-10:45:16.791(-4)?     rawTrailers: [],
I20250410-10:45:16.791(-4)?     aborted: false,
I20250410-10:45:16.792(-4)?     upgrade: false,
I20250410-10:45:16.792(-4)?     url: '/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg',
I20250410-10:45:16.792(-4)?     method: 'GET',
I20250410-10:45:16.792(-4)?     statusCode: null,
I20250410-10:45:16.792(-4)?     statusMessage: null,
I20250410-10:45:16.792(-4)?     client: Socket {
I20250410-10:45:16.792(-4)?       connecting: false,
I20250410-10:45:16.792(-4)?       _hadError: false,
I20250410-10:45:16.792(-4)?       _parent: null,
I20250410-10:45:16.793(-4)?       _host: null,
I20250410-10:45:16.793(-4)?       _readableState: [ReadableState],
I20250410-10:45:16.793(-4)?       _events: [Object: null prototype],
I20250410-10:45:16.793(-4)?       _eventsCount: 8,
I20250410-10:45:16.793(-4)?       _maxListeners: undefined,
I20250410-10:45:16.793(-4)?       _writableState: [WritableState],
I20250410-10:45:16.793(-4)?       allowHalfOpen: true,
I20250410-10:45:16.793(-4)?       _sockname: null,
I20250410-10:45:16.793(-4)?       _pendingData: null,
I20250410-10:45:16.794(-4)?       _pendingEncoding: '',
I20250410-10:45:16.794(-4)?       server: [Server],
I20250410-10:45:16.794(-4)?       _server: [Server],
I20250410-10:45:16.794(-4)?       timeout: 120000,
I20250410-10:45:16.794(-4)?       parser: [HTTPParser],
I20250410-10:45:16.794(-4)?       on: [Function: socketListenerWrap],
I20250410-10:45:16.794(-4)?       addListener: [Function: socketListenerWrap],
I20250410-10:45:16.794(-4)?       prependListener: [Function: socketListenerWrap],
I20250410-10:45:16.794(-4)?       _paused: false,
I20250410-10:45:16.795(-4)?       _httpMessage: [ServerResponse],
I20250410-10:45:16.795(-4)?       [Symbol(async_id_symbol)]: 5829,
I20250410-10:45:16.795(-4)?       [Symbol(kHandle)]: [TCP],
I20250410-10:45:16.795(-4)?       [Symbol(kSetNoDelay)]: false,
I20250410-10:45:16.795(-4)?       [Symbol(lastWriteQueueSize)]: 0,
I20250410-10:45:16.795(-4)?       [Symbol(timeout)]: Timeout {
I20250410-10:45:16.795(-4)?         _idleTimeout: 120000,
I20250410-10:45:16.795(-4)?         _idlePrev: [TimersList],
I20250410-10:45:16.795(-4)?         _idleNext: [TimersList],
I20250410-10:45:16.796(-4)?         _idleStart: 167756,
I20250410-10:45:16.796(-4)?         _onTimeout: [Function: bound ],
I20250410-10:45:16.796(-4)?         _timerArgs: undefined,
I20250410-10:45:16.796(-4)?         _repeat: null,
I20250410-10:45:16.796(-4)?         _destroyed: false,
I20250410-10:45:16.796(-4)?         [Symbol(refed)]: false,
I20250410-10:45:16.796(-4)?         [Symbol(kHasPrimitive)]: false,
I20250410-10:45:16.797(-4)?         [Symbol(asyncId)]: 5834,
I20250410-10:45:16.797(-4)?         [Symbol(triggerId)]: 5831
I20250410-10:45:16.797(-4)?       },
I20250410-10:45:16.797(-4)?       [Symbol(kBuffer)]: null,
I20250410-10:45:16.797(-4)?       [Symbol(kBufferCb)]: null,
I20250410-10:45:16.797(-4)?       [Symbol(kBufferGen)]: null,
I20250410-10:45:16.798(-4)?       [Symbol(kCapture)]: false,
I20250410-10:45:16.798(-4)?       [Symbol(kBytesRead)]: 0,
I20250410-10:45:16.798(-4)?       [Symbol(kBytesWritten)]: 0,
I20250410-10:45:16.798(-4)?       [Symbol(RequestTimeout)]: undefined
I20250410-10:45:16.798(-4)?     },
I20250410-10:45:16.799(-4)?     _consuming: false,
I20250410-10:45:16.799(-4)?     _dumped: false,
I20250410-10:45:16.799(-4)?     originalUrl: '/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg',
I20250410-10:45:16.800(-4)?     _parsedUrl: Url {
I20250410-10:45:16.800(-4)?       protocol: null,
I20250410-10:45:16.800(-4)?       slashes: null,
I20250410-10:45:16.800(-4)?       auth: null,
I20250410-10:45:16.800(-4)?       host: null,
I20250410-10:45:16.800(-4)?       port: null,
I20250410-10:45:16.800(-4)?       hostname: null,
I20250410-10:45:16.800(-4)?       hash: null,
I20250410-10:45:16.801(-4)?       search: null,
I20250410-10:45:16.801(-4)?       query: null,
I20250410-10:45:16.801(-4)?       pathname: '/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg',
I20250410-10:45:16.801(-4)?       path: '/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg',
I20250410-10:45:16.801(-4)?       href: '/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg',
I20250410-10:45:16.801(-4)?       _raw: '/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg'
I20250410-10:45:16.802(-4)?     },
I20250410-10:45:16.802(-4)?     secret: undefined,
I20250410-10:45:16.802(-4)?     cookies: [Object: null prototype] {},
I20250410-10:45:16.802(-4)?     signedCookies: [Object: null prototype] {},
I20250410-10:45:16.802(-4)?     query: {},
I20250410-10:45:16.802(-4)?     Cookies: __cookies {
I20250410-10:45:16.802(-4)?       __pendingCookies: [],
I20250410-10:45:16.802(-4)?       TTL: false,
I20250410-10:45:16.803(-4)?       response: [ServerResponse],
I20250410-10:45:16.803(-4)?       runOnServer: true,
I20250410-10:45:16.803(-4)?       allowQueryStringCookies: false,
I20250410-10:45:16.803(-4)?       allowedCordovaOrigins: false,
I20250410-10:45:16.803(-4)?       originRE: /^https?:\/\/(http:\/\/192.168.1.218:3000|http:\/\/192.168.1.218:3000\/)$/,
I20250410-10:45:16.803(-4)?       cookies: {}
I20250410-10:45:16.803(-4)?     },
I20250410-10:45:16.803(-4)?     [Symbol(kCapture)]: false,
I20250410-10:45:16.803(-4)?     [Symbol(RequestTimeout)]: undefined
I20250410-10:45:16.803(-4)?   },
I20250410-10:45:16.804(-4)?   response: <ref *1> ServerResponse {
I20250410-10:45:16.804(-4)?     _events: [Object: null prototype] { finish: [Array] },
I20250410-10:45:16.804(-4)?     _eventsCount: 1,
I20250410-10:45:16.804(-4)?     _maxListeners: undefined,
I20250410-10:45:16.805(-4)?     outputData: [],
I20250410-10:45:16.805(-4)?     outputSize: 0,
I20250410-10:45:16.805(-4)?     writable: true,
I20250410-10:45:16.805(-4)?     destroyed: false,
I20250410-10:45:16.805(-4)?     _last: false,
I20250410-10:45:16.805(-4)?     chunkedEncoding: false,
I20250410-10:45:16.805(-4)?     shouldKeepAlive: true,
I20250410-10:45:16.805(-4)?     _defaultKeepAlive: true,
I20250410-10:45:16.805(-4)?     useChunkedEncodingByDefault: true,
I20250410-10:45:16.806(-4)?     sendDate: true,
I20250410-10:45:16.806(-4)?     _removedConnection: false,
I20250410-10:45:16.806(-4)?     _removedContLen: false,
I20250410-10:45:16.806(-4)?     _removedTE: false,
I20250410-10:45:16.806(-4)?     _contentLength: null,
I20250410-10:45:16.806(-4)?     _hasBody: true,
I20250410-10:45:16.806(-4)?     _trailer: '',
I20250410-10:45:16.806(-4)?     finished: false,
I20250410-10:45:16.806(-4)?     _headerSent: false,
I20250410-10:45:16.807(-4)?     socket: Socket {
I20250410-10:45:16.807(-4)?       connecting: false,
I20250410-10:45:16.807(-4)?       _hadError: false,
I20250410-10:45:16.807(-4)?       _parent: null,
I20250410-10:45:16.807(-4)?       _host: null,
I20250410-10:45:16.807(-4)?       _readableState: [ReadableState],
I20250410-10:45:16.807(-4)?       _events: [Object: null prototype],
I20250410-10:45:16.807(-4)?       _eventsCount: 8,
I20250410-10:45:16.807(-4)?       _maxListeners: undefined,
I20250410-10:45:16.808(-4)?       _writableState: [WritableState],
I20250410-10:45:16.808(-4)?       allowHalfOpen: true,
I20250410-10:45:16.808(-4)?       _sockname: null,
I20250410-10:45:16.808(-4)?       _pendingData: null,
I20250410-10:45:16.808(-4)?       _pendingEncoding: '',
I20250410-10:45:16.808(-4)?       server: [Server],
I20250410-10:45:16.808(-4)?       _server: [Server],
I20250410-10:45:16.809(-4)?       timeout: 120000,
I20250410-10:45:16.809(-4)?       parser: [HTTPParser],
I20250410-10:45:16.809(-4)?       on: [Function: socketListenerWrap],
I20250410-10:45:16.809(-4)?       addListener: [Function: socketListenerWrap],
I20250410-10:45:16.809(-4)?       prependListener: [Function: socketListenerWrap],
I20250410-10:45:16.810(-4)?       _paused: false,
I20250410-10:45:16.810(-4)?       _httpMessage: [Circular *1],
I20250410-10:45:16.810(-4)?       [Symbol(async_id_symbol)]: 5829,
I20250410-10:45:16.810(-4)?       [Symbol(kHandle)]: [TCP],
I20250410-10:45:16.810(-4)?       [Symbol(kSetNoDelay)]: false,
I20250410-10:45:16.811(-4)?       [Symbol(lastWriteQueueSize)]: 0,
I20250410-10:45:16.811(-4)?       [Symbol(timeout)]: Timeout {
I20250410-10:45:16.811(-4)?         _idleTimeout: 120000,
I20250410-10:45:16.811(-4)?         _idlePrev: [TimersList],
I20250410-10:45:16.811(-4)?         _idleNext: [TimersList],
I20250410-10:45:16.811(-4)?         _idleStart: 167756,
I20250410-10:45:16.812(-4)?         _onTimeout: [Function: bound ],
I20250410-10:45:16.812(-4)?         _timerArgs: undefined,
I20250410-10:45:16.812(-4)?         _repeat: null,
I20250410-10:45:16.812(-4)?         _destroyed: false,
I20250410-10:45:16.812(-4)?         [Symbol(refed)]: false,
I20250410-10:45:16.812(-4)?         [Symbol(kHasPrimitive)]: false,
I20250410-10:45:16.812(-4)?         [Symbol(asyncId)]: 5834,
I20250410-10:45:16.812(-4)?         [Symbol(triggerId)]: 5831
I20250410-10:45:16.813(-4)?       },
I20250410-10:45:16.813(-4)?       [Symbol(kBuffer)]: null,
I20250410-10:45:16.813(-4)?       [Symbol(kBufferCb)]: null,
I20250410-10:45:16.813(-4)?       [Symbol(kBufferGen)]: null,
I20250410-10:45:16.813(-4)?       [Symbol(kCapture)]: false,
I20250410-10:45:16.813(-4)?       [Symbol(kBytesRead)]: 0,
I20250410-10:45:16.813(-4)?       [Symbol(kBytesWritten)]: 0,
I20250410-10:45:16.813(-4)?       [Symbol(RequestTimeout)]: undefined
I20250410-10:45:16.814(-4)?     },
I20250410-10:45:16.814(-4)?     _header: null,
I20250410-10:45:16.814(-4)?     _keepAliveTimeout: 5000,
I20250410-10:45:16.814(-4)?     _onPendingData: [Function: bound updateOutgoingData],
I20250410-10:45:16.814(-4)?     _sent100: false,
I20250410-10:45:16.814(-4)?     _expect_continue: false,
I20250410-10:45:16.815(-4)?     flush: [Function: flush],
I20250410-10:45:16.815(-4)?     write: [Function: write],
I20250410-10:45:16.815(-4)?     end: [Function: end],
I20250410-10:45:16.815(-4)?     on: [Function: on],
I20250410-10:45:16.815(-4)?     writeHead: [Function: writeHead],
I20250410-10:45:16.815(-4)?     [Symbol(kCapture)]: false,
I20250410-10:45:16.815(-4)?     [Symbol(kNeedDrain)]: false,
I20250410-10:45:16.815(-4)?     [Symbol(corked)]: 0,
I20250410-10:45:16.816(-4)?     [Symbol(kOutHeaders)]: null
I20250410-10:45:16.816(-4)?   },
I20250410-10:45:16.816(-4)?   params: {
I20250410-10:45:16.816(-4)?     _id: '4EZkvSztExmJ4qFLE',
I20250410-10:45:16.816(-4)?     query: {},
I20250410-10:45:16.816(-4)?     name: '4EZkvSztExmJ4qFLE.jpg',
I20250410-10:45:16.816(-4)?     version: 'original'
I20250410-10:45:16.816(-4)?   }
I20250410-10:45:16.816(-4)? } 
I20250410-10:45:16.817(-4)? file:  {
I20250410-10:45:16.817(-4)?   _id: '4EZkvSztExmJ4qFLE',
I20250410-10:45:16.817(-4)?   name: 'Polyviewer-App.jpg',
I20250410-10:45:16.817(-4)?   extension: 'jpg',
I20250410-10:45:16.817(-4)?   ext: 'jpg',
I20250410-10:45:16.817(-4)?   extensionWithDot: '.jpg',
I20250410-10:45:16.817(-4)?   path: '',
I20250410-10:45:16.817(-4)?   meta: { gridFsFileId: '7a56171f2cca6be8b9ab34b6' },
I20250410-10:45:16.817(-4)?   type: 'image/jpeg',
I20250410-10:45:16.817(-4)?   mime: 'image/jpeg',
I20250410-10:45:16.818(-4)?   'mime-type': 'image/jpeg',
I20250410-10:45:16.818(-4)?   size: 102015,
I20250410-10:45:16.818(-4)?   userId: null,
I20250410-10:45:16.818(-4)?   versions: {
I20250410-10:45:16.818(-4)?     original: {
I20250410-10:45:16.818(-4)?       path: '',
I20250410-10:45:16.818(-4)?       size: 102015,
I20250410-10:45:16.818(-4)?       type: 'image/jpeg',
I20250410-10:45:16.818(-4)?       extension: 'jpg',
I20250410-10:45:16.819(-4)?       _id: '4EZkvSztExmJ4qFLE'
I20250410-10:45:16.819(-4)?     }
I20250410-10:45:16.819(-4)?   },
I20250410-10:45:16.819(-4)?   _downloadRoute: '/cdn/storage',
I20250410-10:45:16.819(-4)?   _collectionName: 'PageImages',
I20250410-10:45:16.819(-4)?   isVideo: false,
I20250410-10:45:16.819(-4)?   isAudio: false,
I20250410-10:45:16.819(-4)?   isImage: true,
I20250410-10:45:16.820(-4)?   isText: false,
I20250410-10:45:16.820(-4)?   isJSON: false,
I20250410-10:45:16.820(-4)?   isPDF: false,
I20250410-10:45:16.820(-4)?   _storagePath: 'gridFs'
I20250410-10:45:16.820(-4)? } 
I20250410-10:45:16.820(-4)? versionName:  original
I20250410-10:45:16.821(-4)? gridFsFileId: undefined
I20250410-10:45:16.821(-4)? [FilesCollection] [download(/cdn/storage/PageImages/4EZkvSztExmJ4qFLE/original/4EZkvSztExmJ4qFLE.jpg)] [_404] File not found


@dr-dimitru
Copy link
Member

@dpatte I thinks it's obvious that issue in gridFsFileId: undefined
debug createObjectId function it should be simple, log MongoInternals.NpmModules.mongodb.module.ObjectId ensuring it's exists at some point MongoDB renamed this function from ObjectID to ObjectId

Also I have concerns about your createBucket where you comment crashes the router.

In my code, createBucket and createInterceptDownload are now only done on the server side

Note: this part should only be at Server

@dpatte
Copy link

dpatte commented Apr 10, 2025

i will try to debug it.

And yes, I realized that it should only be serverside.

I mentioned it,, though, because in my earlier post I said that it was in lib/, but by removing it from lib/, and putting it in the server side only, i resolved the createBucket issue I had previously mentioned.

@dpatte
Copy link

dpatte commented Apr 12, 2025

I have it working!

I will send you a copy of this handler this weekend;

As I mentioned, I have several apps that must be converted in this manner, one with 10,000 images, so your help has been much appreciated!

@dpatte
Copy link

dpatte commented Apr 12, 2025

Here is my working gridAccess functionality. It is used to convert a VsiVsi:FileCollection into an Ostrio: FilesCollection
Commenting out certain parts then alllows you to run the converted Ostrio:FilesCollection (and remove the VsiVsi package
At this point it doesn't support uploads to the db, or deleting.

In the download handler, i changed 3 lines compared to your 2.3.3 bucket handling code in your github docs, to make it work. your original unchanged lines are commented out for comparison.

// BUCKET ACCESS

import { MongoInternals } from 'meteor/mongo';

const createBucket = (bucketName) => {
  const options = bucketName ? {bucketName} : (void 0);
  return new MongoInternals.NpmModules.mongodb.module.GridFSBucket(MongoInternals.defaultRemoteCollectionDriver().mongo.db, options);
}

const createObjectId = ( gridFsFileId ) => new MongoInternals.NpmModules.mongodb.module.ObjectId(gridFsFileId);

const createInterceptDownload = bucket =>
  function interceptDownload (http, file, versionName) {

//  const { gridFsFileId } = file.versions[ versionName ].meta || {};
    const gridFsFileId  = file.meta.gridFsFileId || {};
    if (gridFsFileId) {

//    const gfsId = createObjectId({ gridFsFileId });
      const gfsId = createObjectId( gridFsFileId );

      const readStream = bucket.openDownloadStream(gfsId);
      readStream.on('data', (data) => {
          http.response.write(data);
      });

      readStream.on('end', () => {
        http.response.end('end');
      });

      readStream.on('error', () => {
        http.response.statusCode = 404;
        http.response.end('not found');
      });

      http.response.setHeader('Cache-Control', this.cacheControl);

//    added line
      const dispositionType = '';

      const dispositionName = "filename=\"" + (encodeURIComponent(file.name)) + "\"; filename=*UTF-8\"" + (encodeURIComponent(file.name)) + "\"; ";
      const dispositionEncoding = 'charset=utf-8';
      http.response.setHeader('Content-Disposition', dispositionType + dispositionName + dispositionEncoding);
    }
    return Boolean(gridFsFileId); // Serve file from either GridFS or FS if it wasn't uploaded yet
  }


// MAIN FUNCTION

gridAccess = function(vsiName,vsiIdx,ostrioName) {

/*

  // ACCESS OLD VsiVsi:FileCollection
  // DISABLED AFTER CONVERSION

  let vsiCollection = FileCollection(vsiName, {
    resumable: true,     // Enable built-in resumable.js chunked upload support
    resumableIndexName: vsiIdx,
    http: [{             // Define HTTP route
      method: 'get',     // Enable a GET endpoint
      path: '/:filename',     // this will be at route "/gridfs/filepage/:filename"
      lookup: function (params, query) {      // uses express style url params
        return { filename: params.filename }; // a query mapping url to myFiles
      }
    }]
  });

  if (Meteor.isServer) {
    vsiCollection.allow({ // T/F
      insert(userId,file) { return isRole('admin',userId); },
      remove(userId,file) { return isRole('admin',userId); },
      read(userId,file) { // anyone can get, if page is active
        if (isRole('admin',userId)) return true;
        // should also check visibility!
        let page = Pages.findOne({file:file._id._str});
        return page && page.active;
      },
      write(userId,file) { return isRole('admin',userId); },
    });
  }

*/

// ACCESS NEW Osrio:FilesCollection

  let ostrioCollection;

  if (Meteor.isServer) {
    ostrioCollection = new FilesCollection({
      collectionName: ostrioName,
      debug: true,
      interceptDownload: createInterceptDownload(createBucket(vsiName)), // only on server
    });
  } else {
    ostrioCollection = new FilesCollection({
      collectionName: ostrioName,
      debug: true,
    });
  }

/*

// COVERT VsiVsi TO ostrio
// DISABLED AFTER CONVERSION

  const filesFromGridFs = vsiCollection.find();

  let count = 0;
  filesFromGridFs.forEach( (file) => {

    console.log(++count);
    console.log('old',file);

    const migratedFile = ostrioCollection._dataToSchema({
      name: file.filename,
      path: '',
      meta: {
        ...file.metadata,
        gridFsFileId: file._id.toHexString(),
      },
      type: file.contentType,
      size: file.length,
      // userId: file.metadata.owner, // <- assign if you have used this field
      ...ostrioCollection._getExt(file.filename),
      _storagePath: 'gridFs',
    });

    console.log('new',migratedFile);

    ostrioCollection.collection.insert(migratedFile);
  });

*/

  return ostrioCollection;

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants