【Eclipse插件开发】资源不同步问题分析

【Eclipse插件开发】资源不同步问题分析

        资源不同步的问题对于插件开发的哥们应该都不是很陌生,记得刚到两年前刚接触插件开发的时候,由于产品中代码中很多都是用java.io.File操作文件资源,导致经常有这种问题发生,例如删除不掉、内容更新失败等。下班之前,以资源删除失败为例子,写篇小随笔,把这个资源不同步的问题多少说道说道。^_^

        首先看个可能不陌生的错误(错误本质上都是不同步引起,但是可能包装形式很多):
        
        
    下面是引起错误的代码:

 1  public   void  run(IAction action) {
 2           try  {
 3               // 获取一个存在的文件
 4              IFile eclipseFile  =  ResourcesPlugin.getWorkspace().getRoot().getProject( " project " ).getFile( new  Path( " folder/file.txt " ));
 5              
 6               // 用java IO更新底层资源
 7              eclipseFile.getLocation().toFile().setLastModified(eclipseFile.getLocalTimeStamp()  +   100 );
 8              
 9               // 删除文件
10              eclipseFile.delete( false null );
11          }  catch  (CoreException e) {
12              Activator.getDefault().getLog().log( new  Status(IStatus.ERROR,  " aaa " 99 " 删除资源失败 " , e));
13          }
14      }

        
        【猜测】是不是我们用Java IO修改了文件,而Eclipse工作区不知道,这可能就是不同步?

        【两种状态:ResourceInfo VS IFileInfo】
           一个文件资源的状态描述,我们可以从两个层面来看:一个Eclipse工作区层面的资源状态;二是文件系统层面的资源状态。对于这两者,Eclipse资源管理模块中都有对应的类型支持。

        ResourceInfo :封装了工作区对一个文件资源的描述,也就是我们常说的工作区资源树上的一个数据节点,Eclipse资源管理中的IResource系列接口本身也是ResourceInfo的代理,ResourceInfo主要操作如下:
                
                
                

                ResourceInfo的主要常规获取方式,Workspace.getResourceInfo:

 1  public  ResourceInfo getResourceInfo(IPath path,  boolean  phantom,  boolean  mutable) {
 2           try  {
 3               if  (path.segmentCount()  ==   0 ) {
 4                  ResourceInfo info  =  (ResourceInfo) tree.getTreeData();
 5                  Assert.isNotNull(info,  " Tree root info must never be null " );  // $NON-NLS-1$
 6                   return  info;
 7              }
 8              ResourceInfo result  =   null ;
 9               if  ( ! tree.includes(path))
10                   return   null ;
11               if  (mutable)
12                  result  =  (ResourceInfo) tree.openElementData(path);
13               else
14                  result  =  (ResourceInfo) tree.getElementData(path);
15               if  (result  !=   null   &&  ( ! phantom  &&  result.isSet(M_PHANTOM)))
16                   return   null ;
17               return  result;
18          }  catch  (IllegalArgumentException e) {
19               return   null ;
20          }
21      }

            资源树的影子出来了,我们获取resource info的过程其实就是在资源树上面定位对应数据节点的过程。那可以自然的猜测(有兴趣的Tx可以接着撒两眼资源树是如何实现的),ResourceInfo的获取过程并不是每次都会产生一个新的ResourceInfo实例,因为直觉告诉我们这可能是性能敏感的。
                
        IFileInfo:一个资源在特定时间点上的状态快照(snapshot),可以理解为一个底层文件系统资源对应的静态只读信息的集合。我们看一下它的获取方式,IFileStore.fetchInfo()实现(有关IFleStore这里就省略了^_^):

 1  public  IFileInfo fetchInfo( int  options, IProgressMonitor monitor) {
 2           if  (LocalFileNatives.usingNatives()) {
 3              FileInfo info  =  LocalFileNatives.fetchFileInfo(filePath);
 4               // natives don't set the file name on all platforms
 5               if  (info.getName().length()  ==   0 )
 6                  info.setName(file.getName());
 7               return  info;
 8          }
 9           // in-lined non-native implementation
10          FileInfo info  =   new  FileInfo(file.getName());
11           final   long  lastModified  =  file.lastModified();
12           if  (lastModified  <=   0 ) {
13               // if the file doesn't exist, all other attributes should be default values
14              info.setExists( false );
15               return  info;
16          }
17          info.setLastModified(lastModified);
18          info.setExists( true );
19          info.setLength(file.length());
20          info.setDirectory(file.isDirectory());
21          info.setAttribute(EFS.ATTRIBUTE_READ_ONLY, file.exists()  &&   ! file.canWrite());
22          info.setAttribute(EFS.ATTRIBUTE_HIDDEN, file.isHidden());
23           return  info;
24      }

        可以看出来,每次获取IFileInfo的过程是每次都产生新的实例,这个新的实例来描述该时间点上的文件状态。        
        
        到这里我们知道了,ResourceInfo其实是内存中Eclipse维护的一个东东,IFileInfo是实时获取的,那么在特定的时间点上面,前者有可能和后者不统一。这是不是就是不同步问题的根源呢?

        【资源不同步检查】
        为了验证上面的猜测,我们追踪调试一下文章开头提到的测试代码:
        
        
        找到了检查文件资源是否同步的代码,FileSystemResourceManager.isSynchronized(IResource target, int depth),资源树在检查一个资源是否同步时候(IResourceTree.isisSynchronized(IResource target, int depth)),也是委托给了FileSystemResourceManager: 

 1  public   boolean  isSynchronized(IResource target,  int  depth) {
 2           switch  (target.getType()) {
 3               case  IResource.ROOT :
 4                   if  (depth  ==  IResource.DEPTH_ZERO)
 5                       return   true ;
 6                   // check sync on child projects.
 7                  depth  =  depth  ==  IResource.DEPTH_ONE  ?  IResource.DEPTH_ZERO : depth;
 8                  IProject[] projects  =  ((IWorkspaceRoot) target).getProjects();
 9                   for  ( int  i  =   0 ; i  <  projects.length; i ++ ) {
10                       if  ( ! isSynchronized(projects[i], depth))
11                           return   false ;
12                  }
13                   return   true ;
14               case  IResource.PROJECT :
15                   if  ( ! target.isAccessible())
16                       return   true ;
17                   break ;
18               case  IResource.FILE :
19                   if  (fastIsSynchronized((File) target))
20                       return   true ;
21                   break ;
22          }
23          IsSynchronizedVisitor visitor  =   new  IsSynchronizedVisitor(Policy.monitorFor( null ));
24          UnifiedTree tree  =   new  UnifiedTree(target);
25           try  {
26              tree.accept(visitor, depth);
27          }  catch  (CoreException e) {
28              Policy.log(e);
29               return   false ;
30          }  catch  (IsSynchronizedVisitor.ResourceChangedException e) {
31               // visitor throws an exception if out of sync
32               return   false ;
33          }
34           return   true ;
35      }

        
            我们接着看一下上面负责File级别同步检查的FileSystemResourceManager.fastIsSynchronized方法:

1  public   boolean  fastIsSynchronized(File target) {
2          ResourceInfo info  =  target.getResourceInfo( false false );
3           if  (target.exists(target.getFlags(info),  true )) {
4              IFileInfo fileInfo  =  getStore(target).fetchInfo();
5               if  ( ! fileInfo.isDirectory()  &&  info.getLocalSyncInfo() == fileInfo.getLastModified() )
6                   return   true ;
7          }
8           return   false ;
9      }
        上面的红线部分告诉我们: 对于一个文件级别的资源,判断是否同步就是检查Eclipse维护的时间戳和底层文件系统的时间戳是否一致!!!

        如果用Eclipse IResource API来修改文件资源,Eclipse自己会知道;如果用java IO或者java NIO来修改文件资源,Eclipse就一无所知,状态维护就会出问题。不知道就出事情了,不同步只是后果之一,变化跟踪也将失效,resource change event也将无从产生了^_^

        【后记】
       1、对于非File级别的资源,为什么同步检查不是很严格呢?
           因为在不同操作系统不同类型分区上面,一个文件夹下面的文件资源被修改了,文件夹的时间戳并不保证会及时更新。这是很底层的东西了,就不接着讲了。  如果你想修正这个问题,你可以对目录时间戳做自己的维护(这是可行的,我们产品里面就是这么干的)。

            2、ResourceInfo和IFileInfo好像不怎么使用?
            确实,Ecipse也不想让开发者去直接使用它。例如:getResourceInfo是在Resource中提供的,而不是在IResource接口中定义的。

           3、那如何同步呢?
                IResource.refreshLocal(int depth, IProgressMonitor monitor)     


本博客中的所有文章、随笔除了标题中含有引用或者转载字样的,其他均为原创。转载请注明出处,谢谢!

你可能感兴趣的:(【Eclipse插件开发】资源不同步问题分析)