code: func (cifs *CCInfoFSImpl) ListInstalledChaincodes(dir string, ls DirEnumerator, ccFromPath ChaincodeExtractor) ([]chaincode.InstalledChaincode, error) {
  	var chaincodes []chaincode.InstalledChaincode
  	if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
  		return nil, nil
  	}
  	files, err := ls(dir)
  	if err != nil {
  		return nil, errors.Wrapf(err, "failed reading directory %s", dir)
  	}
  
  	for _, f := range files {
  		// Skip directories, we're only interested in normal files
  		if f.IsDir() {
  			continue
  		}
  		// A chaincode file name is of the type "name.version"
  		// We're only interested in the name.
  		// Skip files that don't adhere to the file naming convention of "A.B"
  		i := strings.Index(f.Name(), ".")
  		if i == -1 {
  			ccproviderLogger.Info("Skipping", f.Name(), "because of missing separator '.'")
  			continue
  		}
  		ccName := f.Name()[:i]      // Everything before the separator
  		ccVersion := f.Name()[i+1:] // Everything after the separator
  
  		ccPackage, err := ccFromPath(ccName, ccVersion, dir)
  		if err != nil {
  			ccproviderLogger.Warning("Failed obtaining chaincode information about", ccName, ccVersion, ":", err)
  			return nil, errors.Wrapf(err, "failed obtaining information about %s, version %s", ccName, ccVersion)
  		}
  
  		chaincodes = append(chaincodes, chaincode.InstalledChaincode{
  			Name:    ccName,
  			Version: ccVersion,
  			Id:      ccPackage.GetId(),
  		})
  	}
  	ccproviderLogger.Debug("Returning", chaincodes)
  	return chaincodes, nil
  }
docs: // ListInstalledChaincodes retrieves the installed chaincodes