github.com/zignig/go-ipfs@v0.0.0-20141111235910-c9e5fdf55a52/unixfs/io/dagmodifier.go (about)

     1  package io
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  
     7  	proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
     8  
     9  	chunk "github.com/jbenet/go-ipfs/importer/chunk"
    10  	mdag "github.com/jbenet/go-ipfs/merkledag"
    11  	ft "github.com/jbenet/go-ipfs/unixfs"
    12  	ftpb "github.com/jbenet/go-ipfs/unixfs/pb"
    13  	u "github.com/jbenet/go-ipfs/util"
    14  )
    15  
    16  var log = u.Logger("dagio")
    17  
    18  // DagModifier is the only struct licensed and able to correctly
    19  // perform surgery on a DAG 'file'
    20  // Dear god, please rename this to something more pleasant
    21  type DagModifier struct {
    22  	dagserv mdag.DAGService
    23  	curNode *mdag.Node
    24  
    25  	pbdata   *ftpb.Data
    26  	splitter chunk.BlockSplitter
    27  }
    28  
    29  func NewDagModifier(from *mdag.Node, serv mdag.DAGService, spl chunk.BlockSplitter) (*DagModifier, error) {
    30  	pbd, err := ft.FromBytes(from.Data)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	return &DagModifier{
    36  		curNode:  from.Copy(),
    37  		dagserv:  serv,
    38  		pbdata:   pbd,
    39  		splitter: spl,
    40  	}, nil
    41  }
    42  
    43  // WriteAt will modify a dag file in place
    44  // NOTE: it currently assumes only a single level of indirection
    45  func (dm *DagModifier) WriteAt(b []byte, offset uint64) (int, error) {
    46  
    47  	// Check bounds
    48  	if dm.pbdata.GetFilesize() < offset {
    49  		return 0, errors.New("Attempted to perform write starting past end of file")
    50  	}
    51  
    52  	// First need to find where we are writing at
    53  	end := uint64(len(b)) + offset
    54  
    55  	// This shouldnt be necessary if we do subblocks sizes properly
    56  	newsize := dm.pbdata.GetFilesize()
    57  	if end > dm.pbdata.GetFilesize() {
    58  		newsize = end
    59  	}
    60  	zeroblocklen := uint64(len(dm.pbdata.Data))
    61  	origlen := len(b)
    62  
    63  	if end <= zeroblocklen {
    64  		log.Debug("Writing into zero block")
    65  		// Replacing zeroeth data block (embedded in the root node)
    66  		//TODO: check chunking here
    67  		copy(dm.pbdata.Data[offset:], b)
    68  		return len(b), nil
    69  	}
    70  
    71  	// Find where write should start
    72  	var traversed uint64
    73  	startsubblk := len(dm.pbdata.Blocksizes)
    74  	if offset < zeroblocklen {
    75  		dm.pbdata.Data = dm.pbdata.Data[:offset]
    76  		startsubblk = 0
    77  	} else {
    78  		traversed = uint64(zeroblocklen)
    79  		for i, size := range dm.pbdata.Blocksizes {
    80  			if uint64(offset) < traversed+size {
    81  				log.Debugf("Starting mod at block %d. [%d < %d + %d]", i, offset, traversed, size)
    82  				// Here is where we start
    83  				startsubblk = i
    84  				lnk := dm.curNode.Links[i]
    85  				node, err := dm.dagserv.Get(u.Key(lnk.Hash))
    86  				if err != nil {
    87  					return 0, err
    88  				}
    89  				data, err := ft.UnwrapData(node.Data)
    90  				if err != nil {
    91  					return 0, err
    92  				}
    93  
    94  				// We have to rewrite the data before our write in this block.
    95  				b = append(data[:offset-traversed], b...)
    96  				break
    97  			}
    98  			traversed += size
    99  		}
   100  		if startsubblk == len(dm.pbdata.Blocksizes) {
   101  			// TODO: Im not sure if theres any case that isnt being handled here.
   102  			// leaving this note here as a future reference in case something breaks
   103  		}
   104  	}
   105  
   106  	// Find blocks that need to be overwritten
   107  	var changed []int
   108  	mid := -1
   109  	var midoff uint64
   110  	for i, size := range dm.pbdata.Blocksizes[startsubblk:] {
   111  		if end > traversed {
   112  			changed = append(changed, i+startsubblk)
   113  		} else {
   114  			break
   115  		}
   116  		traversed += size
   117  		if end < traversed {
   118  			mid = i + startsubblk
   119  			midoff = end - (traversed - size)
   120  			break
   121  		}
   122  	}
   123  
   124  	// If our write starts in the middle of a block...
   125  	var midlnk *mdag.Link
   126  	if mid >= 0 {
   127  		midlnk = dm.curNode.Links[mid]
   128  		midnode, err := dm.dagserv.Get(u.Key(midlnk.Hash))
   129  		if err != nil {
   130  			return 0, err
   131  		}
   132  
   133  		// NOTE: this may have to be changed later when we have multiple
   134  		// layers of indirection
   135  		data, err := ft.UnwrapData(midnode.Data)
   136  		if err != nil {
   137  			return 0, err
   138  		}
   139  		b = append(b, data[midoff:]...)
   140  	}
   141  
   142  	// Generate new sub-blocks, and sizes
   143  	subblocks := splitBytes(b, dm.splitter)
   144  	var links []*mdag.Link
   145  	var sizes []uint64
   146  	for _, sb := range subblocks {
   147  		n := &mdag.Node{Data: ft.WrapData(sb)}
   148  		_, err := dm.dagserv.Add(n)
   149  		if err != nil {
   150  			log.Errorf("Failed adding node to DAG service: %s", err)
   151  			return 0, err
   152  		}
   153  		lnk, err := mdag.MakeLink(n)
   154  		if err != nil {
   155  			return 0, err
   156  		}
   157  		links = append(links, lnk)
   158  		sizes = append(sizes, uint64(len(sb)))
   159  	}
   160  
   161  	// This is disgusting (and can be rewritten if performance demands)
   162  	if len(changed) > 0 {
   163  		sechalflink := append(links, dm.curNode.Links[changed[len(changed)-1]+1:]...)
   164  		dm.curNode.Links = append(dm.curNode.Links[:changed[0]], sechalflink...)
   165  		sechalfblks := append(sizes, dm.pbdata.Blocksizes[changed[len(changed)-1]+1:]...)
   166  		dm.pbdata.Blocksizes = append(dm.pbdata.Blocksizes[:changed[0]], sechalfblks...)
   167  	} else {
   168  		dm.curNode.Links = append(dm.curNode.Links, links...)
   169  		dm.pbdata.Blocksizes = append(dm.pbdata.Blocksizes, sizes...)
   170  	}
   171  	dm.pbdata.Filesize = proto.Uint64(newsize)
   172  
   173  	return origlen, nil
   174  }
   175  
   176  func (dm *DagModifier) Size() uint64 {
   177  	if dm == nil {
   178  		return 0
   179  	}
   180  	return dm.pbdata.GetFilesize()
   181  }
   182  
   183  // splitBytes uses a splitterFunc to turn a large array of bytes
   184  // into many smaller arrays of bytes
   185  func splitBytes(b []byte, spl chunk.BlockSplitter) [][]byte {
   186  	out := spl.Split(bytes.NewReader(b))
   187  	var arr [][]byte
   188  	for blk := range out {
   189  		arr = append(arr, blk)
   190  	}
   191  	return arr
   192  }
   193  
   194  // GetNode gets the modified DAG Node
   195  func (dm *DagModifier) GetNode() (*mdag.Node, error) {
   196  	b, err := proto.Marshal(dm.pbdata)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	dm.curNode.Data = b
   201  	return dm.curNode.Copy(), nil
   202  }