github.com/zhongdalu/gf@v1.0.0/g/os/gspath/gspath.go (about)

     1  // Copyright 2018 gf Author(https://github.com/zhongdalu/gf). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/zhongdalu/gf.
     6  
     7  // Package gspath implements file index and search for folders.
     8  //
     9  // 搜索目录管理,
    10  // 可以添加搜索目录,按照添加的优先级进行文件检索,并在内部进行高效缓存处理(可选)。
    11  // 注意:当开启缓存功能后,在新增/删除文件时,会存在检索延迟。
    12  package gspath
    13  
    14  import (
    15  	"errors"
    16  	"fmt"
    17  	"github.com/zhongdalu/gf/g/container/garray"
    18  	"github.com/zhongdalu/gf/g/container/gmap"
    19  	"github.com/zhongdalu/gf/g/os/gfile"
    20  	"github.com/zhongdalu/gf/g/text/gstr"
    21  	"os"
    22  	"sort"
    23  	"strings"
    24  )
    25  
    26  // 文件目录搜索管理对象
    27  type SPath struct {
    28  	paths *garray.StringArray // 搜索路径,按照优先级进行排序
    29  	cache *gmap.StrStrMap     // 搜索结果缓存map(如果未nil表示未启用缓存功能)
    30  }
    31  
    32  // 文件搜索缓存项
    33  type SPathCacheItem struct {
    34  	path  string // 文件/目录绝对路径
    35  	isDir bool   // 是否目录
    36  }
    37  
    38  var (
    39  	// 单个目录路径对应的SPath对象指针,用于路径检索对象复用
    40  	pathsMap      = gmap.NewStrAnyMap()
    41  	pathsCacheMap = gmap.NewStrAnyMap()
    42  )
    43  
    44  // 创建一个搜索对象
    45  func New(path string, cache bool) *SPath {
    46  	sp := &SPath{
    47  		paths: garray.NewStringArray(),
    48  	}
    49  	if cache {
    50  		sp.cache = gmap.NewStrStrMap()
    51  	}
    52  	if len(path) > 0 {
    53  		if _, err := sp.Add(path); err != nil {
    54  			//fmt.Errorf(err.Error())
    55  		}
    56  	}
    57  	return sp
    58  }
    59  
    60  // 创建/获取一个单例的搜索对象, root必须为目录的绝对路径
    61  func Get(root string, cache bool) *SPath {
    62  	return pathsMap.GetOrSetFuncLock(root, func() interface{} {
    63  		return New(root, cache)
    64  	}).(*SPath)
    65  }
    66  
    67  // 检索root目录(必须为绝对路径)下面的name文件的绝对路径,indexFiles用于指定当检索到的结果为目录时,同时检索是否存在这些indexFiles文件
    68  func Search(root string, name string, indexFiles ...string) (filePath string, isDir bool) {
    69  	return Get(root, false).Search(name, indexFiles...)
    70  }
    71  
    72  // 检索root目录(必须为绝对路径)下面的name文件的绝对路径,indexFiles用于指定当检索到的结果为目录时,同时检索是否存在这些indexFiles文件
    73  func SearchWithCache(root string, name string, indexFiles ...string) (filePath string, isDir bool) {
    74  	return Get(root, true).Search(name, indexFiles...)
    75  }
    76  
    77  // 设置搜索路径,只保留当前设置项,其他搜索路径被清空
    78  func (sp *SPath) Set(path string) (realPath string, err error) {
    79  	realPath = gfile.RealPath(path)
    80  	if realPath == "" {
    81  		realPath, _ = sp.Search(path)
    82  		if realPath == "" {
    83  			realPath = gfile.RealPath(gfile.Pwd() + gfile.Separator + path)
    84  		}
    85  	}
    86  	if realPath == "" {
    87  		return realPath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
    88  	}
    89  	// 设置的搜索路径必须为目录
    90  	if gfile.IsDir(realPath) {
    91  		realPath = strings.TrimRight(realPath, gfile.Separator)
    92  		if sp.paths.Search(realPath) != -1 {
    93  			for _, v := range sp.paths.Slice() {
    94  				sp.removeMonitorByPath(v)
    95  			}
    96  		}
    97  		sp.paths.Clear()
    98  		if sp.cache != nil {
    99  			sp.cache.Clear()
   100  		}
   101  		sp.paths.Append(realPath)
   102  		sp.updateCacheByPath(realPath)
   103  		sp.addMonitorByPath(realPath)
   104  		return realPath, nil
   105  	} else {
   106  		return "", errors.New(path + " should be a folder")
   107  	}
   108  }
   109  
   110  // 添加搜索路径
   111  func (sp *SPath) Add(path string) (realPath string, err error) {
   112  	realPath = gfile.RealPath(path)
   113  	if realPath == "" {
   114  		realPath, _ = sp.Search(path)
   115  		if realPath == "" {
   116  			realPath = gfile.RealPath(gfile.Pwd() + gfile.Separator + path)
   117  		}
   118  	}
   119  	if realPath == "" {
   120  		return realPath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
   121  	}
   122  	// 添加的搜索路径必须为目录
   123  	if gfile.IsDir(realPath) {
   124  		//fmt.Println("gspath:", realPath, sp.paths.Search(realPath))
   125  		// 如果已经添加则不再添加
   126  		if sp.paths.Search(realPath) < 0 {
   127  			realPath = strings.TrimRight(realPath, gfile.Separator)
   128  			sp.paths.Append(realPath)
   129  			sp.updateCacheByPath(realPath)
   130  			sp.addMonitorByPath(realPath)
   131  		}
   132  		return realPath, nil
   133  	} else {
   134  		return "", errors.New(path + " should be a folder")
   135  	}
   136  }
   137  
   138  // 给定的name只是相对文件路径,找不到该文件时,返回空字符串;
   139  // 当给定indexFiles时,如果name是一个目录,那么会进一步检索其下对应的indexFiles文件是否存在,存在则返回indexFile绝对路径;
   140  // 否则返回name目录绝对路径。
   141  func (sp *SPath) Search(name string, indexFiles ...string) (filePath string, isDir bool) {
   142  	// 不使用缓存
   143  	if sp.cache == nil {
   144  		sp.paths.LockFunc(func(array []string) {
   145  			path := ""
   146  			for _, v := range array {
   147  				path = v + gfile.Separator + name
   148  				if stat, err := os.Stat(path); !os.IsNotExist(err) {
   149  					filePath = path
   150  					isDir = stat.IsDir()
   151  					break
   152  				}
   153  			}
   154  		})
   155  		if len(indexFiles) > 0 && isDir {
   156  			if name == "/" {
   157  				name = ""
   158  			}
   159  			path := ""
   160  			for _, file := range indexFiles {
   161  				path = filePath + gfile.Separator + file
   162  				if gfile.Exists(path) {
   163  					filePath = path
   164  					isDir = false
   165  					break
   166  				}
   167  			}
   168  		}
   169  		return
   170  	}
   171  	// 使用缓存功能
   172  	name = sp.formatCacheName(name)
   173  	if v := sp.cache.Get(name); v != "" {
   174  		filePath, isDir = sp.parseCacheValue(v)
   175  		if len(indexFiles) > 0 && isDir {
   176  			if name == "/" {
   177  				name = ""
   178  			}
   179  			for _, file := range indexFiles {
   180  				if v := sp.cache.Get(name + "/" + file); v != "" {
   181  					return sp.parseCacheValue(v)
   182  				}
   183  			}
   184  		}
   185  	}
   186  	return
   187  }
   188  
   189  // 从搜索路径中移除指定的文件,这样该文件无法给搜索。
   190  // path可以是绝对路径,也可以相对路径。
   191  func (sp *SPath) Remove(path string) {
   192  	if sp.cache == nil {
   193  		return
   194  	}
   195  	if gfile.Exists(path) {
   196  		for _, v := range sp.paths.Slice() {
   197  			name := gstr.Replace(path, v, "")
   198  			name = sp.formatCacheName(name)
   199  			sp.cache.Remove(name)
   200  		}
   201  	} else {
   202  		name := sp.formatCacheName(path)
   203  		sp.cache.Remove(name)
   204  	}
   205  }
   206  
   207  // 返回当前对象搜索目录路径列表
   208  func (sp *SPath) Paths() []string {
   209  	return sp.paths.Slice()
   210  }
   211  
   212  // 返回当前对象缓存的所有路径列表
   213  func (sp *SPath) AllPaths() []string {
   214  	if sp.cache == nil {
   215  		return nil
   216  	}
   217  	paths := sp.cache.Keys()
   218  	if len(paths) > 0 {
   219  		sort.Strings(paths)
   220  	}
   221  	return paths
   222  }
   223  
   224  // 当前的搜索路径数量
   225  func (sp *SPath) Size() int {
   226  	return sp.paths.Len()
   227  }