adminjs-upload icon indicating copy to clipboard operation
adminjs-upload copied to clipboard

EXDEV: cross-device link not permitted, rename 'C:/.../Temp/upload_xxxx' -> 'D:/projectPath/filename.png'

Open xyz-dev-ops opened this issue 3 years ago • 13 comments

Description

I use Sequelize as Orm and postgresql as db. I wanna to store images in json. When I'm trying to upload a file to a resource, it gives an error to the console Error: EXDEV: cross-device link not permitted, rename 'C:/.../Temp/upload_xxxx' -> 'D:/projectPath/filename.png'

and doesnt save anything to images field in db. It saves random file called upload_${somenumbers} to Temp folder, although I specified folder by { bucket: path.join(__dirname, '../public') }

adminRouter.js file

...
const adminJs = new AdminJS({ 
  databases: [db],
  rootPath: '/admin',
  resources: [
    {
      resource: User,
      options: { listProperties: ['id', 'email', 'ava', 'avaTxt', 'images'] },
      features: [
        uploadFeature({
          provider: { local: { bucket: path.join(__dirname, '../public') } },
          properties: {
            file: 'images.file',
            filePath: 'images.path',
            filename: 'images.filename',
            filesToDelete: 'images.toDelete',
            key: 'images.key',
            mimeType: 'images.mimeType',
            bucket: 'images.bucket',
          },
        }),
      ],
    },
  ],
});
...

server.js

...
server.use(express.static('public'));
const adminRouter = require('./Routes/adminRouter');
server.use('/admin', adminRouter);
...

xyz-dev-ops avatar Mar 26 '22 22:03 xyz-dev-ops

I think it's just a windows issue where it doesn't allow you to move files from one partition to another

dziraf avatar Mar 28 '22 11:03 dziraf

I think it's just a windows issue where it doesn't allow you to move files from one partition to another

I have the same issue when using docker

JumbleTron avatar Apr 21 '22 12:04 JumbleTron

Same problem using ArchLinux. I think is because the /tmp folder is in the root partition while the upload folder is in the home partition

Darioazzali avatar Apr 26 '22 07:04 Darioazzali

I have the same issue in docker. Anyone solved this?

etomarat avatar May 18 '22 01:05 etomarat

i have same problem

toutpuissantged avatar May 18 '22 21:05 toutpuissantged

Solution: Made a custom provider using the move method instead of rename (look at the upload method)

import fs, { existsSync } from "fs";
import { move } from "fs-extra";
import path from "path";
import { UploadedFile } from "adminjs";
import { BaseProvider } from "@adminjs/upload";
import { UPLOADS_DIR } from "../env";

export default class UploadProvider extends BaseProvider {
  constructor() {
    super(UPLOADS_DIR);
    if (!existsSync(UPLOADS_DIR)) {
      throw new Error(`directory: "${UPLOADS_DIR}" does not exists. Create it before running LocalAdapter`);
    }
  }

  // * Fixed this method because original does rename instead of move and it doesn't work with docker volume
  public async upload(file: UploadedFile, key: string): Promise<any> {
    const filePath = process.platform === "win32" ? this.path(key) : this.path(key).slice(1); // adjusting file path according to OS
    await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
    await move(file.path, filePath, { overwrite: true });
  }

  public async delete(key: string, bucket: string): Promise<any> {
    await fs.promises.unlink(process.platform === "win32" ? this.path(key, bucket) : this.path(key, bucket).slice(1)); // adjusting file path according to OS
  }

  // eslint-disable-next-line class-methods-use-this
  public path(key: string, bucket?: string): string {
    // Windows doesn't requires the '/' in path, while UNIX system does
    return process.platform === "win32"
      ? `${path.join(bucket || this.bucket, key)}`
      : `/${path.join(bucket || this.bucket, key)}`;
  }
}

And I connect it to the resource like this:

uploadFeature({
      provider: new UploadProvider(),
      validation: {
        mimeTypes: ["image/jpeg", "image/png"],
      },
})

It works fine!

etomarat avatar May 19 '22 00:05 etomarat

it works on windows provided that the project is on the same partition as the windows installation, and on linux also it works provided you have the right admin rights and the right distribution: example the code does not work on heroku but works fine on aws ec2

toutpuissantged avatar May 20 '22 11:05 toutpuissantged

The original LocaleProvider also doesn't work in docker. The code above should work everywhere, but I haven't tested it on Windows.

etomarat avatar May 20 '22 16:05 etomarat

Addition: The original code does not work in docker if the "uploads" folder is mounted as "volume"

etomarat avatar May 20 '22 16:05 etomarat

Can I also use this custom uploader for cloudinary? I have tried making something out of it but I could not, I would really appreciate if I can get any solution

owujib avatar May 09 '23 00:05 owujib

@etomarat solution might be even simplified to the shape:

import path from 'path';

import fsExtra from 'fs-extra';
import {LocalProvider} from '@adminjs/upload';
import {UploadedFile} from 'adminjs';

class LocalProvider2 extends LocalProvider {
  public async upload(file: UploadedFile, key: string): Promise<any> {
    const filePath = process.platform === 'win32' ? this.path(key) : this.path(key).slice(1); // adjusting file path according to OS
    await fsExtra.mkdir(path.dirname(filePath), {recursive: true});
    await fsExtra.move(file.path, filePath, {overwrite: true});
  }
}

kamilglod avatar Aug 28 '23 20:08 kamilglod

Does anyone know about such a problem:

Executing (default): INSERT INTO "brands" ("id","title") VALUES (DEFAULT,$1) RETURNING "id","title","relatedItems","file","s3Key","bucket","mime";
Error: You cannot upload file for not persisted record. Save record first
    at buildRemotePath (file:///C:/.../adminJs/node_modules/@adminjs/upload/build/features/upload-file/utils/build-remote-path.js:16:15)
    at updateRecord (file:///C:/.../adminJs/node_modules/@adminjs/upload/build/features/upload-file/factories/update-record-factory.js:74:29)
    at file:///C:/.../adminJs/node_modules/adminjs/lib/backend/decorators/action/action-decorator.js:120:99
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

It appeared after replacing the original UploadProvider with the one suggested by @etomarat. I use PostgreSQL with ExpressJS

keyready avatar Feb 21 '24 13:02 keyready