Skip to content
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

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

Open
IvanSeagull opened this issue Mar 26, 2022 · 12 comments

Comments

@IvanSeagull
Copy link

IvanSeagull commented Mar 26, 2022

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);
...
@dziraf
Copy link
Contributor

dziraf commented Mar 28, 2022

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

@JumbleTron
Copy link

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

@Darioazzali
Copy link

Darioazzali commented Apr 26, 2022

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

@etomarat
Copy link

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

@toutpuissantged
Copy link

i have same problem

@etomarat
Copy link

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!

@toutpuissantged
Copy link

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

@etomarat
Copy link

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

@etomarat
Copy link

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

@owujib
Copy link

owujib commented May 9, 2023

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

@kamilglod
Copy link

kamilglod commented Aug 28, 2023

@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});
  }
}

@keyready
Copy link

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

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

No branches or pull requests

9 participants