paint-brush
FUSE এবং Node.js-এর সাহায্যে কীভাবে একটি ডাইনামিক ফাইল সিস্টেম তৈরি করবেন: একটি ব্যবহারিক পদ্ধতিদ্বারা@rglr
386 পড়া
386 পড়া

FUSE এবং Node.js-এর সাহায্যে কীভাবে একটি ডাইনামিক ফাইল সিস্টেম তৈরি করবেন: একটি ব্যবহারিক পদ্ধতি

দ্বারা Aleksandr Zinin33m2024/06/13
Read on Terminal Reader

অতিদীর্ঘ; পড়তে

আপনি কি কখনও ভাবছেন যখন আপনি sshfs user@remote:~/ /mnt/remoteroot চালান তখন কী হয়? কিভাবে একটি দূরবর্তী সার্ভার থেকে ফাইলগুলি আপনার স্থানীয় সিস্টেমে উপস্থিত হয় এবং এত দ্রুত সিঙ্ক্রোনাইজ হয়? আপনি কি উইকিপিডিয়াএফএস সম্পর্কে শুনেছেন, যা আপনাকে উইকিপিডিয়া নিবন্ধ সম্পাদনা করতে দেয় যেন এটি আপনার ফাইল সিস্টেমের একটি ফাইল? এটা জাদু নয়—এটি FUSE এর শক্তি (ইউজারস্পেসে ফাইল সিস্টেম)। FUSE আপনাকে OS কার্নেল বা নিম্ন-স্তরের প্রোগ্রামিং ভাষার গভীর জ্ঞানের প্রয়োজন ছাড়াই আপনার নিজস্ব ফাইল সিস্টেম তৈরি করতে দেয়। এই নিবন্ধটি Node.js এবং TypeScript এর সাথে FUSE ব্যবহার করে একটি ব্যবহারিক সমাধান উপস্থাপন করে। আমরা অন্বেষণ করব কিভাবে FUSE হুডের নিচে কাজ করে এবং একটি বাস্তব-বিশ্বের কাজ সমাধান করে এর প্রয়োগ প্রদর্শন করব। FUSE এবং Node.js এর জগতে একটি উত্তেজনাপূর্ণ অ্যাডভেঞ্চারে আমার সাথে যোগ দিন।
featured image - FUSE এবং Node.js-এর সাহায্যে কীভাবে একটি ডাইনামিক ফাইল সিস্টেম তৈরি করবেন: একটি ব্যবহারিক পদ্ধতি
Aleksandr Zinin HackerNoon profile picture

আপনি কি কখনও ভাবছেন যখন আপনি sshfs user@remote:~/ /mnt/remoteroot চালান তখন কী হয়? কিভাবে একটি দূরবর্তী সার্ভার থেকে ফাইলগুলি আপনার স্থানীয় সিস্টেমে উপস্থিত হয় এবং এত দ্রুত সিঙ্ক্রোনাইজ হয়? আপনি কি WikipediaFS এর কথা শুনেছেন, যা আপনাকে উইকিপিডিয়া নিবন্ধ সম্পাদনা করতে দেয় যেন এটি আপনার ফাইল সিস্টেমের একটি ফাইল? এটা জাদু নয়—এটি FUSE এর শক্তি (ইউজারস্পেসে ফাইল সিস্টেম)। FUSE আপনাকে OS কার্নেল বা নিম্ন-স্তরের প্রোগ্রামিং ভাষার গভীর জ্ঞানের প্রয়োজন ছাড়াই আপনার নিজস্ব ফাইল সিস্টেম তৈরি করতে দেয়।


এই নিবন্ধটি Node.js এবং TypeScript এর সাথে FUSE ব্যবহার করে একটি ব্যবহারিক সমাধান উপস্থাপন করে। আমরা অন্বেষণ করব কিভাবে FUSE হুডের নিচে কাজ করে এবং একটি বাস্তব-বিশ্বের কাজ সমাধান করে এর প্রয়োগ প্রদর্শন করব। FUSE এবং Node.js এর জগতে একটি উত্তেজনাপূর্ণ অ্যাডভেঞ্চারে আমার সাথে যোগ দিন।

ভূমিকা

আমি আমার কাজের মিডিয়া ফাইলের (প্রাথমিকভাবে ছবি) জন্য দায়ী ছিলাম। এর মধ্যে অনেক কিছু রয়েছে: সাইড- বা টপ-ব্যানার, চ্যাটে মিডিয়া, স্টিকার ইত্যাদি। অবশ্যই, এর জন্য অনেক প্রয়োজনীয়তা রয়েছে, যেমন "ব্যানার হল PNG বা WEBP, 300x1000 পিক্সেল।" প্রয়োজনীয়তা পূরণ না হলে, আমাদের ব্যাক অফিস একটি ইমেজ মাধ্যমে যেতে দেবে না. এবং একটি অবজেক্ট ডিডুপ্লিকেশন মেকানিজম আছে: কোনো ছবি একই নদীতে দুইবার প্রবেশ করতে পারে না।


এটি আমাদের এমন একটি পরিস্থিতির দিকে নিয়ে যায় যেখানে পরীক্ষার উদ্দেশ্যে আমাদের কাছে চিত্রগুলির একটি বিশাল সেট রয়েছে৷ আমি আমার জীবনকে সহজ করতে শেল ওয়ান-লাইনার বা উপনাম ব্যবহার করেছি।


এই ক্ষেত্রে:

 convert -size 300x1000 xc:gray +noise random /tmp/out.png


নয়েজ ইমেজের উদাহরণ


bash এবং convert সংমিশ্রণ একটি দুর্দান্ত সরঞ্জাম, তবে স্পষ্টতই, এটি সমস্যা সমাধানের সবচেয়ে সুবিধাজনক উপায় নয়। QA দলের পরিস্থিতি নিয়ে আলোচনা করা আরও জটিলতা প্রকাশ করে। ইমেজ তৈরিতে ব্যয় করা প্রশংসনীয় সময় ছাড়াও, যখন আমরা একটি সমস্যা তদন্ত করি তখন প্রথম প্রশ্নটি হল "আপনি কি নিশ্চিত যে আপনি একটি অনন্য ছবি আপলোড করেছেন?" আমি বিশ্বাস করি আপনি বুঝতে পেরেছেন যে এটি কতটা বিরক্তিকর।

আসুন আমরা যে প্রযুক্তিটি ব্যবহার করতে চাই তা চয়ন করি

আপনি একটি সহজ পদ্ধতি অবলম্বন করতে পারেন: একটি ওয়েব পরিষেবা তৈরি করুন যা একটি স্ব-ব্যাখ্যামূলক ফাইল সহ একটি রুট পরিবেশন করে, যেমন GET /image/1000x100/random.zip?imagesCount=100 । রুটটি অনন্য ইমেজের সেট সহ একটি জিপ ফাইল ফিরিয়ে দেবে। এটি ভাল শোনাচ্ছে, কিন্তু এটি আমাদের মূল সমস্যাটির সমাধান করে না: সমস্ত আপলোড করা ফাইলগুলি পরীক্ষার জন্য অনন্য হতে হবে৷


আপনার পরবর্তী চিন্তা হতে পারে "আমরা কি পেলোডটি পাঠানোর সময় প্রতিস্থাপন করতে পারি?" QA দল API কলের জন্য পোস্টম্যান ব্যবহার করে। আমি পোস্টম্যানের অভ্যন্তরীণ তদন্ত করেছি এবং বুঝতে পেরেছি যে আমরা "উড়লে" অনুরোধের বডি পরিবর্তন করতে পারি না


আরেকটি সমাধান হল ফাইল সিস্টেমে একটি ফাইল প্রতিস্থাপন করা যখন কিছু ফাইল পড়ার চেষ্টা করে। লিনাক্সে Inotify নামে একটি নোটিফিকেশন সাবসিস্টেম রয়েছে, যা আপনাকে ফাইল সিস্টেম ইভেন্ট যেমন ডিরেক্টরির পরিবর্তন বা ফাইল পরিবর্তনের বিষয়ে সতর্ক করে। আপনি যদি "ভিজুয়াল স্টুডিও কোডটি এই বৃহৎ কর্মক্ষেত্রে ফাইল পরিবর্তনের জন্য দেখতে অক্ষম" পেয়ে থাকেন, তাহলে Inotify-এ একটি সমস্যা আছে। এটি একটি ইভেন্ট ফায়ার করতে পারে যখন একটি ডিরেক্টরি পরিবর্তন করা হয়, একটি ফাইলের নাম পরিবর্তন করা হয়, একটি ফাইল খোলা হয় এবং তাই।


ইভেন্টের সম্পূর্ণ তালিকা এখানে পাওয়া যাবে: https://sites.uclouvain.be/SystInfo/usr/include/linux/inotify.h.html


সুতরাং, পরিকল্পনা হল:

  1. IN_OPEN ইভেন্টটি শোনা এবং ফাইল বর্ণনাকারী গণনা করা।

  2. IN_CLOSE ইভেন্টটি শোনা; গণনা 0 এ নেমে গেলে, আমরা ফাইলটি প্রতিস্থাপন করব।


ভাল শোনাচ্ছে, কিন্তু এর সাথে কয়েকটি সমস্যা রয়েছে:

  • শুধুমাত্র লিনাক্স inotify সমর্থন করে।
  • ফাইলের সমান্তরাল অনুরোধ একই তথ্য ফেরত দেওয়া উচিত.
  • যদি একটি ফাইলের নিবিড় IO-অপারেশন থাকে, প্রতিস্থাপন কখনই ঘটবে না।
  • Inotify ইভেন্টগুলি পরিবেশন করে এমন একটি পরিষেবা ক্র্যাশ হলে, ফাইলগুলি ব্যবহারকারী ফাইল সিস্টেমে থাকবে।


এই সমস্যাগুলি সমাধান করার জন্য, আমরা আমাদের নিজস্ব ফাইল সিস্টেম লিখতে পারি। কিন্তু আরেকটি সমস্যা আছে: নিয়মিত ফাইল সিস্টেম ওএস কার্নেল স্পেসে চলে। এটির জন্য আমাদের OS কার্নেল এবং C/Rust-এর মতো ভাষা ব্যবহার সম্পর্কে জানতে হবে। এছাড়াও, প্রতিটি কার্নেলের জন্য, আমাদের একটি নির্দিষ্ট মডিউল (ড্রাইভার) লিখতে হবে।


অতএব, আমরা যে সমস্যার সমাধান করতে চাই তার জন্য একটি ফাইল সিস্টেম লিখতে ওভারকিল; এমনকি যদি সামনে একটি দীর্ঘ সপ্তাহান্ত থাকে। সৌভাগ্যবশত, এই জন্তুটিকে নিয়ন্ত্রণ করার একটি উপায় রয়েছে: F ilesystem in Use rspace (FUSE)। FUSE হল একটি প্রজেক্ট যা আপনাকে কার্নেল কোড এডিট না করেই ফাইল সিস্টেম তৈরি করতে দেয়। এর অর্থ হল FUSE-এর মাধ্যমে যেকোনো প্রোগ্রাম বা স্ক্রিপ্ট, কোনো জটিল মূল-সম্পর্কিত যুক্তি ছাড়াই, একটি ফ্ল্যাশ, হার্ড ড্রাইভ বা SSD অনুকরণ করতে সক্ষম।


অন্য কথায়, একটি সাধারণ ইউজারস্পেস প্রক্রিয়া তার নিজস্ব ফাইল সিস্টেম তৈরি করতে পারে, যা আপনার ইচ্ছামত যেকোনো সাধারণ প্রোগ্রামের মাধ্যমে অ্যাক্সেস করা যেতে পারে - নটিলাস, ডলফিন, এলএস, ইত্যাদি।


কেন FUSE আমাদের প্রয়োজনীয়তাগুলি কভার করার জন্য ভাল? FUSE-ভিত্তিক ফাইল সিস্টেমগুলি ব্যবহারকারী-স্পেসের প্রক্রিয়াগুলির উপর নির্মিত হয়। অতএব, আপনি যে কোনো ভাষা ব্যবহার করতে পারেন যা আপনার জানার সাথে libfuse এর সাথে বাঁধা আছে। এছাড়াও, আপনি FUSE এর সাথে একটি ক্রস-প্ল্যাটফর্ম সমাধান পাবেন।


NodeJS এবং TypeScript এর সাথে আমার অনেক অভিজ্ঞতা আছে, এবং আমি এই (বিস্ময়কর) সমন্বয়টিকে আমাদের ব্র্যান্ড-নতুন FS-এর জন্য কার্যকরী পরিবেশ হিসেবে বেছে নিতে চাই। উপরন্তু, TypeScript একটি চমৎকার বস্তু-ভিত্তিক বেস প্রদান করে। এটি আমাকে শুধুমাত্র সোর্স কোডই নয়, যা আপনি পাবলিক গিটহাব রেপোতে খুঁজে পেতে পারেন কিন্তু প্রকল্পের কাঠামোও দেখাতে পারবেন।

FUSE-এ গভীরভাবে ডুব দিন

আমাকে অফিসিয়াল FUSE পৃষ্ঠা থেকে একটি স্পিকিং উদ্ধৃতি প্রদান করতে দিন:

FUSE হল একটি ইউজারস্পেস ফাইল সিস্টেম ফ্রেমওয়ার্ক। এটি একটি কার্নেল মডিউল (fuse.ko), একটি ইউজারস্পেস লাইব্রেরি (libfuse) এবং একটি মাউন্ট ইউটিলিটি (fusermount) নিয়ে গঠিত।


ফাইল সিস্টেম লেখার জন্য একটি কাঠামো উত্তেজনাপূর্ণ শোনাচ্ছে।


প্রতিটি FUSE অংশের অর্থ কী তা আমার ব্যাখ্যা করা উচিত:

  1. fuse.ko সমস্ত কার্নেল-সম্পর্কিত নিম্ন-স্তরের কাজ করছে; এটি আমাদের একটি OS কার্নেলে হস্তক্ষেপ এড়াতে দেয়।


  2. libfuse হল একটি লাইব্রেরি যা fuse.ko এর সাথে যোগাযোগের জন্য একটি উচ্চ-স্তরের স্তর প্রদান করে।


  3. fusermount ব্যবহারকারীদের ইউজারস্পেস ফাইল সিস্টেম মাউন্ট/আনমাউন্ট করতে দেয় (আমাকে ক্যাপ্টেন অবভিয়স বলুন!)।


সাধারণ নীতিগুলি এইরকম দেখায়:
FUSE এর সাধারণ নীতি


ইউজারস্পেস প্রক্রিয়া (এই ক্ষেত্রে ls ) ভার্চুয়াল ফাইল সিস্টেম কার্নেলের কাছে একটি অনুরোধ করে যা অনুরোধটিকে FUSE কার্নেল মডিউলে রুট করে। FUSE মডিউল, পরিবর্তে, অনুরোধটিকে ইউজারস্পেসে ফাইল সিস্টেম উপলব্ধির দিকে নিয়ে যায় (উপরের ছবিতে ./hello )।


ভার্চুয়াল ফাইল সিস্টেম নাম দ্বারা প্রতারিত হবেন না. এটি সরাসরি FUSE এর সাথে সম্পর্কিত নয়। এটি কার্নেলের সফ্টওয়্যার স্তর যা ইউজারস্পেস প্রোগ্রামগুলিতে ফাইল সিস্টেম ইন্টারফেস প্রদান করে। সরলতার জন্য, আপনি এটিকে একটি যৌগিক প্যাটার্ন হিসাবে উপলব্ধি করতে পারেন।


libfuse দুটি ধরণের API অফার করে: উচ্চ-স্তরের এবং নিম্ন-স্তরের। তাদের মিল রয়েছে তবে গুরুত্বপূর্ণ পার্থক্য রয়েছে। নিম্ন-স্তরের একটি অ্যাসিঙ্ক্রোনাস এবং শুধুমাত্র inodes সাথে কাজ করে। অ্যাসিঙ্ক্রোনাস, এই ক্ষেত্রে, এর অর্থ হল নিম্ন-স্তরের API ব্যবহার করে এমন একটি ক্লায়েন্টকে নিজেই প্রতিক্রিয়া পদ্ধতিগুলি কল করা উচিত।


উচ্চ-স্তরের একটি সুবিধাজনক পথ ব্যবহার করার ক্ষমতা প্রদান করে (উদাহরণস্বরূপ, /etc/shadow ) আরও "বিমূর্ত" inodes পরিবর্তে এবং একটি সিঙ্ক উপায়ে প্রতিক্রিয়া প্রদান করে। এই নিবন্ধে, আমি ব্যাখ্যা করব কীভাবে নিম্ন-স্তরের এবং inodes চেয়ে উচ্চ-স্তরের কাজ করে।


আপনি যদি আপনার নিজস্ব ফাইল সিস্টেম বাস্তবায়ন করতে চান, তাহলে আপনার VFS থেকে অনুরোধ পরিবেশনের জন্য দায়ী পদ্ধতির একটি সেট প্রয়োগ করা উচিত। সবচেয়ে সাধারণ পদ্ধতি হল:


  • open(path, accessFlags): fd -- পাথ দ্বারা একটি ফাইল খুলুন। পদ্ধতিটি একটি নম্বর শনাক্তকারী প্রদান করবে, তথাকথিত ফাইল বর্ণনাকারী (এখান থেকে fd )। একটি অ্যাক্সেস ফ্ল্যাগ হল একটি বাইনারি মাস্ক যা বর্ণনা করে যে ক্লায়েন্ট প্রোগ্রামটি কোন অপারেশনটি সম্পাদন করতে চায় (শুধুমাত্র-পড়ুন, কেবলমাত্র লিখুন, পড়তে-লিখুন, চালানো বা অনুসন্ধান করুন)।


  • read(path, fd, Buffer, size, offset): count of bytes read -- পাস করা বাফারের সাথে fd ফাইল বর্ণনাকারীর সাথে লিঙ্ক করা ফাইল থেকে size বাইট পড়া। path আর্গুমেন্ট উপেক্ষা করা হয়েছে কারণ আমরা fd ব্যবহার করব।


  • write(path, fd, Buffer, size, offset): count of bytes written -- fd এর সাথে লিঙ্ক করা ফাইলে বাফার থেকে size বাইট লিখুন।


  • release(fd) -- fd বন্ধ করুন।


  • truncate(path, size) -- একটি ফাইলের আকার পরিবর্তন করুন। আপনি যদি ফাইলগুলি পুনরায় লিখতে চান তবে পদ্ধতিটি সংজ্ঞায়িত করা উচিত (এবং আমরা করি)।


  • getattr(path) -- ফাইলের প্যারামিটার যেমন সাইজ, এ তৈরি, অ্যাক্সেস করা ইত্যাদি প্রদান করে। পদ্ধতিটি ফাইল সিস্টেমের সবচেয়ে কলযোগ্য পদ্ধতি, তাই নিশ্চিত করুন যে আপনি সর্বোত্তম একটি তৈরি করেছেন।


  • readdir(path) -- সমস্ত সাবডিরেক্টরি পড়ুন।


উপরের পদ্ধতিগুলি উচ্চ-স্তরের FUSE API-এর উপরে নির্মিত প্রতিটি সম্পূর্ণরূপে অপারেবল ফাইল সিস্টেমের জন্য গুরুত্বপূর্ণ। কিন্তু তালিকা সম্পূর্ণ নয়; সম্পূর্ণ তালিকা আপনি https://libfuse.github.io/doxygen/structfuse__operations.html এ খুঁজে পেতে পারেন


ফাইল বর্ণনাকারীর ধারণাটি পুনরায় দেখার জন্য: UNIX-এর মতো সিস্টেমে, MacOS সহ, একটি ফাইল বর্ণনাকারী হল ফাইল এবং অন্যান্য I/O সংস্থান যেমন সকেট এবং পাইপগুলির জন্য একটি বিমূর্ততা। যখন একটি প্রোগ্রাম একটি ফাইল খোলে, OS একটি সংখ্যাসূচক শনাক্তকারী প্রদান করে যাকে একটি ফাইল বর্ণনাকারী বলা হয়। এই পূর্ণসংখ্যা প্রতিটি প্রক্রিয়ার জন্য OS এর ফাইল বর্ণনাকারী টেবিলে একটি সূচক হিসাবে কাজ করে। FUSE ব্যবহার করে একটি ফাইল সিস্টেম বাস্তবায়ন করার সময়, আমাদের নিজেদেরকে ফাইল বর্ণনাকারী তৈরি করতে হবে।


ক্লায়েন্ট যখন একটি ফাইল খোলে তখন কল প্রবাহ বিবেচনা করুন:

  1. getattr(path: /random.png) → { size: 98 }; ক্লায়েন্ট ফাইলের আকার পেয়েছে।


  2. open(path: /random.png) → 10; পথ দ্বারা ফাইল খোলা; FUSE বাস্তবায়ন একটি ফাইল বর্ণনাকারী নম্বর প্রদান করে।


  3. read(path: /random.png, fd: 10 buffer, size: 50, offset: 0) → 50; প্রথম 50 বাইট পড়ুন।


  4. read(path: /random.png, fd: 10 buffer, size: 50, offset: 50) → 48; পরবর্তী 50 পড়ুন। ফাইলের আকারের কারণে 48 বাইট পড়া হয়েছে।


  5. release(10); সমস্ত ডেটা পড়া হয়েছিল, তাই fd এর কাছাকাছি।

আসুন একটি ন্যূনতম-ভাইয়েবল পণ্য লিখি এবং এটিতে পোস্টম্যানের প্রতিক্রিয়া পরীক্ষা করি

পোস্টম্যান কীভাবে একটি কাস্টম ফাইল সিস্টেমের সাথে ইন্টারঅ্যাক্ট করবে তা পরীক্ষা করার জন্য আমাদের পরবর্তী পদক্ষেপ হল libfuse এর উপর ভিত্তি করে একটি ন্যূনতম ফাইল সিস্টেম তৈরি করা।


FS-এর জন্য গ্রহণযোগ্যতার প্রয়োজনীয়তাগুলি সহজবোধ্য: FS-এর রুটে একটি random.txt ফাইল থাকা উচিত, যার বিষয়বস্তু প্রতিবার পড়ার সময় অনন্য হওয়া উচিত (আসুন এটিকে "সর্বদা অনন্য পঠন" বলি)। বিষয়বস্তুতে একটি এলোমেলো UUID এবং একটি নতুন লাইন দ্বারা পৃথক করা ISO বিন্যাসে একটি বর্তমান সময় থাকা উচিত। উদাহরণ স্বরূপ:

 3790d212-7e47-403a-a695-4d680f21b81c 2012-12-12T04:30:30


ন্যূনতম পণ্য দুটি অংশ গঠিত হবে. প্রথমটি হল একটি সাধারণ ওয়েব পরিষেবা যা HTTP POST অনুরোধগুলি গ্রহণ করবে এবং টার্মিনালে একটি অনুরোধের বডি প্রিন্ট করবে। কোডটি বেশ সহজ এবং আমাদের সময়ের মূল্য নয়, প্রধানত কারণ নিবন্ধটি FUSE সম্পর্কে, এক্সপ্রেস নয়। দ্বিতীয় অংশ হল ফাইল সিস্টেমের বাস্তবায়ন যা প্রয়োজনীয়তা পূরণ করে। এটিতে কোডের মাত্র 83টি লাইন রয়েছে।


কোডের জন্য, আমরা নোড-ফিউজ-বাইন্ডিং লাইব্রেরি ব্যবহার করব, যা libfuse এর উচ্চ-স্তরের API-কে বাইন্ডিং প্রদান করে।


আপনি নীচের কোডটি এড়িয়ে যেতে পারেন; আমি নীচে একটি কোড সারাংশ লিখতে যাচ্ছি.

 const crypto = require('crypto'); const fuse = require('node-fuse-bindings'); // MOUNT_PATH is the path where our filesystem will be available. For Windows, this will be a path like 'D://' const MOUNT_PATH = process.env.MOUNT_PATH || './mnt'; function getRandomContent() { const txt = [crypto.randomUUID(), new Date().toISOString(), ''].join('\n'); return Buffer.from(txt); } function main() { // fdCounter is a simple counter that increments each time a file is opened // using this we can get the file content, which is unique for each opening let fdCounter = 0; // fd2ContentMap is a map that stores file content by fd const fd2ContentMap = new Map(); // Postman does not work reliably if we give it a file with size 0 or just the wrong size, // so we precompute the file size // it is guaranteed that the file size will always be the same within one run, so there will be no problems with this const randomTxtSize = getRandomContent().length; // fuse.mount is a function that mounts the filesystem fuse.mount( MOUNT_PATH, { readdir(path, cb) { console.log('readdir(%s)', path); if (path === '/') { return cb(0, ['random.txt']); } return cb(0, []); }, getattr(path, cb) { console.log('getattr(%s)', path); if (path === '/') { return cb(0, { // mtime is the file modification time mtime: new Date(), // atime is the file access time atime: new Date(), // ctime is the metadata or file content change time ctime: new Date(), size: 100, // mode is the file access flags // this is a mask that defines access rights to the file for different types of users // and the type of file itself mode: 16877, // file owners // in our case, it will be the owner of the current process uid: process.getuid(), gid: process.getgid(), }); } if (path === '/random.txt') { return cb(0, { mtime: new Date(), atime: new Date(), ctime: new Date(), size: randomTxtSize, mode: 33188, uid: process.getuid(), gid: process.getgid(), }); } cb(fuse.ENOENT); }, open(path, flags, cb) { console.log('open(%s, %d)', path, flags); if (path !== '/random.txt') return cb(fuse.ENOENT, 0); const fd = fdCounter++; fd2ContentMap.set(fd, getRandomContent()); cb(0, fd); }, read(path, fd, buf, len, pos, cb) { console.log('read(%s, %d, %d, %d)', path, fd, len, pos); const buffer = fd2ContentMap.get(fd); if (!buffer) { return cb(fuse.EBADF); } const slice = buffer.slice(pos, pos + len); slice.copy(buf); return cb(slice.length); }, release(path, fd, cb) { console.log('release(%s, %d)', path, fd); fd2ContentMap.delete(fd); cb(0); }, }, function (err) { if (err) throw err; console.log('filesystem mounted on ' + MOUNT_PATH); }, ); } // Handle the SIGINT signal separately to correctly unmount the filesystem // Without this, the filesystem will not be unmounted and will hang in the system // If for some reason unmount was not called, you can forcibly unmount the filesystem using the command // fusermount -u ./MOUNT_PATH process.on('SIGINT', function () { fuse.unmount(MOUNT_PATH, function () { console.log('filesystem at ' + MOUNT_PATH + ' unmounted'); process.exit(); }); }); main();


আমি একটি ফাইলে অনুমতি বিট সম্পর্কে আমাদের জ্ঞান রিফ্রেশ করার পরামর্শ দিই। অনুমতি বিট হল একটি ফাইলের সাথে যুক্ত বিটের একটি সেট; তারা ফাইলটি পড়তে/লিখতে/চালানোর অনুমতি দেয় তার একটি বাইনারি উপস্থাপনা। "কে" তিনটি গ্রুপ অন্তর্ভুক্ত করে: মালিক, মালিক গোষ্ঠী এবং অন্যান্য।


প্রতিটি গ্রুপের জন্য আলাদাভাবে অনুমতি সেট করা যেতে পারে। সাধারণত, প্রতিটি অনুমতি একটি তিন-সংখ্যার সংখ্যা দ্বারা প্রতিনিধিত্ব করা হয়: পড়ুন (বাইনারী সংখ্যা পদ্ধতিতে 4 বা '100'), লিখুন (2 বা '010'), এবং সম্পাদন (1 বা '001')। আপনি যদি এই সংখ্যাগুলি একসাথে যোগ করেন তবে আপনি একটি সম্মিলিত অনুমতি তৈরি করবেন। উদাহরণস্বরূপ, 4 + 2 (বা '100' + '010') 6 ('110') তৈরি করবে, যার অর্থ রিড + রাইট (RO) অনুমতি।


যদি ফাইলের মালিকের অ্যাক্সেস মাস্ক থাকে 7 (বাইনারিতে 111, যার অর্থ রিড, রাইট এবং এক্সিকিউট), গ্রুপে 5 (101, যার অর্থ রিড এবং এক্সিকিউট), এবং অন্যদের 4 (100, মানে শুধুমাত্র পঠনযোগ্য)। সুতরাং, ফাইলের জন্য সম্পূর্ণ অ্যাক্সেস মাস্ক দশমিকে 754। মনে রাখবেন যে এক্সিকিউশন অনুমতি ডিরেক্টরির জন্য পড়ার অনুমতি হয়ে যায়।


আসুন ফাইল সিস্টেম বাস্তবায়নে ফিরে যাই এবং এটির একটি পাঠ্য সংস্করণ তৈরি করি: প্রতিবার যখন একটি ফাইল খোলা হয় (একটি open কলের মাধ্যমে), পূর্ণসংখ্যার কাউন্টার বৃদ্ধি পায়, উন্মুক্ত কলের মাধ্যমে ফিরে আসা ফাইল বর্ণনাকারী তৈরি করে। র্যান্ডম বিষয়বস্তু তারপর তৈরি এবং একটি কী-মানের দোকানে ফাইল বর্ণনাকারীকে কী হিসাবে সংরক্ষণ করা হয়। যখন একটি পঠিত কল করা হয়, তখন সংশ্লিষ্ট বিষয়বস্তুর অংশ ফেরত দেওয়া হয়।


একটি রিলিজ কল করার পরে, বিষয়বস্তু মুছে ফেলা হয়। Ctrl+C চাপার পরে ফাইল সিস্টেম আনমাউন্ট করতে SIGINT পরিচালনা করতে মনে রাখবেন। অন্যথায়, আমাদের টার্মিনালে fusermount -u ./MOUNT_PATH ব্যবহার করে ম্যানুয়ালি এটি করতে হবে।


এখন, পরীক্ষায় ঝাঁপ দাও। আমরা ওয়েব সার্ভার চালাই, তারপর আসন্ন FS-এর জন্য রুট ফোল্ডার হিসাবে একটি খালি ফোল্ডার তৈরি করি এবং মূল স্ক্রিপ্ট চালাই। "3000 পোর্টে সার্ভার শোনা" লাইন প্রিন্ট করার পরে, পোস্টম্যান খুলুন, এবং কোনো পরামিতি পরিবর্তন না করেই ওয়েব সার্ভারে একটি সারিতে কয়েকটি অনুরোধ পাঠান।
বাম দিক হল FS, ডানদিকে হল ওয়েব সার্ভার


সবকিছু ভাল দেখায়! প্রতিটি অনুরোধে অনন্য ফাইলের বিষয়বস্তু থাকে, যেমনটি আমরা আগে থেকেই দেখেছি। লগগুলিও প্রমাণ করে যে উপরে বর্ণিত ফাইল খোলা কলের প্রবাহ "FUSE এর গভীরে ডুব" বিভাগে সঠিক।


MVP সহ GitHub রেপো: https://github.com/pinkiesky/node-fuse-mvp । আপনি আপনার স্থানীয় পরিবেশে এই কোডটি চালাতে পারেন বা আপনার নিজস্ব ফাইল সিস্টেম বাস্তবায়নের জন্য এই রেপোটিকে বয়লারপ্লেট হিসাবে ব্যবহার করতে পারেন।

মূল ধারণা

পদ্ধতিটি পরীক্ষা করা হয়েছে-এখন প্রাথমিক বাস্তবায়নের সময়।


"সর্বদা অনন্য পঠন" বাস্তবায়নের আগে, আমাদের প্রথম যে জিনিসটি বাস্তবায়ন করা উচিত তা হল মূল ফাইলগুলির জন্য ক্রিয়াকলাপ তৈরি এবং মুছে ফেলা। আমরা আমাদের ভার্চুয়াল ফাইল সিস্টেমের মধ্যে একটি ডিরেক্টরির মাধ্যমে এই ইন্টারফেসটি বাস্তবায়ন করব। ব্যবহারকারী মূল চিত্রগুলি রাখবেন যা তারা "সর্বদা অনন্য" বা "এলোমেলো" করতে চান এবং ফাইল সিস্টেম বাকিগুলি প্রস্তুত করবে।


এখানে এবং নিম্নলিখিত বিভাগে, "সর্বদা অনন্য পঠন", "র্যান্ডম ইমেজ" বা "র্যান্ডম ফাইল" এমন একটি ফাইলকে বোঝায় যেটি প্রতিবার পড়ার সময় বাইনারি অর্থে অনন্য বিষয়বস্তু ফেরত দেয়, যদিও দৃশ্যত, এটি যতটা সম্ভব একই থাকে। মূল থেকে


ফাইল সিস্টেমের রুটে দুটি ডিরেক্টরি থাকবে: ইমেজ ম্যানেজার এবং ইমেজ। প্রথমটি ব্যবহারকারীর আসল ফাইলগুলি পরিচালনা করার জন্য একটি ফোল্ডার (আপনি এটিকে একটি CRUD সংগ্রহস্থল হিসাবে ভাবতে পারেন)। দ্বিতীয়টি হল ব্যবহারকারীর দৃষ্টিকোণ থেকে অব্যবস্থাপিত ডিরেক্টরি যাতে র্যান্ডম ছবি থাকে।
ব্যবহারকারী ফাইল সিস্টেমের সাথে ইন্টারঅ্যাক্ট করে


টার্মিনাল আউটপুট হিসাবে FS গাছ


আপনি উপরের ছবিতে দেখতে পাচ্ছেন, আমরা শুধুমাত্র "সর্বদা অনন্য" ছবিই নয়, একটি ফাইল রূপান্তরকারীও বাস্তবায়ন করব! এটি একটি অতিরিক্ত বোনাস।


আমাদের বাস্তবায়নের মূল ধারণা হল যে প্রোগ্রামটিতে একটি অবজেক্ট ট্রি থাকবে, প্রতিটি নোড এবং পাতার সাথে সাধারণ FUSE পদ্ধতিগুলি প্রদান করবে। যখন প্রোগ্রামটি একটি এফএস কল পায়, তখন এটি সংশ্লিষ্ট পথ দ্বারা গাছের মধ্যে একটি নোড বা একটি পাতা খুঁজে পায়। উদাহরণস্বরূপ, প্রোগ্রামটি getattr(/Images/1/original/) কল পায় এবং তারপর নোডটি খুঁজে বের করার চেষ্টা করে যেখানে পথটি ঠিকানা দেওয়া হয়েছে।


এটার মতো কিছু: এফএস গাছের উদাহরণ


পরবর্তী প্রশ্ন হল কিভাবে আমরা মূল ছবি সংরক্ষণ করব। প্রোগ্রামের একটি ছবিতে বাইনারি ডেটা এবং মেটা তথ্য থাকবে (একটি মেটাতে একটি আসল ফাইলের নাম, ফাইল মাইম-টাইপ, ইত্যাদি অন্তর্ভুক্ত)। বাইনারি ডেটা বাইনারি স্টোরেজে সংরক্ষণ করা হবে। আসুন এটিকে সরলীকরণ করি এবং ব্যবহারকারী (বা হোস্ট) ফাইল সিস্টেমে বাইনারি ফাইলগুলির একটি সেট হিসাবে বাইনারি স্টোরেজ তৈরি করি। মেটা তথ্য একইভাবে সংরক্ষণ করা হবে: ব্যবহারকারী ফাইল সিস্টেমে পাঠ্য ফাইলের ভিতরে JSON।


আপনি মনে করতে পারেন, "আসুন একটি ন্যূনতম-ভালো পণ্য লিখি" বিভাগে, আমরা একটি ফাইল সিস্টেম তৈরি করেছি যা একটি টেমপ্লেট দ্বারা একটি পাঠ্য ফাইল ফেরত দেয়। এটিতে একটি র্যান্ডম UUID এবং একটি বর্তমান তারিখ রয়েছে, তাই ডেটার স্বতন্ত্রতা সমস্যা ছিল না - ডেটার সংজ্ঞা দ্বারা অনন্যতা অর্জন করা হয়েছিল৷ যাইহোক, এই বিন্দু থেকে, প্রোগ্রামটি প্রিলোড করা ব্যবহারকারীর চিত্রগুলির সাথে কাজ করা উচিত। সুতরাং, আমরা কীভাবে এমন চিত্রগুলি তৈরি করতে পারি যা একই রকম তবে সর্বদা অনন্য (বাইট এবং ফলস্বরূপ হ্যাশের ক্ষেত্রে) আসলটির উপর ভিত্তি করে?


আমি প্রস্তাবিত সমাধান বেশ সহজ। আসুন একটি চিত্রের উপরের বাম কোণে একটি RGB নয়েজ স্কোয়ার রাখি। নয়েজ স্কোয়ার 16x16 পিক্সেল হওয়া উচিত। এটি প্রায় একই ছবি প্রদান করে কিন্তু বাইটের একটি অনন্য ক্রম গ্যারান্টি দেয়। এটা বিভিন্ন ইমেজ অনেক নিশ্চিত করার জন্য যথেষ্ট হবে? এর কিছু গণিত করা যাক. বর্গক্ষেত্রের আকার হল 16. 16×16 = 256 RGB পিক্সেল একটি একক বর্গক্ষেত্রে। প্রতিটি পিক্সেলের 256×256×256 = 16,777,216 ভেরিয়েন্ট রয়েছে।


এইভাবে, অনন্য বর্গক্ষেত্রের গণনা হল 16,777,216^256 -- 1,558 সংখ্যা বিশিষ্ট একটি সংখ্যা, যা পর্যবেক্ষণযোগ্য মহাবিশ্বের পরমাণুর সংখ্যার চেয়ে অনেক বেশি। এর মানে কি আমরা বর্গ আকার কমাতে পারি? দুর্ভাগ্যবশত, JPEG-এর মতো ক্ষতিকর কম্প্রেশন অনন্য স্কোয়ারের সংখ্যা উল্লেখযোগ্যভাবে কমিয়ে দেবে, তাই 16x16 হল সর্বোত্তম আকার।


শব্দ স্কোয়ার সহ চিত্রগুলির উদাহরণ

ক্লাস ওভার পাসথ্রু

গাছটি
UML ক্লাস ডায়াগ্রাম একটি FUSE-ভিত্তিক সিস্টেমের জন্য ইন্টারফেস এবং ক্লাস দেখাচ্ছে। IFUSETreeNode বাস্তবায়নকারী FileFUSETreeNode এবং DirectoryFUSETreeNode সহ IFUSEHandler, ObjectTreeNode এবং IFUSETreeNode ইন্টারফেসগুলি অন্তর্ভুক্ত করে৷ প্রতিটি ইন্টারফেস এবং ক্লাস তাদের সম্পর্ক এবং শ্রেণিবিন্যাস চিত্রিত করে বৈশিষ্ট্য এবং পদ্ধতি তালিকাভুক্ত করে

IFUSEHandler হল একটি ইন্টারফেস যা সাধারণ FUSE কলগুলি পরিবেশন করে৷ আপনি দেখতে পাচ্ছেন যে আমি যথাক্রমে read/write এর পরিবর্তে readAll/writeAll দিয়েছি। আমি পঠন এবং লেখার ক্রিয়াকলাপগুলিকে সহজ করার জন্য এটি করেছি: যখন IFUSEHandler একটি সম্পূর্ণ অংশের জন্য পঠন/লেখা তৈরি করে, তখন আমরা আংশিক পঠন/লেখার যুক্তি অন্য জায়গায় সরাতে সক্ষম হই। এর মানে IFUSEHandler ফাইল বর্ণনাকারী, বাইনারি ডেটা ইত্যাদি সম্পর্কে কিছু জানার দরকার নেই।


open FUSE পদ্ধতিতেও একই জিনিস ঘটেছে। গাছের একটি উল্লেখযোগ্য দিক হল এটি চাহিদা অনুযায়ী উৎপন্ন হয়। পুরো গাছটিকে মেমরিতে সংরক্ষণ করার পরিবর্তে, প্রোগ্রামটি নোড তৈরি করে যখন সেগুলি অ্যাক্সেস করা হয়। এই আচরণটি প্রোগ্রামটিকে নোড তৈরি বা অপসারণের ক্ষেত্রে ট্রি পুনর্নির্মাণের সমস্যা এড়াতে অনুমতি দেয়।


ObjectTreeNode ইন্টারফেস চেক করুন, এবং আপনি দেখতে পাবেন যে children একটি অ্যারে নয় বরং একটি পদ্ধতি, তাই এইভাবে তারা চাহিদা অনুযায়ী তৈরি হয়। FileFUSETreeNode এবং DirectoryFUSETreeNode হল বিমূর্ত ক্লাস যেখানে কিছু পদ্ধতি একটি NotSupported ত্রুটি ছুঁড়ে দেয় (স্পষ্টতই, FileFUSETreeNode কখনই readdir প্রয়োগ করা উচিত নয়)।

FUSEFacade

UML ক্লাস ডায়াগ্রাম একটি FUSE সিস্টেমের জন্য ইন্টারফেস এবং তাদের সম্পর্ক দেখাচ্ছে। চিত্রটিতে IFUSEHandler, IFUSETreeNode, IFileDescriptorStorage ইন্টারফেস এবং FUSEFacade ক্লাস অন্তর্ভুক্ত রয়েছে। IFUSEHandler-এর অ্যাট্রিবিউটের নাম এবং পদ্ধতি আছে চেক অ্যাভাইলেবিলিটি, ক্রিয়েট, গেট্যাটর, রিডঅল, রিমুভ এবং রাইট অ্যাল। IFileDescriptorStorage এর আছে মেথড গেট, openRO, openWO, এবং রিলিজ। IFUSETreeNode IFUSEHandlerকে প্রসারিত করে। FUSEFacade-এ কনস্ট্রাক্টর, তৈরি, getattr, open, read, readdir, রিলিজ, rmdir, safeGetNode, আনলিঙ্ক এবং লেখার পদ্ধতি রয়েছে এবং IFUSETreeNode এবং IFileDescriptorStorage উভয়ের সাথে ইন্টারঅ্যাক্ট করে।


FUSEFacade হল সবচেয়ে গুরুত্বপূর্ণ শ্রেণী যা প্রোগ্রামের মূল যুক্তি প্রয়োগ করে এবং বিভিন্ন অংশকে একত্রে আবদ্ধ করে। node-fuse-bindings একটি কলব্যাক-ভিত্তিক API আছে, কিন্তু FUSEFacade পদ্ধতিগুলি একটি প্রতিশ্রুতি-ভিত্তিক একটি দিয়ে তৈরি করা হয়। এই অসুবিধা মোকাবেলা করার জন্য, আমি এই মত একটি কোড ব্যবহার করেছি:

 const handleResultWrapper = <T>( promise: Promise<T>, cb: (err: number, result: T) => void, ) => { promise .then((result) => { cb(0, result); }) .catch((err) => { if (err instanceof FUSEError) { fuseLogger.info(`FUSE error: ${err}`); return cb(err.code, null as T); } fuseLogger.warn(err); cb(fuse.EIO, null as T); }); }; // Ex. usage: // open(path, flags, cb) { // handleResultWrapper(fuseFacade.open(path, flags), cb); // },


FUSEFacade পদ্ধতিগুলি handleResultWrapper মোড়ানো হয়। FUSEFacade এর প্রতিটি পদ্ধতি যা একটি পাথ ব্যবহার করে সহজভাবে পাথ পার্স করে, ট্রিতে একটি নোড খুঁজে পায় এবং অনুরোধ করা পদ্ধতিটিকে কল করে।


FUSEFacade ক্লাস থেকে কয়েকটি পদ্ধতি বিবেচনা করুন।

 async create(path: string, mode: number): Promise<number> { this.logger.info(`create(${path})`); // Convert path `/Image Manager/1/image.jpg` in // `['Image Manager', '1', 'image.jpg']` // splitPath will throw error if something goes wrong const parsedPath = this.splitPath(path); // `['Image Manager', '1', 'image.jpg']` const name = parsedPath.pop()!; // 'image.jpg' // Get node by path (`/Image Manager/1` after `pop` call) // or throw an error if node not found const node = await this.safeGetNode(parsedPath); // Call the IFUSEHandler method. Pass only a name, not a full path! await node.create(name, mode); // Create a file descriptor const fdObject = this.fdStorage.openWO(); return fdObject.fd; } async readdir(path: string): Promise<string[]> { this.logger.info(`readdir(${path})`); const node = await this.safeGetNode(path); // As you see, the tree is generated on the fly return (await node.children()).map((child) => child.name); } async open(path: string, flags: number): Promise<number> { this.logger.info(`open(${path}, ${flags})`); const node = await this.safeGetNode(path); // A leaf node is a directory if (!node.isLeaf) { throw new FUSEError(fuse.EACCES, 'invalid path'); } // Usually checkAvailability checks access await node.checkAvailability(flags); // Get node content and put it in created file descriptor const fileData: Buffer = await node.readAll(); // fdStorage is IFileDescriptorStorage, we will consider it below const fdObject = this.fdStorage.openRO(fileData); return fdObject.fd; }

একটি ফাইল বর্ণনাকারী

পরবর্তী পদক্ষেপ নেওয়ার আগে, আমাদের প্রোগ্রামের প্রেক্ষাপটে একটি ফাইল বর্ণনাকারী কী তা ঘনিষ্ঠভাবে দেখে নেওয়া যাক।

ইউএমএল ক্লাস ডায়াগ্রাম একটি FUSE সিস্টেমে ফাইল বর্ণনাকারীদের জন্য ইন্টারফেস এবং তাদের সম্পর্ক দেখাচ্ছে। ডায়াগ্রামে IFileDescriptor, IFileDescriptorStorage ইন্টারফেস এবং ReadWriteFileDescriptor, ReadFileDescriptor, এবং WriteFileDescriptor ক্লাস অন্তর্ভুক্ত রয়েছে। IFileDescriptor-এ বাইনারি, এফডি, সাইজ এবং মেথড readToBuffer, writeToBuffer বৈশিষ্ট্য রয়েছে। IFileDescriptorStorage এর আছে মেথড গেট, openRO, openWO, এবং রিলিজ। ReadWriteFileDescriptor অতিরিক্ত কনস্ট্রাক্টর, readToBuffer এবং writeToBuffer পদ্ধতির সাথে IFileDescriptor প্রয়োগ করে। ReadFileDescriptor এবং WriteFileDescriptor ReadWriteFileDescriptor প্রসারিত করে, ReadFileDescriptor এর একটি writeToBuffer পদ্ধতি এবং WriteFileDescriptor এর একটি readToBuffer পদ্ধতি রয়েছে

ReadWriteFileDescriptor হল একটি শ্রেণী যা ফাইল বর্ণনাকারীকে একটি সংখ্যা এবং বাফার হিসাবে বাইনারি ডেটা সংরক্ষণ করে। ক্লাসটিতে readToBuffer এবং writeToBuffer পদ্ধতি রয়েছে যা একটি ফাইল বর্ণনাকারী বাফারে ডেটা পড়তে এবং লেখার ক্ষমতা প্রদান করে। ReadFileDescriptor এবং WriteFileDescriptor হল শুধুমাত্র-পঠন এবং শুধুমাত্র-লেখার বর্ণনাকারীর বাস্তবায়ন।


IFileDescriptorStorage হল একটি ইন্টারফেস যা ফাইল বর্ণনাকারী স্টোরেজ বর্ণনা করে। এই ইন্টারফেসের জন্য প্রোগ্রামটির শুধুমাত্র একটি বাস্তবায়ন রয়েছে: InMemoryFileDescriptorStorage । আপনি নাম থেকে বলতে পারেন, এটি ফাইল বর্ণনাকারীকে মেমরিতে সংরক্ষণ করে কারণ বর্ণনাকারীদের জন্য আমাদের অধ্যবসায়ের প্রয়োজন নেই।


আসুন দেখুন কিভাবে FUSEFacade ফাইল বর্ণনাকারী এবং স্টোরেজ ব্যবহার করে:

 async read( fd: number, // File descriptor to read from buf: Buffer, // Buffer to store the read data len: number, // Length of data to read pos: number, // Position in the file to start reading from ): Promise<number> { // Retrieve the file descriptor object from storage const fdObject = this.fdStorage.get(fd); if (!fdObject) { // If the file descriptor is invalid, throw an error throw new FUSEError(fuse.EBADF, 'invalid fd'); } // Read data into the buffer and return the number of bytes read return fdObject.readToBuffer(buf, len, pos); } async write( fd: number, // File descriptor to write to buf: Buffer, // Buffer containing the data to write len: number, // Length of data to write pos: number, // Position in the file to start writing at ): Promise<number> { // Retrieve the file descriptor object from storage const fdObject = this.fdStorage.get(fd); if (!fdObject) { // If the file descriptor is invalid, throw an error throw new FUSEError(fuse.EBADF, 'invalid fd'); } // Write data from the buffer and return the number of bytes written return fdObject.writeToBuffer(buf, len, pos); } async release(path: string, fd: number): Promise<0> { // Retrieve the file descriptor object from storage const fdObject = this.fdStorage.get(fd); if (!fdObject) { // If the file descriptor is invalid, throw an error throw new FUSEError(fuse.EBADF, 'invalid fd'); } // Safely get the node corresponding to the file path const node = await this.safeGetNode(path); // Write all the data from the file descriptor object to the node await node.writeAll(fdObject.binary); // Release the file descriptor from storage this.fdStorage.release(fd); // Return 0 indicating success return 0; }


উপরের কোডটি সোজা। এটি ফাইল বর্ণনাকারী থেকে পড়ার, লিখতে এবং প্রকাশ করার পদ্ধতিগুলিকে সংজ্ঞায়িত করে, ক্রিয়া সম্পাদন করার আগে ফাইল বর্ণনাকারী বৈধ কিনা তা নিশ্চিত করে। রিলিজ পদ্ধতিটি ফাইল ডিস্ক্রিপ্টর অবজেক্ট থেকে ফাইল সিস্টেম নোডে ডেটা লেখে এবং ফাইল বর্ণনাকারীকে মুক্ত করে।


আমরা libfuse এবং গাছের চারপাশে কোড দিয়ে সম্পন্ন করেছি। ইমেজ-সম্পর্কিত কোডে ডুব দেওয়ার সময় এসেছে।

ছবি: "ডেটা ট্রান্সফার অবজেক্ট" অংশ
ইউএমএল ক্লাস ডায়াগ্রাম চিত্র পরিচালনার জন্য ইন্টারফেস এবং তাদের সম্পর্ক দেখাচ্ছে। চিত্রটিতে ImageBinary, ImageMeta, Image, এবং IImageMetaStorage ইন্টারফেস অন্তর্ভুক্ত রয়েছে। ইমেজবাইনারিতে বাফার এবং আকারের বৈশিষ্ট্য রয়েছে। ইমেজমেটাতে আইডি, নাম, আসল ফাইলের নাম এবং আসল ফাইল টাইপ বৈশিষ্ট্য রয়েছে। ছবিতে বাইনারি এবং মেটা বৈশিষ্ট্য রয়েছে, যেখানে বাইনারি হল ImageBinary এবং মেটা হল ImageMeta টাইপ৷ IImageMetaStorage-এর পদ্ধতি তৈরি করা, পাওয়া, তালিকা করা এবং অপসারণ করা হয়েছে


ImageMeta হল একটি বস্তু যা একটি চিত্র সম্পর্কে মেটা তথ্য সঞ্চয় করে। IImageMetaStorage হল একটি ইন্টারফেস যা মেটার জন্য একটি স্টোরেজ বর্ণনা করে। প্রোগ্রামটির ইন্টারফেসের জন্য শুধুমাত্র একটি বাস্তবায়ন রয়েছে: FSImageMetaStorage ক্লাস একটি একক JSON ফাইলে সংরক্ষিত ইমেজ মেটাডেটা পরিচালনা করতে IImageMetaStorage ইন্টারফেস প্রয়োগ করে।


এটি মেমরিতে মেটাডেটা সঞ্চয় করতে একটি ক্যাশে ব্যবহার করে এবং প্রয়োজনের সময় JSON ফাইল থেকে পড়ার মাধ্যমে ক্যাশে হাইড্রেট করা নিশ্চিত করে। ক্লাসটি ইমেজ মেটাডেটা তৈরি, পুনরুদ্ধার, তালিকা এবং মুছে ফেলার পদ্ধতি সরবরাহ করে এবং এটি আপডেটগুলি অব্যাহত রাখতে JSON ফাইলে পরিবর্তনগুলি লিখে দেয়। ক্যাশে একটি IO অপারেশন গণনা হ্রাস করে কর্মক্ষমতা উন্নত করে।


ImageBinary , স্পষ্টতই, একটি বস্তু যা বাইনারি ইমেজ ডেটা আছে। Image ইন্টারফেস হল ImageMeta এবং ImageBinary এর সংমিশ্রণ।

ছবি: বাইনারি স্টোরেজ এবং জেনারেটর

ইউএমএল ক্লাস ডায়াগ্রাম চিত্র তৈরি এবং বাইনারি স্টোরেজের জন্য ইন্টারফেস এবং তাদের সম্পর্ক দেখাচ্ছে। চিত্রটিতে IBinaryStorage, IImageGenerator ইন্টারফেস এবং FSBinaryStorage, ImageGeneratorComposite, PassThroughImageGenerator, TextImageGenerator এবং ImageLoaderFacade ক্লাস অন্তর্ভুক্ত রয়েছে। IBinaryStorage এর লোড, অপসারণ এবং লেখার পদ্ধতি রয়েছে। FSBinaryStorage IBinaryStorage প্রয়োগ করে এবং একটি অতিরিক্ত কনস্ট্রাক্টর রয়েছে। IImageGenerator এর একটি মেথড জেনারেট আছে। PassThroughImageGenerator এবং TextImageGenerator IImageGenerator প্রয়োগ করে। ইমেজজেনারেটর কম্পোজিট-এর রয়েছে অ্যাডজেনারেটর এবং জেনারেট করার পদ্ধতি। ImageLoaderFacade একটি কনস্ট্রাক্টর এবং একটি লোড পদ্ধতি আছে, এবং IBinaryStorage এবং IImageGenerator এর সাথে ইন্টারঅ্যাক্ট করে


IBinaryStorage হল বাইনারি ডেটা স্টোরেজের জন্য একটি ইন্টারফেস। বাইনারি স্টোরেজ ইমেজ থেকে আনলিঙ্ক করা উচিত এবং যেকোনো ডেটা সঞ্চয় করতে পারে: ছবি, ভিডিও, JSON, বা টেক্সট। এই সত্যটি আমাদের কাছে গুরুত্বপূর্ণ, এবং আপনি কেন তা দেখতে পাবেন।


IImageGenerator একটি ইন্টারফেস যা একটি জেনারেটর বর্ণনা করে। জেনারেটর প্রোগ্রামের একটি গুরুত্বপূর্ণ অংশ। এটি কাঁচা বাইনারি ডেটা প্লাস মেটা নেয় এবং এর উপর ভিত্তি করে একটি চিত্র তৈরি করে। কেন প্রোগ্রাম জেনারেটর প্রয়োজন? তাদের ছাড়া প্রোগ্রাম কাজ করতে পারে?


এটা করতে পারে, কিন্তু জেনারেটর বাস্তবায়নে নমনীয়তা যোগ করবে। জেনারেটর ব্যবহারকারীদের ছবি, টেক্সট ডেটা আপলোড করার অনুমতি দেয় এবং বিস্তৃতভাবে বলতে গেলে, যে কোনো ডেটার জন্য আপনি একটি জেনারেটর লেখেন।


IImageGenerator ইন্টারফেস ব্যবহার করে একটি টেক্সট ফাইলকে একটি ছবিতে রূপান্তর করার প্রক্রিয়া দেখানো ডায়াগ্রাম। বাম দিকে, 'হ্যালো, ওয়ার্ল্ড!' বিষয়বস্তু সহ 'myfile.txt' লেবেলযুক্ত একটি পাঠ্য ফাইলের জন্য একটি আইকন রয়েছে৷ 'IImageGenerator' লেবেলযুক্ত একটি তীর ডানদিকে নির্দেশ করে, যেখানে একই টেক্সট 'হ্যালো, ওয়ার্ল্ড!' সহ 'myfile.png' লেবেলযুক্ত একটি চিত্র ফাইলের জন্য একটি আইকন রয়েছে। ছবিতে প্রদর্শিত হয়


প্রবাহটি নিম্নরূপ: বাইনারি ডেটা স্টোরেজ থেকে লোড করা হয় ( উপরের ছবিতে myfile.txt ), এবং তারপর বাইনারি একটি জেনারেটরে চলে যায়। এটি একটি ইমেজ তৈরি করে "মাছিতে।" আপনি এটিকে এক ফর্ম্যাট থেকে অন্য ফর্ম্যাটে রূপান্তরকারী হিসাবে উপলব্ধি করতে পারেন যা আমাদের জন্য আরও সুবিধাজনক৷


আসুন একটি জেনারেটরের একটি উদাহরণ দেখুন:

 import { createCanvas } from 'canvas'; // Import createCanvas function from the canvas library to create and manipulate images const IMAGE_SIZE_RE = /(\d+)x(\d+)/; // Regular expression to extract width and height dimensions from a string export class TextImageGenerator implements IImageGenerator { // method to generate an image from text async generate(meta: ImageMeta, rawBuffer: Buffer): Promise<Image | null> { // Step 1: Verify the MIME type is text if (meta.originalFileType !== MimeType.TXT) { // If the file type is not text, return null indicating no image generation return null; } // Step 2: Determine the size of the image const imageSize = { width: 800, // Default width height: 600, // Default height }; // Extract dimensions from the name if present const imageSizeRaw = IMAGE_SIZE_RE.exec(meta.name); if (imageSizeRaw) { // Update the width and height based on extracted values, or keep defaults imageSize.width = Number(imageSizeRaw[1]) || imageSize.width; imageSize.height = Number(imageSizeRaw[2]) || imageSize.height; } // Step 3: Convert the raw buffer to a string to get the text content const imageText = rawBuffer.toString('utf-8'); // Step 4: Create a canvas with the determined size const canvas = createCanvas(imageSize.width, imageSize.height); const ctx = canvas.getContext('2d'); // Get the 2D drawing context // Step 5: Prepare the canvas background ctx.fillStyle = '#000000'; // Set fill color to black ctx.fillRect(0, 0, imageSize.width, imageSize.height); // Fill the entire canvas with the background color // Step 6: Draw the text onto the canvas ctx.textAlign = 'start'; // Align text to the start (left) ctx.textBaseline = 'top'; // Align text to the top ctx.fillStyle = '#ffffff'; // Set text color to white ctx.font = '30px Open Sans'; // Set font style and size ctx.fillText(imageText, 10, 10); // Draw the text with a margin // Step 7: Convert the canvas to a PNG buffer and create the Image object return { meta, // Include the original metadata binary: { buffer: canvas.toBuffer('image/png'), // Convert canvas content to a PNG buffer }, }; } }


ImageLoaderFacade ক্লাস হল একটি সম্মুখভাগ যা যৌক্তিকভাবে স্টোরেজ এবং জেনারেটরকে একত্রিত করে- অন্য কথায়, এটি আপনি উপরে যে প্রবাহটি পড়েছেন তা প্রয়োগ করে।

ছবি: বৈকল্পিক

ইউএমএল ক্লাস ডায়াগ্রাম চিত্র তৈরি এবং বাইনারি স্টোরেজের জন্য ইন্টারফেস এবং তাদের সম্পর্ক দেখাচ্ছে। চিত্রটিতে IBinaryStorage, IImageGenerator ইন্টারফেস এবং FSBinaryStorage, ImageGeneratorComposite, PassThroughImageGenerator, TextImageGenerator এবং ImageLoaderFacade ক্লাস অন্তর্ভুক্ত রয়েছে। IBinaryStorage এর লোড, অপসারণ এবং লেখার পদ্ধতি রয়েছে। FSBinaryStorage IBinaryStorage প্রয়োগ করে এবং একটি অতিরিক্ত কনস্ট্রাক্টর রয়েছে। IImageGenerator এর একটি মেথড জেনারেট আছে। PassThroughImageGenerator এবং TextImageGenerator IImageGenerator প্রয়োগ করে। ইমেজজেনারেটর কম্পোজিট-এর রয়েছে অ্যাডজেনারেটর এবং জেনারেট করার পদ্ধতি। ImageLoaderFacade একটি কনস্ট্রাক্টর এবং একটি লোড পদ্ধতি আছে এবং IBinaryStorage এবং IImageGenerator এর সাথে ইন্টারঅ্যাক্ট করে


IImageVariant হল বিভিন্ন ইমেজ ভেরিয়েন্ট তৈরি করার জন্য একটি ইন্টারফেস। এই প্রেক্ষাপটে, একটি বৈকল্পিক হল "ফ্লাইতে" তৈরি করা একটি চিত্র যা আমাদের ফাইল সিস্টেমে ফাইল দেখার সময় ব্যবহারকারীর কাছে প্রদর্শিত হবে। জেনারেটর থেকে প্রধান পার্থক্য হল যে এটি কাঁচা ডেটার পরিবর্তে একটি ছবিকে ইনপুট হিসাবে নেয়।


প্রোগ্রামটির তিনটি রূপ রয়েছে: ImageAlwaysRandom , ImageOriginalVariant , এবং ImageWithTextImageAlwaysRandom একটি র‍্যান্ডম RGB নয়েজ স্কোয়ার সহ আসল চিত্রটি ফেরত দেয়।


 export class ImageAlwaysRandomVariant implements IImageVariant { // Define a constant for the size of the random square edge in pixels private readonly randomSquareEdgeSizePx = 16; // Constructor takes the desired output format for the image constructor(private readonly outputFormat: ImageFormat) {} // Asynchronous method to generate a random variant of an image async generate(image: Image): Promise<ImageBinary> { // Step 1: Load the image using the sharp library const sharpImage = sharp(image.binary.buffer); // Step 2: Retrieve metadata and raw buffer from the image const metadata = await sharpImage.metadata(); // Get image metadata const buffer = await sharpImage.raw().toBuffer(); // Get raw pixel data // the buffer size is plain array with size of image width * image height * channels count (3 or 4) // Step 3: Apply random pixel values to a small square region in the image for (let y = 0; y < this.randomSquareEdgeSizePx; y++) { for (let x = 0; x < this.randomSquareEdgeSizePx; x++) { // Calculate the buffer offset for the current pixel const offset = y * metadata.width! * metadata.channels! + x * metadata.channels!; // Set random values for RGB channels buffer[offset + 0] = randInt(0, 255); // Red channel buffer[offset + 1] = randInt(0, 255); // Green channel buffer[offset + 2] = randInt(0, 255); // Blue channel // If the image has an alpha channel, set it to 255 (fully opaque) if (metadata.channels === 4) { buffer[offset + 3] = 255; // Alpha channel } } } // Step 4: Create a new sharp image from the modified buffer and convert it to the desired format const result = await sharp(buffer, { raw: { width: metadata.width!, height: metadata.height!, channels: metadata.channels!, }, }) .toFormat(this.outputFormat) // Convert to the specified output format .toBuffer(); // Get the final image buffer // Step 5: Return the generated image binary data return { buffer: result, // Buffer containing the generated image }; } }


আমি নোডজেএস-এ চিত্রগুলি পরিচালনা করার সবচেয়ে সুবিধাজনক উপায় হিসাবে sharp লাইব্রেরি ব্যবহার করি: https://github.com/lovell/sharp


ImageOriginalVariant কোনো পরিবর্তন ছাড়াই একটি ছবি ফেরত দেয় (কিন্তু এটি একটি ভিন্ন কম্প্রেশন বিন্যাসে একটি ছবি ফিরিয়ে দিতে পারে)। ImageWithText এর উপরে লিখিত টেক্সট সহ একটি চিত্র প্রদান করে। এটি সহায়ক হবে যখন আমরা একটি একক চিত্রের পূর্বনির্ধারিত রূপগুলি তৈরি করি। উদাহরণস্বরূপ, যদি আমাদের একটি চিত্রের 10টি র্যান্ডম বৈচিত্র্যের প্রয়োজন হয় তবে আমাদের অবশ্যই এই বৈচিত্রগুলিকে একে অপরের থেকে আলাদা করতে হবে।


এখানে সমাধান হল আসলটির উপর ভিত্তি করে 10টি ছবি তৈরি করা, যেখানে আমরা প্রতিটি ছবির উপরের-বাম কোণে 0 থেকে 9 পর্যন্ত একটি ক্রমিক সংখ্যা রেন্ডার করি।

ছবিগুলির একটি ক্রম প্রশস্ত চোখ সহ একটি সাদা এবং কালো বিড়াল দেখাচ্ছে৷ চিত্রগুলিকে বাম দিকে 0 থেকে শুরু করে, 1 দ্বারা বৃদ্ধি করে এবং ডানদিকে 9 পর্যন্ত উপবৃত্তের সাথে অবিরত সংখ্যা সহ লেবেল করা হয়৷ বিড়ালের অভিব্যক্তি প্রতিটি ছবিতে একই থাকে


ImageCacheWrapper ভেরিয়েন্ট থেকে আলাদা উদ্দেশ্য আছে এবং বিশেষ IImageVariant ক্লাসের ফলাফল ক্যাশ করে একটি মোড়ক হিসেবে কাজ করে। ইমেজ কনভার্টার, টেক্সট-টু-ইমেজ জেনারেটর ইত্যাদির মতো পরিবর্তন না হওয়া সত্তাগুলিকে মোড়ানোর জন্য এটি ব্যবহার করা হবে। এই ক্যাশিং প্রক্রিয়াটি দ্রুত ডেটা পুনরুদ্ধার সক্ষম করে, প্রধানত যখন একই ছবি একাধিকবার পড়া হয়।


ঠিক আছে, আমরা প্রোগ্রামের সমস্ত প্রাথমিক অংশ কভার করেছি। এটা একসাথে সবকিছু একত্রিত করার সময়.

গাছের গঠন

UML ক্লাস ডায়াগ্রাম চিত্র পরিচালনার সাথে সম্পর্কিত বিভিন্ন FUSE ট্রি নোডের মধ্যে শ্রেণিবিন্যাস এবং সম্পর্ক দেখায়। ক্লাসের মধ্যে রয়েছে ImageVariantFileFUSETreeNode, ImageCacheWrapper, ImageItemAlwaysRandomDirFUSETreeNode, ImageItemOriginalDirFUSETreeNode, ImageItemCounterDirFUSETreeNode, ImageManagerItemFileFUSETreeNode, ImageCacheWrapper de, ImagesDirFUSETreeNode, এবং RootDirFUSETreeNode। প্রতিটি ক্লাসে ইমেজ মেটাডেটা, বাইনারি ডেটা এবং ফাইল ক্রিয়াকলাপ যেমন তৈরি, readAll, writeAll, রিমুভ করা এবং getattr এর সাথে প্রাসঙ্গিক বৈশিষ্ট্য এবং পদ্ধতি রয়েছে


নীচের ক্লাস ডায়াগ্রামটি প্রতিনিধিত্ব করে যে কীভাবে ট্রি ক্লাসগুলি তাদের চিত্রের প্রতিরূপের সাথে একত্রিত হয়। ডায়াগ্রামটি নীচে থেকে উপরে পড়তে হবে। RootDir (আমাকে নামের FUSETreeNode পোস্টফিক্স এড়িয়ে চলুন) হল ফাইল সিস্টেমের রুট ডির যা প্রোগ্রামটি বাস্তবায়ন করছে। উপরের সারিতে গিয়ে দুটি ডির দেখুন: ImagesDir এবং ImagesManagerDirImagesManagerDir ব্যবহারকারীর ছবি তালিকা ধারণ করে এবং সেগুলি নিয়ন্ত্রণ করতে দেয়। তারপর, ImagesManagerItemFile একটি নির্দিষ্ট ফাইলের জন্য একটি নোড। এই ক্লাস CRUD অপারেশন বাস্তবায়ন করে।


একটি নোডের একটি স্বাভাবিক বাস্তবায়ন হিসাবে ImagesManagerDir বিবেচনা করুন:

 class ImageManagerDirFUSETreeNode extends DirectoryFUSETreeNode { name = 'Image Manager'; // Name of the directory constructor( private readonly imageMetaStorage: IImageMetaStorage, private readonly imageBinaryStorage: IBinaryStorage, ) { super(); // Call the parent class constructor } async children(): Promise<IFUSETreeNode[]> { // Dynamically create child nodes // In some cases, dynamic behavior can be problematic, requiring a cache of child nodes // to avoid redundant creation of IFUSETreeNode instances const list = await this.imageMetaStorage.list(); return list.map( (meta) => new ImageManagerItemFileFUSETreeNode( this.imageMetaStorage, this.imageBinaryStorage, meta, ), ); } async create(name: string, mode: number): Promise<void> { // Create a new image metadata entry await this.imageMetaStorage.create(name); } async getattr(): Promise<Stats> { return { // File modification date mtime: new Date(), // File last access date atime: new Date(), // File creation date // We do not store dates for our images, // so we simply return the current date ctime: new Date(), // Number of links nlink: 1, size: 100, // File access flags mode: FUSEMode.directory( FUSEMode.ALLOW_RWX, // Owner access rights FUSEMode.ALLOW_RX, // Group access rights FUSEMode.ALLOW_RX, // Access rights for all others ), // User ID of the file owner uid: process.getuid ? process.getuid() : 0, // Group ID for which the file is accessible gid: process.getgid ? process.getgid() : 0, }; } // Explicitly forbid deleting the 'Images Manager' folder remove(): Promise<void> { throw FUSEError.accessDenied(); } }


সামনের দিকে এগিয়ে যাওয়া, ImagesDir ব্যবহারকারীর ছবির নাম অনুসারে সাব-ডিরেক্টরি রয়েছে। ImagesItemDir প্রতিটি ডিরেক্টরির জন্য দায়ী। এটি সমস্ত উপলব্ধ বৈকল্পিক অন্তর্ভুক্ত; আপনার মনে আছে, বৈকল্পিক সংখ্যা তিনটি। প্রতিটি ভেরিয়েন্ট হল একটি ডিরেক্টরি যাতে বিভিন্ন ফরম্যাটে চূড়ান্ত চিত্র ফাইল থাকে (বর্তমানে: jpeg, png, এবং webm)। ImagesItemOriginalDir এবং ImagesItemCounterDir একটি ক্যাশে সমস্ত উদ্ভাবিত ImageVariantFile দৃষ্টান্ত মোড়ানো।


এটি মূল চিত্রগুলির ধ্রুবক পুনরায় এনকোডিং এড়াতে প্রয়োজনীয় কারণ এনকোডিং CPU-সাপেক্ষ। চিত্রের শীর্ষে রয়েছে ImageVariantFile । এটি বাস্তবায়নের মুকুট গহনা এবং পূর্বে বর্ণিত IFUSEHandler এবং IImageVariant এর রচনা। এটি সেই ফাইল যার দিকে আমাদের সমস্ত প্রচেষ্টা তৈরি করা হয়েছে।

পরীক্ষামূলক

আসুন পরীক্ষা করি কিভাবে চূড়ান্ত ফাইল সিস্টেম একই ফাইলের সমান্তরাল অনুরোধগুলি পরিচালনা করে। এটি করার জন্য, আমরা একাধিক থ্রেডে md5sum ইউটিলিটি চালাব, যা ফাইল সিস্টেম থেকে ফাইলগুলি পড়বে এবং তাদের হ্যাশগুলি গণনা করবে। তারপর, আমরা এই হ্যাশ তুলনা করব. যদি সবকিছু সঠিকভাবে কাজ করে তবে হ্যাশগুলি আলাদা হওয়া উচিত।

 #!/bin/bash # Loop to run the md5sum command 5 times in parallel for i in {1..5} do echo "Run $i..." # `&` at the end of the command runs it in the background md5sum ./mnt/Images/2020-09-10_22-43/always_random/2020-09-10_22-43.png & done echo 'wait...' # Wait for all background processes to finish wait


আমি স্ক্রিপ্টটি চালিয়েছি এবং নিম্নলিখিত আউটপুটটি পরীক্ষা করেছি (স্বচ্ছতার জন্য কিছুটা পরিষ্কার করা হয়েছে):

 Run 1... Run 2... Run 3... Run 4... Run 5... wait... bcdda97c480db74e14b8779a4e5c9d64 0954d3b204c849ab553f1f5106d576aa 564eeadfd8d0b3e204f018c6716c36e9 73a92c5ef27992498ee038b1f4cfb05e 77db129e37fdd51ef68d93416fec4f65


চমৎকার! সমস্ত হ্যাশ ভিন্ন, মানে ফাইল সিস্টেম প্রতিবার একটি অনন্য চিত্র প্রদান করে!

উপসংহার

আমি আশা করি এই নিবন্ধটি আপনাকে আপনার নিজস্ব FUSE বাস্তবায়ন লিখতে অনুপ্রাণিত করেছে। মনে রাখবেন, এই প্রকল্পের উত্স কোড এখানে উপলব্ধ: https://github.com/pinkiesky/node-fuse-images


আমরা যে ফাইল সিস্টেমটি তৈরি করেছি তা FUSE এবং Node.js এর সাথে কাজ করার মূল নীতিগুলি প্রদর্শন করার জন্য সরলীকৃত হয়েছে। উদাহরণস্বরূপ, এটি সঠিক তারিখ বিবেচনা করে না। উন্নতির জন্য প্রচুর জায়গা আছে। ব্যবহারকারীর GIF ফাইলগুলি থেকে ফ্রেম নিষ্কাশন, ভিডিও ট্রান্সকোডিং বা এমনকি কর্মীদের মাধ্যমে সমান্তরাল করার মতো কার্যকারিতা যুক্ত করার কল্পনা করুন।


যাইহোক, নিখুঁত ভাল শত্রু. আপনার যা আছে তা দিয়ে শুরু করুন, এটি কাজ করুন এবং তারপরে পুনরাবৃত্তি করুন। শুভ কোডিং!