github.com/decred/politeia@v1.4.0/politeiad/cmd/legacypoliteia/git_filepath.go (about)

     1  // Copyright (c) 2022 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"io/fs"
    10  	"regexp"
    11  	"sort"
    12  	"strconv"
    13  
    14  	"path/filepath"
    15  
    16  	"github.com/decred/politeia/politeiad/cmd/legacypoliteia/gitbe"
    17  )
    18  
    19  // git_filepath.go contains the code that parses information from the legacy
    20  // git repo file paths and that determines the file paths of the various pieces
    21  // of proposal data.
    22  
    23  // parseProposalTokens recursively walks the provided directory and returns an
    24  // inventory of all legacy proposal tokens that are parsed from the git repo
    25  // file paths. The tokens are returned in alphabetical order.
    26  func parseProposalTokens(dirPath string) ([]string, error) {
    27  	tokens := make(map[string]struct{}, 256)
    28  	err := filepath.WalkDir(dirPath,
    29  		func(path string, d fs.DirEntry, err error) error {
    30  			// We only care about directories
    31  			if !d.IsDir() {
    32  				return nil
    33  			}
    34  
    35  			// Parse the token from the path
    36  			token, ok := parseProposalToken(path)
    37  			if ok {
    38  				tokens[token] = struct{}{}
    39  				return nil
    40  			}
    41  
    42  			return nil
    43  		})
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	// Put the tokens into a slice and sort them alphabetically
    49  	sorted := make([]string, 0, len(tokens))
    50  	for token := range tokens {
    51  		sorted = append(sorted, token)
    52  	}
    53  	sort.SliceStable(sorted, func(i, j int) bool {
    54  		return sorted[i] < sorted[j]
    55  	})
    56  
    57  	return sorted, nil
    58  }
    59  
    60  var (
    61  	// Regular expression that matches the git proposal token from a proposal
    62  	// parent directory.
    63  	expLegacyProposalToken   = `[0-9a-f]{64}`
    64  	regexLegacyProposalToken = regexp.MustCompile(expLegacyProposalToken)
    65  )
    66  
    67  // parseProposalToken parses and returns the legacy proposal token from a
    68  // git repo path.
    69  //
    70  // Input:
    71  // "fdd68c87961549750adf29e178128210cb310294080211cf6a35792aa1bb7f63.json"
    72  // Output:
    73  // "fdd68c87961549750adf29e178128210cb310294080211cf6a35792aa1bb7f63", true
    74  //
    75  // Input:
    76  // "mainnet/fdd68c87961549750adf29e178128210cb310294080211cf6a35792aa1bb7f63/1"
    77  // Output:
    78  // "fdd68c87961549750adf29e178128210cb310294080211cf6a35792aa1bb7f63", true
    79  func parseProposalToken(path string) (string, bool) {
    80  	var (
    81  		token = regexLegacyProposalToken.FindString(path)
    82  		ok    = (token != "")
    83  	)
    84  	return token, ok
    85  }
    86  
    87  // praseLatestProposalVersion parses latest version of a legacy proposal from
    88  // the git file path and returns it.
    89  //
    90  // Example path: [gitRepo]/[token]/[version]/
    91  func parseLatestProposalVersion(gitRepo, token string) (uint64, error) {
    92  	// Compile a list of all directories. The version numbers
    93  	// are the directory name.
    94  	dirs := make(map[string]struct{}, 64)
    95  	err := filepath.WalkDir(filepath.Join(gitRepo, token),
    96  		func(path string, d fs.DirEntry, err error) error {
    97  			if !d.IsDir() {
    98  				return nil
    99  			}
   100  			dirs[d.Name()] = struct{}{}
   101  			return nil
   102  		})
   103  	if err != nil {
   104  		return 0, err
   105  	}
   106  
   107  	// Parse the version number from the directory name
   108  	versions := make(map[uint64]struct{}, 64)
   109  	for dirname := range dirs {
   110  		u, err := strconv.ParseUint(dirname, 10, 32)
   111  		if err != nil {
   112  			// Not a version directory
   113  			continue
   114  		}
   115  		versions[u] = struct{}{}
   116  	}
   117  	if err != nil {
   118  		return 0, err
   119  	}
   120  
   121  	// Find the most recent version
   122  	var latest uint64
   123  	for version := range versions {
   124  		if version > latest {
   125  			latest = version
   126  		}
   127  	}
   128  	if latest == 0 {
   129  		return 0, fmt.Errorf("latest version not found for %v", token)
   130  	}
   131  
   132  	return latest, nil
   133  }
   134  
   135  var (
   136  	// Regular expersion that matches the token and version number from a
   137  	// proposal directory path.
   138  	expProposalVersion   = `[0-9a-f]{64}\/[0-9]{1,}`
   139  	regexProposalVersion = regexp.MustCompile(expProposalVersion)
   140  )
   141  
   142  // proposalVersion parses the version number for the proposal directory path
   143  // and returns it.
   144  func parseProposalVersion(proposalDir string) (uint32, error) {
   145  	var (
   146  		subPath    = regexProposalVersion.FindString(proposalDir)
   147  		versionStr = filepath.Base(subPath)
   148  	)
   149  	u, err := strconv.ParseUint(versionStr, 10, 32)
   150  	if err != nil {
   151  		return 0, err
   152  	}
   153  
   154  	return uint32(u), nil
   155  }
   156  
   157  // parseProposalAttachmentFiles parses the filenames of all proposal attachment
   158  // files and returns them. This function does NOT return the file path, just
   159  // the file name. The proposal index file and proposal metadata file are not
   160  // considered to be attachments.
   161  func parseProposalAttachmentFilenames(proposalDir string) ([]string, error) {
   162  	var (
   163  		notAnAttachmentFile = map[string]struct{}{
   164  			gitbe.IndexFilename:            {},
   165  			gitbe.ProposalMetadataFilename: {},
   166  		}
   167  
   168  		payloadDir = payloadDirPath(proposalDir)
   169  		filenames  = make([]string, 0, 64)
   170  	)
   171  
   172  	// Walk the payload directory
   173  	err := filepath.WalkDir(payloadDir,
   174  		func(path string, d fs.DirEntry, err error) error {
   175  			// There shouldn't be any nested directories
   176  			// in the payload directory, but check just
   177  			// in case.
   178  			if d.IsDir() {
   179  				return nil
   180  			}
   181  
   182  			if _, ok := notAnAttachmentFile[d.Name()]; ok {
   183  				// Not an attachment; skip
   184  				return nil
   185  			}
   186  
   187  			// This is an attachment file
   188  			filenames = append(filenames, d.Name())
   189  
   190  			return nil
   191  		})
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  
   196  	return filenames, nil
   197  }
   198  
   199  func recordMetadataPath(proposalDir string) string {
   200  	return filepath.Join(proposalDir, gitbe.RecordMetadataFilename)
   201  }
   202  
   203  func payloadDirPath(proposalDir string) string {
   204  	return filepath.Join(proposalDir, gitbe.RecordPayloadPath)
   205  }
   206  
   207  func indexFilePath(proposalDir string) string {
   208  	return filepath.Join(payloadDirPath(proposalDir), gitbe.IndexFilename)
   209  }
   210  
   211  func attachmentFilePath(proposalDir, attachmentFilename string) string {
   212  	return filepath.Join(payloadDirPath(proposalDir), attachmentFilename)
   213  }
   214  
   215  func proposalMetadataPath(proposalDir string) string {
   216  	return filepath.Join(payloadDirPath(proposalDir),
   217  		gitbe.ProposalMetadataFilename)
   218  }
   219  
   220  func proposalGeneralPath(proposalDir string) string {
   221  	return filepath.Join(proposalDir, gitbe.MDStreamProposalGeneral)
   222  }
   223  
   224  func statusChangesPath(proposalDir string) string {
   225  	return filepath.Join(proposalDir, gitbe.MDStreamStatusChanges)
   226  }
   227  
   228  func commentsJournalPath(proposalDir string) string {
   229  	return filepath.Join(decredPluginPath(proposalDir),
   230  		gitbe.CommentsJournalFilename)
   231  }
   232  
   233  func authorizeVotePath(proposalDir string) string {
   234  	return filepath.Join(proposalDir, gitbe.MDStreamAuthorizeVote)
   235  }
   236  
   237  func startVotePath(proposalDir string) string {
   238  	return filepath.Join(proposalDir, gitbe.MDStreamStartVote)
   239  }
   240  
   241  func startVoteReplyPath(proposalDir string) string {
   242  	return filepath.Join(proposalDir, gitbe.MDStreamStartVoteReply)
   243  }
   244  
   245  func decredPluginPath(proposalDir string) string {
   246  	return filepath.Join(proposalDir, gitbe.DecredPluginPath)
   247  }
   248  
   249  func ballotsJournalPath(proposalDir string) string {
   250  	return filepath.Join(decredPluginPath(proposalDir),
   251  		gitbe.BallotJournalFilename)
   252  }