Chromium 书签加载过程分析c++

news2024/10/10 17:08:46

一、书签存储路径:

%localappdata%\Chromium\User Data\Default\Bookmarks

%localappdata%\Chromium\User Data\Default\Bookmarks.bak 【备份文件】

本机测试存储的Bookmarks文件如下(未加密的可以直接打开):

{
   "checksum": "edefd2b00f017cd9e3dcb3c0d194d950",
   "roots": {
      "bookmark_bar": {
         "children": [ {
            "date_added": "13372945710911965",
            "date_last_used": "0",
            "guid": "bf9fbd31-8ce1-48a0-b5f9-7ad492da0c7d",
            "id": "7",
            "meta_info": {
               "power_bookmark_meta": ""
            },
            "name": "百度一下,你就知道",
            "type": "url",
            "url": "https://www.baidu.com/"
         } ],
         "date_added": "13372779889012656",
         "date_last_used": "0",
         "date_modified": "13372945725936476",
         "guid": "0bc5d13f-2cba-5d74-951f-3f233fe6c908",
         "id": "1",
         "name": "书签栏",
         "type": "folder"
      },
      "other": {
         "children": [  ],
         "date_added": "13372779889012676",
         "date_last_used": "0",
         "date_modified": "13372945710911965",
         "guid": "82b081ec-3dd3-529c-8475-ab6c344590dd",
         "id": "2",
         "name": "其他书签",
         "type": "folder"
      },
      "synced": {
         "children": [  ],
         "date_added": "13372779889012687",
         "date_last_used": "0",
         "date_modified": "0",
         "guid": "4cf2e351-0e85-532b-bb37-df045d8f8d0f",
         "id": "3",
         "name": "移动设备书签",
         "type": "folder"
      }
   },
   "version": 1
}

二、浏览器启动过程加载书签过程:

1、bookmark_model_factory.cc文件BuildBookmarkModel函数

    调用BookmarkModel::Load()进行书签加载。

namespace {

using bookmarks::BookmarkModel;

std::unique_ptr<KeyedService> BuildBookmarkModel(
    content::BrowserContext* context) {
  Profile* profile = Profile::FromBrowserContext(context);
  auto bookmark_model =
      std::make_unique<BookmarkModel>(std::make_unique<ChromeBookmarkClient>(
          profile, ManagedBookmarkServiceFactory::GetForProfile(profile),
          LocalOrSyncableBookmarkSyncServiceFactory::GetForProfile(profile),
          AccountBookmarkSyncServiceFactory::GetForProfile(profile),
          BookmarkUndoServiceFactory::GetForProfile(profile)));
#if defined(TOOLKIT_VIEWS)
  // BookmarkExpandedStateTracker depends on the loading event, so this
  // coupling must happen before the loading happens.
  BookmarkExpandedStateTrackerFactory::GetForProfile(profile)->Init(
      bookmark_model.get());
#endif
  //传入user_data路径
  bookmark_model->Load(profile->GetPath(),
                       bookmarks::StorageType::kLocalOrSyncable);
  BookmarkUndoServiceFactory::GetForProfile(profile)
      ->StartObservingBookmarkModel(bookmark_model.get());
  return bookmark_model;
}

}  // namespace

2、进入bookmark_model.cc BookmarkModel::Load函数其中:

     BookmarkStorage store_ 用来存储备份书签
      ModelLoader model_loader_用来加载和解析书签数据。

void BookmarkModel::Load(const base::FilePath& profile_path,
                         StorageType storage_type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // If the store is non-null, it means Load was already invoked. Load should
  // only be invoked once.
  DCHECK(!store_);

  const base::FilePath file_path =
      GetStorageFilePath(profile_path, storage_type);
 //BookmarkStorage 用来存储备份书签
  store_ = std::make_unique<BookmarkStorage>(this, file_path);
  // Creating ModelLoader schedules the load on a backend task runner.
//ModelLoader 用来加载和解析书签数据
  model_loader_ = ModelLoader::Create(
      file_path, std::make_unique<BookmarkLoadDetails>(client_.get()),
      base::BindOnce(&BookmarkModel::DoneLoading, AsWeakPtr()));
}

3、看下ModelLoader::Create加载书签过程(components\bookmarks\browser\model_loader.cc)

// static
scoped_refptr<ModelLoader> ModelLoader::Create(
    const base::FilePath& file_path,
    std::unique_ptr<BookmarkLoadDetails> details,
    LoadCallback callback) {
  // Note: base::MakeRefCounted is not available here, as ModelLoader's
  // constructor is private.
  auto model_loader = base::WrapRefCounted(new ModelLoader());
  model_loader->backend_task_runner_ =
      base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
           base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN});

  model_loader->backend_task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&ModelLoader::DoLoadOnBackgroundThread, model_loader,
                     file_path, std::move(details)),
      std::move(callback));
  return model_loader;
}
std::unique_ptr<BookmarkLoadDetails> ModelLoader::DoLoadOnBackgroundThread(
    const base::FilePath& file_path,
    std::unique_ptr<BookmarkLoadDetails> details) {
  LoadBookmarks(file_path, details.get());
  history_bookmark_model_ = details->url_index();
  loaded_signal_.Signal();
  return details;
}

执行顺序:ModelLoader::Create->DoLoadOnBackgroundThread->LoadBookmarks

看下LoadBookmarks函数定义:

namespace {

// Loads the bookmarks. This is intended to be called on the background thread.
// Updates state in |details| based on the load.
void LoadBookmarks(const base::FilePath& path,
                   BookmarkLoadDetails* details) {
  bool bookmark_file_exists = base::PathExists(path);
  if (bookmark_file_exists) {
    // Titles may end up containing invalid utf and we shouldn't throw away
    // all bookmarks if some titles have invalid utf.
    JSONFileValueDeserializer deserializer(
        path, base::JSON_REPLACE_INVALID_CHARACTERS);
    std::unique_ptr<base::Value> root_value =
        deserializer.Deserialize(nullptr, nullptr);

    if (!root_value) {
      // The bookmark file exists but was not deserialized.
    } else if (const auto* root_dict = root_value->GetIfDict()) {
      int64_t max_node_id = 0;
      std::string sync_metadata_str;
      BookmarkCodec codec;
      codec.Decode(*root_dict, details->bb_node(), details->other_folder_node(),
                   details->mobile_folder_node(), &max_node_id,
                   &sync_metadata_str);
      details->set_sync_metadata_str(std::move(sync_metadata_str));
      details->set_max_id(std::max(max_node_id, details->max_id()));
      details->set_computed_checksum(codec.computed_checksum());
      details->set_stored_checksum(codec.stored_checksum());
      details->set_ids_reassigned(codec.ids_reassigned());
      details->set_uuids_reassigned(codec.uuids_reassigned());
      details->set_model_meta_info_map(codec.model_meta_info_map());
    }
  }

  details->LoadManagedNode();

  // Building the indices can take a while so it's done on the background
  // thread.
  details->CreateIndices();

  UrlLoadStats stats = details->url_index()->ComputeStats();
  metrics::RecordUrlLoadStatsOnProfileLoad(stats);

  int64_t file_size_bytes;
  if (bookmark_file_exists && base::GetFileSize(path, &file_size_bytes)) {
    metrics::RecordFileSizeAtStartup(file_size_bytes);
    metrics::RecordAverageNodeSizeAtStartup(
        stats.total_url_bookmark_count == 0
            ? 0
            : file_size_bytes / stats.total_url_bookmark_count);
  }
}

}  // namespace

LoadBookmarks函数里面读取boolmarks文件进行解析完毕运行callback

BookmarkModel::DoneLoading在通知观察者加载书签完毕。

 // Notify our direct observers.
  for (BookmarkModelObserver& observer : observers_) {
    observer.BookmarkModelLoaded(this, details->ids_reassigned());
  }

void BookmarkModel::DoneLoading(std::unique_ptr<BookmarkLoadDetails> details) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(details);
  DCHECK(!loaded_);

  next_node_id_ = details->max_id();
  if (details->computed_checksum() != details->stored_checksum() ||
      details->ids_reassigned() || details->uuids_reassigned()) {
    // If bookmarks file changed externally, the IDs may have changed
    // externally. In that case, the decoder may have reassigned IDs to make
    // them unique. So when the file has changed externally, we should save the
    // bookmarks file to persist such changes. The same applies if new UUIDs
    // have been assigned to bookmarks.
    if (store_) {
      store_->ScheduleSave();
    }
  }

  titled_url_index_ = details->owned_titled_url_index();
  uuid_index_ = details->owned_uuid_index();
  url_index_ = details->url_index();
  root_ = details->root_node();
  // See declaration for details on why `owned_root_` is reset.
  owned_root_.reset();
  bookmark_bar_node_ = details->bb_node();
  other_node_ = details->other_folder_node();
  mobile_node_ = details->mobile_folder_node();

  // TODO(crbug.com/1494120): Load nodes for account storage as well.

  titled_url_index_->SetNodeSorter(
      std::make_unique<TypedCountSorter>(client_.get()));
  // Sorting the permanent nodes has to happen on the main thread, so we do it
  // here, after loading completes.
  root_->SortChildren(VisibilityComparator(client_.get()));

  root_->SetMetaInfoMap(details->model_meta_info_map());

  loaded_ = true;
  client_->DecodeBookmarkSyncMetadata(
      details->sync_metadata_str(),
      store_ ? base::BindRepeating(&BookmarkStorage::ScheduleSave,
                                   base::Unretained(store_.get()))
             : base::DoNothing());

  const base::TimeDelta load_duration =
      base::TimeTicks::Now() - details->load_start();
  metrics::RecordTimeToLoadAtStartup(load_duration,
                                     client_->GetStorageStateForUma());

  // Notify our direct observers.
  for (BookmarkModelObserver& observer : observers_) {
    observer.BookmarkModelLoaded(this, details->ids_reassigned());
  }
}

4、看下观察者定义:components\bookmarks\browser\bookmark_model_observer.h


// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_MODEL_OBSERVER_H_
#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_MODEL_OBSERVER_H_

#include <set>

#include <stddef.h>

#include "base/observer_list_types.h"

class GURL;

namespace bookmarks {

class BookmarkModel;
class BookmarkNode;

// Observer for the BookmarkModel.
class BookmarkModelObserver : public base::CheckedObserver {
 public:
  BookmarkModelObserver(const BookmarkModelObserver&) = delete;
  BookmarkModelObserver& operator=(const BookmarkModelObserver&) = delete;

  // Invoked when the model has finished loading. |ids_reassigned| mirrors
  // that of BookmarkLoadDetails::ids_reassigned. See it for details.
  virtual void BookmarkModelLoaded(BookmarkModel* model,
                                   bool ids_reassigned) = 0;

  // Invoked from the destructor of the BookmarkModel.
  virtual void BookmarkModelBeingDeleted(BookmarkModel* model) {}

  // Invoked when a node has moved.
  virtual void BookmarkNodeMoved(BookmarkModel* model,
                                 const BookmarkNode* old_parent,
                                 size_t old_index,
                                 const BookmarkNode* new_parent,
                                 size_t new_index) = 0;

  // Invoked when a node has been added. If the added node has any descendants,
  // BookmarkModel` will invoke `BookmarkNodeAdded` recursively for all these
  // descendants.
  // TODO(crbug.com/1440384): See if this should send only one notification,
  //                          for consistency with `BookmarkNodeRemoved`.
  //
  // `added_by_user` is true when a new bookmark was added by the user and false
  // when a node is added by sync or duplicated.
  virtual void BookmarkNodeAdded(BookmarkModel* model,
                                 const BookmarkNode* parent,
                                 size_t index,
                                 bool added_by_user) = 0;

  // Invoked prior to removing a node from the model. When a node is removed
  // it's descendants are implicitly removed from the model as
  // well. Notification is only sent for the node itself, not any
  // descendants. For example, if folder 'A' has the children 'A1' and 'A2',
  // model->Remove('A') generates a single notification for 'A'; no notification
  // is sent for 'A1' or 'A2'.
  //
  // |parent| the parent of the node that will be removed.
  // |old_index| the index of the node about to be removed in |parent|.
  // |node| is the node to be removed.
  virtual void OnWillRemoveBookmarks(BookmarkModel* model,
                                     const BookmarkNode* parent,
                                     size_t old_index,
                                     const BookmarkNode* node) {}

  // Invoked after a node has been removed from the model. Removing a node
  // implicitly removes all descendants. Notification is only sent for the node
  // that BookmarkModel::Remove() is invoked on. See description of
  // OnWillRemoveBookmarks() for details.
  //
  // |parent| the parent of the node that was removed.
  // |old_index| the index of the removed node in |parent| before it was
  // removed.
  // |node| the node that was removed.
  // |no_longer_bookmarked| contains the urls of any nodes that are no longer
  // bookmarked as a result of the removal.
  virtual void BookmarkNodeRemoved(
      BookmarkModel* model,
      const BookmarkNode* parent,
      size_t old_index,
      const BookmarkNode* node,
      const std::set<GURL>& no_longer_bookmarked) = 0;

  // Invoked before the title or url of a node is changed. Subsequent
  // BookmarkNodeChanged call guaranteed to contain the same BookmarkNode.
  virtual void OnWillChangeBookmarkNode(BookmarkModel* model,
                                        const BookmarkNode* node) {}

  // Invoked when the title or url of a node changes. Guaranteed to contain the
  // same BookmarkNode as the preceding OnWillChangeBookmark Node call.
  virtual void BookmarkNodeChanged(BookmarkModel* model,
                                   const BookmarkNode* node) = 0;

  // Invoked before the metainfo of a node is changed.
  virtual void OnWillChangeBookmarkMetaInfo(BookmarkModel* model,
                                            const BookmarkNode* node) {}

  // Invoked when the metainfo on a node changes.
  virtual void BookmarkMetaInfoChanged(BookmarkModel* model,
                                       const BookmarkNode* node) {}

  // Invoked when a favicon has been loaded or changed.
  virtual void BookmarkNodeFaviconChanged(BookmarkModel* model,
                                          const BookmarkNode* node) = 0;

  // Invoked before the direct children of |node| have been reordered in some
  // way, such as sorted.
  virtual void OnWillReorderBookmarkNode(BookmarkModel* model,
                                         const BookmarkNode* node) {}

  // Invoked when the children (just direct children, not descendants) of
  // |node| have been reordered in some way, such as sorted.
  virtual void BookmarkNodeChildrenReordered(BookmarkModel* model,
                                             const BookmarkNode* node) = 0;

  // Invoked before an extensive set of model changes is about to begin.
  // This tells UI intensive observers to wait until the updates finish to
  // update themselves.
  // These methods should only be used for imports and sync.
  // Observers should still respond to BookmarkNodeRemoved immediately,
  // to avoid holding onto stale node pointers.
  virtual void ExtensiveBookmarkChangesBeginning(BookmarkModel* model) {}

  // Invoked after an extensive set of model changes has ended.
  // This tells observers to update themselves if they were waiting for the
  // update to finish.
  virtual void ExtensiveBookmarkChangesEnded(BookmarkModel* model) {}

  // Invoked before all non-permanent bookmark nodes that are editable by
  // the user are removed.
  virtual void OnWillRemoveAllUserBookmarks(BookmarkModel* model) {}

  // Invoked when all non-permanent bookmark nodes that are editable by the
  // user have been removed.
  // |removed_urls| is populated with the urls which no longer have any
  // bookmarks associated with them.
  virtual void BookmarkAllUserNodesRemoved(
      BookmarkModel* model,
      const std::set<GURL>& removed_urls) = 0;

  // Invoked before a set of model changes that is initiated by a single user
  // action. For example, this is called a single time when pasting from the
  // clipboard before each pasted bookmark is added to the bookmark model.
  virtual void GroupedBookmarkChangesBeginning(BookmarkModel* model) {}

  // Invoked after a set of model changes triggered by a single user action has
  // ended.
  virtual void GroupedBookmarkChangesEnded(BookmarkModel* model) {}

 protected:
  BookmarkModelObserver() = default;
  ~BookmarkModelObserver() override = default;
};

}  // namespace bookmarks

#endif  // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_MODEL_OBSERVER_H_

5、最后看下调试堆栈:

   

总结:

components\bookmarks\browser\bookmark_model.h 此文件里面是核心控制逻辑,书签的加载

添加删除排序都在此文件里面控制。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2202573.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Allegro平台正式进军匈牙利市场,Allegro怎么快速上架产品?

近日&#xff0c;波兰电商平台Allegro正式宣布进军匈牙利市场&#xff0c;此举标志着Allegro在中东欧地区的扩张步伐再次加速。作为一家在波兰本土享有盛誉的电商平台&#xff0c;Allegro此举无疑为匈牙利乃至整个中欧地区的电商市场注入了新的活力。 Allegro此次进军匈牙利市…

比较三组迭代次数的变化

(A,B)---6*30*2---(0,1)(1,0) 让A是结构5&#xff0c;让B全是0。收敛误差为7e-4&#xff0c;收敛199次取迭代次数平均值&#xff0c;得到迭代次数为129535.3 (A,B)---6*30*2&#xff08;5&#xff09;---(0,1)(1,0) 然后让A分别是0&#xff0c;1&#xff0c;2&#xff0c;3&a…

服装生产管理:SpringBoot技术实现

1 绪论 1.1 研究背景 当今时代是飞速发展的信息时代。在各行各业中离不开信息处理&#xff0c;这正是计算机被广泛应用于信息管理系统的环境。计算机的最大好处在于利用它能够进行信息管理。使用计算机进行信息控制&#xff0c;不仅提高了工作效率&#xff0c;而且大大的提高…

通过AI技术克服自动化测试难点(上)

本文我们一起分析一下AI技术如何解决现有的自动化测试工具的不足和我们衍生出来的新的测试需求。 首先我们一起看一下计算机视觉的发展历史&#xff0c;在上世纪70年代&#xff0c;处于技术萌芽期&#xff0c;由字符的识别技术慢慢进行演化&#xff0c;发展到现在&#xff0c;人…

C/S模型的简单实现(UDP服务器)、本地套接字(sockaddr_un )的讲解

目录 1.UDP 1.1 UDP服务器 1.2 TPC和UDP的比较 1.3 C/S模型 -- UDP recvfrom、sendto server client 2.本地套接字 2.1 套接字比较 2.2 函数参数选用 2.3 server 2.4 client 2.5 实现对比 1.UDP 1.1 UDP服务器 UDP 是一种无连接的传输协议&#xff0c;类似于发送…

SpringBoot MyBatis连接数据库设置了encoding=utf-8还是不能用中文来查询

properties的MySQL连接时已经指定了字符编码格式&#xff1a; url: jdbc:mysql://localhost:3306/sky_take_out?useUnicodetrue&characterEncodingutf-8使用MyBatis查询&#xff0c;带有中文参数&#xff0c;查询出的内容为空。 执行的语句为&#xff1a; <select id&…

已经被这几种广告彻底逼疯……还好有救了

这个假期回家团聚&#xff0c;爸妈小心翼翼问我手机越来越难用了&#xff0c;让我帮忙看看是不是中病毒了&#xff0c;了解后才知道原来事情是这样的&#xff1a; 以前开屏广告不小心误触已经让人恼火&#xff0c;现在是手机轻微动一动就会进入广告&#xff0c;打开app最后都不…

quantlab_ai版本v0.1代码发布: 从研报中提取因子并建模(附代码与研报集下载)

原创内容第676篇&#xff0c;专注量化投资、个人成长与财富自由。 今天我们继续开发AI大模型自动读研报。 从研报到模型&#xff0c;大致分成几步&#xff1a; ["propose_hypo_exp", "propose", "exp_gen", "coding", "runnin…

【玩转 JS 函数式编程_010】3.2 JS 函数式编程筑基之:以函数式编程的方式活用函数(上)

写在前面 按照惯例&#xff0c;过长的篇幅分开介绍&#xff0c;本篇为 JavaScript 函数式编程核心基础的第二部分——以函数式编程的方式活用函数的上篇&#xff0c;分别介绍了 JS 函数在排序、回调、Promise 期约、以及连续传递等应用场景下的用法演示。和之前章节相比难度又有…

“Flash闪存”基础 及 “SD NAND Flash”产品的测试

本篇除了对flash闪存进行简单介绍外&#xff0c;另给读者推荐一种我本人也在用的小容量闪存。 自带坏块管理的SD NAND Flash&#xff08;贴片式TF卡&#xff09;&#xff0c;尺寸小巧&#xff0c;简单易用&#xff0c;兼容性强&#xff0c;稳定可靠&#xff0c;标准SDIO接口&a…

【C++】map 和 set

目录 一 基础概念 1 关联式容器 2 键值对 3 树形结构的关联式容器 二 map 1 概念 2 基础操作 3 使用实列 1 实例一 2 实例二 3 实例三 4 实例四 4 multimap 1 实例一 三 set 1 概念 2 基础操作 3 使用实例 1 实例一 2 实例二 3 实例三 4 multiset 1 实…

SpringBoot使用esayExcel根据模板导出excel

1、依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.1.3</version></dependency> 2、模板 3、实体类 package com.skybird.iot.addons.productionManagement.qualityTesting…

流程图 LogicFlow

流程图 LogicFlow 官方文档&#xff1a;https://site.logic-flow.cn/tutorial/get-started <script setup> import { onMounted, ref } from vue import { forEach, map, has } from lodash-es import LogicFlow, { ElementState, LogicFlowUtil } from logicflow/core …

字符编码发展史6 — BOM字节序标记

上一篇《字符编码发展史5 — UTF-16和UTF-32》我们讲解了UTF-16和UTF-32编码。本篇我们将继续讲解字符编码中的字节序标记(BOM)。 2.3. 第三个阶段 国际化 2.3.2. Unicode的编码方式 2.3.2.5. BOM 1. 什么是BOM&#xff1f; BOM是Byte Order Mark的缩写&#xff0c;翻译成…

研究生异地报名,需要社保缴费记录,没有社保记录怎么办。

1、户籍在安徽省&#xff0c;在北京工作&#xff0c;想报北京科技大学&#xff1b; 招生简章中没有提社保记录&#xff0c;但是在报名的时候&#xff0c;又出来要求&#xff1a;北京连续6个月的社保记录。这里是指在北京市考试的要求。没有连续社保缴费记录&#xff0c;肯定不能…

Python 与 Pycharm 的简易安装教程,包含Pycharm的修改

一. 官方网站 Python网址&#xff1a;python唯一的官方网址。 Pycharm网址&#xff1a;Pycharm的官方网址。 二. python安装步骤 滑动到红色框内 Downloads 导航栏。 红色框是选择适合自己电脑系统和版本的部分&#xff0c;蓝色框是选择系统的部分&#xff0c;黄色框是版本号。…

【大数据】数据分析之Spark框架介绍

文章目录 概述一、发展历程与背景二、核心特点三、生态系统与组件四、应用场景五、与其他大数据技术的比较 核心概念1. 弹性分布式数据集&#xff08;RDD, Resilient Distributed Dataset&#xff09;2. 转换&#xff08;Transformations&#xff09;和动作&#xff08;Actions…

Rust编程的函数

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust编程与项目实战_夏天又到了的博客-CSDN博客 7.1 函 数 定 义 在Rust中&#xff0c;函数使用fn关键字定义&#xff0c;后跟函数…

how to increase the height of the ps or cdm window

when the line reaches the bottom; directly pull up the top bar of the window after pulling down the bar

【Linux】ComfyUI和SD WebUI之PYTHON环境共享,模型共享,LORA等公共资源共享

需求 一般玩AI绘图都会装ComfyUI和SD WebUI。而且这俩的模型、lora等都是一致的。为了避免空间的浪费&#xff0c;一般会采用共享数据的方式。而且共享的数据可以任意指定分区&#xff0c;这让挂载NAS共享空间成为可能&#xff0c;实现多绘画机ComfyUI和SD WebUI共享资源。 实…